2 * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2020 the Claws Mail team and Hiroyuki Yamamoto
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 3 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
21 #include "claws-features.h"
26 #ifndef PANGO_ENABLE_ENGINE
27 # define PANGO_ENABLE_ENGINE
31 #include <glib/gi18n.h>
32 #include <gdk/gdkkeysyms.h>
35 #include <pango/pango-break.h>
40 #include <sys/types.h>
46 # include <sys/wait.h>
50 #ifndef G_OS_WIN32 /* fixme we should have a configure test. */
54 #if (HAVE_WCTYPE_H && HAVE_WCHAR_H)
61 #include "mainwindow.h"
63 #ifndef USE_ALT_ADDRBOOK
64 #include "addressbook.h"
66 #include "addressbook-dbus.h"
67 #include "addressadd.h"
69 #include "folderview.h"
72 #include "stock_pixmap.h"
73 #include "send_message.h"
76 #include "customheader.h"
77 #include "prefs_common.h"
78 #include "prefs_account.h"
82 #include "procheader.h"
84 #include "statusbar.h"
86 #include "quoted-printable.h"
90 #include "gtkshruler.h"
92 #include "alertpanel.h"
93 #include "manage_window.h"
95 #include "folder_item_prefs.h"
96 #include "addr_compl.h"
97 #include "quote_fmt.h"
99 #include "foldersel.h"
102 #include "message_search.h"
103 #include "combobox.h"
107 #include "autofaces.h"
108 #include "spell_entry.h"
110 #include "file-utils.h"
113 #include "password.h"
114 #include "ldapserver.h"
128 #define N_ATTACH_COLS (N_COL_COLUMNS)
132 COMPOSE_CALL_ADVANCED_ACTION_UNDEFINED = -1,
133 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE = 0,
134 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER,
135 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER,
136 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD,
137 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD,
138 COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE,
139 COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE,
140 COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE,
141 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER,
142 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER,
143 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD,
144 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD,
145 COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE,
146 COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END
147 } ComposeCallAdvancedAction;
151 PRIORITY_HIGHEST = 1,
160 COMPOSE_INSERT_SUCCESS,
161 COMPOSE_INSERT_READ_ERROR,
162 COMPOSE_INSERT_INVALID_CHARACTER,
163 COMPOSE_INSERT_NO_FILE
164 } ComposeInsertResult;
168 COMPOSE_WRITE_FOR_SEND,
169 COMPOSE_WRITE_FOR_STORE
174 COMPOSE_QUOTE_FORCED,
181 SUBJECT_FIELD_PRESENT,
186 #define B64_LINE_SIZE 57
187 #define B64_BUFFSIZE 77
189 #define MAX_REFERENCES_LEN 999
191 #define COMPOSE_DRAFT_TIMEOUT_UNSET -1
192 #define COMPOSE_DRAFT_TIMEOUT_FORBIDDEN -2
194 #define COMPOSE_PRIVACY_WARNING() { \
195 alertpanel_error(_("You have opted to sign and/or encrypt this " \
196 "message but have not selected a privacy system.\n\n" \
197 "Signing and encrypting have been disabled for this " \
201 static GdkColor default_header_bgcolor = {
208 static GdkColor default_header_color = {
215 static GList *compose_list = NULL;
216 static GSList *extra_headers = NULL;
218 static Compose *compose_generic_new (PrefsAccount *account,
222 GList *listAddress );
224 static Compose *compose_create (PrefsAccount *account,
229 static void compose_entry_indicate (Compose *compose,
230 const gchar *address);
231 static Compose *compose_followup_and_reply_to (MsgInfo *msginfo,
232 ComposeQuoteMode quote_mode,
236 static Compose *compose_forward_multiple (PrefsAccount *account,
237 GSList *msginfo_list);
238 static Compose *compose_reply (MsgInfo *msginfo,
239 ComposeQuoteMode quote_mode,
244 static Compose *compose_reply_mode (ComposeMode mode,
245 GSList *msginfo_list,
247 static void compose_template_apply_fields(Compose *compose, Template *tmpl);
248 static void compose_update_privacy_systems_menu(Compose *compose);
250 static GtkWidget *compose_account_option_menu_create
252 static void compose_set_out_encoding (Compose *compose);
253 static void compose_set_template_menu (Compose *compose);
254 static void compose_destroy (Compose *compose);
256 static MailField compose_entries_set (Compose *compose,
258 ComposeEntryType to_type);
259 static gint compose_parse_header (Compose *compose,
261 static gint compose_parse_manual_headers (Compose *compose,
263 HeaderEntry *entries);
264 static gchar *compose_parse_references (const gchar *ref,
267 static gchar *compose_quote_fmt (Compose *compose,
273 gboolean need_unescape,
274 const gchar *err_msg);
276 static void compose_reply_set_entry (Compose *compose,
282 followup_and_reply_to);
283 static void compose_reedit_set_entry (Compose *compose,
286 static void compose_insert_sig (Compose *compose,
288 static ComposeInsertResult compose_insert_file (Compose *compose,
291 static gboolean compose_attach_append (Compose *compose,
294 const gchar *content_type,
295 const gchar *charset);
296 static void compose_attach_parts (Compose *compose,
299 static gboolean compose_beautify_paragraph (Compose *compose,
300 GtkTextIter *par_iter,
302 static void compose_wrap_all (Compose *compose);
303 static void compose_wrap_all_full (Compose *compose,
306 static void compose_set_title (Compose *compose);
307 static void compose_select_account (Compose *compose,
308 PrefsAccount *account,
311 static PrefsAccount *compose_current_mail_account(void);
312 /* static gint compose_send (Compose *compose); */
313 static gboolean compose_check_for_valid_recipient
315 static gboolean compose_check_entries (Compose *compose,
316 gboolean check_everything);
317 static gint compose_write_to_file (Compose *compose,
320 gboolean attach_parts);
321 static gint compose_write_body_to_file (Compose *compose,
323 static gint compose_remove_reedit_target (Compose *compose,
325 static void compose_remove_draft (Compose *compose);
326 static ComposeQueueResult compose_queue_sub (Compose *compose,
330 gboolean perform_checks,
331 gboolean remove_reedit_target);
332 static int compose_add_attachments (Compose *compose,
334 static gchar *compose_get_header (Compose *compose);
335 static gchar *compose_get_manual_headers_info (Compose *compose);
337 static void compose_convert_header (Compose *compose,
342 gboolean addr_field);
344 static void compose_attach_info_free (AttachInfo *ainfo);
345 static void compose_attach_remove_selected (GtkAction *action,
348 static void compose_template_apply (Compose *compose,
351 static void compose_attach_property (GtkAction *action,
353 static void compose_attach_property_create (gboolean *cancelled);
354 static void attach_property_ok (GtkWidget *widget,
355 gboolean *cancelled);
356 static void attach_property_cancel (GtkWidget *widget,
357 gboolean *cancelled);
358 static gint attach_property_delete_event (GtkWidget *widget,
360 gboolean *cancelled);
361 static gboolean attach_property_key_pressed (GtkWidget *widget,
363 gboolean *cancelled);
365 static void compose_exec_ext_editor (Compose *compose);
367 static gint compose_exec_ext_editor_real (const gchar *file,
368 GdkNativeWindow socket_wid);
369 static gboolean compose_ext_editor_kill (Compose *compose);
370 static gboolean compose_input_cb (GIOChannel *source,
371 GIOCondition condition,
373 static void compose_set_ext_editor_sensitive (Compose *compose,
375 static gboolean compose_get_ext_editor_cmd_valid();
376 static gboolean compose_get_ext_editor_uses_socket();
377 static gboolean compose_ext_editor_plug_removed_cb
380 #endif /* G_OS_UNIX */
382 static void compose_undo_state_changed (UndoMain *undostruct,
387 static void compose_create_header_entry (Compose *compose);
388 static void compose_add_header_entry (Compose *compose, const gchar *header,
389 gchar *text, ComposePrefType pref_type);
390 static void compose_remove_header_entries(Compose *compose);
392 static void compose_update_priority_menu_item(Compose * compose);
394 static void compose_spell_menu_changed (void *data);
395 static void compose_dict_changed (void *data);
397 static void compose_add_field_list ( Compose *compose,
398 GList *listAddress );
400 /* callback functions */
402 static void compose_notebook_size_alloc (GtkNotebook *notebook,
403 GtkAllocation *allocation,
405 static gboolean compose_edit_size_alloc (GtkEditable *widget,
406 GtkAllocation *allocation,
407 GtkSHRuler *shruler);
408 static void account_activated (GtkComboBox *optmenu,
410 static void attach_selected (GtkTreeView *tree_view,
411 GtkTreePath *tree_path,
412 GtkTreeViewColumn *column,
414 static gboolean attach_button_pressed (GtkWidget *widget,
415 GdkEventButton *event,
417 static gboolean attach_key_pressed (GtkWidget *widget,
420 static void compose_send_cb (GtkAction *action, gpointer data);
421 static void compose_send_later_cb (GtkAction *action, gpointer data);
423 static void compose_save_cb (GtkAction *action,
426 static void compose_attach_cb (GtkAction *action,
428 static void compose_insert_file_cb (GtkAction *action,
430 static void compose_insert_sig_cb (GtkAction *action,
432 static void compose_replace_sig_cb (GtkAction *action,
435 static void compose_close_cb (GtkAction *action,
437 static void compose_print_cb (GtkAction *action,
440 static void compose_set_encoding_cb (GtkAction *action, GtkRadioAction *current, gpointer data);
442 static void compose_address_cb (GtkAction *action,
444 static void about_show_cb (GtkAction *action,
446 static void compose_template_activate_cb(GtkWidget *widget,
449 static void compose_ext_editor_cb (GtkAction *action,
452 static gint compose_delete_cb (GtkWidget *widget,
456 static void compose_undo_cb (GtkAction *action,
458 static void compose_redo_cb (GtkAction *action,
460 static void compose_cut_cb (GtkAction *action,
462 static void compose_copy_cb (GtkAction *action,
464 static void compose_paste_cb (GtkAction *action,
466 static void compose_paste_as_quote_cb (GtkAction *action,
468 static void compose_paste_no_wrap_cb (GtkAction *action,
470 static void compose_paste_wrap_cb (GtkAction *action,
472 static void compose_allsel_cb (GtkAction *action,
475 static void compose_advanced_action_cb (GtkAction *action,
478 static void compose_grab_focus_cb (GtkWidget *widget,
481 static void compose_changed_cb (GtkTextBuffer *textbuf,
484 static void compose_wrap_cb (GtkAction *action,
486 static void compose_wrap_all_cb (GtkAction *action,
488 static void compose_find_cb (GtkAction *action,
490 static void compose_toggle_autowrap_cb (GtkToggleAction *action,
492 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
495 static void compose_toggle_ruler_cb (GtkToggleAction *action,
497 static void compose_toggle_sign_cb (GtkToggleAction *action,
499 static void compose_toggle_encrypt_cb (GtkToggleAction *action,
501 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data);
502 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn);
503 static void compose_activate_privacy_system (Compose *compose,
504 PrefsAccount *account,
506 static void compose_apply_folder_privacy_settings(Compose *compose, FolderItem *folder_item);
507 static void compose_toggle_return_receipt_cb(GtkToggleAction *action,
509 static void compose_toggle_remove_refs_cb(GtkToggleAction *action,
511 static void compose_set_priority_cb (GtkAction *action, GtkRadioAction *current, gpointer data);
512 static void compose_reply_change_mode (Compose *compose, ComposeMode action);
513 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data);
515 static void compose_attach_drag_received_cb (GtkWidget *widget,
516 GdkDragContext *drag_context,
519 GtkSelectionData *data,
523 static void compose_insert_drag_received_cb (GtkWidget *widget,
524 GdkDragContext *drag_context,
527 GtkSelectionData *data,
531 static void compose_header_drag_received_cb (GtkWidget *widget,
532 GdkDragContext *drag_context,
535 GtkSelectionData *data,
540 static gboolean compose_drag_drop (GtkWidget *widget,
541 GdkDragContext *drag_context,
543 guint time, gpointer user_data);
544 static gboolean completion_set_focus_to_subject
549 static void text_inserted (GtkTextBuffer *buffer,
554 static Compose *compose_generic_reply(MsgInfo *msginfo,
555 ComposeQuoteMode quote_mode,
559 gboolean followup_and_reply_to,
562 static void compose_headerentry_changed_cb (GtkWidget *entry,
563 ComposeHeaderEntry *headerentry);
564 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
566 ComposeHeaderEntry *headerentry);
567 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
568 ComposeHeaderEntry *headerentry);
570 static void compose_show_first_last_header (Compose *compose, gboolean show_first);
572 static void compose_allow_user_actions (Compose *compose, gboolean allow);
574 static void compose_nothing_cb (GtkAction *action, gpointer data)
580 static void compose_check_all (GtkAction *action, gpointer data);
581 static void compose_highlight_all (GtkAction *action, gpointer data);
582 static void compose_check_backwards (GtkAction *action, gpointer data);
583 static void compose_check_forwards_go (GtkAction *action, gpointer data);
586 static PrefsAccount *compose_find_account (MsgInfo *msginfo);
588 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose);
591 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
592 FolderItem *folder_item);
594 static void compose_attach_update_label(Compose *compose);
595 static void compose_set_folder_prefs(Compose *compose, FolderItem *folder,
596 gboolean respect_default_to);
597 static void compose_subject_entry_activated(GtkWidget *widget, gpointer data);
598 static void from_name_activate_cb(GtkWidget *widget, gpointer data);
600 static GtkActionEntry compose_popup_entries[] =
602 {"Compose", NULL, "Compose", NULL, NULL, NULL },
603 {"Compose/Add", NULL, N_("_Add..."), NULL, NULL, G_CALLBACK(compose_attach_cb) },
604 {"Compose/Remove", NULL, N_("_Remove"), NULL, NULL, G_CALLBACK(compose_attach_remove_selected) },
605 {"Compose/---", NULL, "---", NULL, NULL, NULL },
606 {"Compose/Properties", NULL, N_("_Properties..."), NULL, NULL, G_CALLBACK(compose_attach_property) },
609 static GtkActionEntry compose_entries[] =
611 {"Menu", NULL, "Menu", NULL, NULL, NULL },
613 {"Message", NULL, N_("_Message"), NULL, NULL, NULL },
614 {"Edit", NULL, N_("_Edit"), NULL, NULL, NULL },
616 {"Spelling", NULL, N_("_Spelling"), NULL, NULL, NULL },
618 {"Options", NULL, N_("_Options"), NULL, NULL, NULL },
619 {"Tools", NULL, N_("_Tools"), NULL, NULL, NULL },
620 {"Help", NULL, N_("_Help"), NULL, NULL, NULL },
622 {"Message/Send", NULL, N_("S_end"), "<control>Return", NULL, G_CALLBACK(compose_send_cb) },
623 {"Message/SendLater", NULL, N_("Send _later"), "<shift><control>S", NULL, G_CALLBACK(compose_send_later_cb) },
624 {"Message/---", NULL, "---", NULL, NULL, NULL },
626 {"Message/AttachFile", NULL, N_("_Attach file"), "<control>M", NULL, G_CALLBACK(compose_attach_cb) },
627 {"Message/InsertFile", NULL, N_("_Insert file"), "<control>I", NULL, G_CALLBACK(compose_insert_file_cb) },
628 {"Message/InsertSig", NULL, N_("Insert si_gnature"), "<control>G", NULL, G_CALLBACK(compose_insert_sig_cb) },
629 {"Message/ReplaceSig", NULL, N_("_Replace signature"), NULL, NULL, G_CALLBACK(compose_replace_sig_cb) },
630 /* {"Message/---", NULL, "---", NULL, NULL, NULL }, */
631 {"Message/Save", NULL, N_("_Save"), "<control>S", NULL, G_CALLBACK(compose_save_cb) }, /*COMPOSE_KEEP_EDITING*/
632 /* {"Message/---", NULL, "---", NULL, NULL, NULL }, */
633 {"Message/Print", NULL, N_("_Print"), NULL, NULL, G_CALLBACK(compose_print_cb) },
634 /* {"Message/---", NULL, "---", NULL, NULL, NULL }, */
635 {"Message/Close", NULL, N_("_Close"), "<control>W", NULL, G_CALLBACK(compose_close_cb) },
638 {"Edit/Undo", NULL, N_("_Undo"), "<control>Z", NULL, G_CALLBACK(compose_undo_cb) },
639 {"Edit/Redo", NULL, N_("_Redo"), "<control>Y", NULL, G_CALLBACK(compose_redo_cb) },
640 {"Edit/---", NULL, "---", NULL, NULL, NULL },
642 {"Edit/Cut", NULL, N_("Cu_t"), "<control>X", NULL, G_CALLBACK(compose_cut_cb) },
643 {"Edit/Copy", NULL, N_("_Copy"), "<control>C", NULL, G_CALLBACK(compose_copy_cb) },
644 {"Edit/Paste", NULL, N_("_Paste"), "<control>V", NULL, G_CALLBACK(compose_paste_cb) },
646 {"Edit/SpecialPaste", NULL, N_("_Special paste"), NULL, NULL, NULL },
647 {"Edit/SpecialPaste/AsQuotation", NULL, N_("As _quotation"), NULL, NULL, G_CALLBACK(compose_paste_as_quote_cb) },
648 {"Edit/SpecialPaste/Wrapped", NULL, N_("_Wrapped"), NULL, NULL, G_CALLBACK(compose_paste_wrap_cb) },
649 {"Edit/SpecialPaste/Unwrapped", NULL, N_("_Unwrapped"), NULL, NULL, G_CALLBACK(compose_paste_no_wrap_cb) },
651 {"Edit/SelectAll", NULL, N_("Select _all"), "<control>A", NULL, G_CALLBACK(compose_allsel_cb) },
653 {"Edit/Advanced", NULL, N_("A_dvanced"), NULL, NULL, NULL },
654 {"Edit/Advanced/BackChar", NULL, N_("Move a character backward"), "<shift><control>B", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER*/
655 {"Edit/Advanced/ForwChar", NULL, N_("Move a character forward"), "<shift><control>F", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER*/
656 {"Edit/Advanced/BackWord", NULL, N_("Move a word backward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD*/
657 {"Edit/Advanced/ForwWord", NULL, N_("Move a word forward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD*/
658 {"Edit/Advanced/BegLine", NULL, N_("Move to beginning of line"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE*/
659 {"Edit/Advanced/EndLine", NULL, N_("Move to end of line"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE*/
660 {"Edit/Advanced/PrevLine", NULL, N_("Move to previous line"), "<control>P", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE*/
661 {"Edit/Advanced/NextLine", NULL, N_("Move to next line"), "<control>N", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE*/
662 {"Edit/Advanced/DelBackChar", NULL, N_("Delete a character backward"), "<control>H", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER*/
663 {"Edit/Advanced/DelForwChar", NULL, N_("Delete a character forward"), "<control>D", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER*/
664 {"Edit/Advanced/DelBackWord", NULL, N_("Delete a word backward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD*/
665 {"Edit/Advanced/DelForwWord", NULL, N_("Delete a word forward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD*/
666 {"Edit/Advanced/DelLine", NULL, N_("Delete line"), "<control>U", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE*/
667 {"Edit/Advanced/DelEndLine", NULL, N_("Delete to end of line"), "<control>K", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END*/
669 /* {"Edit/---", NULL, "---", NULL, NULL, NULL }, */
670 {"Edit/Find", NULL, N_("_Find"), "<control>F", NULL, G_CALLBACK(compose_find_cb) },
672 /* {"Edit/---", NULL, "---", NULL, NULL, NULL }, */
673 {"Edit/WrapPara", NULL, N_("_Wrap current paragraph"), "<control>L", NULL, G_CALLBACK(compose_wrap_cb) }, /* 0 */
674 {"Edit/WrapAllLines", NULL, N_("Wrap all long _lines"), "<control><alt>L", NULL, G_CALLBACK(compose_wrap_all_cb) }, /* 1 */
675 /* {"Edit/---", NULL, "---", NULL, NULL, NULL }, */
676 {"Edit/ExtEditor", NULL, N_("Edit with e_xternal editor"), "<shift><control>X", NULL, G_CALLBACK(compose_ext_editor_cb) },
679 {"Spelling/CheckAllSel", NULL, N_("_Check all or check selection"), NULL, NULL, G_CALLBACK(compose_check_all) },
680 {"Spelling/HighlightAll", NULL, N_("_Highlight all misspelled words"), NULL, NULL, G_CALLBACK(compose_highlight_all) },
681 {"Spelling/CheckBackwards", NULL, N_("Check _backwards misspelled word"), NULL, NULL, G_CALLBACK(compose_check_backwards) },
682 {"Spelling/ForwardNext", NULL, N_("_Forward to next misspelled word"), NULL, NULL, G_CALLBACK(compose_check_forwards_go) },
684 {"Spelling/---", NULL, "---", NULL, NULL, NULL },
685 {"Spelling/Options", NULL, N_("_Options"), NULL, NULL, NULL },
689 {"Options/ReplyMode", NULL, N_("Reply _mode"), NULL, NULL, NULL },
690 {"Options/---", NULL, "---", NULL, NULL, NULL },
691 {"Options/PrivacySystem", NULL, N_("Privacy _System"), NULL, NULL, NULL },
692 {"Options/PrivacySystem/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
694 /* {"Options/---", NULL, "---", NULL, NULL, NULL }, */
695 {"Options/Priority", NULL, N_("_Priority"), NULL, NULL, NULL },
697 {"Options/Encoding", NULL, N_("Character _encoding"), NULL, NULL, NULL },
698 {"Options/Encoding/---", NULL, "---", NULL, NULL, NULL },
699 #define ENC_ACTION(cs_char,c_char,string) \
700 {"Options/Encoding/" cs_char, NULL, N_(string), NULL, NULL, c_char }
702 {"Options/Encoding/Western", NULL, N_("Western European"), NULL, NULL, NULL },
703 {"Options/Encoding/Baltic", NULL, N_("Baltic"), NULL, NULL, NULL },
704 {"Options/Encoding/Hebrew", NULL, N_("Hebrew"), NULL, NULL, NULL },
705 {"Options/Encoding/Arabic", NULL, N_("Arabic"), NULL, NULL, NULL },
706 {"Options/Encoding/Cyrillic", NULL, N_("Cyrillic"), NULL, NULL, NULL },
707 {"Options/Encoding/Japanese", NULL, N_("Japanese"), NULL, NULL, NULL },
708 {"Options/Encoding/Chinese", NULL, N_("Chinese"), NULL, NULL, NULL },
709 {"Options/Encoding/Korean", NULL, N_("Korean"), NULL, NULL, NULL },
710 {"Options/Encoding/Thai", NULL, N_("Thai"), NULL, NULL, NULL },
713 {"Tools/AddressBook", NULL, N_("_Address book"), NULL, NULL, G_CALLBACK(compose_address_cb) },
715 {"Tools/Template", NULL, N_("_Template"), NULL, NULL, NULL },
716 {"Tools/Template/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
717 {"Tools/Actions", NULL, N_("Actio_ns"), NULL, NULL, NULL },
718 {"Tools/Actions/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
721 {"Help/About", NULL, N_("_About"), NULL, NULL, G_CALLBACK(about_show_cb) },
724 static GtkToggleActionEntry compose_toggle_entries[] =
726 {"Edit/AutoWrap", NULL, N_("Aut_o wrapping"), "<shift><control>L", NULL, G_CALLBACK(compose_toggle_autowrap_cb), FALSE }, /* Toggle */
727 {"Edit/AutoIndent", NULL, N_("Auto _indent"), NULL, NULL, G_CALLBACK(compose_toggle_autoindent_cb), FALSE }, /* Toggle */
728 {"Options/Sign", NULL, N_("Si_gn"), NULL, NULL, G_CALLBACK(compose_toggle_sign_cb), FALSE }, /* Toggle */
729 {"Options/Encrypt", NULL, N_("_Encrypt"), NULL, NULL, G_CALLBACK(compose_toggle_encrypt_cb), FALSE }, /* Toggle */
730 {"Options/RequestRetRcpt", NULL, N_("_Request Return Receipt"), NULL, NULL, G_CALLBACK(compose_toggle_return_receipt_cb), FALSE }, /* Toggle */
731 {"Options/RemoveReferences", NULL, N_("Remo_ve references"), NULL, NULL, G_CALLBACK(compose_toggle_remove_refs_cb), FALSE }, /* Toggle */
732 {"Tools/ShowRuler", NULL, N_("Show _ruler"), NULL, NULL, G_CALLBACK(compose_toggle_ruler_cb), FALSE }, /* Toggle */
735 static GtkRadioActionEntry compose_radio_rm_entries[] =
737 {"Options/ReplyMode/Normal", NULL, N_("_Normal"), NULL, NULL, COMPOSE_REPLY }, /* RADIO compose_reply_change_mode_cb */
738 {"Options/ReplyMode/All", NULL, N_("_All"), NULL, NULL, COMPOSE_REPLY_TO_ALL }, /* RADIO compose_reply_change_mode_cb */
739 {"Options/ReplyMode/Sender", NULL, N_("_Sender"), NULL, NULL, COMPOSE_REPLY_TO_SENDER }, /* RADIO compose_reply_change_mode_cb */
740 {"Options/ReplyMode/List", NULL, N_("_Mailing-list"), NULL, NULL, COMPOSE_REPLY_TO_LIST }, /* RADIO compose_reply_change_mode_cb */
743 static GtkRadioActionEntry compose_radio_prio_entries[] =
745 {"Options/Priority/Highest", NULL, N_("_Highest"), NULL, NULL, PRIORITY_HIGHEST }, /* RADIO compose_set_priority_cb */
746 {"Options/Priority/High", NULL, N_("Hi_gh"), NULL, NULL, PRIORITY_HIGH }, /* RADIO compose_set_priority_cb */
747 {"Options/Priority/Normal", NULL, N_("_Normal"), NULL, NULL, PRIORITY_NORMAL }, /* RADIO compose_set_priority_cb */
748 {"Options/Priority/Low", NULL, N_("Lo_w"), NULL, NULL, PRIORITY_LOW }, /* RADIO compose_set_priority_cb */
749 {"Options/Priority/Lowest", NULL, N_("_Lowest"), NULL, NULL, PRIORITY_LOWEST }, /* RADIO compose_set_priority_cb */
752 static GtkRadioActionEntry compose_radio_enc_entries[] =
754 ENC_ACTION(CS_AUTO, C_AUTO, N_("_Automatic")), /* RADIO compose_set_encoding_cb */
755 ENC_ACTION(CS_US_ASCII, C_US_ASCII, N_("7bit ASCII (US-ASC_II)")), /* RADIO compose_set_encoding_cb */
756 ENC_ACTION(CS_UTF_8, C_UTF_8, N_("Unicode (_UTF-8)")), /* RADIO compose_set_encoding_cb */
757 ENC_ACTION("Western/"CS_ISO_8859_1, C_ISO_8859_1, "ISO-8859-_1"), /* RADIO compose_set_encoding_cb */
758 ENC_ACTION("Western/"CS_ISO_8859_15, C_ISO_8859_15, "ISO-8859-15"), /* RADIO compose_set_encoding_cb */
759 ENC_ACTION("Western/"CS_WINDOWS_1252, C_WINDOWS_1252, "Windows-1252"), /* RADIO compose_set_encoding_cb */
760 ENC_ACTION(CS_ISO_8859_2, C_ISO_8859_2, N_("Central European (ISO-8859-_2)")), /* RADIO compose_set_encoding_cb */
761 ENC_ACTION("Baltic/"CS_ISO_8859_13, C_ISO_8859_13, "ISO-8859-13"), /* RADIO compose_set_encoding_cb */
762 ENC_ACTION("Baltic/"CS_ISO_8859_4, C_ISO_8859_14, "ISO-8859-_4"), /* RADIO compose_set_encoding_cb */
763 ENC_ACTION(CS_ISO_8859_7, C_ISO_8859_7, N_("Greek (ISO-8859-_7)")), /* RADIO compose_set_encoding_cb */
764 ENC_ACTION("Hebrew/"CS_ISO_8859_8, C_ISO_8859_8, "ISO-8859-_8"), /* RADIO compose_set_encoding_cb */
765 ENC_ACTION("Hebrew/"CS_WINDOWS_1255, C_WINDOWS_1255, "Windows-1255"), /* RADIO compose_set_encoding_cb */
766 ENC_ACTION("Arabic/"CS_ISO_8859_6, C_ISO_8859_6, "ISO-8859-_6"), /* RADIO compose_set_encoding_cb */
767 ENC_ACTION("Arabic/"CS_WINDOWS_1256, C_WINDOWS_1256, "Windows-1256"), /* RADIO compose_set_encoding_cb */
768 ENC_ACTION(CS_ISO_8859_9, C_ISO_8859_9, N_("Turkish (ISO-8859-_9)")), /* RADIO compose_set_encoding_cb */
769 ENC_ACTION("Cyrillic/"CS_ISO_8859_5, C_ISO_8859_5, "ISO-8859-_5"), /* RADIO compose_set_encoding_cb */
770 ENC_ACTION("Cyrillic/"CS_KOI8_R, C_KOI8_R, "KOI8-_R"), /* RADIO compose_set_encoding_cb */
771 ENC_ACTION("Cyrillic/"CS_MACCYR, C_MACCYR, "_Mac-Cyrillic"), /* RADIO compose_set_encoding_cb */
772 ENC_ACTION("Cyrillic/"CS_KOI8_U, C_KOI8_U, "KOI8-_U"), /* RADIO compose_set_encoding_cb */
773 ENC_ACTION("Cyrillic/"CS_WINDOWS_1251, C_WINDOWS_1251, "Windows-1251"), /* RADIO compose_set_encoding_cb */
774 ENC_ACTION("Japanese/"CS_ISO_2022_JP, C_ISO_2022_JP, "ISO-2022-_JP"), /* RADIO compose_set_encoding_cb */
775 ENC_ACTION("Japanese/"CS_ISO_2022_JP_2, C_ISO_2022_JP_2, "ISO-2022-JP-_2"), /* RADIO compose_set_encoding_cb */
776 ENC_ACTION("Japanese/"CS_EUC_JP, C_EUC_JP, "_EUC-JP"), /* RADIO compose_set_encoding_cb */
777 ENC_ACTION("Japanese/"CS_SHIFT_JIS, C_SHIFT_JIS, "_Shift-JIS"), /* RADIO compose_set_encoding_cb */
778 ENC_ACTION("Chinese/"CS_GB18030, C_GB18030, "_GB18030"), /* RADIO compose_set_encoding_cb */
779 ENC_ACTION("Chinese/"CS_GB2312, C_GB2312, "_GB2312"), /* RADIO compose_set_encoding_cb */
780 ENC_ACTION("Chinese/"CS_GBK, C_GBK, "GB_K"), /* RADIO compose_set_encoding_cb */
781 ENC_ACTION("Chinese/"CS_BIG5, C_BIG5, "_Big5-JP"), /* RADIO compose_set_encoding_cb */
782 ENC_ACTION("Chinese/"CS_EUC_TW, C_EUC_TW, "EUC-_TW"), /* RADIO compose_set_encoding_cb */
783 ENC_ACTION("Korean/"CS_EUC_KR, C_EUC_KR, "_EUC-KR"), /* RADIO compose_set_encoding_cb */
784 ENC_ACTION("Korean/"CS_ISO_2022_KR, C_ISO_2022_KR, "_ISO-2022-KR"), /* RADIO compose_set_encoding_cb */
785 ENC_ACTION("Thai/"CS_TIS_620, C_TIS_620, "_TIS-620-KR"), /* RADIO compose_set_encoding_cb */
786 ENC_ACTION("Thai/"CS_WINDOWS_874, C_WINDOWS_874, "_Windows-874"), /* RADIO compose_set_encoding_cb */
789 static GtkTargetEntry compose_mime_types[] =
791 {"text/uri-list", 0, 0},
792 {"UTF8_STRING", 0, 0},
796 static gboolean compose_put_existing_to_front(MsgInfo *info)
798 const GList *compose_list = compose_get_compose_list();
799 const GList *elem = NULL;
802 for (elem = compose_list; elem != NULL && elem->data != NULL;
804 Compose *c = (Compose*)elem->data;
806 if (!c->targetinfo || !c->targetinfo->msgid ||
810 if (!strcmp(c->targetinfo->msgid, info->msgid)) {
811 gtkut_window_popup(c->window);
819 static GdkColor quote_color1 =
820 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
821 static GdkColor quote_color2 =
822 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
823 static GdkColor quote_color3 =
824 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
826 static GdkColor quote_bgcolor1 =
827 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
828 static GdkColor quote_bgcolor2 =
829 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
830 static GdkColor quote_bgcolor3 =
831 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
833 static GdkColor signature_color = {
840 static GdkColor uri_color = {
847 static void compose_create_tags(GtkTextView *text, Compose *compose)
849 GtkTextBuffer *buffer;
850 GdkColor black = {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
852 buffer = gtk_text_view_get_buffer(text);
854 if (prefs_common.enable_color) {
855 /* grab the quote colors, converting from an int to a GdkColor */
856 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_QUOTE_LEVEL1],
858 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_QUOTE_LEVEL2],
860 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_QUOTE_LEVEL3],
862 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_QUOTE_LEVEL1_BG],
864 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_QUOTE_LEVEL2_BG],
866 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_QUOTE_LEVEL3_BG],
868 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_SIGNATURE],
870 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_URI],
873 signature_color = quote_color1 = quote_color2 = quote_color3 =
874 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 = uri_color = black;
877 if (prefs_common.enable_color && prefs_common.enable_bgcolor) {
878 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
879 "foreground-gdk", "e_color1,
880 "paragraph-background-gdk", "e_bgcolor1,
882 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
883 "foreground-gdk", "e_color2,
884 "paragraph-background-gdk", "e_bgcolor2,
886 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
887 "foreground-gdk", "e_color3,
888 "paragraph-background-gdk", "e_bgcolor3,
891 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
892 "foreground-gdk", "e_color1,
894 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
895 "foreground-gdk", "e_color2,
897 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
898 "foreground-gdk", "e_color3,
902 compose->signature_tag = gtk_text_buffer_create_tag(buffer, "signature",
903 "foreground-gdk", &signature_color,
906 compose->uri_tag = gtk_text_buffer_create_tag(buffer, "link",
907 "foreground-gdk", &uri_color,
909 compose->no_wrap_tag = gtk_text_buffer_create_tag(buffer, "no_wrap", NULL);
910 compose->no_join_tag = gtk_text_buffer_create_tag(buffer, "no_join", NULL);
913 Compose *compose_new(PrefsAccount *account, const gchar *mailto,
916 return compose_generic_new(account, mailto, NULL, attach_files, NULL);
919 Compose *compose_new_with_folderitem(PrefsAccount *account, FolderItem *item, const gchar *mailto)
921 return compose_generic_new(account, mailto, item, NULL, NULL);
924 Compose *compose_new_with_list( PrefsAccount *account, GList *listAddress )
926 return compose_generic_new( account, NULL, NULL, NULL, listAddress );
929 #define SCROLL_TO_CURSOR(compose) { \
930 GtkTextMark *cmark = gtk_text_buffer_get_insert( \
931 gtk_text_view_get_buffer( \
932 GTK_TEXT_VIEW(compose->text))); \
933 gtk_text_view_scroll_mark_onscreen( \
934 GTK_TEXT_VIEW(compose->text), \
938 static void compose_set_save_to(Compose *compose, const gchar *folderidentifier)
941 if (folderidentifier) {
942 combobox_unset_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo));
943 prefs_common.compose_save_to_history = add_history(
944 prefs_common.compose_save_to_history, folderidentifier);
945 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo),
946 prefs_common.compose_save_to_history);
949 entry = GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(compose->savemsg_combo)));
950 if (folderidentifier)
951 gtk_entry_set_text(GTK_ENTRY(entry), folderidentifier);
953 gtk_entry_set_text(GTK_ENTRY(entry), "");
956 static gchar *compose_get_save_to(Compose *compose)
959 gchar *result = NULL;
960 entry = GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(compose->savemsg_combo)));
961 result = gtk_editable_get_chars(entry, 0, -1);
964 combobox_unset_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo));
965 prefs_common.compose_save_to_history = add_history(
966 prefs_common.compose_save_to_history, result);
967 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo),
968 prefs_common.compose_save_to_history);
973 Compose *compose_generic_new(PrefsAccount *account, const gchar *mailto, FolderItem *item,
974 GList *attach_files, GList *listAddress )
977 GtkTextView *textview;
978 GtkTextBuffer *textbuf;
980 const gchar *subject_format = NULL;
981 const gchar *body_format = NULL;
982 gchar *mailto_from = NULL;
983 PrefsAccount *mailto_account = NULL;
984 MsgInfo* dummyinfo = NULL;
985 gint cursor_pos = -1;
986 MailField mfield = NO_FIELD_PRESENT;
990 /* check if mailto defines a from */
991 if (mailto && *mailto != '\0') {
992 scan_mailto_url(mailto, &mailto_from, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
993 /* mailto defines a from, check if we can get account prefs from it,
994 if not, the account prefs will be guessed using other ways, but we'll keep
997 mailto_account = account_find_from_address(mailto_from, TRUE);
998 if (mailto_account == NULL) {
1000 Xstrdup_a(tmp_from, mailto_from, return NULL);
1001 extract_address(tmp_from);
1002 mailto_account = account_find_from_address(tmp_from, TRUE);
1006 account = mailto_account;
1009 /* if no account prefs set from mailto, set if from folder prefs (if any) */
1010 if (!mailto_account && item && item->prefs && item->prefs->enable_default_account)
1011 account = account_find_from_id(item->prefs->default_account);
1013 /* if no account prefs set, fallback to the current one */
1014 if (!account) account = cur_account;
1015 cm_return_val_if_fail(account != NULL, NULL);
1017 compose = compose_create(account, item, COMPOSE_NEW, FALSE);
1018 compose_apply_folder_privacy_settings(compose, item);
1020 if (privacy_system_can_sign(compose->privacy_system) == FALSE &&
1021 (account->default_encrypt || account->default_sign))
1022 COMPOSE_PRIVACY_WARNING();
1024 /* override from name if mailto asked for it */
1026 gtk_entry_set_text(GTK_ENTRY(compose->from_name), mailto_from);
1027 g_free(mailto_from);
1029 /* override from name according to folder properties */
1030 if (item && item->prefs &&
1031 item->prefs->compose_with_format &&
1032 item->prefs->compose_override_from_format &&
1033 *item->prefs->compose_override_from_format != '\0') {
1038 dummyinfo = compose_msginfo_new_from_compose(compose);
1040 /* decode \-escape sequences in the internal representation of the quote format */
1041 tmp = g_malloc(strlen(item->prefs->compose_override_from_format)+1);
1042 pref_get_unescaped_pref(tmp, item->prefs->compose_override_from_format);
1045 quote_fmt_init(dummyinfo, NULL, NULL, FALSE, compose->account, FALSE,
1046 compose->gtkaspell);
1048 quote_fmt_init(dummyinfo, NULL, NULL, FALSE, compose->account, FALSE);
1050 quote_fmt_scan_string(tmp);
1053 buf = quote_fmt_get_buffer();
1055 alertpanel_error(_("New message From format error."));
1057 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1058 quote_fmt_reset_vartable();
1059 quote_fmtlex_destroy();
1064 compose->replyinfo = NULL;
1065 compose->fwdinfo = NULL;
1067 textview = GTK_TEXT_VIEW(compose->text);
1068 textbuf = gtk_text_view_get_buffer(textview);
1069 compose_create_tags(textview, compose);
1071 undo_block(compose->undostruct);
1073 compose_set_dictionaries_from_folder_prefs(compose, item);
1076 if (account->auto_sig)
1077 compose_insert_sig(compose, FALSE);
1078 gtk_text_buffer_get_start_iter(textbuf, &iter);
1079 gtk_text_buffer_place_cursor(textbuf, &iter);
1081 if (account->protocol != A_NNTP) {
1082 if (mailto && *mailto != '\0') {
1083 mfield = compose_entries_set(compose, mailto, COMPOSE_TO);
1086 compose_set_folder_prefs(compose, item, TRUE);
1088 if (item && item->ret_rcpt) {
1089 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
1092 if (mailto && *mailto != '\0') {
1093 if (!strchr(mailto, '@'))
1094 mfield = compose_entries_set(compose, mailto, COMPOSE_NEWSGROUPS);
1096 mfield = compose_entries_set(compose, mailto, COMPOSE_TO);
1097 } else if (item && FOLDER_CLASS(item->folder) == news_get_class()) {
1098 compose_entry_append(compose, item->path, COMPOSE_NEWSGROUPS, PREF_FOLDER);
1099 mfield = TO_FIELD_PRESENT;
1102 * CLAWS: just don't allow return receipt request, even if the user
1103 * may want to send an email. simple but foolproof.
1105 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", FALSE);
1107 compose_add_field_list( compose, listAddress );
1109 if (item && item->prefs && item->prefs->compose_with_format) {
1110 subject_format = item->prefs->compose_subject_format;
1111 body_format = item->prefs->compose_body_format;
1112 } else if (account->compose_with_format) {
1113 subject_format = account->compose_subject_format;
1114 body_format = account->compose_body_format;
1115 } else if (prefs_common.compose_with_format) {
1116 subject_format = prefs_common.compose_subject_format;
1117 body_format = prefs_common.compose_body_format;
1120 if (subject_format || body_format) {
1123 && *subject_format != '\0' )
1125 gchar *subject = NULL;
1130 dummyinfo = compose_msginfo_new_from_compose(compose);
1132 /* decode \-escape sequences in the internal representation of the quote format */
1133 tmp = g_malloc(strlen(subject_format)+1);
1134 pref_get_unescaped_pref(tmp, subject_format);
1136 subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1138 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account, FALSE,
1139 compose->gtkaspell);
1141 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account, FALSE);
1143 quote_fmt_scan_string(tmp);
1146 buf = quote_fmt_get_buffer();
1148 alertpanel_error(_("New message subject format error."));
1150 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1151 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1152 quote_fmt_reset_vartable();
1153 quote_fmtlex_destroy();
1157 mfield = SUBJECT_FIELD_PRESENT;
1161 && *body_format != '\0' )
1164 GtkTextBuffer *buffer;
1165 GtkTextIter start, end;
1169 dummyinfo = compose_msginfo_new_from_compose(compose);
1171 text = GTK_TEXT_VIEW(compose->text);
1172 buffer = gtk_text_view_get_buffer(text);
1173 gtk_text_buffer_get_start_iter(buffer, &start);
1174 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
1175 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
1177 compose_quote_fmt(compose, dummyinfo,
1179 NULL, tmp, FALSE, TRUE,
1180 _("The body of the \"New message\" template has an error at line %d."));
1181 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1182 quote_fmt_reset_vartable();
1186 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1187 gtkaspell_highlight_all(compose->gtkaspell);
1189 mfield = BODY_FIELD_PRESENT;
1193 procmsg_msginfo_free( &dummyinfo );
1199 for (curr = attach_files ; curr != NULL ; curr = curr->next) {
1200 ainfo = (AttachInfo *) curr->data;
1202 compose_insert_file(compose, ainfo->file);
1204 compose_attach_append(compose, ainfo->file, ainfo->file,
1205 ainfo->content_type, ainfo->charset);
1209 compose_show_first_last_header(compose, TRUE);
1211 /* Set save folder */
1212 if (item && item->prefs && item->prefs->save_copy_to_folder) {
1213 gchar *folderidentifier;
1215 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1216 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo), TRUE);
1217 folderidentifier = folder_item_get_identifier(item);
1218 compose_set_save_to(compose, folderidentifier);
1219 g_free(folderidentifier);
1222 /* Place cursor according to provided input (mfield) */
1224 case NO_FIELD_PRESENT:
1225 if (compose->header_last)
1226 gtk_widget_grab_focus(compose->header_last->entry);
1228 case TO_FIELD_PRESENT:
1229 buf = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1231 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1234 gtk_widget_grab_focus(compose->subject_entry);
1236 case SUBJECT_FIELD_PRESENT:
1237 textview = GTK_TEXT_VIEW(compose->text);
1240 textbuf = gtk_text_view_get_buffer(textview);
1243 mark = gtk_text_buffer_get_insert(textbuf);
1244 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
1245 gtk_text_buffer_insert(textbuf, &iter, "", -1);
1247 * SUBJECT_FIELD_PRESENT and BODY_FIELD_PRESENT
1248 * only defers where it comes to the variable body
1249 * is not null. If no body is present compose->text
1250 * will be null in which case you cannot place the
1251 * cursor inside the component so. An empty component
1252 * is therefore created before placing the cursor
1254 case BODY_FIELD_PRESENT:
1255 cursor_pos = quote_fmt_get_cursor_pos();
1256 if (cursor_pos == -1)
1257 gtk_widget_grab_focus(compose->header_last->entry);
1259 gtk_widget_grab_focus(compose->text);
1263 undo_unblock(compose->undostruct);
1265 if (prefs_common.auto_exteditor)
1266 compose_exec_ext_editor(compose);
1268 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
1270 SCROLL_TO_CURSOR(compose);
1272 compose->modified = FALSE;
1273 compose_set_title(compose);
1275 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
1280 static void compose_force_encryption(Compose *compose, PrefsAccount *account,
1281 gboolean override_pref, const gchar *system)
1283 const gchar *privacy = NULL;
1285 cm_return_if_fail(compose != NULL);
1286 cm_return_if_fail(account != NULL);
1288 if (privacy_system_can_encrypt(compose->privacy_system) == FALSE ||
1289 (override_pref == FALSE && account->default_encrypt_reply == FALSE))
1292 if (account->default_privacy_system && strlen(account->default_privacy_system))
1293 privacy = account->default_privacy_system;
1297 GSList *privacy_avail = privacy_get_system_ids();
1298 if (privacy_avail && g_slist_length(privacy_avail)) {
1299 privacy = (gchar *)(privacy_avail->data);
1301 g_slist_free_full(privacy_avail, g_free);
1303 if (privacy != NULL) {
1305 g_free(compose->privacy_system);
1306 compose->privacy_system = NULL;
1307 g_free(compose->encdata);
1308 compose->encdata = NULL;
1310 if (compose->privacy_system == NULL)
1311 compose->privacy_system = g_strdup(privacy);
1312 else if (*(compose->privacy_system) == '\0') {
1313 g_free(compose->privacy_system);
1314 g_free(compose->encdata);
1315 compose->encdata = NULL;
1316 compose->privacy_system = g_strdup(privacy);
1318 compose_update_privacy_system_menu_item(compose, FALSE);
1319 compose_use_encryption(compose, TRUE);
1323 static void compose_force_signing(Compose *compose, PrefsAccount *account, const gchar *system)
1325 const gchar *privacy = NULL;
1326 if (privacy_system_can_sign(compose->privacy_system) == FALSE)
1329 if (account->default_privacy_system && strlen(account->default_privacy_system))
1330 privacy = account->default_privacy_system;
1334 GSList *privacy_avail = privacy_get_system_ids();
1335 if (privacy_avail && g_slist_length(privacy_avail)) {
1336 privacy = (gchar *)(privacy_avail->data);
1340 if (privacy != NULL) {
1342 g_free(compose->privacy_system);
1343 compose->privacy_system = NULL;
1344 g_free(compose->encdata);
1345 compose->encdata = NULL;
1347 if (compose->privacy_system == NULL)
1348 compose->privacy_system = g_strdup(privacy);
1349 compose_update_privacy_system_menu_item(compose, FALSE);
1350 compose_use_signing(compose, TRUE);
1354 static Compose *compose_reply_mode(ComposeMode mode, GSList *msginfo_list, gchar *body)
1358 Compose *compose = NULL;
1360 cm_return_val_if_fail(msginfo_list != NULL, NULL);
1362 msginfo = (MsgInfo*)g_slist_nth_data(msginfo_list, 0);
1363 cm_return_val_if_fail(msginfo != NULL, NULL);
1365 list_len = g_slist_length(msginfo_list);
1369 case COMPOSE_REPLY_TO_ADDRESS:
1370 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1371 FALSE, prefs_common.default_reply_list, FALSE, body);
1373 case COMPOSE_REPLY_WITH_QUOTE:
1374 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1375 FALSE, prefs_common.default_reply_list, FALSE, body);
1377 case COMPOSE_REPLY_WITHOUT_QUOTE:
1378 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1379 FALSE, prefs_common.default_reply_list, FALSE, NULL);
1381 case COMPOSE_REPLY_TO_SENDER:
1382 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1383 FALSE, FALSE, TRUE, body);
1385 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1386 compose = compose_followup_and_reply_to(msginfo,
1387 COMPOSE_QUOTE_CHECK,
1388 FALSE, FALSE, body);
1390 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1391 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1392 FALSE, FALSE, TRUE, body);
1394 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1395 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1396 FALSE, FALSE, TRUE, NULL);
1398 case COMPOSE_REPLY_TO_ALL:
1399 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1400 TRUE, FALSE, FALSE, body);
1402 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1403 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1404 TRUE, FALSE, FALSE, body);
1406 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1407 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1408 TRUE, FALSE, FALSE, NULL);
1410 case COMPOSE_REPLY_TO_LIST:
1411 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1412 FALSE, TRUE, FALSE, body);
1414 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1415 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1416 FALSE, TRUE, FALSE, body);
1418 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1419 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1420 FALSE, TRUE, FALSE, NULL);
1422 case COMPOSE_FORWARD:
1423 if (prefs_common.forward_as_attachment) {
1424 compose = compose_reply_mode(COMPOSE_FORWARD_AS_ATTACH, msginfo_list, body);
1427 compose = compose_reply_mode(COMPOSE_FORWARD_INLINE, msginfo_list, body);
1431 case COMPOSE_FORWARD_INLINE:
1432 /* check if we reply to more than one Message */
1433 if (list_len == 1) {
1434 compose = compose_forward(NULL, msginfo, FALSE, body, FALSE, FALSE);
1437 /* more messages FALL THROUGH */
1438 case COMPOSE_FORWARD_AS_ATTACH:
1439 compose = compose_forward_multiple(NULL, msginfo_list);
1441 case COMPOSE_REDIRECT:
1442 compose = compose_redirect(NULL, msginfo, FALSE);
1445 g_warning("compose_reply_mode(): invalid Compose Mode: %d", mode);
1448 if (compose == NULL) {
1449 alertpanel_error(_("Unable to reply. The original email probably doesn't exist."));
1453 compose->rmode = mode;
1454 switch (compose->rmode) {
1456 case COMPOSE_REPLY_WITH_QUOTE:
1457 case COMPOSE_REPLY_WITHOUT_QUOTE:
1458 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1459 debug_print("reply mode Normal\n");
1460 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/Normal", TRUE);
1461 compose_reply_change_mode(compose, COMPOSE_REPLY); /* force update */
1463 case COMPOSE_REPLY_TO_SENDER:
1464 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1465 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1466 debug_print("reply mode Sender\n");
1467 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/Sender", TRUE);
1469 case COMPOSE_REPLY_TO_ALL:
1470 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1471 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1472 debug_print("reply mode All\n");
1473 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/All", TRUE);
1475 case COMPOSE_REPLY_TO_LIST:
1476 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1477 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1478 debug_print("reply mode List\n");
1479 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/List", TRUE);
1481 case COMPOSE_REPLY_TO_ADDRESS:
1482 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", FALSE);
1490 static Compose *compose_reply(MsgInfo *msginfo,
1491 ComposeQuoteMode quote_mode,
1497 return compose_generic_reply(msginfo, quote_mode, to_all, to_ml,
1498 to_sender, FALSE, body);
1501 static Compose *compose_followup_and_reply_to(MsgInfo *msginfo,
1502 ComposeQuoteMode quote_mode,
1507 return compose_generic_reply(msginfo, quote_mode, to_all, FALSE,
1508 to_sender, TRUE, body);
1511 static void compose_extract_original_charset(Compose *compose)
1513 MsgInfo *info = NULL;
1514 if (compose->replyinfo) {
1515 info = compose->replyinfo;
1516 } else if (compose->fwdinfo) {
1517 info = compose->fwdinfo;
1518 } else if (compose->targetinfo) {
1519 info = compose->targetinfo;
1522 MimeInfo *mimeinfo = procmime_scan_message_short(info);
1523 MimeInfo *partinfo = mimeinfo;
1524 while (partinfo && partinfo->type != MIMETYPE_TEXT)
1525 partinfo = procmime_mimeinfo_next(partinfo);
1527 compose->orig_charset =
1528 g_strdup(procmime_mimeinfo_get_parameter(
1529 partinfo, "charset"));
1531 procmime_mimeinfo_free_all(&mimeinfo);
1535 #define SIGNAL_BLOCK(buffer) { \
1536 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1537 G_CALLBACK(compose_changed_cb), \
1539 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1540 G_CALLBACK(text_inserted), \
1544 #define SIGNAL_UNBLOCK(buffer) { \
1545 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1546 G_CALLBACK(compose_changed_cb), \
1548 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1549 G_CALLBACK(text_inserted), \
1553 static Compose *compose_generic_reply(MsgInfo *msginfo,
1554 ComposeQuoteMode quote_mode,
1555 gboolean to_all, gboolean to_ml,
1557 gboolean followup_and_reply_to,
1561 PrefsAccount *account = NULL;
1562 GtkTextView *textview;
1563 GtkTextBuffer *textbuf;
1564 gboolean quote = FALSE;
1565 const gchar *qmark = NULL;
1566 const gchar *body_fmt = NULL;
1567 gchar *s_system = NULL;
1569 cm_return_val_if_fail(msginfo != NULL, NULL);
1570 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
1572 account = account_get_reply_account(msginfo, prefs_common.reply_account_autosel);
1574 cm_return_val_if_fail(account != NULL, NULL);
1576 compose = compose_create(account, msginfo->folder, COMPOSE_REPLY, FALSE);
1577 compose_apply_folder_privacy_settings(compose, msginfo->folder);
1579 compose->updating = TRUE;
1581 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
1582 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", TRUE);
1584 compose->replyinfo = procmsg_msginfo_get_full_info(msginfo);
1585 if (!compose->replyinfo)
1586 compose->replyinfo = procmsg_msginfo_copy(msginfo);
1588 compose_extract_original_charset(compose);
1590 if (msginfo->folder && msginfo->folder->ret_rcpt)
1591 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
1593 /* Set save folder */
1594 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1595 gchar *folderidentifier;
1597 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1598 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo), TRUE);
1599 folderidentifier = folder_item_get_identifier(msginfo->folder);
1600 compose_set_save_to(compose, folderidentifier);
1601 g_free(folderidentifier);
1604 if (compose_parse_header(compose, msginfo) < 0) {
1605 compose->updating = FALSE;
1606 compose_destroy(compose);
1610 /* override from name according to folder properties */
1611 if (msginfo->folder && msginfo->folder->prefs &&
1612 msginfo->folder->prefs->reply_with_format &&
1613 msginfo->folder->prefs->reply_override_from_format &&
1614 *msginfo->folder->prefs->reply_override_from_format != '\0') {
1619 /* decode \-escape sequences in the internal representation of the quote format */
1620 tmp = g_malloc(strlen(msginfo->folder->prefs->reply_override_from_format)+1);
1621 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->reply_override_from_format);
1624 quote_fmt_init(compose->replyinfo, NULL, NULL, FALSE, compose->account, FALSE,
1625 compose->gtkaspell);
1627 quote_fmt_init(compose->replyinfo, NULL, NULL, FALSE, compose->account, FALSE);
1629 quote_fmt_scan_string(tmp);
1632 buf = quote_fmt_get_buffer();
1634 alertpanel_error(_("The \"From\" field of the \"Reply\" template contains an invalid email address."));
1636 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1637 quote_fmt_reset_vartable();
1638 quote_fmtlex_destroy();
1643 textview = (GTK_TEXT_VIEW(compose->text));
1644 textbuf = gtk_text_view_get_buffer(textview);
1645 compose_create_tags(textview, compose);
1647 undo_block(compose->undostruct);
1649 compose_set_dictionaries_from_folder_prefs(compose, msginfo->folder);
1650 gtkaspell_block_check(compose->gtkaspell);
1653 if (quote_mode == COMPOSE_QUOTE_FORCED ||
1654 (quote_mode == COMPOSE_QUOTE_CHECK && prefs_common.reply_with_quote)) {
1655 /* use the reply format of folder (if enabled), or the account's one
1656 (if enabled) or fallback to the global reply format, which is always
1657 enabled (even if empty), and use the relevant quotemark */
1659 if (msginfo->folder && msginfo->folder->prefs &&
1660 msginfo->folder->prefs->reply_with_format) {
1661 qmark = msginfo->folder->prefs->reply_quotemark;
1662 body_fmt = msginfo->folder->prefs->reply_body_format;
1664 } else if (account->reply_with_format) {
1665 qmark = account->reply_quotemark;
1666 body_fmt = account->reply_body_format;
1669 qmark = prefs_common.quotemark;
1670 if (prefs_common.quotefmt && *prefs_common.quotefmt)
1671 body_fmt = gettext(prefs_common.quotefmt);
1678 /* empty quotemark is not allowed */
1679 if (qmark == NULL || *qmark == '\0')
1681 compose_quote_fmt(compose, compose->replyinfo,
1682 body_fmt, qmark, body, FALSE, TRUE,
1683 _("The body of the \"Reply\" template has an error at line %d."));
1684 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1685 quote_fmt_reset_vartable();
1688 if (MSG_IS_ENCRYPTED(compose->replyinfo->flags)) {
1689 compose_force_encryption(compose, account, FALSE, s_system);
1692 privacy_msginfo_get_signed_state(compose->replyinfo, &s_system);
1693 if (MSG_IS_SIGNED(compose->replyinfo->flags) && account->default_sign_reply) {
1694 compose_force_signing(compose, account, s_system);
1698 if (privacy_system_can_sign(compose->privacy_system) == FALSE &&
1699 ((account->default_encrypt || account->default_sign) ||
1700 (account->default_encrypt_reply && MSG_IS_ENCRYPTED(compose->replyinfo->flags)) ||
1701 (account->default_sign_reply && MSG_IS_SIGNED(compose->replyinfo->flags))))
1702 COMPOSE_PRIVACY_WARNING();
1704 SIGNAL_BLOCK(textbuf);
1706 if (account->auto_sig)
1707 compose_insert_sig(compose, FALSE);
1709 compose_wrap_all(compose);
1712 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1713 gtkaspell_highlight_all(compose->gtkaspell);
1714 gtkaspell_unblock_check(compose->gtkaspell);
1716 SIGNAL_UNBLOCK(textbuf);
1718 gtk_widget_grab_focus(compose->text);
1720 undo_unblock(compose->undostruct);
1722 if (prefs_common.auto_exteditor)
1723 compose_exec_ext_editor(compose);
1725 compose->modified = FALSE;
1726 compose_set_title(compose);
1728 compose->updating = FALSE;
1729 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
1730 SCROLL_TO_CURSOR(compose);
1732 if (compose->deferred_destroy) {
1733 compose_destroy(compose);
1741 #define INSERT_FW_HEADER(var, hdr) \
1742 if (msginfo->var && *msginfo->var) { \
1743 gtk_stext_insert(text, NULL, NULL, NULL, hdr, -1); \
1744 gtk_stext_insert(text, NULL, NULL, NULL, msginfo->var, -1); \
1745 gtk_stext_insert(text, NULL, NULL, NULL, "\n", 1); \
1748 Compose *compose_forward(PrefsAccount *account, MsgInfo *msginfo,
1749 gboolean as_attach, const gchar *body,
1750 gboolean no_extedit,
1754 GtkTextView *textview;
1755 GtkTextBuffer *textbuf;
1756 gint cursor_pos = -1;
1759 cm_return_val_if_fail(msginfo != NULL, NULL);
1760 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
1762 if (!account && !(account = compose_find_account(msginfo)))
1763 account = cur_account;
1765 if (!prefs_common.forward_as_attachment)
1766 mode = COMPOSE_FORWARD_INLINE;
1768 mode = COMPOSE_FORWARD;
1769 compose = compose_create(account, msginfo->folder, mode, batch);
1770 compose_apply_folder_privacy_settings(compose, msginfo->folder);
1772 compose->updating = TRUE;
1773 compose->fwdinfo = procmsg_msginfo_get_full_info(msginfo);
1774 if (!compose->fwdinfo)
1775 compose->fwdinfo = procmsg_msginfo_copy(msginfo);
1777 compose_extract_original_charset(compose);
1779 if (msginfo->subject && *msginfo->subject) {
1780 gchar *buf, *buf2, *p;
1782 buf = p = g_strdup(msginfo->subject);
1783 p += subject_get_prefix_length(p);
1784 memmove(buf, p, strlen(p) + 1);
1786 buf2 = g_strdup_printf("Fw: %s", buf);
1787 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
1793 /* override from name according to folder properties */
1794 if (msginfo->folder && msginfo->folder->prefs &&
1795 msginfo->folder->prefs->forward_with_format &&
1796 msginfo->folder->prefs->forward_override_from_format &&
1797 *msginfo->folder->prefs->forward_override_from_format != '\0') {
1801 MsgInfo *full_msginfo = NULL;
1804 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1806 full_msginfo = procmsg_msginfo_copy(msginfo);
1808 /* decode \-escape sequences in the internal representation of the quote format */
1809 tmp = g_malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
1810 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
1813 gtkaspell_block_check(compose->gtkaspell);
1814 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE,
1815 compose->gtkaspell);
1817 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE);
1819 quote_fmt_scan_string(tmp);
1822 buf = quote_fmt_get_buffer();
1824 alertpanel_error(_("The \"From\" field of the \"Forward\" template contains an invalid email address."));
1826 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1827 quote_fmt_reset_vartable();
1828 quote_fmtlex_destroy();
1831 procmsg_msginfo_free(&full_msginfo);
1834 textview = GTK_TEXT_VIEW(compose->text);
1835 textbuf = gtk_text_view_get_buffer(textview);
1836 compose_create_tags(textview, compose);
1838 undo_block(compose->undostruct);
1842 msgfile = procmsg_get_message_file(msginfo);
1843 if (!is_file_exist(msgfile))
1844 g_warning("%s: file does not exist", msgfile);
1846 compose_attach_append(compose, msgfile, msgfile,
1847 "message/rfc822", NULL);
1851 const gchar *qmark = NULL;
1852 const gchar *body_fmt = NULL;
1853 MsgInfo *full_msginfo;
1855 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1857 full_msginfo = procmsg_msginfo_copy(msginfo);
1859 /* use the forward format of folder (if enabled), or the account's one
1860 (if enabled) or fallback to the global forward format, which is always
1861 enabled (even if empty), and use the relevant quotemark */
1862 if (msginfo->folder && msginfo->folder->prefs &&
1863 msginfo->folder->prefs->forward_with_format) {
1864 qmark = msginfo->folder->prefs->forward_quotemark;
1865 body_fmt = msginfo->folder->prefs->forward_body_format;
1867 } else if (account->forward_with_format) {
1868 qmark = account->forward_quotemark;
1869 body_fmt = account->forward_body_format;
1872 qmark = prefs_common.fw_quotemark;
1873 if (prefs_common.fw_quotefmt && *prefs_common.fw_quotefmt)
1874 body_fmt = gettext(prefs_common.fw_quotefmt);
1879 /* empty quotemark is not allowed */
1880 if (qmark == NULL || *qmark == '\0')
1883 compose_quote_fmt(compose, full_msginfo,
1884 body_fmt, qmark, body, FALSE, TRUE,
1885 _("The body of the \"Forward\" template has an error at line %d."));
1886 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1887 quote_fmt_reset_vartable();
1888 compose_attach_parts(compose, msginfo);
1890 procmsg_msginfo_free(&full_msginfo);
1893 SIGNAL_BLOCK(textbuf);
1895 if (account->auto_sig)
1896 compose_insert_sig(compose, FALSE);
1898 compose_wrap_all(compose);
1901 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1902 gtkaspell_highlight_all(compose->gtkaspell);
1903 gtkaspell_unblock_check(compose->gtkaspell);
1905 SIGNAL_UNBLOCK(textbuf);
1907 if (privacy_system_can_sign(compose->privacy_system) == FALSE &&
1908 (account->default_encrypt || account->default_sign))
1909 COMPOSE_PRIVACY_WARNING();
1911 cursor_pos = quote_fmt_get_cursor_pos();
1912 if (cursor_pos == -1)
1913 gtk_widget_grab_focus(compose->header_last->entry);
1915 gtk_widget_grab_focus(compose->text);
1917 if (!no_extedit && prefs_common.auto_exteditor)
1918 compose_exec_ext_editor(compose);
1921 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1922 gchar *folderidentifier;
1924 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1925 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo), TRUE);
1926 folderidentifier = folder_item_get_identifier(msginfo->folder);
1927 compose_set_save_to(compose, folderidentifier);
1928 g_free(folderidentifier);
1931 undo_unblock(compose->undostruct);
1933 compose->modified = FALSE;
1934 compose_set_title(compose);
1936 compose->updating = FALSE;
1937 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
1938 SCROLL_TO_CURSOR(compose);
1940 if (compose->deferred_destroy) {
1941 compose_destroy(compose);
1945 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
1950 #undef INSERT_FW_HEADER
1952 static Compose *compose_forward_multiple(PrefsAccount *account, GSList *msginfo_list)
1955 GtkTextView *textview;
1956 GtkTextBuffer *textbuf;
1960 gboolean single_mail = TRUE;
1962 cm_return_val_if_fail(msginfo_list != NULL, NULL);
1964 if (g_slist_length(msginfo_list) > 1)
1965 single_mail = FALSE;
1967 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next)
1968 if (((MsgInfo *)msginfo->data)->folder == NULL)
1971 /* guess account from first selected message */
1973 !(account = compose_find_account(msginfo_list->data)))
1974 account = cur_account;
1976 cm_return_val_if_fail(account != NULL, NULL);
1978 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
1979 if (msginfo->data) {
1980 MSG_UNSET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_REPLIED);
1981 MSG_SET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_FORWARDED);
1985 if (msginfo_list == NULL || msginfo_list->data == NULL) {
1986 g_warning("no msginfo_list");
1990 compose = compose_create(account, ((MsgInfo *)msginfo_list->data)->folder, COMPOSE_FORWARD, FALSE);
1991 compose_apply_folder_privacy_settings(compose, ((MsgInfo *)msginfo_list->data)->folder);
1992 if (privacy_system_can_sign(compose->privacy_system) == FALSE &&
1993 (account->default_encrypt || account->default_sign))
1994 COMPOSE_PRIVACY_WARNING();
1996 compose->updating = TRUE;
1998 /* override from name according to folder properties */
1999 if (msginfo_list->data) {
2000 MsgInfo *msginfo = msginfo_list->data;
2002 if (msginfo->folder && msginfo->folder->prefs &&
2003 msginfo->folder->prefs->forward_with_format &&
2004 msginfo->folder->prefs->forward_override_from_format &&
2005 *msginfo->folder->prefs->forward_override_from_format != '\0') {
2010 /* decode \-escape sequences in the internal representation of the quote format */
2011 tmp = g_malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
2012 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
2015 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
2016 compose->gtkaspell);
2018 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
2020 quote_fmt_scan_string(tmp);
2023 buf = quote_fmt_get_buffer();
2025 alertpanel_error(_("The \"From\" field of the \"Forward\" template contains an invalid email address."));
2027 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
2028 quote_fmt_reset_vartable();
2029 quote_fmtlex_destroy();
2035 textview = GTK_TEXT_VIEW(compose->text);
2036 textbuf = gtk_text_view_get_buffer(textview);
2037 compose_create_tags(textview, compose);
2039 undo_block(compose->undostruct);
2040 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
2041 msgfile = procmsg_get_message_file((MsgInfo *)msginfo->data);
2043 if (!is_file_exist(msgfile))
2044 g_warning("%s: file does not exist", msgfile);
2046 compose_attach_append(compose, msgfile, msgfile,
2047 "message/rfc822", NULL);
2052 MsgInfo *info = (MsgInfo *)msginfo_list->data;
2053 if (info->subject && *info->subject) {
2054 gchar *buf, *buf2, *p;
2056 buf = p = g_strdup(info->subject);
2057 p += subject_get_prefix_length(p);
2058 memmove(buf, p, strlen(p) + 1);
2060 buf2 = g_strdup_printf("Fw: %s", buf);
2061 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
2067 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2068 _("Fw: multiple emails"));
2071 SIGNAL_BLOCK(textbuf);
2073 if (account->auto_sig)
2074 compose_insert_sig(compose, FALSE);
2076 compose_wrap_all(compose);
2078 SIGNAL_UNBLOCK(textbuf);
2080 gtk_text_buffer_get_start_iter(textbuf, &iter);
2081 gtk_text_buffer_place_cursor(textbuf, &iter);
2083 if (prefs_common.auto_exteditor)
2084 compose_exec_ext_editor(compose);
2086 gtk_widget_grab_focus(compose->header_last->entry);
2087 undo_unblock(compose->undostruct);
2088 compose->modified = FALSE;
2089 compose_set_title(compose);
2091 compose->updating = FALSE;
2092 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2093 SCROLL_TO_CURSOR(compose);
2095 if (compose->deferred_destroy) {
2096 compose_destroy(compose);
2100 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2105 static gboolean compose_is_sig_separator(Compose *compose, GtkTextBuffer *textbuf, GtkTextIter *iter)
2107 GtkTextIter start = *iter;
2108 GtkTextIter end_iter;
2109 int start_pos = gtk_text_iter_get_offset(&start);
2111 if (!compose->account->sig_sep)
2114 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
2115 start_pos+strlen(compose->account->sig_sep));
2117 /* check sig separator */
2118 str = gtk_text_iter_get_text(&start, &end_iter);
2119 if (!strcmp(str, compose->account->sig_sep)) {
2121 /* check end of line (\n) */
2122 gtk_text_buffer_get_iter_at_offset(textbuf, &start,
2123 start_pos+strlen(compose->account->sig_sep));
2124 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
2125 start_pos+strlen(compose->account->sig_sep)+1);
2126 tmp = gtk_text_iter_get_text(&start, &end_iter);
2127 if (!strcmp(tmp,"\n")) {
2139 static gboolean compose_update_folder_hook(gpointer source, gpointer data)
2141 FolderUpdateData *hookdata = (FolderUpdateData *)source;
2142 Compose *compose = (Compose *)data;
2143 FolderItem *old_item = NULL;
2144 FolderItem *new_item = NULL;
2145 gchar *old_id, *new_id;
2147 if (!(hookdata->update_flags & FOLDER_REMOVE_FOLDERITEM)
2148 && !(hookdata->update_flags & FOLDER_MOVE_FOLDERITEM))
2151 old_item = hookdata->item;
2152 new_item = hookdata->item2;
2154 old_id = folder_item_get_identifier(old_item);
2155 new_id = new_item ? folder_item_get_identifier(new_item) : g_strdup("NULL");
2157 if (compose->targetinfo && compose->targetinfo->folder == old_item) {
2158 debug_print("updating targetinfo folder: %s -> %s\n", old_id, new_id);
2159 compose->targetinfo->folder = new_item;
2162 if (compose->replyinfo && compose->replyinfo->folder == old_item) {
2163 debug_print("updating replyinfo folder: %s -> %s\n", old_id, new_id);
2164 compose->replyinfo->folder = new_item;
2167 if (compose->fwdinfo && compose->fwdinfo->folder == old_item) {
2168 debug_print("updating fwdinfo folder: %s -> %s\n", old_id, new_id);
2169 compose->fwdinfo->folder = new_item;
2177 static void compose_colorize_signature(Compose *compose)
2179 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
2181 GtkTextIter end_iter;
2182 gtk_text_buffer_get_start_iter(buffer, &iter);
2183 while (gtk_text_iter_forward_line(&iter))
2184 if (compose_is_sig_separator(compose, buffer, &iter)) {
2185 gtk_text_buffer_get_end_iter(buffer, &end_iter);
2186 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &end_iter);
2190 #define BLOCK_WRAP() { \
2191 prev_autowrap = compose->autowrap; \
2192 buffer = gtk_text_view_get_buffer( \
2193 GTK_TEXT_VIEW(compose->text)); \
2194 compose->autowrap = FALSE; \
2196 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2197 G_CALLBACK(compose_changed_cb), \
2199 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2200 G_CALLBACK(text_inserted), \
2203 #define UNBLOCK_WRAP() { \
2204 compose->autowrap = prev_autowrap; \
2205 if (compose->autowrap) { \
2206 gint old = compose->draft_timeout_tag; \
2207 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN; \
2208 compose_wrap_all(compose); \
2209 compose->draft_timeout_tag = old; \
2212 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2213 G_CALLBACK(compose_changed_cb), \
2215 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2216 G_CALLBACK(text_inserted), \
2220 Compose *compose_reedit(MsgInfo *msginfo, gboolean batch)
2222 Compose *compose = NULL;
2223 PrefsAccount *account = NULL;
2224 GtkTextView *textview;
2225 GtkTextBuffer *textbuf;
2229 gboolean use_signing = FALSE;
2230 gboolean use_encryption = FALSE;
2231 gchar *privacy_system = NULL;
2232 int priority = PRIORITY_NORMAL;
2233 MsgInfo *replyinfo = NULL, *fwdinfo = NULL;
2234 gboolean autowrap = prefs_common.autowrap;
2235 gboolean autoindent = prefs_common.auto_indent;
2236 HeaderEntry *manual_headers = NULL;
2238 cm_return_val_if_fail(msginfo != NULL, NULL);
2239 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
2241 if (compose_put_existing_to_front(msginfo)) {
2245 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2246 folder_has_parent_of_type(msginfo->folder, F_DRAFT) ||
2247 folder_has_parent_of_type(msginfo->folder, F_OUTBOX)) {
2248 gchar *queueheader_buf = NULL;
2251 /* Select Account from queue headers */
2252 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2253 "X-Claws-Account-Id:")) {
2254 id = atoi(&queueheader_buf[strlen("X-Claws-Account-Id:")]);
2255 account = account_find_from_id(id);
2256 g_free(queueheader_buf);
2258 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2259 "X-Sylpheed-Account-Id:")) {
2260 id = atoi(&queueheader_buf[strlen("X-Sylpheed-Account-Id:")]);
2261 account = account_find_from_id(id);
2262 g_free(queueheader_buf);
2264 if (!account && !procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2266 id = atoi(&queueheader_buf[strlen("NAID:")]);
2267 account = account_find_from_id(id);
2268 g_free(queueheader_buf);
2270 if (!account && !procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2272 id = atoi(&queueheader_buf[strlen("MAID:")]);
2273 account = account_find_from_id(id);
2274 g_free(queueheader_buf);
2276 if (!account && !procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2278 account = account_find_from_address(queueheader_buf, FALSE);
2279 g_free(queueheader_buf);
2281 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2283 param = atoi(&queueheader_buf[strlen("X-Claws-Sign:")]);
2284 use_signing = param;
2285 g_free(queueheader_buf);
2287 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2288 "X-Sylpheed-Sign:")) {
2289 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Sign:")]);
2290 use_signing = param;
2291 g_free(queueheader_buf);
2293 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2294 "X-Claws-Encrypt:")) {
2295 param = atoi(&queueheader_buf[strlen("X-Claws-Encrypt:")]);
2296 use_encryption = param;
2297 g_free(queueheader_buf);
2299 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2300 "X-Sylpheed-Encrypt:")) {
2301 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Encrypt:")]);
2302 use_encryption = param;
2303 g_free(queueheader_buf);
2305 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2306 "X-Claws-Auto-Wrapping:")) {
2307 param = atoi(&queueheader_buf[strlen("X-Claws-Auto-Wrapping:")]);
2309 g_free(queueheader_buf);
2311 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2312 "X-Claws-Auto-Indent:")) {
2313 param = atoi(&queueheader_buf[strlen("X-Claws-Auto-Indent:")]);
2315 g_free(queueheader_buf);
2317 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2318 "X-Claws-Privacy-System:")) {
2319 privacy_system = g_strdup(&queueheader_buf[strlen("X-Claws-Privacy-System:")]);
2320 g_free(queueheader_buf);
2322 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2323 "X-Sylpheed-Privacy-System:")) {
2324 privacy_system = g_strdup(&queueheader_buf[strlen("X-Sylpheed-Privacy-System:")]);
2325 g_free(queueheader_buf);
2327 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2329 param = atoi(&queueheader_buf[strlen("X-Priority: ")]); /* mind the space */
2331 g_free(queueheader_buf);
2333 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2335 gchar **tokens = g_strsplit(&queueheader_buf[strlen("RMID:")], "\t", 0);
2336 if (tokens && tokens[0] && tokens[1] && tokens[2]) {
2337 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2338 if (orig_item != NULL) {
2339 replyinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2343 g_free(queueheader_buf);
2345 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2347 gchar **tokens = g_strsplit(&queueheader_buf[strlen("FMID:")], "\t", 0);
2348 if (tokens && tokens[0] && tokens[1] && tokens[2]) {
2349 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2350 if (orig_item != NULL) {
2351 fwdinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2355 g_free(queueheader_buf);
2357 /* Get manual headers */
2358 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2359 "X-Claws-Manual-Headers:")) {
2360 gchar *listmh = g_strdup(&queueheader_buf[strlen("X-Claws-Manual-Headers:")]);
2361 if (listmh && *listmh != '\0') {
2362 debug_print("Got manual headers: %s\n", listmh);
2363 manual_headers = procheader_entries_from_str(listmh);
2366 g_free(queueheader_buf);
2369 account = msginfo->folder->folder->account;
2372 if (!account && prefs_common.reedit_account_autosel) {
2374 if (!procheader_get_header_from_msginfo(msginfo, &from, "FROM:")) {
2375 extract_address(from);
2376 account = account_find_from_address(from, FALSE);
2381 account = cur_account;
2383 cm_return_val_if_fail(account != NULL, NULL);
2385 compose = compose_create(account, msginfo->folder, COMPOSE_REEDIT, batch);
2387 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", autowrap);
2388 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", autoindent);
2389 compose->autowrap = autowrap;
2390 compose->replyinfo = replyinfo;
2391 compose->fwdinfo = fwdinfo;
2393 compose->updating = TRUE;
2394 compose->priority = priority;
2396 if (privacy_system != NULL) {
2397 compose->privacy_system = privacy_system;
2398 compose_use_signing(compose, use_signing);
2399 compose_use_encryption(compose, use_encryption);
2400 compose_update_privacy_system_menu_item(compose, FALSE);
2402 compose_activate_privacy_system(compose, account, FALSE);
2404 compose_apply_folder_privacy_settings(compose, msginfo->folder);
2405 if (privacy_system_can_sign(compose->privacy_system) == FALSE &&
2406 (account->default_encrypt || account->default_sign))
2407 COMPOSE_PRIVACY_WARNING();
2409 compose->targetinfo = procmsg_msginfo_copy(msginfo);
2410 compose->targetinfo->tags = g_slist_copy(msginfo->tags);
2412 compose_extract_original_charset(compose);
2414 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2415 folder_has_parent_of_type(msginfo->folder, F_DRAFT) ||
2416 folder_has_parent_of_type(msginfo->folder, F_OUTBOX)) {
2417 gchar *queueheader_buf = NULL;
2419 /* Set message save folder */
2420 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf, "SCF:")) {
2421 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
2422 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo), TRUE);
2423 compose_set_save_to(compose, &queueheader_buf[4]);
2424 g_free(queueheader_buf);
2426 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf, "RRCPT:")) {
2427 gint active = atoi(&queueheader_buf[strlen("RRCPT:")]);
2429 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
2431 g_free(queueheader_buf);
2435 if (compose_parse_header(compose, msginfo) < 0) {
2436 compose->updating = FALSE;
2437 compose_destroy(compose);
2440 compose_reedit_set_entry(compose, msginfo);
2442 textview = GTK_TEXT_VIEW(compose->text);
2443 textbuf = gtk_text_view_get_buffer(textview);
2444 compose_create_tags(textview, compose);
2446 mark = gtk_text_buffer_get_insert(textbuf);
2447 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
2449 g_signal_handlers_block_by_func(G_OBJECT(textbuf),
2450 G_CALLBACK(compose_changed_cb),
2453 if (MSG_IS_ENCRYPTED(msginfo->flags)) {
2454 fp = procmime_get_first_encrypted_text_content(msginfo);
2456 compose_force_encryption(compose, account, TRUE, NULL);
2459 fp = procmime_get_first_text_content(msginfo);
2462 g_warning("Can't get text part");
2466 gchar buf[BUFFSIZE];
2467 gboolean prev_autowrap;
2468 GtkTextBuffer *buffer;
2470 while (claws_fgets(buf, sizeof(buf), fp) != NULL) {
2472 gtk_text_buffer_insert(textbuf, &iter, buf, -1);
2478 compose_attach_parts(compose, msginfo);
2480 compose_colorize_signature(compose);
2482 g_signal_handlers_unblock_by_func(G_OBJECT(textbuf),
2483 G_CALLBACK(compose_changed_cb),
2486 if (manual_headers != NULL) {
2487 if (compose_parse_manual_headers(compose, msginfo, manual_headers) < 0) {
2488 procheader_entries_free(manual_headers);
2489 compose->updating = FALSE;
2490 compose_destroy(compose);
2493 procheader_entries_free(manual_headers);
2496 gtk_widget_grab_focus(compose->text);
2498 if (prefs_common.auto_exteditor) {
2499 compose_exec_ext_editor(compose);
2501 compose->modified = FALSE;
2502 compose_set_title(compose);
2504 compose->updating = FALSE;
2505 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2506 SCROLL_TO_CURSOR(compose);
2508 if (compose->deferred_destroy) {
2509 compose_destroy(compose);
2513 compose->sig_str = account_get_signature_str(compose->account);
2515 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2520 Compose *compose_redirect(PrefsAccount *account, MsgInfo *msginfo,
2527 cm_return_val_if_fail(msginfo != NULL, NULL);
2530 account = account_get_reply_account(msginfo,
2531 prefs_common.reply_account_autosel);
2532 cm_return_val_if_fail(account != NULL, NULL);
2534 compose = compose_create(account, msginfo->folder, COMPOSE_REDIRECT, batch);
2536 compose->updating = TRUE;
2538 compose_create_tags(GTK_TEXT_VIEW(compose->text), compose);
2539 compose->replyinfo = NULL;
2540 compose->fwdinfo = NULL;
2542 compose_show_first_last_header(compose, TRUE);
2544 gtk_widget_grab_focus(compose->header_last->entry);
2546 filename = procmsg_get_message_file(msginfo);
2548 if (filename == NULL) {
2549 compose->updating = FALSE;
2550 compose_destroy(compose);
2555 compose->redirect_filename = filename;
2557 /* Set save folder */
2558 item = msginfo->folder;
2559 if (item && item->prefs && item->prefs->save_copy_to_folder) {
2560 gchar *folderidentifier;
2562 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
2563 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo), TRUE);
2564 folderidentifier = folder_item_get_identifier(item);
2565 compose_set_save_to(compose, folderidentifier);
2566 g_free(folderidentifier);
2569 compose_attach_parts(compose, msginfo);
2571 if (msginfo->subject)
2572 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2574 gtk_editable_set_editable(GTK_EDITABLE(compose->subject_entry), FALSE);
2576 compose_quote_fmt(compose, msginfo, "%M", NULL, NULL, FALSE, FALSE,
2577 _("The body of the \"Redirect\" template has an error at line %d."));
2578 quote_fmt_reset_vartable();
2579 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), FALSE);
2581 compose_colorize_signature(compose);
2584 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Add", FALSE);
2585 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", FALSE);
2586 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", FALSE);
2588 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/Save", FALSE);
2589 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertFile", FALSE);
2590 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/AttachFile", FALSE);
2591 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertSig", FALSE);
2592 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/ReplaceSig", FALSE);
2593 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", FALSE);
2594 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", FALSE);
2595 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/ShowRuler", FALSE);
2596 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Actions", FALSE);
2598 if (compose->toolbar->draft_btn)
2599 gtk_widget_set_sensitive(compose->toolbar->draft_btn, FALSE);
2600 if (compose->toolbar->insert_btn)
2601 gtk_widget_set_sensitive(compose->toolbar->insert_btn, FALSE);
2602 if (compose->toolbar->attach_btn)
2603 gtk_widget_set_sensitive(compose->toolbar->attach_btn, FALSE);
2604 if (compose->toolbar->sig_btn)
2605 gtk_widget_set_sensitive(compose->toolbar->sig_btn, FALSE);
2606 if (compose->toolbar->exteditor_btn)
2607 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, FALSE);
2608 if (compose->toolbar->linewrap_current_btn)
2609 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, FALSE);
2610 if (compose->toolbar->linewrap_all_btn)
2611 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, FALSE);
2612 if (compose->toolbar->privacy_sign_btn)
2613 gtk_widget_set_sensitive(compose->toolbar->privacy_sign_btn, FALSE);
2614 if (compose->toolbar->privacy_encrypt_btn)
2615 gtk_widget_set_sensitive(compose->toolbar->privacy_encrypt_btn, FALSE);
2617 compose->modified = FALSE;
2618 compose_set_title(compose);
2619 compose->updating = FALSE;
2620 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2621 SCROLL_TO_CURSOR(compose);
2623 if (compose->deferred_destroy) {
2624 compose_destroy(compose);
2628 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2633 const GList *compose_get_compose_list(void)
2635 return compose_list;
2638 void compose_entry_append(Compose *compose, const gchar *address,
2639 ComposeEntryType type, ComposePrefType pref_type)
2641 const gchar *header;
2643 gboolean in_quote = FALSE;
2644 if (!address || *address == '\0') return;
2651 header = N_("Bcc:");
2653 case COMPOSE_REPLYTO:
2654 header = N_("Reply-To:");
2656 case COMPOSE_NEWSGROUPS:
2657 header = N_("Newsgroups:");
2659 case COMPOSE_FOLLOWUPTO:
2660 header = N_( "Followup-To:");
2662 case COMPOSE_INREPLYTO:
2663 header = N_( "In-Reply-To:");
2670 header = prefs_common_translated_header_name(header);
2672 cur = begin = (gchar *)address;
2674 /* we separate the line by commas, but not if we're inside a quoted
2676 while (*cur != '\0') {
2678 in_quote = !in_quote;
2679 if (*cur == ',' && !in_quote) {
2680 gchar *tmp = g_strdup(begin);
2682 tmp[cur-begin]='\0';
2685 while (*tmp == ' ' || *tmp == '\t')
2687 compose_add_header_entry(compose, header, tmp, pref_type);
2688 compose_entry_indicate(compose, tmp);
2695 gchar *tmp = g_strdup(begin);
2697 tmp[cur-begin]='\0';
2698 while (*tmp == ' ' || *tmp == '\t')
2700 compose_add_header_entry(compose, header, tmp, pref_type);
2701 compose_entry_indicate(compose, tmp);
2706 static void compose_entry_indicate(Compose *compose, const gchar *mailto)
2711 for (h_list = compose->header_list; h_list != NULL; h_list = h_list->next) {
2712 entry = GTK_ENTRY(((ComposeHeaderEntry *)h_list->data)->entry);
2713 if (gtk_entry_get_text(entry) &&
2714 !g_utf8_collate(gtk_entry_get_text(entry), mailto)) {
2715 gtk_widget_modify_base(
2716 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2717 GTK_STATE_NORMAL, &default_header_bgcolor);
2718 gtk_widget_modify_text(
2719 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2720 GTK_STATE_NORMAL, &default_header_color);
2725 void compose_toolbar_cb(gint action, gpointer data)
2727 ToolbarItem *toolbar_item = (ToolbarItem*)data;
2728 Compose *compose = (Compose*)toolbar_item->parent;
2730 cm_return_if_fail(compose != NULL);
2734 compose_send_cb(NULL, compose);
2737 compose_send_later_cb(NULL, compose);
2740 compose_draft(compose, COMPOSE_QUIT_EDITING);
2743 compose_insert_file_cb(NULL, compose);
2746 compose_attach_cb(NULL, compose);
2749 compose_insert_sig(compose, FALSE);
2752 compose_insert_sig(compose, TRUE);
2755 compose_ext_editor_cb(NULL, compose);
2757 case A_LINEWRAP_CURRENT:
2758 compose_beautify_paragraph(compose, NULL, TRUE);
2760 case A_LINEWRAP_ALL:
2761 compose_wrap_all_full(compose, TRUE);
2764 compose_address_cb(NULL, compose);
2767 case A_CHECK_SPELLING:
2768 compose_check_all(NULL, compose);
2771 case A_PRIVACY_SIGN:
2773 case A_PRIVACY_ENCRYPT:
2780 static MailField compose_entries_set(Compose *compose, const gchar *mailto, ComposeEntryType to_type)
2785 gchar *subject = NULL;
2789 gchar **attach = NULL;
2790 gchar *inreplyto = NULL;
2791 MailField mfield = NO_FIELD_PRESENT;
2793 /* get mailto parts but skip from */
2794 scan_mailto_url(mailto, NULL, &to, &cc, &bcc, &subject, &body, &attach, &inreplyto);
2797 compose_entry_append(compose, to, to_type, PREF_MAILTO);
2798 mfield = TO_FIELD_PRESENT;
2801 compose_entry_append(compose, cc, COMPOSE_CC, PREF_MAILTO);
2803 compose_entry_append(compose, bcc, COMPOSE_BCC, PREF_MAILTO);
2805 if (!g_utf8_validate (subject, -1, NULL)) {
2806 temp = g_locale_to_utf8 (subject, -1, NULL, &len, NULL);
2807 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), temp);
2810 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), subject);
2812 mfield = SUBJECT_FIELD_PRESENT;
2815 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2816 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2819 gboolean prev_autowrap = compose->autowrap;
2821 compose->autowrap = FALSE;
2823 mark = gtk_text_buffer_get_insert(buffer);
2824 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2826 if (!g_utf8_validate (body, -1, NULL)) {
2827 temp = g_locale_to_utf8 (body, -1, NULL, &len, NULL);
2828 gtk_text_buffer_insert(buffer, &iter, temp, -1);
2831 gtk_text_buffer_insert(buffer, &iter, body, -1);
2833 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
2835 compose->autowrap = prev_autowrap;
2836 if (compose->autowrap)
2837 compose_wrap_all(compose);
2838 mfield = BODY_FIELD_PRESENT;
2842 gint i = 0, att = 0;
2843 gchar *warn_files = NULL;
2844 while (attach[i] != NULL) {
2845 gchar *utf8_filename = conv_filename_to_utf8(attach[i]);
2846 if (utf8_filename) {
2847 if (compose_attach_append(compose, attach[i], utf8_filename, NULL, NULL)) {
2848 gchar *tmp = g_strdup_printf("%s%s\n",
2849 warn_files?warn_files:"",
2855 g_free(utf8_filename);
2857 alertpanel_error(_("Couldn't attach a file (charset conversion failed)."));
2862 alertpanel_notice(ngettext(
2863 "The following file has been attached: \n%s",
2864 "The following files have been attached: \n%s", att), warn_files);
2869 compose_entry_append(compose, inreplyto, COMPOSE_INREPLYTO, PREF_MAILTO);
2882 static gint compose_parse_header(Compose *compose, MsgInfo *msginfo)
2884 static HeaderEntry hentry[] = {
2885 {"Reply-To:", NULL, TRUE },
2886 {"Cc:", NULL, TRUE },
2887 {"References:", NULL, FALSE },
2888 {"Bcc:", NULL, TRUE },
2889 {"Newsgroups:", NULL, TRUE },
2890 {"Followup-To:", NULL, TRUE },
2891 {"List-Post:", NULL, FALSE },
2892 {"X-Priority:", NULL, FALSE },
2893 {NULL, NULL, FALSE }
2910 cm_return_val_if_fail(msginfo != NULL, -1);
2912 if ((fp = procmsg_open_message(msginfo, FALSE)) == NULL) return -1;
2913 procheader_get_header_fields(fp, hentry);
2916 if (hentry[H_REPLY_TO].body != NULL) {
2917 if (hentry[H_REPLY_TO].body[0] != '\0') {
2919 conv_unmime_header(hentry[H_REPLY_TO].body,
2922 g_free(hentry[H_REPLY_TO].body);
2923 hentry[H_REPLY_TO].body = NULL;
2925 if (hentry[H_CC].body != NULL) {
2926 compose->cc = conv_unmime_header(hentry[H_CC].body, NULL, TRUE);
2927 g_free(hentry[H_CC].body);
2928 hentry[H_CC].body = NULL;
2930 if (hentry[H_REFERENCES].body != NULL) {
2931 if (compose->mode == COMPOSE_REEDIT)
2932 compose->references = hentry[H_REFERENCES].body;
2934 compose->references = compose_parse_references
2935 (hentry[H_REFERENCES].body, msginfo->msgid);
2936 g_free(hentry[H_REFERENCES].body);
2938 hentry[H_REFERENCES].body = NULL;
2940 if (hentry[H_BCC].body != NULL) {
2941 if (compose->mode == COMPOSE_REEDIT)
2943 conv_unmime_header(hentry[H_BCC].body, NULL, TRUE);
2944 g_free(hentry[H_BCC].body);
2945 hentry[H_BCC].body = NULL;
2947 if (hentry[H_NEWSGROUPS].body != NULL) {
2948 compose->newsgroups = hentry[H_NEWSGROUPS].body;
2949 hentry[H_NEWSGROUPS].body = NULL;
2951 if (hentry[H_FOLLOWUP_TO].body != NULL) {
2952 if (hentry[H_FOLLOWUP_TO].body[0] != '\0') {
2953 compose->followup_to =
2954 conv_unmime_header(hentry[H_FOLLOWUP_TO].body,
2957 g_free(hentry[H_FOLLOWUP_TO].body);
2958 hentry[H_FOLLOWUP_TO].body = NULL;
2960 if (hentry[H_LIST_POST].body != NULL) {
2961 gchar *to = NULL, *start = NULL;
2963 extract_address(hentry[H_LIST_POST].body);
2964 if (hentry[H_LIST_POST].body[0] != '\0') {
2965 start = strstr(hentry[H_LIST_POST].body, "mailto:");
2967 scan_mailto_url(start ? start : hentry[H_LIST_POST].body,
2968 NULL, &to, NULL, NULL, NULL, NULL, NULL, NULL);
2971 g_free(compose->ml_post);
2972 compose->ml_post = to;
2975 g_free(hentry[H_LIST_POST].body);
2976 hentry[H_LIST_POST].body = NULL;
2979 /* CLAWS - X-Priority */
2980 if (compose->mode == COMPOSE_REEDIT)
2981 if (hentry[H_X_PRIORITY].body != NULL) {
2984 priority = atoi(hentry[H_X_PRIORITY].body);
2985 g_free(hentry[H_X_PRIORITY].body);
2987 hentry[H_X_PRIORITY].body = NULL;
2989 if (priority < PRIORITY_HIGHEST ||
2990 priority > PRIORITY_LOWEST)
2991 priority = PRIORITY_NORMAL;
2993 compose->priority = priority;
2996 if (compose->mode == COMPOSE_REEDIT) {
2997 if (msginfo->inreplyto && *msginfo->inreplyto)
2998 compose->inreplyto = g_strdup(msginfo->inreplyto);
3000 if (msginfo->msgid && *msginfo->msgid &&
3001 compose->folder != NULL &&
3002 compose->folder->stype == F_DRAFT)
3003 compose->msgid = g_strdup(msginfo->msgid);
3005 if (msginfo->msgid && *msginfo->msgid)
3006 compose->inreplyto = g_strdup(msginfo->msgid);
3008 if (!compose->references) {
3009 if (msginfo->msgid && *msginfo->msgid) {
3010 if (msginfo->inreplyto && *msginfo->inreplyto)
3011 compose->references =
3012 g_strdup_printf("<%s>\n\t<%s>",
3016 compose->references =
3017 g_strconcat("<", msginfo->msgid, ">",
3019 } else if (msginfo->inreplyto && *msginfo->inreplyto) {
3020 compose->references =
3021 g_strconcat("<", msginfo->inreplyto, ">",
3030 static gint compose_parse_manual_headers(Compose *compose, MsgInfo *msginfo, HeaderEntry *entries)
3035 cm_return_val_if_fail(msginfo != NULL, -1);
3037 if ((fp = procmsg_open_message(msginfo, FALSE)) == NULL) return -1;
3038 procheader_get_header_fields(fp, entries);
3042 while (he != NULL && he->name != NULL) {
3044 GtkListStore *model = NULL;
3046 debug_print("Adding manual header: %s with value %s\n", he->name, he->body);
3047 model = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(compose->header_last->combo)));
3048 COMBOBOX_ADD(model, he->name, COMPOSE_TO);
3049 gtk_combo_box_set_active_iter(GTK_COMBO_BOX(compose->header_last->combo), &iter);
3050 gtk_entry_set_text(GTK_ENTRY(compose->header_last->entry), he->body);
3057 static gchar *compose_parse_references(const gchar *ref, const gchar *msgid)
3059 GSList *ref_id_list, *cur;
3063 ref_id_list = references_list_append(NULL, ref);
3064 if (!ref_id_list) return NULL;
3065 if (msgid && *msgid)
3066 ref_id_list = g_slist_append(ref_id_list, g_strdup(msgid));
3071 for (cur = ref_id_list; cur != NULL; cur = cur->next)
3072 /* "<" + Message-ID + ">" + CR+LF+TAB */
3073 len += strlen((gchar *)cur->data) + 5;
3075 if (len > MAX_REFERENCES_LEN) {
3076 /* remove second message-ID */
3077 if (ref_id_list && ref_id_list->next &&
3078 ref_id_list->next->next) {
3079 g_free(ref_id_list->next->data);
3080 ref_id_list = g_slist_remove
3081 (ref_id_list, ref_id_list->next->data);
3083 slist_free_strings_full(ref_id_list);
3090 new_ref = g_string_new("");
3091 for (cur = ref_id_list; cur != NULL; cur = cur->next) {
3092 if (new_ref->len > 0)
3093 g_string_append(new_ref, "\n\t");
3094 g_string_append_printf(new_ref, "<%s>", (gchar *)cur->data);
3097 slist_free_strings_full(ref_id_list);
3099 new_ref_str = new_ref->str;
3100 g_string_free(new_ref, FALSE);
3105 static gchar *compose_quote_fmt(Compose *compose, MsgInfo *msginfo,
3106 const gchar *fmt, const gchar *qmark,
3107 const gchar *body, gboolean rewrap,
3108 gboolean need_unescape,
3109 const gchar *err_msg)
3111 MsgInfo* dummyinfo = NULL;
3112 gchar *quote_str = NULL;
3114 gboolean prev_autowrap;
3115 const gchar *trimmed_body = body;
3116 gint cursor_pos = -1;
3117 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3118 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3123 SIGNAL_BLOCK(buffer);
3126 dummyinfo = compose_msginfo_new_from_compose(compose);
3127 msginfo = dummyinfo;
3130 if (qmark != NULL) {
3132 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
3133 compose->gtkaspell);
3135 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
3137 quote_fmt_scan_string(qmark);
3140 buf = quote_fmt_get_buffer();
3143 alertpanel_error(_("The \"Quotation mark\" of the template is invalid."));
3145 Xstrdup_a(quote_str, buf, goto error)
3148 if (fmt && *fmt != '\0') {
3151 while (*trimmed_body == '\n')
3155 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE,
3156 compose->gtkaspell);
3158 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE);
3160 if (need_unescape) {
3163 /* decode \-escape sequences in the internal representation of the quote format */
3164 tmp = g_malloc(strlen(fmt)+1);
3165 pref_get_unescaped_pref(tmp, fmt);
3166 quote_fmt_scan_string(tmp);
3170 quote_fmt_scan_string(fmt);
3174 buf = quote_fmt_get_buffer();
3177 gint line = quote_fmt_get_line();
3178 alertpanel_error(err_msg, line);
3186 prev_autowrap = compose->autowrap;
3187 compose->autowrap = FALSE;
3189 mark = gtk_text_buffer_get_insert(buffer);
3190 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3191 if (g_utf8_validate(buf, -1, NULL)) {
3192 gtk_text_buffer_insert(buffer, &iter, buf, -1);
3194 gchar *tmpout = NULL;
3195 tmpout = conv_codeset_strdup
3196 (buf, conv_get_locale_charset_str_no_utf8(),
3198 if (!tmpout || !g_utf8_validate(tmpout, -1, NULL)) {
3200 tmpout = g_malloc(strlen(buf)*2+1);
3201 conv_localetodisp(tmpout, strlen(buf)*2+1, buf);
3203 gtk_text_buffer_insert(buffer, &iter, tmpout, -1);
3207 cursor_pos = quote_fmt_get_cursor_pos();
3208 if (cursor_pos == -1)
3209 cursor_pos = gtk_text_iter_get_offset(&iter);
3210 compose->set_cursor_pos = cursor_pos;
3212 gtk_text_buffer_get_start_iter(buffer, &iter);
3213 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
3214 gtk_text_buffer_place_cursor(buffer, &iter);
3216 compose->autowrap = prev_autowrap;
3217 if (compose->autowrap && rewrap)
3218 compose_wrap_all(compose);
3225 SIGNAL_UNBLOCK(buffer);
3227 procmsg_msginfo_free( &dummyinfo );
3232 /* if ml_post is of type addr@host and from is of type
3233 * addr-anything@host, return TRUE
3235 static gboolean is_subscription(const gchar *ml_post, const gchar *from)
3237 gchar *left_ml = NULL;
3238 gchar *right_ml = NULL;
3239 gchar *left_from = NULL;
3240 gchar *right_from = NULL;
3241 gboolean result = FALSE;
3243 if (!ml_post || !from)
3246 left_ml = g_strdup(ml_post);
3247 if (strstr(left_ml, "@")) {
3248 right_ml = strstr(left_ml, "@")+1;
3249 *(strstr(left_ml, "@")) = '\0';
3252 left_from = g_strdup(from);
3253 if (strstr(left_from, "@")) {
3254 right_from = strstr(left_from, "@")+1;
3255 *(strstr(left_from, "@")) = '\0';
3258 if (right_ml && right_from
3259 && !strncmp(left_from, left_ml, strlen(left_ml))
3260 && !strcmp(right_from, right_ml)) {
3269 static void compose_set_folder_prefs(Compose *compose, FolderItem *folder,
3270 gboolean respect_default_to)
3274 if (!folder || !folder->prefs)
3277 if (respect_default_to && folder->prefs->enable_default_to) {
3278 compose_entry_append(compose, folder->prefs->default_to,
3279 COMPOSE_TO, PREF_FOLDER);
3280 compose_entry_indicate(compose, folder->prefs->default_to);
3282 if (folder->prefs->enable_default_cc) {
3283 compose_entry_append(compose, folder->prefs->default_cc,
3284 COMPOSE_CC, PREF_FOLDER);
3285 compose_entry_indicate(compose, folder->prefs->default_cc);
3287 if (folder->prefs->enable_default_bcc) {
3288 compose_entry_append(compose, folder->prefs->default_bcc,
3289 COMPOSE_BCC, PREF_FOLDER);
3290 compose_entry_indicate(compose, folder->prefs->default_bcc);
3292 if (folder->prefs->enable_default_replyto) {
3293 compose_entry_append(compose, folder->prefs->default_replyto,
3294 COMPOSE_REPLYTO, PREF_FOLDER);
3295 compose_entry_indicate(compose, folder->prefs->default_replyto);
3299 static void compose_reply_set_subject(Compose *compose, MsgInfo *msginfo)
3304 if (!compose || !msginfo)
3307 if (msginfo->subject && *msginfo->subject) {
3308 buf = p = g_strdup(msginfo->subject);
3309 p += subject_get_prefix_length(p);
3310 memmove(buf, p, strlen(p) + 1);
3312 buf2 = g_strdup_printf("Re: %s", buf);
3313 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
3318 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), "Re: ");
3321 static void compose_reply_set_entry(Compose *compose, MsgInfo *msginfo,
3322 gboolean to_all, gboolean to_ml,
3324 gboolean followup_and_reply_to)
3326 GSList *cc_list = NULL;
3329 gchar *replyto = NULL;
3330 gchar *ac_email = NULL;
3332 gboolean reply_to_ml = FALSE;
3333 gboolean default_reply_to = FALSE;
3335 cm_return_if_fail(compose->account != NULL);
3336 cm_return_if_fail(msginfo != NULL);
3338 reply_to_ml = to_ml && compose->ml_post;
3340 default_reply_to = msginfo->folder &&
3341 msginfo->folder->prefs->enable_default_reply_to;
3343 if (compose->account->protocol != A_NNTP) {
3344 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
3346 if (reply_to_ml && !default_reply_to) {
3348 gboolean is_subscr = is_subscription(compose->ml_post,
3351 /* normal answer to ml post with a reply-to */
3352 compose_entry_append(compose,
3354 COMPOSE_TO, PREF_ML);
3355 if (compose->replyto)
3356 compose_entry_append(compose,
3358 COMPOSE_CC, PREF_ML);
3360 /* answer to subscription confirmation */
3361 if (compose->replyto)
3362 compose_entry_append(compose,
3364 COMPOSE_TO, PREF_ML);
3365 else if (msginfo->from)
3366 compose_entry_append(compose,
3368 COMPOSE_TO, PREF_ML);
3371 else if (!(to_all || to_sender) && default_reply_to) {
3372 compose_entry_append(compose,
3373 msginfo->folder->prefs->default_reply_to,
3374 COMPOSE_TO, PREF_FOLDER);
3375 compose_entry_indicate(compose,
3376 msginfo->folder->prefs->default_reply_to);
3382 compose_entry_append(compose, msginfo->from,
3383 COMPOSE_TO, PREF_NONE);
3385 Xstrdup_a(tmp1, msginfo->from, return);
3386 extract_address(tmp1);
3387 compose_entry_append(compose,
3388 (!account_find_from_address(tmp1, FALSE))
3391 COMPOSE_TO, PREF_NONE);
3392 if (compose->replyto)
3393 compose_entry_append(compose,
3395 COMPOSE_CC, PREF_NONE);
3397 if (!folder_has_parent_of_type(msginfo->folder, F_QUEUE) &&
3398 !folder_has_parent_of_type(msginfo->folder, F_OUTBOX) &&
3399 !folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
3400 if (compose->replyto) {
3401 compose_entry_append(compose,
3403 COMPOSE_TO, PREF_NONE);
3405 compose_entry_append(compose,
3406 msginfo->from ? msginfo->from : "",
3407 COMPOSE_TO, PREF_NONE);
3410 /* replying to own mail, use original recp */
3411 compose_entry_append(compose,
3412 msginfo->to ? msginfo->to : "",
3413 COMPOSE_TO, PREF_NONE);
3414 compose_entry_append(compose,
3415 msginfo->cc ? msginfo->cc : "",
3416 COMPOSE_CC, PREF_NONE);
3421 if (to_sender || (compose->followup_to &&
3422 !strncmp(compose->followup_to, "poster", 6)))
3423 compose_entry_append
3425 (compose->replyto ? compose->replyto :
3426 msginfo->from ? msginfo->from : ""),
3427 COMPOSE_TO, PREF_NONE);
3429 else if (followup_and_reply_to || to_all) {
3430 compose_entry_append
3432 (compose->replyto ? compose->replyto :
3433 msginfo->from ? msginfo->from : ""),
3434 COMPOSE_TO, PREF_NONE);
3436 compose_entry_append
3438 compose->followup_to ? compose->followup_to :
3439 compose->newsgroups ? compose->newsgroups : "",
3440 COMPOSE_NEWSGROUPS, PREF_NONE);
3442 compose_entry_append
3444 msginfo->cc ? msginfo->cc : "",
3445 COMPOSE_CC, PREF_NONE);
3448 compose_entry_append
3450 compose->followup_to ? compose->followup_to :
3451 compose->newsgroups ? compose->newsgroups : "",
3452 COMPOSE_NEWSGROUPS, PREF_NONE);
3454 compose_reply_set_subject(compose, msginfo);
3456 if (to_ml && compose->ml_post) return;
3457 if (!to_all || compose->account->protocol == A_NNTP) return;
3459 if (compose->replyto) {
3460 Xstrdup_a(replyto, compose->replyto, return);
3461 extract_address(replyto);
3463 if (msginfo->from) {
3464 Xstrdup_a(from, msginfo->from, return);
3465 extract_address(from);
3468 if (replyto && from)
3469 cc_list = address_list_append_with_comments(cc_list, from);
3470 if (to_all && msginfo->folder &&
3471 msginfo->folder->prefs->enable_default_reply_to)
3472 cc_list = address_list_append_with_comments(cc_list,
3473 msginfo->folder->prefs->default_reply_to);
3474 cc_list = address_list_append_with_comments(cc_list, msginfo->to);
3475 cc_list = address_list_append_with_comments(cc_list, compose->cc);
3477 ac_email = g_utf8_strdown(compose->account->address, -1);
3480 for (cur = cc_list; cur != NULL; cur = cur->next) {
3481 gchar *addr = g_utf8_strdown(cur->data, -1);
3482 extract_address(addr);
3484 if (strcmp(ac_email, addr))
3485 compose_entry_append(compose, (gchar *)cur->data,
3486 COMPOSE_CC, PREF_NONE);
3488 debug_print("Cc address same as compose account's, ignoring\n");
3493 slist_free_strings_full(cc_list);
3499 #define SET_ENTRY(entry, str) \
3502 gtk_entry_set_text(GTK_ENTRY(compose->entry), str); \
3505 #define SET_ADDRESS(type, str) \
3508 compose_entry_append(compose, str, type, PREF_NONE); \
3511 static void compose_reedit_set_entry(Compose *compose, MsgInfo *msginfo)
3513 cm_return_if_fail(msginfo != NULL);
3515 SET_ENTRY(subject_entry, msginfo->subject);
3516 SET_ENTRY(from_name, msginfo->from);
3517 SET_ADDRESS(COMPOSE_TO, msginfo->to);
3518 SET_ADDRESS(COMPOSE_CC, compose->cc);
3519 SET_ADDRESS(COMPOSE_BCC, compose->bcc);
3520 SET_ADDRESS(COMPOSE_REPLYTO, compose->replyto);
3521 SET_ADDRESS(COMPOSE_NEWSGROUPS, compose->newsgroups);
3522 SET_ADDRESS(COMPOSE_FOLLOWUPTO, compose->followup_to);
3524 compose_update_priority_menu_item(compose);
3525 compose_update_privacy_system_menu_item(compose, FALSE);
3526 compose_show_first_last_header(compose, TRUE);
3532 static void compose_insert_sig(Compose *compose, gboolean replace)
3534 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3535 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3537 GtkTextIter iter, iter_end;
3538 gint cur_pos, ins_pos;
3539 gboolean prev_autowrap;
3540 gboolean found = FALSE;
3541 gboolean exists = FALSE;
3543 cm_return_if_fail(compose->account != NULL);
3547 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3548 G_CALLBACK(compose_changed_cb),
3551 mark = gtk_text_buffer_get_insert(buffer);
3552 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3553 cur_pos = gtk_text_iter_get_offset (&iter);
3556 gtk_text_buffer_get_end_iter(buffer, &iter);
3558 exists = (compose->sig_str != NULL);
3561 GtkTextIter first_iter, start_iter, end_iter;
3563 gtk_text_buffer_get_start_iter(buffer, &first_iter);
3565 if (!exists || compose->sig_str[0] == '\0')
3568 found = gtk_text_iter_forward_to_tag_toggle(&first_iter,
3569 compose->signature_tag);
3572 /* include previous \n\n */
3573 gtk_text_iter_backward_chars(&first_iter, 1);
3574 start_iter = first_iter;
3575 end_iter = first_iter;
3577 found = gtk_text_iter_forward_to_tag_toggle(&end_iter,
3578 compose->signature_tag);
3579 found &= gtk_text_iter_forward_to_tag_toggle(&end_iter,
3580 compose->signature_tag);
3582 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
3588 g_free(compose->sig_str);
3589 compose->sig_str = account_get_signature_str(compose->account);
3591 cur_pos = gtk_text_iter_get_offset(&iter);
3593 if (!compose->sig_str || (replace && !compose->account->auto_sig)) {
3594 g_free(compose->sig_str);
3595 compose->sig_str = NULL;
3597 if (compose->sig_inserted == FALSE)
3598 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
3599 compose->sig_inserted = TRUE;
3601 cur_pos = gtk_text_iter_get_offset(&iter);
3602 gtk_text_buffer_insert(buffer, &iter, compose->sig_str, -1);
3604 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cur_pos);
3605 gtk_text_iter_forward_chars(&iter, 1);
3606 gtk_text_buffer_get_end_iter(buffer, &iter_end);
3607 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &iter_end);
3609 if (cur_pos > gtk_text_buffer_get_char_count (buffer))
3610 cur_pos = gtk_text_buffer_get_char_count (buffer);
3613 /* put the cursor where it should be
3614 * either where the quote_fmt says, either where it was */
3615 if (compose->set_cursor_pos < 0)
3616 gtk_text_buffer_get_iter_at_offset(buffer, &iter, ins_pos);
3618 gtk_text_buffer_get_iter_at_offset(buffer, &iter,
3619 compose->set_cursor_pos);
3621 compose->set_cursor_pos = -1;
3622 gtk_text_buffer_place_cursor(buffer, &iter);
3623 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3624 G_CALLBACK(compose_changed_cb),
3630 static ComposeInsertResult compose_insert_file(Compose *compose, const gchar *file)
3633 GtkTextBuffer *buffer;
3636 const gchar *cur_encoding;
3637 gchar buf[BUFFSIZE];
3640 gboolean prev_autowrap;
3644 GError *error = NULL;
3650 GString *file_contents = NULL;
3651 ComposeInsertResult result = COMPOSE_INSERT_SUCCESS;
3653 cm_return_val_if_fail(file != NULL, COMPOSE_INSERT_NO_FILE);
3655 /* get the size of the file we are about to insert */
3657 f = g_file_new_for_path(file);
3658 fi = g_file_query_info(f, "standard::size",
3659 G_FILE_QUERY_INFO_NONE, NULL, &error);
3661 if (error != NULL) {
3662 g_warning(error->message);
3664 g_error_free(error);
3668 ret = g_stat(file, &file_stat);
3671 gchar *shortfile = g_path_get_basename(file);
3672 alertpanel_error(_("Could not get size of file '%s'."), shortfile);
3674 return COMPOSE_INSERT_NO_FILE;
3675 } else if (prefs_common.warn_large_insert == TRUE) {
3677 size = g_file_info_get_size(fi);
3681 size = file_stat.st_size;
3684 /* ask user for confirmation if the file is large */
3685 if (prefs_common.warn_large_insert_size < 0 ||
3686 size > ((goffset) prefs_common.warn_large_insert_size * 1024)) {
3690 msg = g_strdup_printf(_("You are about to insert a file of %s "
3691 "in the message body. Are you sure you want to do that?"),
3692 to_human_readable(size));
3693 aval = alertpanel_full(_("Are you sure?"), msg, GTK_STOCK_CANCEL,
3694 _("_Insert"), NULL, ALERTFOCUS_SECOND, TRUE,
3695 NULL, ALERT_QUESTION);
3698 /* do we ask for confirmation next time? */
3699 if (aval & G_ALERTDISABLE) {
3700 /* no confirmation next time, disable feature in preferences */
3701 aval &= ~G_ALERTDISABLE;
3702 prefs_common.warn_large_insert = FALSE;
3705 /* abort file insertion if user canceled action */
3706 if (aval != G_ALERTALTERNATE) {
3707 return COMPOSE_INSERT_NO_FILE;
3713 if ((fp = claws_fopen(file, "rb")) == NULL) {
3714 FILE_OP_ERROR(file, "claws_fopen");
3715 return COMPOSE_INSERT_READ_ERROR;
3718 prev_autowrap = compose->autowrap;
3719 compose->autowrap = FALSE;
3721 text = GTK_TEXT_VIEW(compose->text);
3722 buffer = gtk_text_view_get_buffer(text);
3723 mark = gtk_text_buffer_get_insert(buffer);
3724 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3726 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3727 G_CALLBACK(text_inserted),
3730 cur_encoding = conv_get_locale_charset_str_no_utf8();
3732 file_contents = g_string_new("");
3733 while (claws_fgets(buf, sizeof(buf), fp) != NULL) {
3736 if (g_utf8_validate(buf, -1, NULL) == TRUE)
3737 str = g_strdup(buf);
3739 codeconv_set_strict(TRUE);
3740 str = conv_codeset_strdup
3741 (buf, cur_encoding, CS_INTERNAL);
3742 codeconv_set_strict(FALSE);
3745 result = COMPOSE_INSERT_INVALID_CHARACTER;
3751 /* strip <CR> if DOS/Windows file,
3752 replace <CR> with <LF> if Macintosh file. */
3755 if (len > 0 && str[len - 1] != '\n') {
3757 if (str[len] == '\r') str[len] = '\n';
3760 file_contents = g_string_append(file_contents, str);
3764 if (result == COMPOSE_INSERT_SUCCESS) {
3765 gtk_text_buffer_insert(buffer, &iter, file_contents->str, -1);
3767 compose_changed_cb(NULL, compose);
3768 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3769 G_CALLBACK(text_inserted),
3771 compose->autowrap = prev_autowrap;
3772 if (compose->autowrap)
3773 compose_wrap_all(compose);
3776 g_string_free(file_contents, TRUE);
3782 static gboolean compose_attach_append(Compose *compose, const gchar *file,
3783 const gchar *filename,
3784 const gchar *content_type,
3785 const gchar *charset)
3793 GtkListStore *store;
3795 gboolean has_binary = FALSE;
3797 if (!is_file_exist(file)) {
3798 gchar *file_from_uri = g_filename_from_uri(file, NULL, NULL);
3799 gboolean result = FALSE;
3800 if (file_from_uri && is_file_exist(file_from_uri)) {
3801 result = compose_attach_append(
3802 compose, file_from_uri,
3803 filename, content_type,
3806 g_free(file_from_uri);
3809 alertpanel_error("File %s doesn't exist or permission denied\n", filename);
3812 if ((size = get_file_size(file)) < 0) {
3813 alertpanel_error("Can't get file size of %s\n", filename);
3817 /* In batch mode, we allow 0-length files to be attached no questions asked */
3818 if (size == 0 && !compose->batch) {
3819 gchar * msg = g_strdup_printf(_("File %s is empty."), filename);
3820 AlertValue aval = alertpanel_full(_("Empty file"), msg,
3821 GTK_STOCK_CANCEL, _("_Attach anyway"), NULL,
3822 ALERTFOCUS_SECOND, FALSE, NULL, ALERT_WARNING);
3825 if (aval != G_ALERTALTERNATE) {
3829 if ((fp = claws_fopen(file, "rb")) == NULL) {
3830 alertpanel_error(_("Can't read %s."), filename);
3835 ainfo = g_new0(AttachInfo, 1);
3836 auto_ainfo = g_auto_pointer_new_with_free
3837 (ainfo, (GFreeFunc) compose_attach_info_free);
3838 ainfo->file = g_strdup(file);
3841 ainfo->content_type = g_strdup(content_type);
3842 if (!g_ascii_strcasecmp(content_type, "message/rfc822")) {
3844 MsgFlags flags = {0, 0};
3846 if (procmime_get_encoding_for_text_file(file, &has_binary) == ENC_7BIT)
3847 ainfo->encoding = ENC_7BIT;
3849 ainfo->encoding = ENC_8BIT;
3851 msginfo = procheader_parse_file(file, flags, FALSE, FALSE);
3852 if (msginfo && msginfo->subject)
3853 name = g_strdup(msginfo->subject);
3855 name = g_path_get_basename(filename ? filename : file);
3857 ainfo->name = g_strdup_printf(_("Message: %s"), name);
3859 procmsg_msginfo_free(&msginfo);
3861 if (!g_ascii_strncasecmp(content_type, "text/", 5)) {
3862 ainfo->charset = g_strdup(charset);
3863 ainfo->encoding = procmime_get_encoding_for_text_file(file, &has_binary);
3865 ainfo->encoding = ENC_BASE64;
3867 name = g_path_get_basename(filename ? filename : file);
3868 ainfo->name = g_strdup(name);
3872 ainfo->content_type = procmime_get_mime_type(file);
3873 if (!ainfo->content_type) {
3874 ainfo->content_type =
3875 g_strdup("application/octet-stream");
3876 ainfo->encoding = ENC_BASE64;
3877 } else if (!g_ascii_strncasecmp(ainfo->content_type, "text/", 5))
3879 procmime_get_encoding_for_text_file(file, &has_binary);
3881 ainfo->encoding = ENC_BASE64;
3882 name = g_path_get_basename(filename ? filename : file);
3883 ainfo->name = g_strdup(name);
3887 if (ainfo->name != NULL
3888 && !strcmp(ainfo->name, ".")) {
3889 g_free(ainfo->name);
3893 if (!strcmp(ainfo->content_type, "unknown") || has_binary) {
3894 g_free(ainfo->content_type);
3895 ainfo->content_type = g_strdup("application/octet-stream");
3896 g_free(ainfo->charset);
3897 ainfo->charset = NULL;
3900 ainfo->size = (goffset)size;
3901 size_text = to_human_readable((goffset)size);
3903 store = GTK_LIST_STORE(gtk_tree_view_get_model
3904 (GTK_TREE_VIEW(compose->attach_clist)));
3906 gtk_list_store_append(store, &iter);
3907 gtk_list_store_set(store, &iter,
3908 COL_MIMETYPE, ainfo->content_type,
3909 COL_SIZE, size_text,
3910 COL_NAME, ainfo->name,
3911 COL_CHARSET, ainfo->charset,
3913 COL_AUTODATA, auto_ainfo,
3916 g_auto_pointer_free(auto_ainfo);
3917 compose_attach_update_label(compose);
3921 void compose_use_signing(Compose *compose, gboolean use_signing)
3923 compose->use_signing = use_signing;
3924 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", use_signing);
3927 void compose_use_encryption(Compose *compose, gboolean use_encryption)
3929 compose->use_encryption = use_encryption;
3930 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", use_encryption);
3933 #define NEXT_PART_NOT_CHILD(info) \
3935 node = info->node; \
3936 while (node->children) \
3937 node = g_node_last_child(node); \
3938 info = procmime_mimeinfo_next((MimeInfo *)node->data); \
3941 static void compose_attach_parts(Compose *compose, MsgInfo *msginfo)
3945 MimeInfo *firsttext = NULL;
3946 MimeInfo *encrypted = NULL;
3949 const gchar *partname = NULL;
3951 mimeinfo = procmime_scan_message(msginfo);
3952 if (!mimeinfo) return;
3954 if (mimeinfo->node->children == NULL) {
3955 procmime_mimeinfo_free_all(&mimeinfo);
3959 /* find first content part */
3960 child = (MimeInfo *) mimeinfo->node->children->data;
3961 while (child && child->node->children && (child->type == MIMETYPE_MULTIPART))
3962 child = (MimeInfo *)child->node->children->data;
3965 if (child->type == MIMETYPE_TEXT) {
3967 debug_print("First text part found\n");
3968 } else if (compose->mode == COMPOSE_REEDIT &&
3969 child->type == MIMETYPE_APPLICATION &&
3970 !g_ascii_strcasecmp(child->subtype, "pgp-encrypted")) {
3971 encrypted = (MimeInfo *)child->node->parent->data;
3974 child = (MimeInfo *) mimeinfo->node->children->data;
3975 while (child != NULL) {
3978 if (child == encrypted) {
3979 /* skip this part of tree */
3980 NEXT_PART_NOT_CHILD(child);
3984 if (child->type == MIMETYPE_MULTIPART) {
3985 /* get the actual content */
3986 child = procmime_mimeinfo_next(child);
3990 if (child == firsttext) {
3991 child = procmime_mimeinfo_next(child);
3995 outfile = procmime_get_tmp_file_name(child);
3996 if ((err = procmime_get_part(outfile, child)) < 0)
3997 g_warning("Can't get the part of multipart message. (%s)", g_strerror(-err));
3999 gchar *content_type;
4001 content_type = procmime_get_content_type_str(child->type, child->subtype);
4003 /* if we meet a pgp signature, we don't attach it, but
4004 * we force signing. */
4005 if ((strcmp(content_type, "application/pgp-signature") &&
4006 strcmp(content_type, "application/pkcs7-signature") &&
4007 strcmp(content_type, "application/x-pkcs7-signature"))
4008 || compose->mode == COMPOSE_REDIRECT) {
4009 partname = procmime_mimeinfo_get_parameter(child, "filename");
4010 if (partname == NULL)
4011 partname = procmime_mimeinfo_get_parameter(child, "name");
4012 if (partname == NULL)
4014 compose_attach_append(compose, outfile,
4015 partname, content_type,
4016 procmime_mimeinfo_get_parameter(child, "charset"));
4018 compose_force_signing(compose, compose->account, NULL);
4020 g_free(content_type);
4023 NEXT_PART_NOT_CHILD(child);
4025 procmime_mimeinfo_free_all(&mimeinfo);
4028 #undef NEXT_PART_NOT_CHILD
4033 WAIT_FOR_INDENT_CHAR,
4034 WAIT_FOR_INDENT_CHAR_OR_SPACE,
4037 /* return indent length, we allow:
4038 indent characters followed by indent characters or spaces/tabs,
4039 alphabets and numbers immediately followed by indent characters,
4040 and the repeating sequences of the above
4041 If quote ends with multiple spaces, only the first one is included. */
4042 static gchar *compose_get_quote_str(GtkTextBuffer *buffer,
4043 const GtkTextIter *start, gint *len)
4045 GtkTextIter iter = *start;
4049 IndentState state = WAIT_FOR_INDENT_CHAR;
4052 gint alnum_count = 0;
4053 gint space_count = 0;
4056 if (prefs_common.quote_chars == NULL) {
4060 while (!gtk_text_iter_ends_line(&iter)) {
4061 wc = gtk_text_iter_get_char(&iter);
4062 if (g_unichar_iswide(wc))
4064 clen = g_unichar_to_utf8(wc, ch);
4068 is_indent = strchr(prefs_common.quote_chars, ch[0]) ? TRUE : FALSE;
4069 is_space = g_unichar_isspace(wc);
4071 if (state == WAIT_FOR_INDENT_CHAR) {
4072 if (!is_indent && !g_unichar_isalnum(wc))
4075 quote_len += alnum_count + space_count + 1;
4076 alnum_count = space_count = 0;
4077 state = WAIT_FOR_INDENT_CHAR_OR_SPACE;
4080 } else if (state == WAIT_FOR_INDENT_CHAR_OR_SPACE) {
4081 if (!is_indent && !is_space && !g_unichar_isalnum(wc))
4085 else if (is_indent) {
4086 quote_len += alnum_count + space_count + 1;
4087 alnum_count = space_count = 0;
4090 state = WAIT_FOR_INDENT_CHAR;
4094 gtk_text_iter_forward_char(&iter);
4097 if (quote_len > 0 && space_count > 0)
4103 if (quote_len > 0) {
4105 gtk_text_iter_forward_chars(&iter, quote_len);
4106 return gtk_text_buffer_get_text(buffer, start, &iter, FALSE);
4112 /* return >0 if the line is itemized */
4113 static int compose_itemized_length(GtkTextBuffer *buffer,
4114 const GtkTextIter *start)
4116 GtkTextIter iter = *start;
4121 if (gtk_text_iter_ends_line(&iter))
4126 wc = gtk_text_iter_get_char(&iter);
4127 if (!g_unichar_isspace(wc))
4129 gtk_text_iter_forward_char(&iter);
4130 if (gtk_text_iter_ends_line(&iter))
4134 clen = g_unichar_to_utf8(wc, ch);
4135 if (!((clen == 1 && strchr("*-+", ch[0])) ||
4137 wc == 0x2022 || /* BULLET */
4138 wc == 0x2023 || /* TRIANGULAR BULLET */
4139 wc == 0x2043 || /* HYPHEN BULLET */
4140 wc == 0x204c || /* BLACK LEFTWARDS BULLET */
4141 wc == 0x204d || /* BLACK RIGHTWARDS BULLET */
4142 wc == 0x2219 || /* BULLET OPERATOR */
4143 wc == 0x25d8 || /* INVERSE BULLET */
4144 wc == 0x25e6 || /* WHITE BULLET */
4145 wc == 0x2619 || /* REVERSED ROTATED FLORAL HEART BULLET */
4146 wc == 0x2765 || /* ROTATED HEAVY BLACK HEART BULLET */
4147 wc == 0x2767 || /* ROTATED FLORAL HEART BULLET */
4148 wc == 0x29be || /* CIRCLED WHITE BULLET */
4149 wc == 0x29bf /* CIRCLED BULLET */
4153 gtk_text_iter_forward_char(&iter);
4154 if (gtk_text_iter_ends_line(&iter))
4156 wc = gtk_text_iter_get_char(&iter);
4157 if (g_unichar_isspace(wc)) {
4163 /* return the string at the start of the itemization */
4164 static gchar * compose_get_itemized_chars(GtkTextBuffer *buffer,
4165 const GtkTextIter *start)
4167 GtkTextIter iter = *start;
4170 GString *item_chars = g_string_new("");
4173 if (gtk_text_iter_ends_line(&iter))
4178 wc = gtk_text_iter_get_char(&iter);
4179 if (!g_unichar_isspace(wc))
4181 gtk_text_iter_forward_char(&iter);
4182 if (gtk_text_iter_ends_line(&iter))
4184 g_string_append_unichar(item_chars, wc);
4187 str = item_chars->str;
4188 g_string_free(item_chars, FALSE);
4192 /* return the number of spaces at a line's start */
4193 static int compose_left_offset_length(GtkTextBuffer *buffer,
4194 const GtkTextIter *start)
4196 GtkTextIter iter = *start;
4199 if (gtk_text_iter_ends_line(&iter))
4203 wc = gtk_text_iter_get_char(&iter);
4204 if (!g_unichar_isspace(wc))
4207 gtk_text_iter_forward_char(&iter);
4208 if (gtk_text_iter_ends_line(&iter))
4212 gtk_text_iter_forward_char(&iter);
4213 if (gtk_text_iter_ends_line(&iter))
4218 static gboolean compose_get_line_break_pos(GtkTextBuffer *buffer,
4219 const GtkTextIter *start,
4220 GtkTextIter *break_pos,
4224 GtkTextIter iter = *start, line_end = *start;
4225 PangoLogAttr *attrs;
4232 gboolean can_break = FALSE;
4233 gboolean do_break = FALSE;
4234 gboolean was_white = FALSE;
4235 gboolean prev_dont_break = FALSE;
4237 gtk_text_iter_forward_to_line_end(&line_end);
4238 str = gtk_text_buffer_get_text(buffer, &iter, &line_end, FALSE);
4239 len = g_utf8_strlen(str, -1);
4243 g_warning("compose_get_line_break_pos: len = 0!");
4247 /* g_print("breaking line: %d: %s (len = %d)\n",
4248 gtk_text_iter_get_line(&iter), str, len); */
4250 attrs = g_new(PangoLogAttr, len + 1);
4252 pango_default_break(str, -1, NULL, attrs, len + 1);
4256 /* skip quote and leading spaces */
4257 for (i = 0; *p != '\0' && i < len; i++) {
4260 wc = g_utf8_get_char(p);
4261 if (i >= quote_len && !g_unichar_isspace(wc))
4263 if (g_unichar_iswide(wc))
4265 else if (*p == '\t')
4269 p = g_utf8_next_char(p);
4272 for (; *p != '\0' && i < len; i++) {
4273 PangoLogAttr *attr = attrs + i;
4274 gunichar wc = g_utf8_get_char(p);
4277 /* attr->is_line_break will be false for some characters that
4278 * we want to break a line before, like '/' or ':', so we
4279 * also allow breaking on any non-wide character. The
4280 * mentioned pango attribute is still useful to decide on
4281 * line breaks when wide characters are involved. */
4282 if ((!g_unichar_iswide(wc) || attr->is_line_break)
4283 && can_break && was_white && !prev_dont_break)
4286 was_white = attr->is_white;
4288 /* don't wrap URI */
4289 if ((uri_len = get_uri_len(p)) > 0) {
4291 if (pos > 0 && col > max_col) {
4301 if (g_unichar_iswide(wc)) {
4303 if (prev_dont_break && can_break && attr->is_line_break)
4305 } else if (*p == '\t')
4309 if (pos > 0 && col > max_col) {
4314 if (*p == '-' || *p == '/')
4315 prev_dont_break = TRUE;
4317 prev_dont_break = FALSE;
4319 p = g_utf8_next_char(p);
4323 /* debug_print("compose_get_line_break_pos(): do_break = %d, pos = %d, col = %d\n", do_break, pos, col); */
4328 *break_pos = *start;
4329 gtk_text_iter_set_line_offset(break_pos, pos);
4334 static gboolean compose_join_next_line(Compose *compose,
4335 GtkTextBuffer *buffer,
4337 const gchar *quote_str)
4339 GtkTextIter iter_ = *iter, cur, prev, next, end;
4340 PangoLogAttr attrs[3];
4342 gchar *next_quote_str;
4345 gboolean keep_cursor = FALSE;
4347 if (!gtk_text_iter_forward_line(&iter_) ||
4348 gtk_text_iter_ends_line(&iter_)) {
4351 next_quote_str = compose_get_quote_str(buffer, &iter_, "e_len);
4353 if ((quote_str || next_quote_str) &&
4354 g_strcmp0(quote_str, next_quote_str) != 0) {
4355 g_free(next_quote_str);
4358 g_free(next_quote_str);
4361 if (quote_len > 0) {
4362 gtk_text_iter_forward_chars(&end, quote_len);
4363 if (gtk_text_iter_ends_line(&end)) {
4368 /* don't join itemized lines */
4369 if (compose_itemized_length(buffer, &end) > 0) {
4373 /* don't join signature separator */
4374 if (compose_is_sig_separator(compose, buffer, &iter_)) {
4377 /* delete quote str */
4379 gtk_text_buffer_delete(buffer, &iter_, &end);
4381 /* don't join line breaks put by the user */
4383 gtk_text_iter_backward_char(&cur);
4384 if (gtk_text_iter_has_tag(&cur, compose->no_join_tag)) {
4385 gtk_text_iter_forward_char(&cur);
4389 gtk_text_iter_forward_char(&cur);
4390 /* delete linebreak and extra spaces */
4391 while (gtk_text_iter_backward_char(&cur)) {
4392 wc1 = gtk_text_iter_get_char(&cur);
4393 if (!g_unichar_isspace(wc1))
4398 while (!gtk_text_iter_ends_line(&cur)) {
4399 wc1 = gtk_text_iter_get_char(&cur);
4400 if (!g_unichar_isspace(wc1))
4402 gtk_text_iter_forward_char(&cur);
4405 if (!gtk_text_iter_equal(&prev, &next)) {
4408 mark = gtk_text_buffer_get_insert(buffer);
4409 gtk_text_buffer_get_iter_at_mark(buffer, &cur, mark);
4410 if (gtk_text_iter_equal(&prev, &cur))
4412 gtk_text_buffer_delete(buffer, &prev, &next);
4416 /* insert space if required */
4417 gtk_text_iter_backward_char(&prev);
4418 wc1 = gtk_text_iter_get_char(&prev);
4419 wc2 = gtk_text_iter_get_char(&next);
4420 gtk_text_iter_forward_char(&next);
4421 str = gtk_text_buffer_get_text(buffer, &prev, &next, FALSE);
4422 pango_default_break(str, -1, NULL, attrs, 3);
4423 if (!attrs[1].is_line_break ||
4424 (!g_unichar_iswide(wc1) || !g_unichar_iswide(wc2))) {
4425 gtk_text_buffer_insert(buffer, &iter_, " ", 1);
4427 gtk_text_iter_backward_char(&iter_);
4428 gtk_text_buffer_place_cursor(buffer, &iter_);
4437 #define ADD_TXT_POS(bp_, ep_, pti_) \
4438 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
4439 last = last->next; \
4440 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
4441 last->next = NULL; \
4443 g_warning("alloc error scanning URIs"); \
4446 static gboolean compose_beautify_paragraph(Compose *compose, GtkTextIter *par_iter, gboolean force)
4448 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4449 GtkTextBuffer *buffer;
4450 GtkTextIter iter, break_pos, end_of_line;
4451 gchar *quote_str = NULL;
4453 gboolean wrap_quote = force || prefs_common.linewrap_quote;
4454 gboolean prev_autowrap = compose->autowrap;
4455 gint startq_offset = -1, noq_offset = -1;
4456 gint uri_start = -1, uri_stop = -1;
4457 gint nouri_start = -1, nouri_stop = -1;
4458 gint num_blocks = 0;
4459 gint quotelevel = -1;
4460 gboolean modified = force;
4461 gboolean removed = FALSE;
4462 gboolean modified_before_remove = FALSE;
4464 gboolean start = TRUE;
4465 gint itemized_len = 0, rem_item_len = 0;
4466 gchar *itemized_chars = NULL;
4467 gboolean item_continuation = FALSE;
4472 if (compose->draft_timeout_tag == COMPOSE_DRAFT_TIMEOUT_FORBIDDEN) {
4476 compose->autowrap = FALSE;
4478 buffer = gtk_text_view_get_buffer(text);
4479 undo_wrapping(compose->undostruct, TRUE);
4484 mark = gtk_text_buffer_get_insert(buffer);
4485 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
4489 if (compose->draft_timeout_tag == COMPOSE_DRAFT_TIMEOUT_FORBIDDEN) {
4490 if (gtk_text_iter_ends_line(&iter)) {
4491 while (gtk_text_iter_ends_line(&iter) &&
4492 gtk_text_iter_forward_line(&iter))
4495 while (gtk_text_iter_backward_line(&iter)) {
4496 if (gtk_text_iter_ends_line(&iter)) {
4497 gtk_text_iter_forward_line(&iter);
4503 /* move to line start */
4504 gtk_text_iter_set_line_offset(&iter, 0);
4507 itemized_len = compose_itemized_length(buffer, &iter);
4509 if (!itemized_len) {
4510 itemized_len = compose_left_offset_length(buffer, &iter);
4511 item_continuation = TRUE;
4515 itemized_chars = compose_get_itemized_chars(buffer, &iter);
4517 /* go until paragraph end (empty line) */
4518 while (start || !gtk_text_iter_ends_line(&iter)) {
4519 gchar *scanpos = NULL;
4520 /* parse table - in order of priority */
4522 const gchar *needle; /* token */
4524 /* token search function */
4525 gchar *(*search) (const gchar *haystack,
4526 const gchar *needle);
4527 /* part parsing function */
4528 gboolean (*parse) (const gchar *start,
4529 const gchar *scanpos,
4533 /* part to URI function */
4534 gchar *(*build_uri) (const gchar *bp,
4538 static struct table parser[] = {
4539 {"http://", strcasestr, get_uri_part, make_uri_string},
4540 {"https://", strcasestr, get_uri_part, make_uri_string},
4541 {"ftp://", strcasestr, get_uri_part, make_uri_string},
4542 {"sftp://", strcasestr, get_uri_part, make_uri_string},
4543 {"gopher://",strcasestr, get_uri_part, make_uri_string},
4544 {"www.", strcasestr, get_uri_part, make_http_string},
4545 {"mailto:", strcasestr, get_uri_part, make_uri_string},
4546 {"@", strcasestr, get_email_part, make_email_string}
4548 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
4549 gint last_index = PARSE_ELEMS;
4551 gchar *o_walk = NULL, *walk = NULL, *bp = NULL, *ep = NULL;
4555 if (!prev_autowrap && num_blocks == 0) {
4557 g_signal_handlers_block_by_func(G_OBJECT(buffer),
4558 G_CALLBACK(text_inserted),
4561 if (gtk_text_iter_has_tag(&iter, compose->no_wrap_tag) && !force)
4564 uri_start = uri_stop = -1;
4566 quote_str = compose_get_quote_str(buffer, &iter, "e_len);
4569 /* debug_print("compose_beautify_paragraph(): quote_str = '%s'\n", quote_str); */
4570 if (startq_offset == -1)
4571 startq_offset = gtk_text_iter_get_offset(&iter);
4572 quotelevel = get_quote_level(quote_str, prefs_common.quote_chars);
4573 if (quotelevel > 2) {
4574 /* recycle colors */
4575 if (prefs_common.recycle_quote_colors)
4584 if (startq_offset == -1)
4585 noq_offset = gtk_text_iter_get_offset(&iter);
4589 if (prev_autowrap == FALSE && !force && !wrap_quote) {
4592 if (gtk_text_iter_ends_line(&iter)) {
4594 } else if (compose_get_line_break_pos(buffer, &iter, &break_pos,
4595 prefs_common.linewrap_len,
4597 GtkTextIter prev, next, cur;
4598 if (prev_autowrap != FALSE || force) {
4599 compose->automatic_break = TRUE;
4601 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4602 compose->automatic_break = FALSE;
4603 if (itemized_len && compose->autoindent) {
4604 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4605 if (!item_continuation)
4606 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4608 } else if (quote_str && wrap_quote) {
4609 compose->automatic_break = TRUE;
4611 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4612 compose->automatic_break = FALSE;
4613 if (itemized_len && compose->autoindent) {
4614 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4615 if (!item_continuation)
4616 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4620 /* remove trailing spaces */
4622 rem_item_len = itemized_len;
4623 while (compose->autoindent && rem_item_len-- > 0)
4624 gtk_text_iter_backward_char(&cur);
4625 gtk_text_iter_backward_char(&cur);
4628 while (!gtk_text_iter_starts_line(&cur)) {
4631 gtk_text_iter_backward_char(&cur);
4632 wc = gtk_text_iter_get_char(&cur);
4633 if (!g_unichar_isspace(wc))
4637 if (!gtk_text_iter_equal(&prev, &next)) {
4638 gtk_text_buffer_delete(buffer, &prev, &next);
4640 gtk_text_iter_forward_char(&break_pos);
4644 gtk_text_buffer_insert(buffer, &break_pos,
4648 modified |= compose_join_next_line(compose, buffer, &iter, quote_str);
4650 /* move iter to current line start */
4651 gtk_text_iter_set_line_offset(&iter, 0);
4658 /* move iter to next line start */
4664 if (!prev_autowrap && num_blocks > 0) {
4666 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
4667 G_CALLBACK(text_inserted),
4671 while (!gtk_text_iter_ends_line(&end_of_line)) {
4672 gtk_text_iter_forward_char(&end_of_line);
4674 o_walk = walk = gtk_text_buffer_get_text(buffer, &iter, &end_of_line, FALSE);
4676 nouri_start = gtk_text_iter_get_offset(&iter);
4677 nouri_stop = gtk_text_iter_get_offset(&end_of_line);
4679 walk_pos = gtk_text_iter_get_offset(&iter);
4680 /* FIXME: this looks phony. scanning for anything in the parse table */
4681 for (n = 0; n < PARSE_ELEMS; n++) {
4684 tmp = parser[n].search(walk, parser[n].needle);
4686 if (scanpos == NULL || tmp < scanpos) {
4695 /* check if URI can be parsed */
4696 if (parser[last_index].parse(walk, scanpos, (const gchar **)&bp,
4697 (const gchar **)&ep, FALSE)
4698 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
4702 strlen(parser[last_index].needle);
4705 uri_start = walk_pos + (bp - o_walk);
4706 uri_stop = walk_pos + (ep - o_walk);
4710 gtk_text_iter_forward_line(&iter);
4713 if (startq_offset != -1) {
4714 GtkTextIter startquote, endquote;
4715 gtk_text_buffer_get_iter_at_offset(
4716 buffer, &startquote, startq_offset);
4719 switch (quotelevel) {
4721 if (!gtk_text_iter_has_tag(&startquote, compose->quote0_tag) ||
4722 !gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) {
4723 gtk_text_buffer_apply_tag_by_name(
4724 buffer, "quote0", &startquote, &endquote);
4725 gtk_text_buffer_remove_tag_by_name(
4726 buffer, "quote1", &startquote, &endquote);
4727 gtk_text_buffer_remove_tag_by_name(
4728 buffer, "quote2", &startquote, &endquote);
4733 if (!gtk_text_iter_has_tag(&startquote, compose->quote1_tag) ||
4734 !gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) {
4735 gtk_text_buffer_apply_tag_by_name(
4736 buffer, "quote1", &startquote, &endquote);
4737 gtk_text_buffer_remove_tag_by_name(
4738 buffer, "quote0", &startquote, &endquote);
4739 gtk_text_buffer_remove_tag_by_name(
4740 buffer, "quote2", &startquote, &endquote);
4745 if (!gtk_text_iter_has_tag(&startquote, compose->quote2_tag) ||
4746 !gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag)) {
4747 gtk_text_buffer_apply_tag_by_name(
4748 buffer, "quote2", &startquote, &endquote);
4749 gtk_text_buffer_remove_tag_by_name(
4750 buffer, "quote0", &startquote, &endquote);
4751 gtk_text_buffer_remove_tag_by_name(
4752 buffer, "quote1", &startquote, &endquote);
4758 } else if (noq_offset != -1) {
4759 GtkTextIter startnoquote, endnoquote;
4760 gtk_text_buffer_get_iter_at_offset(
4761 buffer, &startnoquote, noq_offset);
4764 if ((gtk_text_iter_has_tag(&startnoquote, compose->quote0_tag)
4765 && gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) ||
4766 (gtk_text_iter_has_tag(&startnoquote, compose->quote1_tag)
4767 && gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) ||
4768 (gtk_text_iter_has_tag(&startnoquote, compose->quote2_tag)
4769 && gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag))) {
4770 gtk_text_buffer_remove_tag_by_name(
4771 buffer, "quote0", &startnoquote, &endnoquote);
4772 gtk_text_buffer_remove_tag_by_name(
4773 buffer, "quote1", &startnoquote, &endnoquote);
4774 gtk_text_buffer_remove_tag_by_name(
4775 buffer, "quote2", &startnoquote, &endnoquote);
4781 if (uri_start != nouri_start && uri_stop != nouri_stop) {
4782 GtkTextIter nouri_start_iter, nouri_end_iter;
4783 gtk_text_buffer_get_iter_at_offset(
4784 buffer, &nouri_start_iter, nouri_start);
4785 gtk_text_buffer_get_iter_at_offset(
4786 buffer, &nouri_end_iter, nouri_stop);
4787 if (gtk_text_iter_has_tag(&nouri_start_iter, compose->uri_tag) &&
4788 gtk_text_iter_has_tag(&nouri_end_iter, compose->uri_tag)) {
4789 gtk_text_buffer_remove_tag_by_name(
4790 buffer, "link", &nouri_start_iter, &nouri_end_iter);
4791 modified_before_remove = modified;
4796 if (uri_start >= 0 && uri_stop > 0) {
4797 GtkTextIter uri_start_iter, uri_end_iter, back;
4798 gtk_text_buffer_get_iter_at_offset(
4799 buffer, &uri_start_iter, uri_start);
4800 gtk_text_buffer_get_iter_at_offset(
4801 buffer, &uri_end_iter, uri_stop);
4802 back = uri_end_iter;
4803 gtk_text_iter_backward_char(&back);
4804 if (!gtk_text_iter_has_tag(&uri_start_iter, compose->uri_tag) ||
4805 !gtk_text_iter_has_tag(&back, compose->uri_tag)) {
4806 gtk_text_buffer_apply_tag_by_name(
4807 buffer, "link", &uri_start_iter, &uri_end_iter);
4809 if (removed && !modified_before_remove) {
4815 /* debug_print("not modified, out after %d lines\n", lines); */
4819 /* debug_print("modified, out after %d lines\n", lines); */
4821 g_free(itemized_chars);
4824 undo_wrapping(compose->undostruct, FALSE);
4825 compose->autowrap = prev_autowrap;
4830 void compose_action_cb(void *data)
4832 Compose *compose = (Compose *)data;
4833 compose_wrap_all(compose);
4836 static void compose_wrap_all(Compose *compose)
4838 compose_wrap_all_full(compose, FALSE);
4841 static void compose_wrap_all_full(Compose *compose, gboolean force)
4843 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4844 GtkTextBuffer *buffer;
4846 gboolean modified = TRUE;
4848 buffer = gtk_text_view_get_buffer(text);
4850 gtk_text_buffer_get_start_iter(buffer, &iter);
4852 undo_wrapping(compose->undostruct, TRUE);
4854 while (!gtk_text_iter_is_end(&iter) && modified)
4855 modified = compose_beautify_paragraph(compose, &iter, force);
4857 undo_wrapping(compose->undostruct, FALSE);
4861 static void compose_set_title(Compose *compose)
4867 edited = compose->modified ? _(" [Edited]") : "";
4869 subject = gtk_editable_get_chars(
4870 GTK_EDITABLE(compose->subject_entry), 0, -1);
4872 #ifndef GENERIC_UMPC
4873 if (subject && strlen(subject))
4874 str = g_strdup_printf(_("%s - Compose message%s"),
4877 str = g_strdup_printf(_("[no subject] - Compose message%s"), edited);
4879 str = g_strdup(_("Compose message"));
4882 gtk_window_set_title(GTK_WINDOW(compose->window), str);
4888 * compose_current_mail_account:
4890 * Find a current mail account (the currently selected account, or the
4891 * default account, if a news account is currently selected). If a
4892 * mail account cannot be found, display an error message.
4894 * Return value: Mail account, or NULL if not found.
4896 static PrefsAccount *
4897 compose_current_mail_account(void)
4901 if (cur_account && cur_account->protocol != A_NNTP)
4904 ac = account_get_default();
4905 if (!ac || ac->protocol == A_NNTP) {
4906 alertpanel_error(_("Account for sending mail is not specified.\n"
4907 "Please select a mail account before sending."));
4914 #define QUOTE_IF_REQUIRED(out, str) \
4916 if (*str != '"' && strpbrk(str, ",.:;[]<>()@\\\"")) { \
4920 len = strlen(str) + 3; \
4921 if ((__tmp = alloca(len)) == NULL) { \
4922 g_warning("can't allocate memory"); \
4923 g_string_free(header, TRUE); \
4926 g_snprintf(__tmp, len, "\"%s\"", str); \
4931 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4932 g_warning("can't allocate memory"); \
4933 g_string_free(header, TRUE); \
4936 strcpy(__tmp, str); \
4942 #define QUOTE_IF_REQUIRED_NORMAL(out, str, errret) \
4944 if (*str != '"' && strpbrk(str, ",.:;[]<>()@\\\"")) { \
4948 len = strlen(str) + 3; \
4949 if ((__tmp = alloca(len)) == NULL) { \
4950 g_warning("can't allocate memory"); \
4953 g_snprintf(__tmp, len, "\"%s\"", str); \
4958 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4959 g_warning("can't allocate memory"); \
4962 strcpy(__tmp, str); \
4968 static void compose_select_account(Compose *compose, PrefsAccount *account,
4971 gchar *from = NULL, *header = NULL;
4972 ComposeHeaderEntry *header_entry;
4975 cm_return_if_fail(account != NULL);
4977 compose->account = account;
4978 if (account->name && *account->name) {
4980 QUOTE_IF_REQUIRED_NORMAL(buf, account->name, return);
4981 qbuf = escape_internal_quotes(buf, '"');
4982 from = g_strdup_printf("%s <%s>",
4983 qbuf, account->address);
4986 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4988 from = g_strdup_printf("<%s>",
4990 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4995 compose_set_title(compose);
4997 compose_activate_privacy_system(compose, account, FALSE);
4999 if (account->default_sign && privacy_system_can_sign(compose->privacy_system) &&
5000 compose->mode != COMPOSE_REDIRECT)
5001 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", TRUE);
5003 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", FALSE);
5004 if (account->default_encrypt && privacy_system_can_encrypt(compose->privacy_system) &&
5005 compose->mode != COMPOSE_REDIRECT)
5006 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", TRUE);
5008 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", FALSE);
5010 if (!init && compose->mode != COMPOSE_REDIRECT) {
5011 undo_block(compose->undostruct);
5012 compose_insert_sig(compose, TRUE);
5013 undo_unblock(compose->undostruct);
5016 header_entry = (ComposeHeaderEntry *) compose->header_list->data;
5017 if (gtk_combo_box_get_active_iter(GTK_COMBO_BOX(header_entry->combo), &iter))
5018 gtk_tree_model_get(gtk_combo_box_get_model(GTK_COMBO_BOX(
5019 header_entry->combo)), &iter, COMBOBOX_TEXT, &header, -1);
5021 if (header && !strlen(gtk_entry_get_text(GTK_ENTRY(header_entry->entry)))) {
5022 if (account->protocol == A_NNTP) {
5023 if (!strcmp(header, _("To:")))
5024 combobox_select_by_text(
5025 GTK_COMBO_BOX(header_entry->combo),
5028 if (!strcmp(header, _("Newsgroups:")))
5029 combobox_select_by_text(
5030 GTK_COMBO_BOX(header_entry->combo),
5038 /* use account's dict info if set */
5039 if (compose->gtkaspell) {
5040 if (account->enable_default_dictionary)
5041 gtkaspell_change_dict(compose->gtkaspell,
5042 account->default_dictionary, FALSE);
5043 if (account->enable_default_alt_dictionary)
5044 gtkaspell_change_alt_dict(compose->gtkaspell,
5045 account->default_alt_dictionary);
5046 if (account->enable_default_dictionary
5047 || account->enable_default_alt_dictionary)
5048 compose_spell_menu_changed(compose);
5053 gboolean compose_check_for_valid_recipient(Compose *compose) {
5054 gchar *recipient_headers_mail[] = {"To:", "Cc:", "Bcc:", NULL};
5055 gchar *recipient_headers_news[] = {"Newsgroups:", NULL};
5056 gboolean recipient_found = FALSE;
5060 /* free to and newsgroup list */
5061 slist_free_strings_full(compose->to_list);
5062 compose->to_list = NULL;
5064 slist_free_strings_full(compose->newsgroup_list);
5065 compose->newsgroup_list = NULL;
5067 /* search header entries for to and newsgroup entries */
5068 for (list = compose->header_list; list; list = list->next) {
5071 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5072 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5075 if (entry[0] != '\0') {
5076 for (strptr = recipient_headers_mail; *strptr != NULL; strptr++) {
5077 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
5078 compose->to_list = address_list_append(compose->to_list, entry);
5079 recipient_found = TRUE;
5082 for (strptr = recipient_headers_news; *strptr != NULL; strptr++) {
5083 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
5084 compose->newsgroup_list = newsgroup_list_append(compose->newsgroup_list, entry);
5085 recipient_found = TRUE;
5092 return recipient_found;
5095 static gboolean compose_check_for_set_recipients(Compose *compose)
5097 if (compose->account->set_autocc && compose->account->auto_cc) {
5098 gboolean found_other = FALSE;
5100 /* search header entries for to and newsgroup entries */
5101 for (list = compose->header_list; list; list = list->next) {
5104 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5105 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5108 if (strcmp(entry, compose->account->auto_cc)
5109 || strcmp(header, prefs_common_translated_header_name("Cc:"))) {
5120 if (compose->batch) {
5121 gtk_widget_show_all(compose->window);
5123 text = g_strdup_printf(_("The only recipient is the default '%s' address. Send anyway?"),
5124 prefs_common_translated_header_name("Cc"));
5125 aval = alertpanel(_("Send"),
5127 GTK_STOCK_CANCEL, _("_Send"), NULL, ALERTFOCUS_SECOND);
5129 if (aval != G_ALERTALTERNATE)
5133 if (compose->account->set_autobcc && compose->account->auto_bcc) {
5134 gboolean found_other = FALSE;
5136 /* search header entries for to and newsgroup entries */
5137 for (list = compose->header_list; list; list = list->next) {
5140 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5141 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5144 if (strcmp(entry, compose->account->auto_bcc)
5145 || strcmp(header, prefs_common_translated_header_name("Bcc:"))) {
5157 if (compose->batch) {
5158 gtk_widget_show_all(compose->window);
5160 text = g_strdup_printf(_("The only recipient is the default '%s' address. Send anyway?"),
5161 prefs_common_translated_header_name("Bcc"));
5162 aval = alertpanel(_("Send"),
5164 GTK_STOCK_CANCEL, _("_Send"), NULL, ALERTFOCUS_SECOND);
5166 if (aval != G_ALERTALTERNATE)
5173 static gboolean compose_check_entries(Compose *compose, gboolean check_everything)
5177 if (compose_check_for_valid_recipient(compose) == FALSE) {
5178 if (compose->batch) {
5179 gtk_widget_show_all(compose->window);
5181 alertpanel_error(_("Recipient is not specified."));
5185 if (compose_check_for_set_recipients(compose) == FALSE) {
5189 if (!compose->batch && prefs_common.warn_empty_subj == TRUE) {
5190 str = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5191 if (*str == '\0' && check_everything == TRUE &&
5192 compose->mode != COMPOSE_REDIRECT) {
5196 message = g_strdup_printf(_("Subject is empty. %s"),
5197 compose->sending?_("Send it anyway?"):
5198 _("Queue it anyway?"));
5200 aval = alertpanel_full(compose->sending?_("Send"):_("Send later"), message,
5201 GTK_STOCK_CANCEL, compose->sending?_("_Send"):_("_Queue"), NULL,
5202 ALERTFOCUS_FIRST, TRUE, NULL, ALERT_QUESTION);
5204 if (aval & G_ALERTDISABLE) {
5205 aval &= ~G_ALERTDISABLE;
5206 prefs_common.warn_empty_subj = FALSE;
5208 if (aval != G_ALERTALTERNATE)
5213 if (!compose->batch && prefs_common.warn_sending_many_recipients_num > 0
5214 && check_everything == TRUE) {
5218 /* count To and Cc recipients */
5219 for (list = compose->header_list; list; list = list->next) {
5223 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5224 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5227 if ((entry[0] != '\0') &&
5228 (!strcmp(header, prefs_common_translated_header_name("To:")) ||
5229 !strcmp(header, prefs_common_translated_header_name("Cc:")))) {
5235 if (cnt > prefs_common.warn_sending_many_recipients_num) {
5239 message = g_strdup_printf(_("Sending to %d recipients. %s"), cnt,
5240 compose->sending?_("Send it anyway?"):
5241 _("Queue it anyway?"));
5243 aval = alertpanel_full(compose->sending?_("Send"):_("Send later"), message,
5244 GTK_STOCK_CANCEL, compose->sending?_("_Send"):_("_Queue"), NULL,
5245 ALERTFOCUS_FIRST, TRUE, NULL, ALERT_QUESTION);
5247 if (aval & G_ALERTDISABLE) {
5248 aval &= ~G_ALERTDISABLE;
5249 prefs_common.warn_sending_many_recipients_num = 0;
5251 if (aval != G_ALERTALTERNATE)
5256 if (check_everything && hooks_invoke(COMPOSE_CHECK_BEFORE_SEND_HOOKLIST, compose))
5262 static void _display_queue_error(ComposeQueueResult val)
5265 case COMPOSE_QUEUE_SUCCESS:
5267 case COMPOSE_QUEUE_ERROR_NO_MSG:
5268 alertpanel_error(_("Could not queue message."));
5270 case COMPOSE_QUEUE_ERROR_WITH_ERRNO:
5271 alertpanel_error(_("Could not queue message:\n\n%s."),
5274 case COMPOSE_QUEUE_ERROR_SIGNING_FAILED:
5275 alertpanel_error(_("Could not queue message for sending:\n\n"
5276 "Signature failed: %s"),
5277 privacy_peek_error() ? privacy_get_error() : _("Unknown error"));
5279 case COMPOSE_QUEUE_ERROR_ENCRYPT_FAILED:
5280 alertpanel_error(_("Could not queue message for sending:\n\n"
5281 "Encryption failed: %s"),
5282 privacy_peek_error() ? privacy_get_error() : _("Unknown error"));
5284 case COMPOSE_QUEUE_ERROR_CHAR_CONVERSION:
5285 alertpanel_error(_("Could not queue message for sending:\n\n"
5286 "Charset conversion failed."));
5288 case COMPOSE_QUEUE_ERROR_NO_ENCRYPTION_KEY:
5289 alertpanel_error(_("Could not queue message for sending:\n\n"
5290 "Couldn't get recipient encryption key."));
5292 case COMPOSE_QUEUE_SIGNING_CANCELLED:
5293 debug_print("signing cancelled\n");
5296 /* unhandled error */
5297 debug_print("oops, unhandled compose_queue() return value %d\n",
5303 gint compose_send(Compose *compose)
5306 FolderItem *folder = NULL;
5307 ComposeQueueResult val = COMPOSE_QUEUE_ERROR_NO_MSG;
5308 gchar *msgpath = NULL;
5309 gboolean discard_window = FALSE;
5310 gchar *errstr = NULL;
5311 gchar *tmsgid = NULL;
5312 MainWindow *mainwin = mainwindow_get_mainwindow();
5313 gboolean queued_removed = FALSE;
5315 if (prefs_common.send_dialog_invisible
5316 || compose->batch == TRUE)
5317 discard_window = TRUE;
5319 compose_allow_user_actions (compose, FALSE);
5320 compose->sending = TRUE;
5322 if (compose_check_entries(compose, TRUE) == FALSE) {
5323 if (compose->batch) {
5324 gtk_widget_show_all(compose->window);
5330 val = compose_queue(compose, &msgnum, &folder, &msgpath, TRUE);
5332 if (val != COMPOSE_QUEUE_SUCCESS) {
5333 if (compose->batch) {
5334 gtk_widget_show_all(compose->window);
5337 _display_queue_error(val);
5342 tmsgid = compose->msgid ? g_strdup(compose->msgid) : NULL;
5343 if (discard_window) {
5344 compose->sending = FALSE;
5345 compose_close(compose);
5346 /* No more compose access in the normal codepath
5347 * after this point! */
5352 alertpanel_error(_("The message was queued but could not be "
5353 "sent.\nUse \"Send queued messages\" from "
5354 "the main window to retry."));
5355 if (!discard_window) {
5362 if (msgpath == NULL) {
5363 msgpath = folder_item_fetch_msg(folder, msgnum);
5364 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5367 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5368 claws_unlink(msgpath);
5371 if (!discard_window) {
5373 if (!queued_removed)
5374 folder_item_remove_msg(folder, msgnum);
5375 folder_item_scan(folder);
5377 /* make sure we delete that */
5378 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5380 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5381 folder_item_remove_msg(folder, tmp->msgnum);
5382 procmsg_msginfo_free(&tmp);
5389 if (!queued_removed)
5390 folder_item_remove_msg(folder, msgnum);
5391 folder_item_scan(folder);
5393 /* make sure we delete that */
5394 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5396 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5397 folder_item_remove_msg(folder, tmp->msgnum);
5398 procmsg_msginfo_free(&tmp);
5401 if (!discard_window) {
5402 compose->sending = FALSE;
5403 compose_allow_user_actions (compose, TRUE);
5404 compose_close(compose);
5408 alertpanel_error_log(_("%s\nUse \"Send queued messages\" from "
5409 "the main window to retry."), errstr);
5412 alertpanel_error_log(_("The message was queued but could not be "
5413 "sent.\nUse \"Send queued messages\" from "
5414 "the main window to retry."));
5416 if (!discard_window) {
5425 toolbar_main_set_sensitive(mainwin);
5426 main_window_set_menu_sensitive(mainwin);
5432 compose_allow_user_actions (compose, TRUE);
5433 compose->sending = FALSE;
5434 compose->modified = TRUE;
5435 toolbar_main_set_sensitive(mainwin);
5436 main_window_set_menu_sensitive(mainwin);
5441 static gboolean compose_use_attach(Compose *compose)
5443 GtkTreeModel *model = gtk_tree_view_get_model
5444 (GTK_TREE_VIEW(compose->attach_clist));
5445 return gtk_tree_model_iter_n_children(model, NULL) > 0;
5448 static gint compose_redirect_write_headers_from_headerlist(Compose *compose,
5451 gchar buf[BUFFSIZE];
5453 gboolean first_to_address;
5454 gboolean first_cc_address;
5456 ComposeHeaderEntry *headerentry;
5457 const gchar *headerentryname;
5458 const gchar *cc_hdr;
5459 const gchar *to_hdr;
5460 gboolean err = FALSE;
5462 debug_print("Writing redirect header\n");
5464 cc_hdr = prefs_common_translated_header_name("Cc:");
5465 to_hdr = prefs_common_translated_header_name("To:");
5467 first_to_address = TRUE;
5468 for (list = compose->header_list; list; list = list->next) {
5469 headerentry = ((ComposeHeaderEntry *)list->data);
5470 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5472 if (g_utf8_collate(headerentryname, to_hdr) == 0) {
5473 const gchar *entstr = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5474 Xstrdup_a(str, entstr, return -1);
5476 if (str[0] != '\0') {
5477 compose_convert_header
5478 (compose, buf, sizeof(buf), str,
5479 strlen("Resent-To") + 2, TRUE);
5481 if (first_to_address) {
5482 err |= (fprintf(fp, "Resent-To: ") < 0);
5483 first_to_address = FALSE;
5485 err |= (fprintf(fp, ",") < 0);
5487 err |= (fprintf(fp, "%s", buf) < 0);
5491 if (!first_to_address) {
5492 err |= (fprintf(fp, "\n") < 0);
5495 first_cc_address = TRUE;
5496 for (list = compose->header_list; list; list = list->next) {
5497 headerentry = ((ComposeHeaderEntry *)list->data);
5498 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5500 if (g_utf8_collate(headerentryname, cc_hdr) == 0) {
5501 const gchar *strg = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5502 Xstrdup_a(str, strg, return -1);
5504 if (str[0] != '\0') {
5505 compose_convert_header
5506 (compose, buf, sizeof(buf), str,
5507 strlen("Resent-Cc") + 2, TRUE);
5509 if (first_cc_address) {
5510 err |= (fprintf(fp, "Resent-Cc: ") < 0);
5511 first_cc_address = FALSE;
5513 err |= (fprintf(fp, ",") < 0);
5515 err |= (fprintf(fp, "%s", buf) < 0);
5519 if (!first_cc_address) {
5520 err |= (fprintf(fp, "\n") < 0);
5523 return (err ? -1:0);
5526 static gint compose_redirect_write_headers(Compose *compose, FILE *fp)
5528 gchar date[RFC822_DATE_BUFFSIZE];
5529 gchar buf[BUFFSIZE];
5531 const gchar *entstr;
5532 /* struct utsname utsbuf; */
5533 gboolean err = FALSE;
5535 cm_return_val_if_fail(fp != NULL, -1);
5536 cm_return_val_if_fail(compose->account != NULL, -1);
5537 cm_return_val_if_fail(compose->account->address != NULL, -1);
5540 if (prefs_common.hide_timezone)
5541 get_rfc822_date_hide_tz(date, sizeof(date));
5543 get_rfc822_date(date, sizeof(date));
5544 err |= (fprintf(fp, "Resent-Date: %s\n", date) < 0);
5547 if (compose->account->name && *compose->account->name) {
5548 compose_convert_header
5549 (compose, buf, sizeof(buf), compose->account->name,
5550 strlen("From: "), TRUE);
5551 err |= (fprintf(fp, "Resent-From: %s <%s>\n",
5552 buf, compose->account->address) < 0);
5554 err |= (fprintf(fp, "Resent-From: %s\n", compose->account->address) < 0);
5557 entstr = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5558 if (*entstr != '\0') {
5559 Xstrdup_a(str, entstr, return -1);
5562 compose_convert_header(compose, buf, sizeof(buf), str,
5563 strlen("Subject: "), FALSE);
5564 err |= (fprintf(fp, "Subject: %s\n", buf) < 0);
5568 /* Resent-Message-ID */
5569 if (compose->account->gen_msgid) {
5570 gchar *addr = prefs_account_generate_msgid(compose->account);
5571 err |= (fprintf(fp, "Resent-Message-ID: <%s>\n", addr) < 0);
5573 g_free(compose->msgid);
5574 compose->msgid = addr;
5576 compose->msgid = NULL;
5579 if (compose_redirect_write_headers_from_headerlist(compose, fp))
5582 /* separator between header and body */
5583 err |= (claws_fputs("\n", fp) == EOF);
5585 return (err ? -1:0);
5588 static gint compose_redirect_write_to_file(Compose *compose, FILE *fdest)
5593 gchar rewrite_buf[BUFFSIZE];
5595 gboolean skip = FALSE;
5596 gboolean err = FALSE;
5597 gchar *not_included[]={
5598 "Return-Path:", "Delivered-To:", "Received:",
5599 "Subject:", "X-UIDL:", "AF:",
5600 "NF:", "PS:", "SRH:",
5601 "SFN:", "DSR:", "MID:",
5602 "CFG:", "PT:", "S:",
5603 "RQ:", "SSV:", "NSV:",
5604 "SSH:", "R:", "MAID:",
5605 "NAID:", "RMID:", "FMID:",
5606 "SCF:", "RRCPT:", "NG:",
5607 "X-Claws-Privacy", "X-Claws-Sign:", "X-Claws-Encrypt",
5608 "X-Claws-End-Special-Headers:", "X-Claws-Account-Id:",
5609 "X-Sylpheed-Privacy", "X-Sylpheed-Sign:", "X-Sylpheed-Encrypt",
5610 "X-Sylpheed-End-Special-Headers:", "X-Sylpheed-Account-Id:",
5611 "X-Claws-Auto-Wrapping:", "X-Claws-Auto-Indent:",
5616 if ((fp = claws_fopen(compose->redirect_filename, "rb")) == NULL) {
5617 FILE_OP_ERROR(compose->redirect_filename, "claws_fopen");
5621 while ((ret = procheader_get_one_field_asis(&buf, fp)) != -1) {
5623 for (i = 0; not_included[i] != NULL; i++) {
5624 if (g_ascii_strncasecmp(buf, not_included[i],
5625 strlen(not_included[i])) == 0) {
5635 if (claws_fputs(buf, fdest) == -1) {
5641 if (!prefs_common.redirect_keep_from) {
5642 if (g_ascii_strncasecmp(buf, "From:",
5643 strlen("From:")) == 0) {
5644 err |= (claws_fputs(" (by way of ", fdest) == EOF);
5645 if (compose->account->name
5646 && *compose->account->name) {
5647 gchar buffer[BUFFSIZE];
5649 compose_convert_header
5650 (compose, buffer, sizeof(buffer),
5651 compose->account->name,
5654 err |= (fprintf(fdest, "%s <%s>",
5656 compose->account->address) < 0);
5658 err |= (fprintf(fdest, "%s",
5659 compose->account->address) < 0);
5660 err |= (claws_fputs(")", fdest) == EOF);
5666 if (claws_fputs("\n", fdest) == -1)
5673 if (compose_redirect_write_headers(compose, fdest))
5676 while ((len = claws_fread(rewrite_buf, sizeof(gchar), sizeof(rewrite_buf), fp)) > 0) {
5677 if (claws_fwrite(rewrite_buf, sizeof(gchar), len, fdest) != len)
5691 static gint compose_write_to_file(Compose *compose, FILE *fp, gint action, gboolean attach_parts)
5693 GtkTextBuffer *buffer;
5694 GtkTextIter start, end, tmp;
5695 gchar *chars, *tmp_enc_file, *content;
5697 const gchar *out_codeset;
5698 EncodingType encoding = ENC_UNKNOWN;
5699 MimeInfo *mimemsg, *mimetext;
5701 const gchar *src_codeset = CS_INTERNAL;
5702 gchar *from_addr = NULL;
5703 gchar *from_name = NULL;
5706 if (action == COMPOSE_WRITE_FOR_SEND) {
5707 attach_parts = TRUE;
5709 /* We're sending the message, generate a Message-ID
5711 if (compose->msgid == NULL &&
5712 compose->account->gen_msgid) {
5713 compose->msgid = prefs_account_generate_msgid(compose->account);
5717 /* create message MimeInfo */
5718 mimemsg = procmime_mimeinfo_new();
5719 mimemsg->type = MIMETYPE_MESSAGE;
5720 mimemsg->subtype = g_strdup("rfc822");
5721 mimemsg->content = MIMECONTENT_MEM;
5722 mimemsg->tmp = TRUE; /* must free content later */
5723 mimemsg->data.mem = compose_get_header(compose);
5725 /* Create text part MimeInfo */
5726 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5727 gtk_text_buffer_get_end_iter(buffer, &end);
5730 /* We make sure that there is a newline at the end. */
5731 if (action == COMPOSE_WRITE_FOR_SEND && gtk_text_iter_backward_char(&tmp)) {
5732 chars = gtk_text_buffer_get_text(buffer, &tmp, &end, FALSE);
5733 if (*chars != '\n') {
5734 gtk_text_buffer_insert(buffer, &end, "\n", 1);
5739 /* get all composed text */
5740 gtk_text_buffer_get_start_iter(buffer, &start);
5741 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5743 out_codeset = conv_get_charset_str(compose->out_encoding);
5745 if (!out_codeset && is_ascii_str(chars)) {
5746 out_codeset = CS_US_ASCII;
5747 } else if (prefs_common.outgoing_fallback_to_ascii &&
5748 is_ascii_str(chars)) {
5749 out_codeset = CS_US_ASCII;
5750 encoding = ENC_7BIT;
5754 gchar *test_conv_global_out = NULL;
5755 gchar *test_conv_reply = NULL;
5757 /* automatic mode. be automatic. */
5758 codeconv_set_strict(TRUE);
5760 out_codeset = conv_get_outgoing_charset_str();
5762 debug_print("trying to convert to %s\n", out_codeset);
5763 test_conv_global_out = conv_codeset_strdup(chars, src_codeset, out_codeset);
5766 if (!test_conv_global_out && compose->orig_charset
5767 && strcmp(compose->orig_charset, CS_US_ASCII)) {
5768 out_codeset = compose->orig_charset;
5769 debug_print("failure; trying to convert to %s\n", out_codeset);
5770 test_conv_reply = conv_codeset_strdup(chars, src_codeset, out_codeset);
5773 if (!test_conv_global_out && !test_conv_reply) {
5775 out_codeset = CS_INTERNAL;
5776 debug_print("failure; finally using %s\n", out_codeset);
5778 g_free(test_conv_global_out);
5779 g_free(test_conv_reply);
5780 codeconv_set_strict(FALSE);
5783 if (encoding == ENC_UNKNOWN) {
5784 if (prefs_common.encoding_method == CTE_BASE64)
5785 encoding = ENC_BASE64;
5786 else if (prefs_common.encoding_method == CTE_QUOTED_PRINTABLE)
5787 encoding = ENC_QUOTED_PRINTABLE;
5788 else if (prefs_common.encoding_method == CTE_8BIT)
5789 encoding = ENC_8BIT;
5791 encoding = procmime_get_encoding_for_charset(out_codeset);
5794 debug_print("src encoding = %s, out encoding = %s, transfer encoding = %s\n",
5795 src_codeset, out_codeset, procmime_get_encoding_str(encoding));
5797 if (action == COMPOSE_WRITE_FOR_SEND) {
5798 codeconv_set_strict(TRUE);
5799 buf = conv_codeset_strdup(chars, src_codeset, out_codeset);
5800 codeconv_set_strict(FALSE);
5805 msg = g_strdup_printf(_("Can't convert the character encoding of the message \n"
5806 "to the specified %s charset.\n"
5807 "Send it as %s?"), out_codeset, src_codeset);
5808 aval = alertpanel_full(_("Error"), msg, GTK_STOCK_CANCEL,
5809 _("_Send"), NULL, ALERTFOCUS_SECOND, FALSE,
5813 if (aval != G_ALERTALTERNATE) {
5815 return COMPOSE_QUEUE_ERROR_CHAR_CONVERSION;
5818 out_codeset = src_codeset;
5824 out_codeset = src_codeset;
5829 if (prefs_common.rewrite_first_from && (encoding == ENC_8BIT || encoding == ENC_7BIT)) {
5830 if (!strncmp(buf, "From ", sizeof("From ")-1) ||
5831 strstr(buf, "\nFrom ") != NULL) {
5832 encoding = ENC_QUOTED_PRINTABLE;
5836 mimetext = procmime_mimeinfo_new();
5837 mimetext->content = MIMECONTENT_MEM;
5838 mimetext->tmp = TRUE; /* must free content later */
5839 /* dup'ed because procmime_encode_content can turn it into a tmpfile
5840 * and free the data, which we need later. */
5841 mimetext->data.mem = g_strdup(buf);
5842 mimetext->type = MIMETYPE_TEXT;
5843 mimetext->subtype = g_strdup("plain");
5844 g_hash_table_insert(mimetext->typeparameters, g_strdup("charset"),
5845 g_strdup(out_codeset));
5847 /* protect trailing spaces when signing message */
5848 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5849 privacy_system_can_sign(compose->privacy_system)) {
5850 encoding = ENC_QUOTED_PRINTABLE;
5854 debug_print("main text: %Id bytes encoded as %s in %d\n",
5856 debug_print("main text: %zd bytes encoded as %s in %d\n",
5858 strlen(buf), out_codeset, encoding);
5860 /* check for line length limit */
5861 if (action == COMPOSE_WRITE_FOR_SEND &&
5862 encoding != ENC_QUOTED_PRINTABLE && encoding != ENC_BASE64 &&
5863 check_line_length(buf, 1000, &line) < 0) {
5866 msg = g_strdup_printf
5867 (_("Line %d exceeds the line length limit (998 bytes).\n"
5868 "The contents of the message might be broken on the way to the delivery.\n"
5870 "Send it anyway?"), line + 1);
5871 aval = alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, GTK_STOCK_OK, NULL,
5874 if (aval != G_ALERTALTERNATE) {
5876 return COMPOSE_QUEUE_ERROR_NO_MSG;
5880 if (encoding != ENC_UNKNOWN)
5881 procmime_encode_content(mimetext, encoding);
5883 /* append attachment parts */
5884 if (compose_use_attach(compose) && attach_parts) {
5885 MimeInfo *mimempart;
5886 gchar *boundary = NULL;
5887 mimempart = procmime_mimeinfo_new();
5888 mimempart->content = MIMECONTENT_EMPTY;
5889 mimempart->type = MIMETYPE_MULTIPART;
5890 mimempart->subtype = g_strdup("mixed");
5894 boundary = generate_mime_boundary(NULL);
5895 } while (strstr(buf, boundary) != NULL);
5897 g_hash_table_insert(mimempart->typeparameters, g_strdup("boundary"),
5900 mimetext->disposition = DISPOSITIONTYPE_INLINE;
5902 g_node_append(mimempart->node, mimetext->node);
5903 g_node_append(mimemsg->node, mimempart->node);
5905 if (compose_add_attachments(compose, mimempart) < 0)
5906 return COMPOSE_QUEUE_ERROR_NO_MSG;
5908 g_node_append(mimemsg->node, mimetext->node);
5912 if (strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) != 0) {
5913 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
5914 /* extract name and address */
5915 if (strstr(spec, " <") && strstr(spec, ">")) {
5916 from_addr = g_strdup(strrchr(spec, '<')+1);
5917 *(strrchr(from_addr, '>')) = '\0';
5918 from_name = g_strdup(spec);
5919 *(strrchr(from_name, '<')) = '\0';
5926 /* sign message if sending */
5927 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5928 privacy_system_can_sign(compose->privacy_system))
5929 if (!privacy_sign(compose->privacy_system, mimemsg,
5930 compose->account, from_addr)) {
5933 if (!privacy_peek_error())
5934 return COMPOSE_QUEUE_SIGNING_CANCELLED;
5936 return COMPOSE_QUEUE_ERROR_SIGNING_FAILED;
5941 if (compose->use_encryption) {
5942 if (compose->encdata != NULL &&
5943 strcmp(compose->encdata, "_DONT_ENCRYPT_")) {
5945 /* First, write an unencrypted copy and save it to outbox, if
5946 * user wants that. */
5947 if (compose->account->save_encrypted_as_clear_text) {
5948 debug_print("saving sent message unencrypted...\n");
5949 FILE *tmpfp = get_tmpfile_in_dir(get_mime_tmp_dir(), &tmp_enc_file);
5951 claws_fclose(tmpfp);
5953 /* fp now points to a file with headers written,
5954 * let's make a copy. */
5956 content = file_read_stream_to_str(fp);
5958 str_write_to_file(content, tmp_enc_file, TRUE);
5961 /* Now write the unencrypted body. */
5962 if ((tmpfp = claws_fopen(tmp_enc_file, "a")) != NULL) {
5963 procmime_write_mimeinfo(mimemsg, tmpfp);
5964 claws_fclose(tmpfp);
5966 outbox = folder_find_item_from_identifier(compose_get_save_to(compose));
5968 outbox = folder_get_default_outbox();
5970 procmsg_save_to_outbox(outbox, tmp_enc_file, TRUE);
5971 claws_unlink(tmp_enc_file);
5973 g_warning("Can't open file '%s'", tmp_enc_file);
5976 g_warning("couldn't get tempfile");
5979 if (!privacy_encrypt(compose->privacy_system, mimemsg, compose->encdata)) {
5980 debug_print("Couldn't encrypt mime structure: %s.\n",
5981 privacy_get_error());
5982 return COMPOSE_QUEUE_ERROR_ENCRYPT_FAILED;
5987 procmime_write_mimeinfo(mimemsg, fp);
5989 procmime_mimeinfo_free_all(&mimemsg);
5994 static gint compose_write_body_to_file(Compose *compose, const gchar *file)
5996 GtkTextBuffer *buffer;
5997 GtkTextIter start, end;
6002 if ((fp = claws_fopen(file, "wb")) == NULL) {
6003 FILE_OP_ERROR(file, "claws_fopen");
6007 /* chmod for security */
6008 if (change_file_mode_rw(fp, file) < 0) {
6009 FILE_OP_ERROR(file, "chmod");
6010 g_warning("can't change file mode");
6013 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
6014 gtk_text_buffer_get_start_iter(buffer, &start);
6015 gtk_text_buffer_get_end_iter(buffer, &end);
6016 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
6018 chars = conv_codeset_strdup
6019 (tmp, CS_INTERNAL, conv_get_locale_charset_str());
6028 len = strlen(chars);
6029 if (claws_fwrite(chars, sizeof(gchar), len, fp) != len) {
6030 FILE_OP_ERROR(file, "claws_fwrite");
6039 if (claws_safe_fclose(fp) == EOF) {
6040 FILE_OP_ERROR(file, "claws_fclose");
6047 static gint compose_remove_reedit_target(Compose *compose, gboolean force)
6050 MsgInfo *msginfo = compose->targetinfo;
6052 cm_return_val_if_fail(compose->mode == COMPOSE_REEDIT, -1);
6053 if (!msginfo) return -1;
6055 if (!force && MSG_IS_LOCKED(msginfo->flags))
6058 item = msginfo->folder;
6059 cm_return_val_if_fail(item != NULL, -1);
6061 if (procmsg_msg_exist(msginfo) &&
6062 (folder_has_parent_of_type(item, F_QUEUE) ||
6063 folder_has_parent_of_type(item, F_DRAFT)
6064 || msginfo == compose->autosaved_draft)) {
6065 if (folder_item_remove_msg(item, msginfo->msgnum) < 0) {
6066 g_warning("can't remove the old message");
6069 debug_print("removed reedit target %d\n", msginfo->msgnum);
6076 static void compose_remove_draft(Compose *compose)
6079 MsgInfo *msginfo = compose->targetinfo;
6080 drafts = account_get_special_folder(compose->account, F_DRAFT);
6082 if (procmsg_msg_exist(msginfo)) {
6083 folder_item_remove_msg(drafts, msginfo->msgnum);
6088 ComposeQueueResult compose_queue(Compose *compose, gint *msgnum, FolderItem **item, gchar **msgpath,
6089 gboolean remove_reedit_target)
6091 return compose_queue_sub (compose, msgnum, item, msgpath, FALSE, remove_reedit_target);
6094 static gboolean compose_warn_encryption(Compose *compose)
6096 const gchar *warning = privacy_get_encrypt_warning(compose->privacy_system);
6097 AlertValue val = G_ALERTALTERNATE;
6099 if (warning == NULL)
6102 val = alertpanel_full(_("Encryption warning"), warning,
6103 GTK_STOCK_CANCEL, _("C_ontinue"), NULL, ALERTFOCUS_SECOND,
6104 TRUE, NULL, ALERT_WARNING);
6105 if (val & G_ALERTDISABLE) {
6106 val &= ~G_ALERTDISABLE;
6107 if (val == G_ALERTALTERNATE)
6108 privacy_inhibit_encrypt_warning(compose->privacy_system,
6112 if (val == G_ALERTALTERNATE) {
6119 static ComposeQueueResult compose_queue_sub(Compose *compose, gint *msgnum, FolderItem **item,
6120 gchar **msgpath, gboolean perform_checks,
6121 gboolean remove_reedit_target)
6128 PrefsAccount *mailac = NULL, *newsac = NULL;
6129 gboolean err = FALSE;
6131 debug_print("queueing message...\n");
6132 cm_return_val_if_fail(compose->account != NULL, -1);
6134 if (compose_check_entries(compose, perform_checks) == FALSE) {
6135 if (compose->batch) {
6136 gtk_widget_show_all(compose->window);
6138 return COMPOSE_QUEUE_ERROR_NO_MSG;
6141 if (!compose->to_list && !compose->newsgroup_list) {
6142 g_warning("can't get recipient list.");
6143 return COMPOSE_QUEUE_ERROR_NO_MSG;
6146 if (compose->to_list) {
6147 if (compose->account->protocol != A_NNTP)
6148 mailac = compose->account;
6149 else if (cur_account && cur_account->protocol != A_NNTP)
6150 mailac = cur_account;
6151 else if (!(mailac = compose_current_mail_account())) {
6152 alertpanel_error(_("No account for sending mails available!"));
6153 return COMPOSE_QUEUE_ERROR_NO_MSG;
6157 if (compose->newsgroup_list) {
6158 if (compose->account->protocol == A_NNTP)
6159 newsac = compose->account;
6161 alertpanel_error(_("Selected account isn't NNTP: Posting is impossible."));
6162 return COMPOSE_QUEUE_ERROR_NO_MSG;
6166 /* write queue header */
6167 tmp = g_strdup_printf("%s%cqueue.%p%08x", get_tmp_dir(),
6168 G_DIR_SEPARATOR, compose, (guint) rand());
6169 debug_print("queuing to %s\n", tmp);
6170 if ((fp = claws_fopen(tmp, "w+b")) == NULL) {
6171 FILE_OP_ERROR(tmp, "claws_fopen");
6173 return COMPOSE_QUEUE_ERROR_WITH_ERRNO;
6176 if (change_file_mode_rw(fp, tmp) < 0) {
6177 FILE_OP_ERROR(tmp, "chmod");
6178 g_warning("can't change file mode");
6181 /* queueing variables */
6182 err |= (fprintf(fp, "AF:\n") < 0);
6183 err |= (fprintf(fp, "NF:0\n") < 0);
6184 err |= (fprintf(fp, "PS:10\n") < 0);
6185 err |= (fprintf(fp, "SRH:1\n") < 0);
6186 err |= (fprintf(fp, "SFN:\n") < 0);
6187 err |= (fprintf(fp, "DSR:\n") < 0);
6189 err |= (fprintf(fp, "MID:<%s>\n", compose->msgid) < 0);
6191 err |= (fprintf(fp, "MID:\n") < 0);
6192 err |= (fprintf(fp, "CFG:\n") < 0);
6193 err |= (fprintf(fp, "PT:0\n") < 0);
6194 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
6195 err |= (fprintf(fp, "RQ:\n") < 0);
6197 err |= (fprintf(fp, "SSV:%s\n", mailac->smtp_server) < 0);
6199 err |= (fprintf(fp, "SSV:\n") < 0);
6201 err |= (fprintf(fp, "NSV:%s\n", newsac->nntp_server) < 0);
6203 err |= (fprintf(fp, "NSV:\n") < 0);
6204 err |= (fprintf(fp, "SSH:\n") < 0);
6205 /* write recipient list */
6206 if (compose->to_list) {
6207 err |= (fprintf(fp, "R:<%s>", (gchar *)compose->to_list->data) < 0);
6208 for (cur = compose->to_list->next; cur != NULL;
6210 err |= (fprintf(fp, ",<%s>", (gchar *)cur->data) < 0);
6211 err |= (fprintf(fp, "\n") < 0);
6213 /* write newsgroup list */
6214 if (compose->newsgroup_list) {
6215 err |= (fprintf(fp, "NG:") < 0);
6216 err |= (fprintf(fp, "%s", (gchar *)compose->newsgroup_list->data) < 0);
6217 for (cur = compose->newsgroup_list->next; cur != NULL; cur = cur->next)
6218 err |= (fprintf(fp, ",%s", (gchar *)cur->data) < 0);
6219 err |= (fprintf(fp, "\n") < 0);
6223 err |= (fprintf(fp, "MAID:%d\n", mailac->account_id) < 0);
6225 err |= (fprintf(fp, "NAID:%d\n", newsac->account_id) < 0);
6228 if (compose->privacy_system != NULL) {
6229 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
6230 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
6231 if (compose->use_encryption) {
6232 if (!compose_warn_encryption(compose)) {
6236 return COMPOSE_QUEUE_ERROR_NO_MSG;
6238 if (mailac && mailac->encrypt_to_self) {
6239 GSList *tmp_list = g_slist_copy(compose->to_list);
6240 tmp_list = g_slist_append(tmp_list, compose->account->address);
6241 compose->encdata = privacy_get_encrypt_data(compose->privacy_system, tmp_list);
6242 g_slist_free(tmp_list);
6244 compose->encdata = privacy_get_encrypt_data(compose->privacy_system, compose->to_list);
6246 if (compose->encdata != NULL) {
6247 if (strcmp(compose->encdata, "_DONT_ENCRYPT_")) {
6248 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
6249 err |= (fprintf(fp, "X-Claws-Encrypt-Data:%s\n",
6250 compose->encdata) < 0);
6251 } /* else we finally dont want to encrypt */
6253 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
6254 /* and if encdata was null, it means there's been a problem in
6257 g_warning("failed to write queue message");
6261 return COMPOSE_QUEUE_ERROR_NO_ENCRYPTION_KEY;
6266 /* Save copy folder */
6267 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
6268 gchar *savefolderid;
6270 savefolderid = compose_get_save_to(compose);
6271 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
6272 g_free(savefolderid);
6274 /* Save copy folder */
6275 if (compose->return_receipt) {
6276 err |= (fprintf(fp, "RRCPT:1\n") < 0);
6278 /* Message-ID of message replying to */
6279 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
6280 gchar *folderid = NULL;
6282 if (compose->replyinfo->folder)
6283 folderid = folder_item_get_identifier(compose->replyinfo->folder);
6284 if (folderid == NULL)
6285 folderid = g_strdup("NULL");
6287 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
6290 /* Message-ID of message forwarding to */
6291 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
6292 gchar *folderid = NULL;
6294 if (compose->fwdinfo->folder)
6295 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
6296 if (folderid == NULL)
6297 folderid = g_strdup("NULL");
6299 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
6303 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
6304 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
6306 /* end of headers */
6307 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
6309 if (compose->redirect_filename != NULL) {
6310 if (compose_redirect_write_to_file(compose, fp) < 0) {
6314 return COMPOSE_QUEUE_ERROR_WITH_ERRNO;
6318 if ((result = compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_SEND, TRUE)) < 0) {
6326 g_warning("failed to write queue message");
6330 return COMPOSE_QUEUE_ERROR_WITH_ERRNO;
6332 if (claws_safe_fclose(fp) == EOF) {
6333 FILE_OP_ERROR(tmp, "claws_fclose");
6336 return COMPOSE_QUEUE_ERROR_WITH_ERRNO;
6339 if (item && *item) {
6342 queue = account_get_special_folder(compose->account, F_QUEUE);
6345 g_warning("can't find queue folder");
6348 return COMPOSE_QUEUE_ERROR_NO_MSG;
6350 folder_item_scan(queue);
6351 if ((num = folder_item_add_msg(queue, tmp, NULL, FALSE)) < 0) {
6352 g_warning("can't queue the message");
6355 return COMPOSE_QUEUE_ERROR_NO_MSG;
6358 if (msgpath == NULL) {
6364 if (compose->mode == COMPOSE_REEDIT && compose->targetinfo) {
6365 MsgInfo *mi = folder_item_get_msginfo(queue, num);
6367 procmsg_msginfo_change_flags(mi,
6368 compose->targetinfo->flags.perm_flags,
6369 compose->targetinfo->flags.tmp_flags & ~(MSG_COPY | MSG_MOVE | MSG_MOVE_DONE),
6372 g_slist_free(mi->tags);
6373 mi->tags = g_slist_copy(compose->targetinfo->tags);
6374 procmsg_msginfo_free(&mi);
6378 if (compose->mode == COMPOSE_REEDIT && remove_reedit_target) {
6379 compose_remove_reedit_target(compose, FALSE);
6382 if ((msgnum != NULL) && (item != NULL)) {
6387 return COMPOSE_QUEUE_SUCCESS;
6390 static int compose_add_attachments(Compose *compose, MimeInfo *parent)
6393 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
6398 GError *error = NULL;
6403 gchar *type, *subtype;
6404 GtkTreeModel *model;
6407 model = gtk_tree_view_get_model(tree_view);
6409 if (!gtk_tree_model_get_iter_first(model, &iter))
6412 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
6414 if (!is_file_exist(ainfo->file)) {
6415 gchar *msg = g_strdup_printf(_("Attachment %s doesn't exist anymore. Ignore?"), ainfo->file);
6416 AlertValue val = alertpanel_full(_("Warning"), msg,
6417 _("Cancel sending"), _("Ignore attachment"), NULL,
6418 ALERTFOCUS_FIRST, FALSE, NULL, ALERT_WARNING);
6420 if (val == G_ALERTDEFAULT) {
6426 f = g_file_new_for_path(ainfo->file);
6427 fi = g_file_query_info(f, "standard::size",
6428 G_FILE_QUERY_INFO_NONE, NULL, &error);
6429 if (error != NULL) {
6430 g_warning(error->message);
6431 g_error_free(error);
6435 size = g_file_info_get_size(fi);
6439 if (g_stat(ainfo->file, &statbuf) < 0)
6441 size = statbuf.st_size;
6444 mimepart = procmime_mimeinfo_new();
6445 mimepart->content = MIMECONTENT_FILE;
6446 mimepart->data.filename = g_strdup(ainfo->file);
6447 mimepart->tmp = FALSE; /* or we destroy our attachment */
6448 mimepart->offset = 0;
6449 mimepart->length = size;
6451 type = g_strdup(ainfo->content_type);
6453 if (!strchr(type, '/')) {
6455 type = g_strdup("application/octet-stream");
6458 subtype = strchr(type, '/') + 1;
6459 *(subtype - 1) = '\0';
6460 mimepart->type = procmime_get_media_type(type);
6461 mimepart->subtype = g_strdup(subtype);
6464 if (mimepart->type == MIMETYPE_MESSAGE &&
6465 !g_ascii_strcasecmp(mimepart->subtype, "rfc822")) {
6466 mimepart->disposition = DISPOSITIONTYPE_INLINE;
6467 } else if (mimepart->type == MIMETYPE_TEXT) {
6468 if (!ainfo->name && g_ascii_strcasecmp(mimepart->subtype, "plain")) {
6469 /* Text parts with no name come from multipart/alternative
6470 * forwards. Make sure the recipient won't look at the
6471 * original HTML part by mistake. */
6472 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6473 ainfo->name = g_strdup_printf(_("Original %s part"),
6477 g_hash_table_insert(mimepart->typeparameters,
6478 g_strdup("charset"), g_strdup(ainfo->charset));
6480 if (ainfo->name && mimepart->type != MIMETYPE_MESSAGE) {
6481 if (mimepart->type == MIMETYPE_APPLICATION &&
6482 !g_strcmp0(mimepart->subtype, "octet-stream"))
6483 g_hash_table_insert(mimepart->typeparameters,
6484 g_strdup("name"), g_strdup(ainfo->name));
6485 g_hash_table_insert(mimepart->dispositionparameters,
6486 g_strdup("filename"), g_strdup(ainfo->name));
6487 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6490 if (mimepart->type == MIMETYPE_MESSAGE
6491 || mimepart->type == MIMETYPE_MULTIPART)
6492 ainfo->encoding = ENC_BINARY;
6493 else if (compose->use_signing || compose->fwdinfo != NULL) {
6494 if (ainfo->encoding == ENC_7BIT)
6495 ainfo->encoding = ENC_QUOTED_PRINTABLE;
6496 else if (ainfo->encoding == ENC_8BIT)
6497 ainfo->encoding = ENC_BASE64;
6500 procmime_encode_content(mimepart, ainfo->encoding);
6502 g_node_append(parent->node, mimepart->node);
6503 } while (gtk_tree_model_iter_next(model, &iter));
6508 static gchar *compose_quote_list_of_addresses(gchar *str)
6510 GSList *list = NULL, *item = NULL;
6511 gchar *qname = NULL, *faddr = NULL, *result = NULL;
6513 list = address_list_append_with_comments(list, str);
6514 for (item = list; item != NULL; item = item->next) {
6515 gchar *spec = item->data;
6516 gchar *endofname = strstr(spec, " <");
6517 if (endofname != NULL) {
6520 QUOTE_IF_REQUIRED_NORMAL(qname, spec, return NULL);
6521 qqname = escape_internal_quotes(qname, '"');
6523 if (*qname != *spec || qqname != qname) { /* has been quoted, compute new */
6524 gchar *addr = g_strdup(endofname);
6525 gchar *name = (qqname != qname)? qqname: g_strdup(qname);
6526 faddr = g_strconcat(name, addr, NULL);
6529 debug_print("new auto-quoted address: '%s'\n", faddr);
6533 result = g_strdup((faddr != NULL)? faddr: spec);
6535 result = g_strconcat(result,
6537 (faddr != NULL)? faddr: spec,
6540 if (faddr != NULL) {
6545 slist_free_strings_full(list);
6550 #define IS_IN_CUSTOM_HEADER(header) \
6551 (compose->account->add_customhdr && \
6552 custom_header_find(compose->account->customhdr_list, header) != NULL)
6554 static const gchar *compose_untranslated_header_name(gchar *header_name)
6556 /* return the untranslated header name, if header_name is a known
6557 header name, in either its translated or untranslated form, with
6558 or without trailing colon. otherwise, returns header_name. */
6559 gchar *translated_header_name;
6560 gchar *translated_header_name_wcolon;
6561 const gchar *untranslated_header_name;
6562 const gchar *untranslated_header_name_wcolon;
6565 cm_return_val_if_fail(header_name != NULL, NULL);
6567 for (i = 0; HEADERS[i].header_name != NULL; i++) {
6568 untranslated_header_name = HEADERS[i].header_name;
6569 untranslated_header_name_wcolon = HEADERS[i].header_name_w_colon;
6571 translated_header_name = gettext(untranslated_header_name);
6572 translated_header_name_wcolon = gettext(untranslated_header_name_wcolon);
6574 if (!strcmp(header_name, untranslated_header_name) ||
6575 !strcmp(header_name, translated_header_name)) {
6576 return untranslated_header_name;
6578 if (!strcmp(header_name, untranslated_header_name_wcolon) ||
6579 !strcmp(header_name, translated_header_name_wcolon)) {
6580 return untranslated_header_name_wcolon;
6584 debug_print("compose_untranslated_header_name: unknown header '%s'\n", header_name);
6588 static void compose_add_headerfield_from_headerlist(Compose *compose,
6590 const gchar *fieldname,
6591 const gchar *seperator)
6593 gchar *str, *fieldname_w_colon;
6594 gboolean add_field = FALSE;
6596 ComposeHeaderEntry *headerentry;
6597 const gchar *headerentryname;
6598 const gchar *trans_fieldname;
6601 if (IS_IN_CUSTOM_HEADER(fieldname))
6604 debug_print("Adding %s-fields\n", fieldname);
6606 fieldstr = g_string_sized_new(64);
6608 fieldname_w_colon = g_strconcat(fieldname, ":", NULL);
6609 trans_fieldname = prefs_common_translated_header_name(fieldname_w_colon);
6611 for (list = compose->header_list; list; list = list->next) {
6612 headerentry = ((ComposeHeaderEntry *)list->data);
6613 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
6615 if (!g_utf8_collate(trans_fieldname, headerentryname)) {
6616 gchar * ustr = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
6618 str = compose_quote_list_of_addresses(ustr);
6620 if (str != NULL && str[0] != '\0') {
6622 g_string_append(fieldstr, seperator);
6623 g_string_append(fieldstr, str);
6632 buf = g_new0(gchar, fieldstr->len * 4 + 256);
6633 compose_convert_header
6634 (compose, buf, fieldstr->len * 4 + 256, fieldstr->str,
6635 strlen(fieldname) + 2, TRUE);
6636 g_string_append_printf(header, "%s: %s\n", fieldname, buf);
6640 g_free(fieldname_w_colon);
6641 g_string_free(fieldstr, TRUE);
6646 static gchar *compose_get_manual_headers_info(Compose *compose)
6648 GString *sh_header = g_string_new(" ");
6650 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6652 for (list = compose->header_list; list; list = list->next) {
6653 ComposeHeaderEntry *headerentry;
6656 gchar *headername_wcolon;
6657 const gchar *headername_trans;
6659 gboolean standard_header = FALSE;
6661 headerentry = ((ComposeHeaderEntry *)list->data);
6663 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6665 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6670 if (!strstr(tmp, ":")) {
6671 headername_wcolon = g_strconcat(tmp, ":", NULL);
6672 headername = g_strdup(tmp);
6674 headername_wcolon = g_strdup(tmp);
6675 headername = g_strdup(strtok(tmp, ":"));
6679 string = std_headers;
6680 while (*string != NULL) {
6681 headername_trans = prefs_common_translated_header_name(*string);
6682 if (!strcmp(headername_trans, headername_wcolon))
6683 standard_header = TRUE;
6686 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
6687 g_string_append_printf(sh_header, "%s ", headername);
6689 g_free(headername_wcolon);
6691 g_string_truncate(sh_header, strlen(sh_header->str) - 1); /* remove last space */
6692 return g_string_free(sh_header, FALSE);
6695 static gchar *compose_get_header(Compose *compose)
6697 gchar date[RFC822_DATE_BUFFSIZE];
6698 gchar buf[BUFFSIZE];
6699 const gchar *entry_str;
6703 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6705 gchar *from_name = NULL, *from_address = NULL;
6708 cm_return_val_if_fail(compose->account != NULL, NULL);
6709 cm_return_val_if_fail(compose->account->address != NULL, NULL);
6711 header = g_string_sized_new(64);
6714 if (prefs_common.hide_timezone)
6715 get_rfc822_date_hide_tz(date, sizeof(date));
6717 get_rfc822_date(date, sizeof(date));
6718 g_string_append_printf(header, "Date: %s\n", date);
6722 if (compose->account->name && *compose->account->name) {
6724 QUOTE_IF_REQUIRED(buf, compose->account->name);
6725 tmp = g_strdup_printf("%s <%s>",
6726 buf, compose->account->address);
6728 tmp = g_strdup_printf("%s",
6729 compose->account->address);
6731 if (!strcmp(gtk_entry_get_text(GTK_ENTRY(compose->from_name)), tmp)
6732 || strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) == 0) {
6734 from_name = compose->account->name ? g_strdup(compose->account->name):NULL;
6735 from_address = g_strdup(compose->account->address);
6737 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
6738 /* extract name and address */
6739 if (strstr(spec, " <") && strstr(spec, ">")) {
6740 from_address = g_strdup(strrchr(spec, '<')+1);
6741 *(strrchr(from_address, '>')) = '\0';
6742 from_name = g_strdup(spec);
6743 *(strrchr(from_name, '<')) = '\0';
6746 from_address = g_strdup(spec);
6753 if (from_name && *from_name) {
6755 compose_convert_header
6756 (compose, buf, sizeof(buf), from_name,
6757 strlen("From: "), TRUE);
6758 QUOTE_IF_REQUIRED(name, buf);
6759 qname = escape_internal_quotes(name, '"');
6761 g_string_append_printf(header, "From: %s <%s>\n",
6762 qname, from_address);
6763 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To") &&
6764 compose->return_receipt) {
6765 compose_convert_header(compose, buf, sizeof(buf), from_name,
6766 strlen("Disposition-Notification-To: "),
6768 g_string_append_printf(header, "Disposition-Notification-To: %s <%s>\n", buf, from_address);
6773 g_string_append_printf(header, "From: %s\n", from_address);
6774 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To") &&
6775 compose->return_receipt)
6776 g_string_append_printf(header, "Disposition-Notification-To: %s\n", from_address);
6780 g_free(from_address);
6783 compose_add_headerfield_from_headerlist(compose, header, "To", ", ");
6786 compose_add_headerfield_from_headerlist(compose, header, "Newsgroups", ",");
6789 compose_add_headerfield_from_headerlist(compose, header, "Cc", ", ");
6793 * If this account is a NNTP account remove Bcc header from
6794 * message body since it otherwise will be publicly shown
6796 if (compose->account->protocol != A_NNTP)
6797 compose_add_headerfield_from_headerlist(compose, header, "Bcc", ", ");
6800 str = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
6802 if (*str != '\0' && !IS_IN_CUSTOM_HEADER("Subject")) {
6805 compose_convert_header(compose, buf, sizeof(buf), str,
6806 strlen("Subject: "), FALSE);
6807 g_string_append_printf(header, "Subject: %s\n", buf);
6813 if (compose->msgid != NULL && strlen(compose->msgid) > 0) {
6814 g_string_append_printf(header, "Message-ID: <%s>\n",
6818 if (compose->remove_references == FALSE) {
6820 if (compose->inreplyto && compose->to_list)
6821 g_string_append_printf(header, "In-Reply-To: <%s>\n", compose->inreplyto);
6824 if (compose->references)
6825 g_string_append_printf(header, "References: %s\n", compose->references);
6829 compose_add_headerfield_from_headerlist(compose, header, "Followup-To", ",");
6832 compose_add_headerfield_from_headerlist(compose, header, "Reply-To", ", ");
6835 if (compose->account->organization &&
6836 strlen(compose->account->organization) &&
6837 !IS_IN_CUSTOM_HEADER("Organization")) {
6838 compose_convert_header(compose, buf, sizeof(buf),
6839 compose->account->organization,
6840 strlen("Organization: "), FALSE);
6841 g_string_append_printf(header, "Organization: %s\n", buf);
6844 /* Program version and system info */
6845 if (compose->account->gen_xmailer &&
6846 g_slist_length(compose->to_list) && !IS_IN_CUSTOM_HEADER("X-Mailer") &&
6847 !compose->newsgroup_list) {
6848 g_string_append_printf(header, "X-Mailer: %s (GTK+ %d.%d.%d; %s)\n",
6850 gtk_major_version, gtk_minor_version, gtk_micro_version,
6853 if (compose->account->gen_xmailer &&
6854 g_slist_length(compose->newsgroup_list) && !IS_IN_CUSTOM_HEADER("X-Newsreader")) {
6855 g_string_append_printf(header, "X-Newsreader: %s (GTK+ %d.%d.%d; %s)\n",
6857 gtk_major_version, gtk_minor_version, gtk_micro_version,
6861 /* custom headers */
6862 if (compose->account->add_customhdr) {
6865 for (cur = compose->account->customhdr_list; cur != NULL;
6867 CustomHeader *chdr = (CustomHeader *)cur->data;
6869 if (custom_header_is_allowed(chdr->name)
6870 && chdr->value != NULL
6871 && *(chdr->value) != '\0') {
6872 compose_convert_header
6873 (compose, buf, sizeof(buf),
6875 strlen(chdr->name) + 2, FALSE);
6876 g_string_append_printf(header, "%s: %s\n", chdr->name, buf);
6881 /* Automatic Faces and X-Faces */
6882 if (get_account_xface (buf, sizeof(buf), compose->account->account_name) == 0) {
6883 g_string_append_printf(header, "X-Face: %s\n", buf);
6885 else if (get_default_xface (buf, sizeof(buf)) == 0) {
6886 g_string_append_printf(header, "X-Face: %s\n", buf);
6888 if (get_account_face (buf, sizeof(buf), compose->account->account_name) == 0) {
6889 g_string_append_printf(header, "Face: %s\n", buf);
6891 else if (get_default_face (buf, sizeof(buf)) == 0) {
6892 g_string_append_printf(header, "Face: %s\n", buf);
6896 switch (compose->priority) {
6897 case PRIORITY_HIGHEST: g_string_append_printf(header, "Importance: high\n"
6898 "X-Priority: 1 (Highest)\n");
6900 case PRIORITY_HIGH: g_string_append_printf(header, "Importance: high\n"
6901 "X-Priority: 2 (High)\n");
6903 case PRIORITY_NORMAL: break;
6904 case PRIORITY_LOW: g_string_append_printf(header, "Importance: low\n"
6905 "X-Priority: 4 (Low)\n");
6907 case PRIORITY_LOWEST: g_string_append_printf(header, "Importance: low\n"
6908 "X-Priority: 5 (Lowest)\n");
6910 default: debug_print("compose: priority unknown : %d\n",
6914 /* get special headers */
6915 for (list = compose->header_list; list; list = list->next) {
6916 ComposeHeaderEntry *headerentry;
6919 gchar *headername_wcolon;
6920 const gchar *headername_trans;
6923 gboolean standard_header = FALSE;
6925 headerentry = ((ComposeHeaderEntry *)list->data);
6927 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6929 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6934 if (!strstr(tmp, ":")) {
6935 headername_wcolon = g_strconcat(tmp, ":", NULL);
6936 headername = g_strdup(tmp);
6938 headername_wcolon = g_strdup(tmp);
6939 headername = g_strdup(strtok(tmp, ":"));
6943 entry_str = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
6944 Xstrdup_a(headervalue, entry_str, return NULL);
6945 subst_char(headervalue, '\r', ' ');
6946 subst_char(headervalue, '\n', ' ');
6947 g_strstrip(headervalue);
6948 if (*headervalue != '\0') {
6949 string = std_headers;
6950 while (*string != NULL && !standard_header) {
6951 headername_trans = prefs_common_translated_header_name(*string);
6952 /* support mixed translated and untranslated headers */
6953 if (!strcmp(headername_trans, headername_wcolon) || !strcmp(*string, headername_wcolon))
6954 standard_header = TRUE;
6957 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername)) {
6958 /* store untranslated header name */
6959 g_string_append_printf(header, "%s %s\n",
6960 compose_untranslated_header_name(headername_wcolon), headervalue);
6964 g_free(headername_wcolon);
6968 g_string_free(header, FALSE);
6973 #undef IS_IN_CUSTOM_HEADER
6975 static void compose_convert_header(Compose *compose, gchar *dest, gint len, gchar *src,
6976 gint header_len, gboolean addr_field)
6978 gchar *tmpstr = NULL;
6979 const gchar *out_codeset = NULL;
6981 cm_return_if_fail(src != NULL);
6982 cm_return_if_fail(dest != NULL);
6984 if (len < 1) return;
6986 tmpstr = g_strdup(src);
6988 subst_char(tmpstr, '\n', ' ');
6989 subst_char(tmpstr, '\r', ' ');
6992 if (!g_utf8_validate(tmpstr, -1, NULL)) {
6993 gchar *mybuf = g_malloc(strlen(tmpstr)*2 +1);
6994 conv_localetodisp(mybuf, strlen(tmpstr)*2 +1, tmpstr);
6999 codeconv_set_strict(TRUE);
7000 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
7001 conv_get_charset_str(compose->out_encoding));
7002 codeconv_set_strict(FALSE);
7004 if (!dest || *dest == '\0') {
7005 gchar *test_conv_global_out = NULL;
7006 gchar *test_conv_reply = NULL;
7008 /* automatic mode. be automatic. */
7009 codeconv_set_strict(TRUE);
7011 out_codeset = conv_get_outgoing_charset_str();
7013 debug_print("trying to convert to %s\n", out_codeset);
7014 test_conv_global_out = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
7017 if (!test_conv_global_out && compose->orig_charset
7018 && strcmp(compose->orig_charset, CS_US_ASCII)) {
7019 out_codeset = compose->orig_charset;
7020 debug_print("failure; trying to convert to %s\n", out_codeset);
7021 test_conv_reply = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
7024 if (!test_conv_global_out && !test_conv_reply) {
7026 out_codeset = CS_INTERNAL;
7027 debug_print("finally using %s\n", out_codeset);
7029 g_free(test_conv_global_out);
7030 g_free(test_conv_reply);
7031 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
7033 codeconv_set_strict(FALSE);
7038 static void compose_add_to_addressbook_cb(GtkMenuItem *menuitem, gpointer user_data)
7042 cm_return_if_fail(user_data != NULL);
7044 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(user_data)));
7045 g_strstrip(address);
7046 if (*address != '\0') {
7047 gchar *name = procheader_get_fromname(address);
7048 extract_address(address);
7049 #ifndef USE_ALT_ADDRBOOK
7050 addressbook_add_contact(name, address, NULL, NULL);
7052 debug_print("%s: %s\n", name, address);
7053 if (addressadd_selection(name, address, NULL, NULL)) {
7054 debug_print( "addressbook_add_contact - added\n" );
7061 static void compose_entry_popup_extend(GtkEntry *entry, GtkMenu *menu, gpointer user_data)
7063 GtkWidget *menuitem;
7066 cm_return_if_fail(menu != NULL);
7067 cm_return_if_fail(GTK_IS_MENU_SHELL(menu));
7069 menuitem = gtk_separator_menu_item_new();
7070 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
7071 gtk_widget_show(menuitem);
7073 menuitem = gtk_menu_item_new_with_mnemonic(_("Add to address _book"));
7074 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
7076 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
7077 g_strstrip(address);
7078 if (*address == '\0') {
7079 gtk_widget_set_sensitive(GTK_WIDGET(menuitem), FALSE);
7082 g_signal_connect(G_OBJECT(menuitem), "activate",
7083 G_CALLBACK(compose_add_to_addressbook_cb), entry);
7084 gtk_widget_show(menuitem);
7087 void compose_add_extra_header(gchar *header, GtkListStore *model)
7090 if (strcmp(header, "")) {
7091 COMBOBOX_ADD(model, header, COMPOSE_TO);
7095 void compose_add_extra_header_entries(GtkListStore *model)
7099 gchar buf[BUFFSIZE];
7102 if (extra_headers == NULL) {
7103 exhrc = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, "extraheaderrc", NULL);
7104 if ((exh = claws_fopen(exhrc, "rb")) == NULL) {
7105 debug_print("extra headers file not found\n");
7106 goto extra_headers_done;
7108 while (claws_fgets(buf, BUFFSIZE, exh) != NULL) {
7109 lastc = strlen(buf) - 1; /* remove trailing control chars */
7110 while (lastc >= 0 && buf[lastc] != ':')
7111 buf[lastc--] = '\0';
7112 if (lastc > 0 && buf[0] != '#' && buf[lastc] == ':') {
7113 buf[lastc] = '\0'; /* remove trailing : for comparison */
7114 if (custom_header_is_allowed(buf)) {
7116 extra_headers = g_slist_prepend(extra_headers, g_strdup(buf));
7119 g_message("disallowed extra header line: %s\n", buf);
7123 g_message("invalid extra header line: %s\n", buf);
7129 extra_headers = g_slist_prepend(extra_headers, g_strdup("")); /* end of list */
7130 extra_headers = g_slist_reverse(extra_headers);
7132 g_slist_foreach(extra_headers, (GFunc)compose_add_extra_header, (gpointer)model);
7136 static void _ldap_srv_func(gpointer data, gpointer user_data)
7138 LdapServer *server = (LdapServer *)data;
7139 gboolean *enable = (gboolean *)user_data;
7141 debug_print("%s server '%s'\n", (*enable == TRUE ? "enabling" : "disabling"), server->control->hostName);
7142 server->searchFlag = *enable;
7146 static void compose_create_header_entry(Compose *compose)
7148 gchar *headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
7155 const gchar *header = NULL;
7156 ComposeHeaderEntry *headerentry;
7157 gboolean standard_header = FALSE;
7158 GtkListStore *model;
7161 headerentry = g_new0(ComposeHeaderEntry, 1);
7163 /* Combo box model */
7164 model = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_BOOLEAN);
7165 COMBOBOX_ADD(model, prefs_common_translated_header_name("To:"),
7167 COMBOBOX_ADD(model, prefs_common_translated_header_name("Cc:"),
7169 COMBOBOX_ADD(model, prefs_common_translated_header_name("Bcc:"),
7171 COMBOBOX_ADD(model, prefs_common_translated_header_name("Newsgroups:"),
7172 COMPOSE_NEWSGROUPS);
7173 COMBOBOX_ADD(model, prefs_common_translated_header_name("Reply-To:"),
7175 COMBOBOX_ADD(model, prefs_common_translated_header_name("Followup-To:"),
7176 COMPOSE_FOLLOWUPTO);
7177 compose_add_extra_header_entries(model);
7180 combo = gtk_combo_box_new_with_model_and_entry(GTK_TREE_MODEL(model));
7181 GtkCellRenderer *cell = gtk_cell_renderer_text_new();
7182 gtk_cell_renderer_set_alignment(cell, 0.0, 0.5);
7183 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), cell, TRUE);
7184 gtk_combo_box_set_entry_text_column(GTK_COMBO_BOX(combo), 0);
7185 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
7186 g_signal_connect(G_OBJECT(gtk_bin_get_child(GTK_BIN(combo))), "grab_focus",
7187 G_CALLBACK(compose_grab_focus_cb), compose);
7188 gtk_widget_show(combo);
7190 /* Putting only the combobox child into focus chain of its parent causes
7191 * the parent to be skipped when changing focus via Tab or Shift+Tab.
7192 * This eliminates need to pres Tab twice in order to really get from the
7193 * combobox to next widget. */
7195 l = g_list_prepend(l, gtk_bin_get_child(GTK_BIN(combo)));
7196 gtk_container_set_focus_chain(GTK_CONTAINER(combo), l);
7199 gtk_table_attach(GTK_TABLE(compose->header_table), combo, 0, 1,
7200 compose->header_nextrow, compose->header_nextrow+1,
7201 GTK_SHRINK, GTK_FILL, 0, 0);
7202 if (compose->header_last && (compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN)) {
7203 const gchar *last_header_entry = gtk_entry_get_text(
7204 GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
7206 while (*string != NULL) {
7207 if (!strcmp(prefs_common_translated_header_name(*string), last_header_entry))
7208 standard_header = TRUE;
7211 if (standard_header)
7212 header = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
7214 if (!compose->header_last || !standard_header) {
7215 switch(compose->account->protocol) {
7217 header = prefs_common_translated_header_name("Newsgroups:");
7220 header = prefs_common_translated_header_name("To:");
7225 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((combo)))), header);
7227 gtk_editable_set_editable(
7228 GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((combo)))),
7229 prefs_common.type_any_header);
7231 g_signal_connect_after(G_OBJECT(gtk_bin_get_child(GTK_BIN((combo)))), "grab_focus",
7232 G_CALLBACK(compose_grab_focus_cb), compose);
7234 /* Entry field with cleanup button */
7235 button = gtk_button_new();
7236 gtk_button_set_image(GTK_BUTTON(button),
7237 gtk_image_new_from_stock(GTK_STOCK_CLEAR, GTK_ICON_SIZE_MENU));
7238 gtk_widget_show(button);
7239 CLAWS_SET_TIP(button,
7240 _("Delete entry contents"));
7241 entry = gtk_entry_new();
7242 gtk_widget_show(entry);
7243 CLAWS_SET_TIP(entry,
7244 _("Use <tab> to autocomplete from addressbook"));
7245 hbox = gtk_hbox_new (FALSE, 0);
7246 gtk_widget_show(hbox);
7247 gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, TRUE, 0);
7248 gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
7249 gtk_table_attach(GTK_TABLE(compose->header_table), hbox, 1, 2,
7250 compose->header_nextrow, compose->header_nextrow+1,
7251 GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
7253 g_signal_connect(G_OBJECT(entry), "key-press-event",
7254 G_CALLBACK(compose_headerentry_key_press_event_cb),
7256 g_signal_connect(G_OBJECT(entry), "changed",
7257 G_CALLBACK(compose_headerentry_changed_cb),
7259 g_signal_connect_after(G_OBJECT(entry), "grab_focus",
7260 G_CALLBACK(compose_grab_focus_cb), compose);
7262 g_signal_connect(G_OBJECT(button), "clicked",
7263 G_CALLBACK(compose_headerentry_button_clicked_cb),
7267 gtk_drag_dest_set(entry, GTK_DEST_DEFAULT_ALL, compose_mime_types,
7268 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7269 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7270 g_signal_connect(G_OBJECT(entry), "drag_data_received",
7271 G_CALLBACK(compose_header_drag_received_cb),
7273 g_signal_connect(G_OBJECT(entry), "drag-drop",
7274 G_CALLBACK(compose_drag_drop),
7276 g_signal_connect(G_OBJECT(entry), "populate-popup",
7277 G_CALLBACK(compose_entry_popup_extend),
7281 #ifndef PASSWORD_CRYPTO_OLD
7282 GSList *pwd_servers = addrindex_get_password_protected_ldap_servers();
7283 if (pwd_servers != NULL && master_passphrase() == NULL) {
7284 gboolean enable = FALSE;
7285 debug_print("Master passphrase not available, disabling password-protected LDAP servers for this compose window.\n");
7286 /* Temporarily disable password-protected LDAP servers,
7287 * because user did not provide a master passphrase.
7288 * We can safely enable searchFlag on all servers in this list
7289 * later, since addrindex_get_password_protected_ldap_servers()
7290 * includes servers which have it enabled initially. */
7291 g_slist_foreach(pwd_servers, _ldap_srv_func, &enable);
7292 compose->passworded_ldap_servers = pwd_servers;
7294 #endif /* PASSWORD_CRYPTO_OLD */
7295 #endif /* USE_LDAP */
7297 address_completion_register_entry(GTK_ENTRY(entry), TRUE);
7299 headerentry->compose = compose;
7300 headerentry->combo = combo;
7301 headerentry->entry = entry;
7302 headerentry->button = button;
7303 headerentry->hbox = hbox;
7304 headerentry->headernum = compose->header_nextrow;
7305 headerentry->type = PREF_NONE;
7307 compose->header_nextrow++;
7308 compose->header_last = headerentry;
7309 compose->header_list =
7310 g_slist_append(compose->header_list,
7314 static void compose_add_header_entry(Compose *compose, const gchar *header,
7315 gchar *text, ComposePrefType pref_type)
7317 ComposeHeaderEntry *last_header = compose->header_last;
7318 gchar *tmp = g_strdup(text), *email;
7319 gboolean replyto_hdr;
7321 replyto_hdr = (!strcasecmp(header,
7322 prefs_common_translated_header_name("Reply-To:")) ||
7324 prefs_common_translated_header_name("Followup-To:")) ||
7326 prefs_common_translated_header_name("In-Reply-To:")));
7328 extract_address(tmp);
7329 email = g_utf8_strdown(tmp, -1);
7331 if (replyto_hdr == FALSE &&
7332 g_hash_table_lookup(compose->email_hashtable, email) != NULL)
7334 debug_print("Ignoring duplicate address - %s %s, pref_type: %d\n",
7335 header, text, (gint) pref_type);
7341 if (!strcasecmp(header, prefs_common_translated_header_name("In-Reply-To:")))
7342 gtk_entry_set_text(GTK_ENTRY(
7343 gtk_bin_get_child(GTK_BIN(last_header->combo))), header);
7345 combobox_select_by_text(GTK_COMBO_BOX(last_header->combo), header);
7346 gtk_entry_set_text(GTK_ENTRY(last_header->entry), text);
7347 last_header->type = pref_type;
7349 if (replyto_hdr == FALSE)
7350 g_hash_table_insert(compose->email_hashtable, email,
7351 GUINT_TO_POINTER(1));
7358 static void compose_destroy_headerentry(Compose *compose,
7359 ComposeHeaderEntry *headerentry)
7361 gchar *text = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
7364 extract_address(text);
7365 email = g_utf8_strdown(text, -1);
7366 g_hash_table_remove(compose->email_hashtable, email);
7370 gtk_widget_destroy(headerentry->combo);
7371 gtk_widget_destroy(headerentry->entry);
7372 gtk_widget_destroy(headerentry->button);
7373 gtk_widget_destroy(headerentry->hbox);
7374 g_free(headerentry);
7377 static void compose_remove_header_entries(Compose *compose)
7380 for (list = compose->header_list; list; list = list->next)
7381 compose_destroy_headerentry(compose, (ComposeHeaderEntry *)list->data);
7383 compose->header_last = NULL;
7384 g_slist_free(compose->header_list);
7385 compose->header_list = NULL;
7386 compose->header_nextrow = 1;
7387 compose_create_header_entry(compose);
7390 static GtkWidget *compose_create_header(Compose *compose)
7392 GtkWidget *from_optmenu_hbox;
7393 GtkWidget *header_table_main;
7394 GtkWidget *header_scrolledwin;
7395 GtkWidget *header_table;
7397 /* parent with account selection and from header */
7398 header_table_main = gtk_table_new(2, 2, FALSE);
7399 gtk_widget_show(header_table_main);
7400 gtk_container_set_border_width(GTK_CONTAINER(header_table_main), BORDER_WIDTH);
7402 from_optmenu_hbox = compose_account_option_menu_create(compose);
7403 gtk_table_attach(GTK_TABLE(header_table_main), from_optmenu_hbox,
7404 0, 2, 0, 1, GTK_EXPAND | GTK_FILL, GTK_SHRINK, 0, 0);
7406 /* child with header labels and entries */
7407 header_scrolledwin = gtk_scrolled_window_new(NULL, NULL);
7408 gtk_widget_show(header_scrolledwin);
7409 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
7411 header_table = gtk_table_new(2, 2, FALSE);
7412 gtk_widget_show(header_table);
7413 gtk_container_set_border_width(GTK_CONTAINER(header_table), 0);
7414 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin), header_table);
7415 gtk_container_set_focus_vadjustment(GTK_CONTAINER(header_table),
7416 gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(header_scrolledwin)));
7417 gtk_viewport_set_shadow_type(GTK_VIEWPORT(gtk_bin_get_child(GTK_BIN(header_scrolledwin))), GTK_SHADOW_NONE);
7419 gtk_table_attach(GTK_TABLE(header_table_main), header_scrolledwin,
7420 0, 2, 1, 2, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 2);
7422 compose->header_table = header_table;
7423 compose->header_list = NULL;
7424 compose->header_nextrow = 0;
7426 compose_create_header_entry(compose);
7428 compose->table = NULL;
7430 return header_table_main;
7433 static gboolean popup_attach_button_pressed(GtkWidget *widget, gpointer data)
7435 Compose *compose = (Compose *)data;
7436 GdkEventButton event;
7439 event.time = gtk_get_current_event_time();
7441 return attach_button_pressed(compose->attach_clist, &event, compose);
7444 static GtkWidget *compose_create_attach(Compose *compose)
7446 GtkWidget *attach_scrwin;
7447 GtkWidget *attach_clist;
7449 GtkListStore *store;
7450 GtkCellRenderer *renderer;
7451 GtkTreeViewColumn *column;
7452 GtkTreeSelection *selection;
7454 /* attachment list */
7455 attach_scrwin = gtk_scrolled_window_new(NULL, NULL);
7456 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(attach_scrwin),
7457 GTK_POLICY_AUTOMATIC,
7458 GTK_POLICY_AUTOMATIC);
7459 gtk_widget_set_size_request(attach_scrwin, -1, 80);
7461 store = gtk_list_store_new(N_ATTACH_COLS,
7467 G_TYPE_AUTO_POINTER,
7469 attach_clist = GTK_WIDGET(gtk_tree_view_new_with_model
7470 (GTK_TREE_MODEL(store)));
7471 gtk_container_add(GTK_CONTAINER(attach_scrwin), attach_clist);
7472 g_object_unref(store);
7474 renderer = gtk_cell_renderer_text_new();
7475 column = gtk_tree_view_column_new_with_attributes
7476 (_("Mime type"), renderer, "text",
7477 COL_MIMETYPE, NULL);
7478 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7480 renderer = gtk_cell_renderer_text_new();
7481 column = gtk_tree_view_column_new_with_attributes
7482 (_("Size"), renderer, "text",
7484 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7486 renderer = gtk_cell_renderer_text_new();
7487 column = gtk_tree_view_column_new_with_attributes
7488 (_("Name"), renderer, "text",
7490 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7492 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(attach_clist),
7493 prefs_common.use_stripes_everywhere);
7494 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(attach_clist));
7495 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
7497 g_signal_connect(G_OBJECT(attach_clist), "row_activated",
7498 G_CALLBACK(attach_selected), compose);
7499 g_signal_connect(G_OBJECT(attach_clist), "button_press_event",
7500 G_CALLBACK(attach_button_pressed), compose);
7501 g_signal_connect(G_OBJECT(attach_clist), "popup-menu",
7502 G_CALLBACK(popup_attach_button_pressed), compose);
7503 g_signal_connect(G_OBJECT(attach_clist), "key_press_event",
7504 G_CALLBACK(attach_key_pressed), compose);
7507 gtk_drag_dest_set(attach_clist,
7508 GTK_DEST_DEFAULT_ALL, compose_mime_types,
7509 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7510 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7511 g_signal_connect(G_OBJECT(attach_clist), "drag_data_received",
7512 G_CALLBACK(compose_attach_drag_received_cb),
7514 g_signal_connect(G_OBJECT(attach_clist), "drag-drop",
7515 G_CALLBACK(compose_drag_drop),
7518 compose->attach_scrwin = attach_scrwin;
7519 compose->attach_clist = attach_clist;
7521 return attach_scrwin;
7524 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose);
7526 static GtkWidget *compose_create_others(Compose *compose)
7529 GtkWidget *savemsg_checkbtn;
7530 GtkWidget *savemsg_combo;
7531 GtkWidget *savemsg_select;
7534 gchar *folderidentifier;
7536 /* Table for settings */
7537 table = gtk_table_new(3, 1, FALSE);
7538 gtk_container_set_border_width(GTK_CONTAINER(table), BORDER_WIDTH);
7539 gtk_widget_show(table);
7540 gtk_table_set_row_spacings(GTK_TABLE(table), VSPACING_NARROW);
7543 /* Save Message to folder */
7544 savemsg_checkbtn = gtk_check_button_new_with_label(_("Save Message to "));
7545 gtk_widget_show(savemsg_checkbtn);
7546 gtk_table_attach(GTK_TABLE(table), savemsg_checkbtn, 0, 1, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7547 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7548 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), prefs_common.savemsg);
7551 savemsg_combo = gtk_combo_box_text_new_with_entry();
7552 compose->savemsg_checkbtn = savemsg_checkbtn;
7553 compose->savemsg_combo = savemsg_combo;
7554 gtk_widget_show(savemsg_combo);
7556 if (prefs_common.compose_save_to_history)
7557 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(savemsg_combo),
7558 prefs_common.compose_save_to_history);
7559 gtk_table_attach(GTK_TABLE(table), savemsg_combo, 1, 2, rowcount, rowcount + 1, GTK_FILL|GTK_EXPAND, GTK_SHRINK, 0, 0);
7560 gtk_widget_set_sensitive(GTK_WIDGET(savemsg_combo), prefs_common.savemsg);
7561 g_signal_connect_after(G_OBJECT(savemsg_combo), "grab_focus",
7562 G_CALLBACK(compose_grab_focus_cb), compose);
7563 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7564 if (compose->account->set_sent_folder || prefs_common.savemsg)
7565 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), TRUE);
7567 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), FALSE);
7568 gtk_widget_set_sensitive(GTK_WIDGET(savemsg_combo), TRUE);
7569 folderidentifier = folder_item_get_identifier(account_get_special_folder
7570 (compose->account, F_OUTBOX));
7571 compose_set_save_to(compose, folderidentifier);
7572 g_free(folderidentifier);
7575 savemsg_select = gtkut_get_browse_file_btn(_("_Browse"));
7576 gtk_widget_show(savemsg_select);
7577 gtk_table_attach(GTK_TABLE(table), savemsg_select, 2, 3, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7578 g_signal_connect(G_OBJECT(savemsg_select), "clicked",
7579 G_CALLBACK(compose_savemsg_select_cb),
7585 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose)
7590 dest = foldersel_folder_sel(NULL, FOLDER_SEL_COPY, NULL, FALSE,
7591 _("Select folder to save message to"));
7594 path = folder_item_get_identifier(dest);
7596 compose_set_save_to(compose, path);
7600 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry, gboolean wrap,
7601 GdkAtom clip, GtkTextIter *insert_place);
7604 static gboolean text_clicked(GtkWidget *text, GdkEventButton *event,
7608 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
7610 if (event->button == 3) {
7612 GtkTextIter sel_start, sel_end;
7613 gboolean stuff_selected;
7615 /* move the cursor to allow GtkAspell to check the word
7616 * under the mouse */
7617 if (event->x && event->y) {
7618 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7619 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7621 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7624 GtkTextMark *mark = gtk_text_buffer_get_insert(buffer);
7625 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7628 stuff_selected = gtk_text_buffer_get_selection_bounds(
7630 &sel_start, &sel_end);
7632 gtk_text_buffer_place_cursor (buffer, &iter);
7633 /* reselect stuff */
7635 && gtk_text_iter_in_range(&iter, &sel_start, &sel_end)) {
7636 gtk_text_buffer_select_range(buffer,
7637 &sel_start, &sel_end);
7639 return FALSE; /* pass the event so that the right-click goes through */
7642 if (event->button == 2) {
7647 /* get the middle-click position to paste at the correct place */
7648 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7649 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7651 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7654 entry_paste_clipboard(compose, text,
7655 prefs_common.linewrap_pastes,
7656 GDK_SELECTION_PRIMARY, &iter);
7664 static void compose_spell_menu_changed(void *data)
7666 Compose *compose = (Compose *)data;
7668 GtkWidget *menuitem;
7669 GtkWidget *parent_item;
7670 GtkMenu *menu = GTK_MENU(gtk_menu_new());
7673 if (compose->gtkaspell == NULL)
7676 parent_item = gtk_ui_manager_get_widget(compose->ui_manager,
7677 "/Menu/Spelling/Options");
7679 /* setting the submenu removes /Spelling/Options from the factory
7680 * so we need to save it */
7682 if (parent_item == NULL) {
7683 parent_item = compose->aspell_options_menu;
7684 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), NULL);
7686 compose->aspell_options_menu = parent_item;
7688 spell_menu = gtkaspell_make_config_menu(compose->gtkaspell);
7690 spell_menu = g_slist_reverse(spell_menu);
7691 for (items = spell_menu;
7692 items; items = items->next) {
7693 menuitem = GTK_WIDGET(GTK_MENU_ITEM(items->data));
7694 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), GTK_WIDGET(menuitem));
7695 gtk_widget_show(GTK_WIDGET(menuitem));
7697 g_slist_free(spell_menu);
7699 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), GTK_WIDGET(menu));
7700 gtk_widget_show(parent_item);
7703 static void compose_dict_changed(void *data)
7705 Compose *compose = (Compose *) data;
7707 if(!compose->gtkaspell)
7709 if(compose->gtkaspell->recheck_when_changing_dict == FALSE)
7712 gtkaspell_highlight_all(compose->gtkaspell);
7713 claws_spell_entry_recheck_all(CLAWS_SPELL_ENTRY(compose->subject_entry));
7717 static gboolean compose_popup_menu(GtkWidget *widget, gpointer data)
7719 Compose *compose = (Compose *)data;
7720 GdkEventButton event;
7723 event.time = gtk_get_current_event_time();
7727 return text_clicked(compose->text, &event, compose);
7730 static gboolean compose_force_window_origin = TRUE;
7731 static Compose *compose_create(PrefsAccount *account,
7740 GtkWidget *handlebox;
7742 GtkWidget *notebook;
7744 GtkWidget *attach_hbox;
7745 GtkWidget *attach_lab1;
7746 GtkWidget *attach_lab2;
7751 GtkWidget *subject_hbox;
7752 GtkWidget *subject_frame;
7753 GtkWidget *subject_entry;
7757 GtkWidget *edit_vbox;
7758 GtkWidget *ruler_hbox;
7760 GtkWidget *scrolledwin;
7762 GtkTextBuffer *buffer;
7763 GtkClipboard *clipboard;
7765 UndoMain *undostruct;
7767 GtkWidget *popupmenu;
7768 GtkWidget *tmpl_menu;
7769 GtkActionGroup *action_group = NULL;
7772 GtkAspell * gtkaspell = NULL;
7775 static GdkGeometry geometry;
7777 cm_return_val_if_fail(account != NULL, NULL);
7779 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_DEFAULT_HEADER_BG],
7780 &default_header_bgcolor);
7781 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_DEFAULT_HEADER],
7782 &default_header_color);
7784 debug_print("Creating compose window...\n");
7785 compose = g_new0(Compose, 1);
7787 compose->batch = batch;
7788 compose->account = account;
7789 compose->folder = folder;
7791 compose->mutex = cm_mutex_new();
7792 compose->set_cursor_pos = -1;
7794 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose");
7796 gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
7797 gtk_widget_set_size_request(window, prefs_common.compose_width,
7798 prefs_common.compose_height);
7800 if (!geometry.max_width) {
7801 geometry.max_width = gdk_screen_width();
7802 geometry.max_height = gdk_screen_height();
7805 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7806 &geometry, GDK_HINT_MAX_SIZE);
7807 if (!geometry.min_width) {
7808 geometry.min_width = 600;
7809 geometry.min_height = 440;
7811 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7812 &geometry, GDK_HINT_MIN_SIZE);
7814 #ifndef GENERIC_UMPC
7815 if (compose_force_window_origin)
7816 gtk_window_move(GTK_WINDOW(window), prefs_common.compose_x,
7817 prefs_common.compose_y);
7819 g_signal_connect(G_OBJECT(window), "delete_event",
7820 G_CALLBACK(compose_delete_cb), compose);
7821 MANAGE_WINDOW_SIGNALS_CONNECT(window);
7822 gtk_widget_realize(window);
7824 gtkut_widget_set_composer_icon(window);
7826 vbox = gtk_vbox_new(FALSE, 0);
7827 gtk_container_add(GTK_CONTAINER(window), vbox);
7829 compose->ui_manager = gtk_ui_manager_new();
7830 action_group = cm_menu_create_action_group_full(compose->ui_manager,"Menu", compose_entries,
7831 G_N_ELEMENTS(compose_entries), (gpointer)compose);
7832 gtk_action_group_add_toggle_actions(action_group, compose_toggle_entries,
7833 G_N_ELEMENTS(compose_toggle_entries), (gpointer)compose);
7834 gtk_action_group_add_radio_actions(action_group, compose_radio_rm_entries,
7835 G_N_ELEMENTS(compose_radio_rm_entries), COMPOSE_REPLY, G_CALLBACK(compose_reply_change_mode_cb), (gpointer)compose);
7836 gtk_action_group_add_radio_actions(action_group, compose_radio_prio_entries,
7837 G_N_ELEMENTS(compose_radio_prio_entries), PRIORITY_NORMAL, G_CALLBACK(compose_set_priority_cb), (gpointer)compose);
7838 gtk_action_group_add_radio_actions(action_group, compose_radio_enc_entries,
7839 G_N_ELEMENTS(compose_radio_enc_entries), C_AUTO, G_CALLBACK(compose_set_encoding_cb), (gpointer)compose);
7841 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Menu", NULL, GTK_UI_MANAGER_MENUBAR)
7843 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Message", "Message", GTK_UI_MANAGER_MENU)
7844 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Edit", "Edit", GTK_UI_MANAGER_MENU)
7846 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Spelling", "Spelling", GTK_UI_MANAGER_MENU)
7848 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Options", "Options", GTK_UI_MANAGER_MENU)
7849 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Tools", "Tools", GTK_UI_MANAGER_MENU)
7850 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Help", "Help", GTK_UI_MANAGER_MENU)
7853 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Send", "Message/Send", GTK_UI_MANAGER_MENUITEM)
7854 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "SendLater", "Message/SendLater", GTK_UI_MANAGER_MENUITEM)
7855 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator1", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7856 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "AttachFile", "Message/AttachFile", GTK_UI_MANAGER_MENUITEM)
7857 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertFile", "Message/InsertFile", GTK_UI_MANAGER_MENUITEM)
7858 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertSig", "Message/InsertSig", GTK_UI_MANAGER_MENUITEM)
7859 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "ReplaceSig", "Message/ReplaceSig", GTK_UI_MANAGER_MENUITEM)
7860 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator2", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7861 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Save", "Message/Save", GTK_UI_MANAGER_MENUITEM)
7862 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator3", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7863 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Print", "Message/Print", GTK_UI_MANAGER_MENUITEM)
7864 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator4", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7865 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Close", "Message/Close", GTK_UI_MANAGER_MENUITEM)
7868 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Undo", "Edit/Undo", GTK_UI_MANAGER_MENUITEM)
7869 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Redo", "Edit/Redo", GTK_UI_MANAGER_MENUITEM)
7870 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator1", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7872 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Cut", "Edit/Cut", GTK_UI_MANAGER_MENUITEM)
7873 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Copy", "Edit/Copy", GTK_UI_MANAGER_MENUITEM)
7874 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Paste", "Edit/Paste", GTK_UI_MANAGER_MENUITEM)
7876 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SpecialPaste", "Edit/SpecialPaste", GTK_UI_MANAGER_MENU)
7877 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "AsQuotation", "Edit/SpecialPaste/AsQuotation", GTK_UI_MANAGER_MENUITEM)
7878 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Wrapped", "Edit/SpecialPaste/Wrapped", GTK_UI_MANAGER_MENUITEM)
7879 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Unwrapped", "Edit/SpecialPaste/Unwrapped", GTK_UI_MANAGER_MENUITEM)
7881 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SelectAll", "Edit/SelectAll", GTK_UI_MANAGER_MENUITEM)
7883 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Advanced", "Edit/Advanced", GTK_UI_MANAGER_MENU)
7884 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackChar", "Edit/Advanced/BackChar", GTK_UI_MANAGER_MENUITEM)
7885 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwChar", "Edit/Advanced/ForwChar", GTK_UI_MANAGER_MENUITEM)
7886 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackWord", "Edit/Advanced/BackWord", GTK_UI_MANAGER_MENUITEM)
7887 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwWord", "Edit/Advanced/ForwWord", GTK_UI_MANAGER_MENUITEM)
7888 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BegLine", "Edit/Advanced/BegLine", GTK_UI_MANAGER_MENUITEM)
7889 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "EndLine", "Edit/Advanced/EndLine", GTK_UI_MANAGER_MENUITEM)
7890 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "PrevLine", "Edit/Advanced/PrevLine", GTK_UI_MANAGER_MENUITEM)
7891 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "NextLine", "Edit/Advanced/NextLine", GTK_UI_MANAGER_MENUITEM)
7892 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackChar", "Edit/Advanced/DelBackChar", GTK_UI_MANAGER_MENUITEM)
7893 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwChar", "Edit/Advanced/DelForwChar", GTK_UI_MANAGER_MENUITEM)
7894 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackWord", "Edit/Advanced/DelBackWord", GTK_UI_MANAGER_MENUITEM)
7895 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwWord", "Edit/Advanced/DelForwWord", GTK_UI_MANAGER_MENUITEM)
7896 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelLine", "Edit/Advanced/DelLine", GTK_UI_MANAGER_MENUITEM)
7897 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelEndLine", "Edit/Advanced/DelEndLine", GTK_UI_MANAGER_MENUITEM)
7899 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator2", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7901 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Find", "Edit/Find", GTK_UI_MANAGER_MENUITEM)
7902 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapPara", "Edit/WrapPara", GTK_UI_MANAGER_MENUITEM)
7903 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapAllLines", "Edit/WrapAllLines", GTK_UI_MANAGER_MENUITEM)
7904 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoWrap", "Edit/AutoWrap", GTK_UI_MANAGER_MENUITEM)
7905 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoIndent", "Edit/AutoIndent", GTK_UI_MANAGER_MENUITEM)
7907 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator3", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7909 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "ExtEditor", "Edit/ExtEditor", GTK_UI_MANAGER_MENUITEM)
7913 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckAllSel", "Spelling/CheckAllSel", GTK_UI_MANAGER_MENUITEM)
7914 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "HighlightAll", "Spelling/HighlightAll", GTK_UI_MANAGER_MENUITEM)
7915 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckBackwards", "Spelling/CheckBackwards", GTK_UI_MANAGER_MENUITEM)
7916 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "ForwardNext", "Spelling/ForwardNext", GTK_UI_MANAGER_MENUITEM)
7917 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Separator1", "Spelling/---", GTK_UI_MANAGER_SEPARATOR)
7918 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Options", "Spelling/Options", GTK_UI_MANAGER_MENU)
7922 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "ReplyMode", "Options/ReplyMode", GTK_UI_MANAGER_MENU)
7923 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Normal", "Options/ReplyMode/Normal", GTK_UI_MANAGER_MENUITEM)
7924 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "All", "Options/ReplyMode/All", GTK_UI_MANAGER_MENUITEM)
7925 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Sender", "Options/ReplyMode/Sender", GTK_UI_MANAGER_MENUITEM)
7926 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "List", "Options/ReplyMode/List", GTK_UI_MANAGER_MENUITEM)
7928 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator1", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7929 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "PrivacySystem", "Options/PrivacySystem", GTK_UI_MANAGER_MENU)
7930 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/PrivacySystem", "PlaceHolder", "Options/PrivacySystem/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7931 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Sign", "Options/Sign", GTK_UI_MANAGER_MENUITEM)
7932 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encrypt", "Options/Encrypt", GTK_UI_MANAGER_MENUITEM)
7935 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator2", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7936 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Priority", "Options/Priority", GTK_UI_MANAGER_MENU)
7937 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Highest", "Options/Priority/Highest", GTK_UI_MANAGER_MENUITEM)
7938 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "High", "Options/Priority/High", GTK_UI_MANAGER_MENUITEM)
7939 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Normal", "Options/Priority/Normal", GTK_UI_MANAGER_MENUITEM)
7940 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Low", "Options/Priority/Low", GTK_UI_MANAGER_MENUITEM)
7941 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Lowest", "Options/Priority/Lowest", GTK_UI_MANAGER_MENUITEM)
7943 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator3", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7944 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RequestRetRcpt", "Options/RequestRetRcpt", GTK_UI_MANAGER_MENUITEM)
7945 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator4", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7946 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RemoveReferences", "Options/RemoveReferences", GTK_UI_MANAGER_MENUITEM)
7947 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator5", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7949 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encoding", "Options/Encoding", GTK_UI_MANAGER_MENU)
7951 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_AUTO, "Options/Encoding/"CS_AUTO, GTK_UI_MANAGER_MENUITEM)
7952 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator1", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7953 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_US_ASCII, "Options/Encoding/"CS_US_ASCII, GTK_UI_MANAGER_MENUITEM)
7954 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_UTF_8, "Options/Encoding/"CS_UTF_8, GTK_UI_MANAGER_MENUITEM)
7955 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator2", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7957 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Western", "Options/Encoding/Western", GTK_UI_MANAGER_MENU)
7958 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Western", CS_ISO_8859_1, "Options/Encoding/Western/"CS_ISO_8859_1, GTK_UI_MANAGER_MENUITEM)
7959 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Western", CS_ISO_8859_15, "Options/Encoding/Western/"CS_ISO_8859_15, GTK_UI_MANAGER_MENUITEM)
7960 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Western", CS_WINDOWS_1252, "Options/Encoding/Western/"CS_WINDOWS_1252, GTK_UI_MANAGER_MENUITEM)
7962 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_2, "Options/Encoding/"CS_ISO_8859_2, GTK_UI_MANAGER_MENUITEM)
7964 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Baltic", "Options/Encoding/Baltic", GTK_UI_MANAGER_MENU)
7965 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Baltic", CS_ISO_8859_13, "Options/Encoding/Baltic/"CS_ISO_8859_13, GTK_UI_MANAGER_MENUITEM)
7966 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Baltic", CS_ISO_8859_4, "Options/Encoding/Baltic/"CS_ISO_8859_4, GTK_UI_MANAGER_MENUITEM)
7968 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_7, "Options/Encoding/"CS_ISO_8859_7, GTK_UI_MANAGER_MENUITEM)
7970 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Hebrew", "Options/Encoding/Hebrew", GTK_UI_MANAGER_MENU)
7971 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Hebrew", CS_ISO_8859_8, "Options/Encoding/Hebrew/"CS_ISO_8859_8, GTK_UI_MANAGER_MENUITEM)
7972 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Hebrew", CS_WINDOWS_1255, "Options/Encoding/Hebrew/"CS_WINDOWS_1255, GTK_UI_MANAGER_MENUITEM)
7974 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Arabic", "Options/Encoding/Arabic", GTK_UI_MANAGER_MENU)
7975 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Arabic", CS_ISO_8859_6, "Options/Encoding/Arabic/"CS_ISO_8859_6, GTK_UI_MANAGER_MENUITEM)
7976 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Arabic", CS_WINDOWS_1256, "Options/Encoding/Arabic/"CS_WINDOWS_1256, GTK_UI_MANAGER_MENUITEM)
7978 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_9, "Options/Encoding/"CS_ISO_8859_9, GTK_UI_MANAGER_MENUITEM)
7980 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Cyrillic", "Options/Encoding/Cyrillic", GTK_UI_MANAGER_MENU)
7981 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_ISO_8859_5, "Options/Encoding/Cyrillic/"CS_ISO_8859_5, GTK_UI_MANAGER_MENUITEM)
7982 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_R, "Options/Encoding/Cyrillic/"CS_KOI8_R, GTK_UI_MANAGER_MENUITEM)
7983 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_MACCYR, "Options/Encoding/Cyrillic/"CS_MACCYR, GTK_UI_MANAGER_MENUITEM)
7984 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_U, "Options/Encoding/Cyrillic/"CS_KOI8_U, GTK_UI_MANAGER_MENUITEM)
7985 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_WINDOWS_1251, "Options/Encoding/Cyrillic/"CS_WINDOWS_1251, GTK_UI_MANAGER_MENUITEM)
7987 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Japanese", "Options/Encoding/Japanese", GTK_UI_MANAGER_MENU)
7988 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_ISO_2022_JP, "Options/Encoding/Japanese/"CS_ISO_2022_JP, GTK_UI_MANAGER_MENUITEM)
7989 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_ISO_2022_JP_2, "Options/Encoding/Japanese/"CS_ISO_2022_JP_2, GTK_UI_MANAGER_MENUITEM)
7990 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_EUC_JP, "Options/Encoding/Japanese/"CS_EUC_JP, GTK_UI_MANAGER_MENUITEM)
7991 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_SHIFT_JIS, "Options/Encoding/Japanese/"CS_SHIFT_JIS, GTK_UI_MANAGER_MENUITEM)
7993 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Chinese", "Options/Encoding/Chinese", GTK_UI_MANAGER_MENU)
7994 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB18030, "Options/Encoding/Chinese/"CS_GB18030, GTK_UI_MANAGER_MENUITEM)
7995 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB2312, "Options/Encoding/Chinese/"CS_GB2312, GTK_UI_MANAGER_MENUITEM)
7996 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GBK, "Options/Encoding/Chinese/"CS_GBK, GTK_UI_MANAGER_MENUITEM)
7997 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_BIG5, "Options/Encoding/Chinese/"CS_BIG5, GTK_UI_MANAGER_MENUITEM)
7998 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_EUC_TW, "Options/Encoding/Chinese/"CS_EUC_TW, GTK_UI_MANAGER_MENUITEM)
8000 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Korean", "Options/Encoding/Korean", GTK_UI_MANAGER_MENU)
8001 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Korean", CS_EUC_KR, "Options/Encoding/Korean/"CS_EUC_KR, GTK_UI_MANAGER_MENUITEM)
8002 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Korean", CS_ISO_2022_KR, "Options/Encoding/Korean/"CS_ISO_2022_KR, GTK_UI_MANAGER_MENUITEM)
8004 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Thai", "Options/Encoding/Thai", GTK_UI_MANAGER_MENU)
8005 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_TIS_620, "Options/Encoding/Thai/"CS_TIS_620, GTK_UI_MANAGER_MENUITEM)
8006 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_WINDOWS_874, "Options/Encoding/Thai/"CS_WINDOWS_874, GTK_UI_MANAGER_MENUITEM)
8010 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "ShowRuler", "Tools/ShowRuler", GTK_UI_MANAGER_MENUITEM)
8011 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "AddressBook", "Tools/AddressBook", GTK_UI_MANAGER_MENUITEM)
8012 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Template", "Tools/Template", GTK_UI_MANAGER_MENU)
8013 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Template", "PlaceHolder", "Tools/Template/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
8014 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Actions", "Tools/Actions", GTK_UI_MANAGER_MENU)
8015 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Actions", "PlaceHolder", "Tools/Actions/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
8018 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Help", "About", "Help/About", GTK_UI_MANAGER_MENUITEM)
8020 menubar = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu");
8021 gtk_widget_show_all(menubar);
8023 gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(compose->ui_manager));
8024 gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, TRUE, 0);
8026 if (prefs_common.toolbar_detachable) {
8027 handlebox = gtk_handle_box_new();
8029 handlebox = gtk_hbox_new(FALSE, 0);
8031 gtk_box_pack_start(GTK_BOX(vbox), handlebox, FALSE, FALSE, 0);
8033 gtk_widget_realize(handlebox);
8034 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, handlebox,
8037 vbox2 = gtk_vbox_new(FALSE, 2);
8038 gtk_box_pack_start(GTK_BOX(vbox), vbox2, TRUE, TRUE, 0);
8039 gtk_container_set_border_width(GTK_CONTAINER(vbox2), 0);
8042 notebook = gtk_notebook_new();
8043 gtk_widget_show(notebook);
8045 /* header labels and entries */
8046 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
8047 compose_create_header(compose),
8048 gtk_label_new_with_mnemonic(_("Hea_der")));
8049 /* attachment list */
8050 attach_hbox = gtk_hbox_new(FALSE, 0);
8051 gtk_widget_show(attach_hbox);
8053 attach_lab1 = gtk_label_new_with_mnemonic(_("_Attachments"));
8054 gtk_widget_show(attach_lab1);
8055 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab1, TRUE, TRUE, 0);
8057 attach_lab2 = gtk_label_new("");
8058 gtk_widget_show(attach_lab2);
8059 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab2, FALSE, FALSE, 0);
8061 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
8062 compose_create_attach(compose),
8065 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
8066 compose_create_others(compose),
8067 gtk_label_new_with_mnemonic(_("Othe_rs")));
8070 subject_hbox = gtk_hbox_new(FALSE, 0);
8071 gtk_widget_show(subject_hbox);
8073 subject_frame = gtk_frame_new(NULL);
8074 gtk_frame_set_shadow_type(GTK_FRAME(subject_frame), GTK_SHADOW_NONE);
8075 gtk_box_pack_start(GTK_BOX(subject_hbox), subject_frame, TRUE, TRUE, 0);
8076 gtk_widget_show(subject_frame);
8078 subject = gtk_hbox_new(FALSE, HSPACING_NARROW);
8079 gtk_container_set_border_width(GTK_CONTAINER(subject), 0);
8080 gtk_widget_show(subject);
8082 label = gtk_label_new_with_mnemonic(_("S_ubject:"));
8083 gtk_box_pack_start(GTK_BOX(subject), label, FALSE, FALSE, 0);
8084 gtk_widget_show(label);
8087 subject_entry = claws_spell_entry_new();
8089 subject_entry = gtk_entry_new();
8091 gtk_box_pack_start(GTK_BOX(subject), subject_entry, TRUE, TRUE, 0);
8092 g_signal_connect_after(G_OBJECT(subject_entry), "grab_focus",
8093 G_CALLBACK(compose_grab_focus_cb), compose);
8094 gtk_label_set_mnemonic_widget(GTK_LABEL(label), subject_entry);
8095 gtk_widget_show(subject_entry);
8096 compose->subject_entry = subject_entry;
8097 gtk_container_add(GTK_CONTAINER(subject_frame), subject);
8099 edit_vbox = gtk_vbox_new(FALSE, 0);
8101 gtk_box_pack_start(GTK_BOX(edit_vbox), subject_hbox, FALSE, FALSE, 0);
8104 ruler_hbox = gtk_hbox_new(FALSE, 0);
8105 gtk_box_pack_start(GTK_BOX(edit_vbox), ruler_hbox, FALSE, FALSE, 0);
8107 ruler = gtk_shruler_new(GTK_ORIENTATION_HORIZONTAL);
8108 gtk_shruler_set_range(GTK_SHRULER(ruler), 0.0, 100.0, 1.0);
8109 gtk_box_pack_start(GTK_BOX(ruler_hbox), ruler, TRUE, TRUE,
8113 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
8114 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
8115 GTK_POLICY_AUTOMATIC,
8116 GTK_POLICY_AUTOMATIC);
8117 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
8119 gtk_box_pack_start(GTK_BOX(edit_vbox), scrolledwin, TRUE, TRUE, 0);
8121 text = gtk_text_view_new();
8122 if (prefs_common.show_compose_margin) {
8123 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text), 6);
8124 gtk_text_view_set_right_margin(GTK_TEXT_VIEW(text), 6);
8126 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
8127 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
8128 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), TRUE);
8129 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
8130 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
8132 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
8133 g_signal_connect_after(G_OBJECT(text), "size_allocate",
8134 G_CALLBACK(compose_edit_size_alloc),
8136 g_signal_connect(G_OBJECT(buffer), "changed",
8137 G_CALLBACK(compose_changed_cb), compose);
8138 g_signal_connect(G_OBJECT(text), "grab_focus",
8139 G_CALLBACK(compose_grab_focus_cb), compose);
8140 g_signal_connect(G_OBJECT(buffer), "insert_text",
8141 G_CALLBACK(text_inserted), compose);
8142 g_signal_connect(G_OBJECT(text), "button_press_event",
8143 G_CALLBACK(text_clicked), compose);
8144 g_signal_connect(G_OBJECT(text), "popup-menu",
8145 G_CALLBACK(compose_popup_menu), compose);
8146 g_signal_connect(G_OBJECT(subject_entry), "changed",
8147 G_CALLBACK(compose_changed_cb), compose);
8148 g_signal_connect(G_OBJECT(subject_entry), "activate",
8149 G_CALLBACK(compose_subject_entry_activated), compose);
8152 gtk_drag_dest_set(text, GTK_DEST_DEFAULT_ALL, compose_mime_types,
8153 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
8154 GDK_ACTION_COPY | GDK_ACTION_MOVE);
8155 g_signal_connect(G_OBJECT(text), "drag_data_received",
8156 G_CALLBACK(compose_insert_drag_received_cb),
8158 g_signal_connect(G_OBJECT(text), "drag-drop",
8159 G_CALLBACK(compose_drag_drop),
8161 g_signal_connect(G_OBJECT(text), "key-press-event",
8162 G_CALLBACK(completion_set_focus_to_subject),
8164 gtk_widget_show_all(vbox);
8166 /* pane between attach clist and text */
8167 paned = gtk_vpaned_new();
8168 gtk_container_add(GTK_CONTAINER(vbox2), paned);
8169 gtk_paned_pack1(GTK_PANED(paned), notebook, FALSE, FALSE);
8170 gtk_paned_pack2(GTK_PANED(paned), edit_vbox, TRUE, FALSE);
8171 gtk_paned_set_position(GTK_PANED(paned), prefs_common.compose_notebook_height);
8172 g_signal_connect(G_OBJECT(notebook), "size_allocate",
8173 G_CALLBACK(compose_notebook_size_alloc), paned);
8175 gtk_widget_show_all(paned);
8178 if (prefs_common.textfont) {
8179 PangoFontDescription *font_desc;
8181 font_desc = pango_font_description_from_string
8182 (prefs_common.textfont);
8184 gtk_widget_modify_font(text, font_desc);
8185 pango_font_description_free(font_desc);
8189 gtk_action_group_add_actions(action_group, compose_popup_entries,
8190 G_N_ELEMENTS(compose_popup_entries), (gpointer)compose);
8191 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Popup", NULL, GTK_UI_MANAGER_MENUBAR)
8192 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup", "Compose", "Compose", GTK_UI_MANAGER_MENU)
8193 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Add", "Compose/Add", GTK_UI_MANAGER_MENUITEM)
8194 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Remove", "Compose/Remove", GTK_UI_MANAGER_MENUITEM)
8195 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Separator1", "Compose/---", GTK_UI_MANAGER_SEPARATOR)
8196 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Properties", "Compose/Properties", GTK_UI_MANAGER_MENUITEM)
8198 popupmenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(gtk_ui_manager_get_widget(compose->ui_manager, "/Popup/Compose")));
8200 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
8201 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
8202 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
8204 tmpl_menu = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu/Tools/Template");
8206 undostruct = undo_init(text);
8207 undo_set_change_state_func(undostruct, &compose_undo_state_changed,
8210 address_completion_start(window);
8212 compose->window = window;
8213 compose->vbox = vbox;
8214 compose->menubar = menubar;
8215 compose->handlebox = handlebox;
8217 compose->vbox2 = vbox2;
8219 compose->paned = paned;
8221 compose->attach_label = attach_lab2;
8223 compose->notebook = notebook;
8224 compose->edit_vbox = edit_vbox;
8225 compose->ruler_hbox = ruler_hbox;
8226 compose->ruler = ruler;
8227 compose->scrolledwin = scrolledwin;
8228 compose->text = text;
8230 compose->focused_editable = NULL;
8232 compose->popupmenu = popupmenu;
8234 compose->tmpl_menu = tmpl_menu;
8236 compose->mode = mode;
8237 compose->rmode = mode;
8239 compose->targetinfo = NULL;
8240 compose->replyinfo = NULL;
8241 compose->fwdinfo = NULL;
8243 compose->email_hashtable = g_hash_table_new_full(g_str_hash,
8244 g_str_equal, (GDestroyNotify) g_free, NULL);
8246 compose->replyto = NULL;
8248 compose->bcc = NULL;
8249 compose->followup_to = NULL;
8251 compose->ml_post = NULL;
8253 compose->inreplyto = NULL;
8254 compose->references = NULL;
8255 compose->msgid = NULL;
8256 compose->boundary = NULL;
8258 compose->autowrap = prefs_common.autowrap;
8259 compose->autoindent = prefs_common.auto_indent;
8260 compose->use_signing = FALSE;
8261 compose->use_encryption = FALSE;
8262 compose->privacy_system = NULL;
8263 compose->encdata = NULL;
8265 compose->modified = FALSE;
8267 compose->return_receipt = FALSE;
8269 compose->to_list = NULL;
8270 compose->newsgroup_list = NULL;
8272 compose->undostruct = undostruct;
8274 compose->sig_str = NULL;
8276 compose->exteditor_file = NULL;
8277 compose->exteditor_pid = -1;
8278 compose->exteditor_tag = -1;
8279 compose->exteditor_socket = NULL;
8280 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN; /* inhibit auto-drafting while loading */
8282 compose->folder_update_callback_id =
8283 hooks_register_hook(FOLDER_UPDATE_HOOKLIST,
8284 compose_update_folder_hook,
8285 (gpointer) compose);
8288 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
8289 if (mode != COMPOSE_REDIRECT) {
8290 if (prefs_common.enable_aspell && prefs_common.dictionary &&
8291 strcmp(prefs_common.dictionary, "")) {
8292 gtkaspell = gtkaspell_new(prefs_common.dictionary,
8293 prefs_common.alt_dictionary,
8294 conv_get_locale_charset_str(),
8295 prefs_common.color[COL_MISSPELLED],
8296 prefs_common.check_while_typing,
8297 prefs_common.recheck_when_changing_dict,
8298 prefs_common.use_alternate,
8299 prefs_common.use_both_dicts,
8300 GTK_TEXT_VIEW(text),
8301 GTK_WINDOW(compose->window),
8302 compose_dict_changed,
8303 compose_spell_menu_changed,
8306 alertpanel_error(_("Spell checker could not "
8308 gtkaspell_checkers_strerror());
8309 gtkaspell_checkers_reset_error();
8311 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", TRUE);
8315 compose->gtkaspell = gtkaspell;
8316 compose_spell_menu_changed(compose);
8317 claws_spell_entry_set_gtkaspell(CLAWS_SPELL_ENTRY(subject_entry), gtkaspell);
8320 compose_select_account(compose, account, TRUE);
8322 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", prefs_common.autowrap);
8323 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", prefs_common.auto_indent);
8325 if (account->set_autocc && account->auto_cc && mode != COMPOSE_REEDIT)
8326 compose_entry_append(compose, account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8328 if (account->set_autobcc && account->auto_bcc && mode != COMPOSE_REEDIT)
8329 compose_entry_append(compose, account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8331 if (account->set_autoreplyto && account->auto_replyto && mode != COMPOSE_REEDIT)
8332 compose_entry_append(compose, account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8334 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", compose->mode == COMPOSE_REPLY);
8335 if (account->protocol != A_NNTP)
8336 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
8337 prefs_common_translated_header_name("To:"));
8339 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
8340 prefs_common_translated_header_name("Newsgroups:"));
8342 #ifndef USE_ALT_ADDRBOOK
8343 addressbook_set_target_compose(compose);
8345 if (mode != COMPOSE_REDIRECT)
8346 compose_set_template_menu(compose);
8348 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Template", FALSE);
8351 compose_list = g_list_append(compose_list, compose);
8353 if (!prefs_common.show_ruler)
8354 gtk_widget_hide(ruler_hbox);
8356 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Tools/ShowRuler", prefs_common.show_ruler);
8359 compose->priority = PRIORITY_NORMAL;
8360 compose_update_priority_menu_item(compose);
8362 compose_set_out_encoding(compose);
8365 compose_update_actions_menu(compose);
8367 /* Privacy Systems menu */
8368 compose_update_privacy_systems_menu(compose);
8369 compose_activate_privacy_system(compose, account, TRUE);
8371 toolbar_set_style(compose->toolbar->toolbar, compose->handlebox, prefs_common.toolbar_style);
8373 gtk_widget_realize(window);
8375 gtk_widget_show(window);
8381 static GtkWidget *compose_account_option_menu_create(Compose *compose)
8386 GtkWidget *optmenubox;
8387 GtkWidget *fromlabel;
8390 GtkWidget *from_name = NULL;
8392 gint num = 0, def_menu = 0;
8394 accounts = account_get_list();
8395 cm_return_val_if_fail(accounts != NULL, NULL);
8397 optmenubox = gtk_event_box_new();
8398 optmenu = gtkut_sc_combobox_create(optmenubox, FALSE);
8399 menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
8401 hbox = gtk_hbox_new(FALSE, 4);
8402 from_name = gtk_entry_new();
8404 g_signal_connect_after(G_OBJECT(from_name), "grab_focus",
8405 G_CALLBACK(compose_grab_focus_cb), compose);
8406 g_signal_connect_after(G_OBJECT(from_name), "activate",
8407 G_CALLBACK(from_name_activate_cb), optmenu);
8409 for (; accounts != NULL; accounts = accounts->next, num++) {
8410 PrefsAccount *ac = (PrefsAccount *)accounts->data;
8411 gchar *name, *from = NULL;
8413 if (ac == compose->account) def_menu = num;
8415 name = g_markup_printf_escaped("<i>%s</i>",
8418 if (ac == compose->account) {
8419 if (ac->name && *ac->name) {
8421 QUOTE_IF_REQUIRED_NORMAL(buf, ac->name, return NULL);
8422 from = g_strdup_printf("%s <%s>",
8424 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8426 from = g_strdup_printf("%s",
8428 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8430 if (cur_account != compose->account) {
8431 gtk_widget_modify_base(
8432 GTK_WIDGET(from_name),
8433 GTK_STATE_NORMAL, &default_header_bgcolor);
8434 gtk_widget_modify_text(
8435 GTK_WIDGET(from_name),
8436 GTK_STATE_NORMAL, &default_header_color);
8439 COMBOBOX_ADD(menu, name, ac->account_id);
8444 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), def_menu);
8446 g_signal_connect(G_OBJECT(optmenu), "changed",
8447 G_CALLBACK(account_activated),
8449 g_signal_connect(G_OBJECT(from_name), "populate-popup",
8450 G_CALLBACK(compose_entry_popup_extend),
8453 fromlabel = gtk_label_new_with_mnemonic(_("_From:"));
8454 gtk_label_set_mnemonic_widget(GTK_LABEL(fromlabel), from_name);
8456 gtk_box_pack_start(GTK_BOX(hbox), fromlabel, FALSE, FALSE, 4);
8457 gtk_box_pack_start(GTK_BOX(hbox), optmenubox, FALSE, FALSE, 0);
8458 gtk_box_pack_start(GTK_BOX(hbox), from_name, TRUE, TRUE, 0);
8460 /* Putting only the GtkEntry into focus chain of parent hbox causes
8461 * the account selector combobox next to it to be unreachable when
8462 * navigating widgets in GtkTable with up/down arrow keys.
8463 * Note: gtk_widget_set_can_focus() was not enough. */
8465 l = g_list_prepend(l, from_name);
8466 gtk_container_set_focus_chain(GTK_CONTAINER(hbox), l);
8469 CLAWS_SET_TIP(optmenubox,
8470 _("Account to use for this email"));
8471 CLAWS_SET_TIP(from_name,
8472 _("Sender address to be used"));
8474 compose->account_combo = optmenu;
8475 compose->from_name = from_name;
8480 static void compose_set_priority_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8482 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8483 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8484 Compose *compose = (Compose *) data;
8486 compose->priority = value;
8490 static void compose_reply_change_mode(Compose *compose,
8493 gboolean was_modified = compose->modified;
8495 gboolean all = FALSE, ml = FALSE, sender = FALSE, followup = FALSE;
8497 cm_return_if_fail(compose->replyinfo != NULL);
8499 if (action == COMPOSE_REPLY && prefs_common.default_reply_list)
8501 if (action == COMPOSE_REPLY && compose->rmode == COMPOSE_FOLLOWUP_AND_REPLY_TO)
8503 if (action == COMPOSE_REPLY_TO_ALL)
8505 if (action == COMPOSE_REPLY_TO_SENDER)
8507 if (action == COMPOSE_REPLY_TO_LIST)
8510 compose_remove_header_entries(compose);
8511 compose_reply_set_entry(compose, compose->replyinfo, all, ml, sender, followup);
8512 if (compose->account->set_autocc && compose->account->auto_cc)
8513 compose_entry_append(compose, compose->account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8515 if (compose->account->set_autobcc && compose->account->auto_bcc)
8516 compose_entry_append(compose, compose->account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8518 if (compose->account->set_autoreplyto && compose->account->auto_replyto)
8519 compose_entry_append(compose, compose->account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8520 compose_show_first_last_header(compose, TRUE);
8521 compose->modified = was_modified;
8522 compose_set_title(compose);
8525 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8527 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8528 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8529 Compose *compose = (Compose *) data;
8532 compose_reply_change_mode(compose, value);
8535 static void compose_update_priority_menu_item(Compose * compose)
8537 GtkWidget *menuitem = NULL;
8538 switch (compose->priority) {
8539 case PRIORITY_HIGHEST:
8540 menuitem = gtk_ui_manager_get_widget
8541 (compose->ui_manager, "/Menu/Options/Priority/Highest");
8544 menuitem = gtk_ui_manager_get_widget
8545 (compose->ui_manager, "/Menu/Options/Priority/High");
8547 case PRIORITY_NORMAL:
8548 menuitem = gtk_ui_manager_get_widget
8549 (compose->ui_manager, "/Menu/Options/Priority/Normal");
8552 menuitem = gtk_ui_manager_get_widget
8553 (compose->ui_manager, "/Menu/Options/Priority/Low");
8555 case PRIORITY_LOWEST:
8556 menuitem = gtk_ui_manager_get_widget
8557 (compose->ui_manager, "/Menu/Options/Priority/Lowest");
8560 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8563 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data)
8565 Compose *compose = (Compose *) data;
8567 gboolean can_sign = FALSE, can_encrypt = FALSE;
8569 cm_return_if_fail(GTK_IS_CHECK_MENU_ITEM(widget));
8571 if (!gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget)))
8574 systemid = g_object_get_data(G_OBJECT(widget), "privacy_system");
8575 g_free(compose->privacy_system);
8576 compose->privacy_system = NULL;
8577 g_free(compose->encdata);
8578 compose->encdata = NULL;
8579 if (systemid != NULL) {
8580 compose->privacy_system = g_strdup(systemid);
8582 can_sign = privacy_system_can_sign(systemid);
8583 can_encrypt = privacy_system_can_encrypt(systemid);
8586 debug_print("activated privacy system: %s\n", systemid != NULL ? systemid : "None");
8588 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8589 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8590 if (compose->toolbar->privacy_sign_btn != NULL) {
8591 gtk_widget_set_sensitive(
8592 GTK_WIDGET(compose->toolbar->privacy_sign_btn),
8594 gtk_toggle_tool_button_set_active(
8595 GTK_TOGGLE_TOOL_BUTTON(compose->toolbar->privacy_sign_btn),
8596 can_sign ? compose->use_signing : FALSE);
8598 if (compose->toolbar->privacy_encrypt_btn != NULL) {
8599 gtk_widget_set_sensitive(
8600 GTK_WIDGET(compose->toolbar->privacy_encrypt_btn),
8602 gtk_toggle_tool_button_set_active(
8603 GTK_TOGGLE_TOOL_BUTTON(compose->toolbar->privacy_encrypt_btn),
8604 can_encrypt ? compose->use_encryption : FALSE);
8608 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn)
8610 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8611 GtkWidget *menuitem = NULL;
8612 GList *children, *amenu;
8613 gboolean can_sign = FALSE, can_encrypt = FALSE;
8614 gboolean found = FALSE;
8616 if (compose->privacy_system != NULL) {
8618 menuitem = gtk_menu_item_get_submenu(GTK_MENU_ITEM(
8619 gtk_ui_manager_get_widget(compose->ui_manager, branch_path)));
8620 cm_return_if_fail(menuitem != NULL);
8622 children = gtk_container_get_children(GTK_CONTAINER(GTK_MENU_SHELL(menuitem)));
8625 while (amenu != NULL) {
8626 systemid = g_object_get_data(G_OBJECT(amenu->data), "privacy_system");
8627 if (systemid != NULL) {
8628 if (strcmp(systemid, compose->privacy_system) == 0 &&
8629 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8630 menuitem = GTK_WIDGET(amenu->data);
8632 can_sign = privacy_system_can_sign(systemid);
8633 can_encrypt = privacy_system_can_encrypt(systemid);
8637 } else if (strlen(compose->privacy_system) == 0 &&
8638 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8639 menuitem = GTK_WIDGET(amenu->data);
8642 can_encrypt = FALSE;
8647 amenu = amenu->next;
8649 g_list_free(children);
8650 if (menuitem != NULL)
8651 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8653 if (warn && !found && strlen(compose->privacy_system)) {
8654 alertpanel_warning(_("The privacy system '%s' cannot be loaded. You "
8655 "will not be able to sign or encrypt this message."),
8656 compose->privacy_system);
8660 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8661 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8662 if (compose->toolbar->privacy_sign_btn != NULL) {
8663 gtk_widget_set_sensitive(
8664 GTK_WIDGET(compose->toolbar->privacy_sign_btn),
8667 if (compose->toolbar->privacy_encrypt_btn != NULL) {
8668 gtk_widget_set_sensitive(
8669 GTK_WIDGET(compose->toolbar->privacy_encrypt_btn),
8674 static void compose_set_out_encoding(Compose *compose)
8676 CharSet out_encoding;
8677 const gchar *branch = NULL;
8678 out_encoding = conv_get_charset_from_str(prefs_common.outgoing_charset);
8680 switch(out_encoding) {
8681 case C_AUTO: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8682 case C_US_ASCII: branch = "Menu/Options/Encoding/" CS_US_ASCII; break;
8683 case C_UTF_8: branch = "Menu/Options/Encoding/" CS_UTF_8; break;
8684 case C_ISO_8859_2: branch = "Menu/Options/Encoding/" CS_ISO_8859_2; break;
8685 case C_ISO_8859_7: branch = "Menu/Options/Encoding/" CS_ISO_8859_7; break;
8686 case C_ISO_8859_9: branch = "Menu/Options/Encoding/" CS_ISO_8859_9; break;
8687 case C_ISO_8859_1: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_1; break;
8688 case C_ISO_8859_15: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_15; break;
8689 case C_WINDOWS_1252: branch = "Menu/Options/Encoding/Western/" CS_WINDOWS_1252; break;
8690 case C_ISO_8859_13: branch = "Menu/Options/Encoding/Baltic/" CS_ISO_8859_13; break;
8691 case C_ISO_8859_4: branch = "Menu/Options/Encoding/Baltic" CS_ISO_8859_4; break;
8692 case C_ISO_8859_8: branch = "Menu/Options/Encoding/Hebrew/" CS_ISO_8859_8; break;
8693 case C_WINDOWS_1255: branch = "Menu/Options/Encoding/Hebrew/" CS_WINDOWS_1255; break;
8694 case C_ISO_8859_6: branch = "Menu/Options/Encoding/Arabic/" CS_ISO_8859_6; break;
8695 case C_WINDOWS_1256: branch = "Menu/Options/Encoding/Arabic/" CS_WINDOWS_1256; break;
8696 case C_ISO_8859_5: branch = "Menu/Options/Encoding/Cyrillic/" CS_ISO_8859_5; break;
8697 case C_KOI8_R: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_R; break;
8698 case C_MACCYR: branch = "Menu/Options/Encoding/Cyrillic/" CS_MACCYR; break;
8699 case C_KOI8_U: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_U; break;
8700 case C_WINDOWS_1251: branch = "Menu/Options/Encoding/Cyrillic/" CS_WINDOWS_1251; break;
8701 case C_ISO_2022_JP: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP; break;
8702 case C_ISO_2022_JP_2: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP_2; break;
8703 case C_EUC_JP: branch = "Menu/Options/Encoding/Japanese/" CS_EUC_JP; break;
8704 case C_SHIFT_JIS: branch = "Menu/Options/Encoding/Japanese/" CS_SHIFT_JIS; break;
8705 case C_GB18030: branch = "Menu/Options/Encoding/Chinese/" CS_GB18030; break;
8706 case C_GB2312: branch = "Menu/Options/Encoding/Chinese/" CS_GB2312; break;
8707 case C_GBK: branch = "Menu/Options/Encoding/Chinese/" CS_GBK; break;
8708 case C_BIG5: branch = "Menu/Options/Encoding/Chinese/" CS_BIG5; break;
8709 case C_EUC_TW: branch = "Menu/Options/Encoding/Chinese/" CS_EUC_TW; break;
8710 case C_EUC_KR: branch = "Menu/Options/Encoding/Korean/" CS_EUC_KR; break;
8711 case C_ISO_2022_KR: branch = "Menu/Options/Encoding/Korean/" CS_ISO_2022_KR; break;
8712 case C_TIS_620: branch = "Menu/Options/Encoding/Thai/" CS_TIS_620; break;
8713 case C_WINDOWS_874: branch = "Menu/Options/Encoding/Thai/" CS_WINDOWS_874; break;
8714 default: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8716 cm_toggle_menu_set_active_full(compose->ui_manager, (gchar *)branch, TRUE);
8719 static void compose_set_template_menu(Compose *compose)
8721 GSList *tmpl_list, *cur;
8725 tmpl_list = template_get_config();
8727 menu = gtk_menu_new();
8729 gtk_menu_set_accel_group (GTK_MENU (menu),
8730 gtk_ui_manager_get_accel_group(compose->ui_manager));
8731 for (cur = tmpl_list; cur != NULL; cur = cur->next) {
8732 Template *tmpl = (Template *)cur->data;
8733 gchar *accel_path = NULL;
8734 item = gtk_menu_item_new_with_label(tmpl->name);
8735 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
8736 g_signal_connect(G_OBJECT(item), "activate",
8737 G_CALLBACK(compose_template_activate_cb),
8739 g_object_set_data(G_OBJECT(item), "template", tmpl);
8740 gtk_widget_show(item);
8741 accel_path = g_strconcat("<ComposeTemplates>" , "/", tmpl->name, NULL);
8742 gtk_menu_item_set_accel_path(GTK_MENU_ITEM(item), accel_path);
8746 gtk_widget_show(menu);
8747 gtk_menu_item_set_submenu(GTK_MENU_ITEM(compose->tmpl_menu), menu);
8750 void compose_update_actions_menu(Compose *compose)
8752 action_update_compose_menu(compose->ui_manager, "/Menu/Tools/Actions", compose);
8755 static void compose_update_privacy_systems_menu(Compose *compose)
8757 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8758 GSList *systems, *cur;
8760 GtkWidget *system_none;
8762 GtkWidget *privacy_menuitem = gtk_ui_manager_get_widget(compose->ui_manager, branch_path);
8763 GtkWidget *privacy_menu = gtk_menu_new();
8765 system_none = gtk_radio_menu_item_new_with_mnemonic(NULL, _("_None"));
8766 g_object_set_data_full(G_OBJECT(system_none), "privacy_system", NULL, NULL);
8768 g_signal_connect(G_OBJECT(system_none), "activate",
8769 G_CALLBACK(compose_set_privacy_system_cb), compose);
8771 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), system_none);
8772 gtk_widget_show(system_none);
8774 systems = privacy_get_system_ids();
8775 for (cur = systems; cur != NULL; cur = g_slist_next(cur)) {
8776 gchar *systemid = cur->data;
8778 group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(system_none));
8779 widget = gtk_radio_menu_item_new_with_label(group,
8780 privacy_system_get_name(systemid));
8781 g_object_set_data_full(G_OBJECT(widget), "privacy_system",
8782 g_strdup(systemid), g_free);
8783 g_signal_connect(G_OBJECT(widget), "activate",
8784 G_CALLBACK(compose_set_privacy_system_cb), compose);
8786 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), widget);
8787 gtk_widget_show(widget);
8790 g_slist_free(systems);
8791 gtk_menu_item_set_submenu(GTK_MENU_ITEM(privacy_menuitem), privacy_menu);
8792 gtk_widget_show_all(privacy_menu);
8793 gtk_widget_show_all(privacy_menuitem);
8796 void compose_reflect_prefs_all(void)
8801 for (cur = compose_list; cur != NULL; cur = cur->next) {
8802 compose = (Compose *)cur->data;
8803 compose_set_template_menu(compose);
8807 void compose_reflect_prefs_pixmap_theme(void)
8812 for (cur = compose_list; cur != NULL; cur = cur->next) {
8813 compose = (Compose *)cur->data;
8814 toolbar_update(TOOLBAR_COMPOSE, compose);
8818 static const gchar *compose_quote_char_from_context(Compose *compose)
8820 const gchar *qmark = NULL;
8822 cm_return_val_if_fail(compose != NULL, NULL);
8824 switch (compose->mode) {
8825 /* use forward-specific quote char */
8826 case COMPOSE_FORWARD:
8827 case COMPOSE_FORWARD_AS_ATTACH:
8828 case COMPOSE_FORWARD_INLINE:
8829 if (compose->folder && compose->folder->prefs &&
8830 compose->folder->prefs->forward_with_format)
8831 qmark = compose->folder->prefs->forward_quotemark;
8832 else if (compose->account->forward_with_format)
8833 qmark = compose->account->forward_quotemark;
8835 qmark = prefs_common.fw_quotemark;
8838 /* use reply-specific quote char in all other modes */
8840 if (compose->folder && compose->folder->prefs &&
8841 compose->folder->prefs->reply_with_format)
8842 qmark = compose->folder->prefs->reply_quotemark;
8843 else if (compose->account->reply_with_format)
8844 qmark = compose->account->reply_quotemark;
8846 qmark = prefs_common.quotemark;
8850 if (qmark == NULL || *qmark == '\0')
8856 static void compose_template_apply(Compose *compose, Template *tmpl,
8860 GtkTextBuffer *buffer;
8864 gchar *parsed_str = NULL;
8865 gint cursor_pos = 0;
8866 const gchar *err_msg = _("The body of the template has an error at line %d.");
8869 /* process the body */
8871 text = GTK_TEXT_VIEW(compose->text);
8872 buffer = gtk_text_view_get_buffer(text);
8875 qmark = compose_quote_char_from_context(compose);
8877 if (compose->replyinfo != NULL) {
8880 gtk_text_buffer_set_text(buffer, "", -1);
8881 mark = gtk_text_buffer_get_insert(buffer);
8882 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8884 parsed_str = compose_quote_fmt(compose, compose->replyinfo,
8885 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8887 } else if (compose->fwdinfo != NULL) {
8890 gtk_text_buffer_set_text(buffer, "", -1);
8891 mark = gtk_text_buffer_get_insert(buffer);
8892 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8894 parsed_str = compose_quote_fmt(compose, compose->fwdinfo,
8895 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8898 MsgInfo* dummyinfo = compose_msginfo_new_from_compose(compose);
8900 GtkTextIter start, end;
8903 gtk_text_buffer_get_start_iter(buffer, &start);
8904 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
8905 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
8907 /* clear the buffer now */
8909 gtk_text_buffer_set_text(buffer, "", -1);
8911 parsed_str = compose_quote_fmt(compose, dummyinfo,
8912 tmpl->value, qmark, tmp, FALSE, FALSE, err_msg);
8913 procmsg_msginfo_free( &dummyinfo );
8919 gtk_text_buffer_set_text(buffer, "", -1);
8920 mark = gtk_text_buffer_get_insert(buffer);
8921 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8924 if (replace && parsed_str && compose->account->auto_sig)
8925 compose_insert_sig(compose, FALSE);
8927 if (replace && parsed_str) {
8928 gtk_text_buffer_get_start_iter(buffer, &iter);
8929 gtk_text_buffer_place_cursor(buffer, &iter);
8933 cursor_pos = quote_fmt_get_cursor_pos();
8934 compose->set_cursor_pos = cursor_pos;
8935 if (cursor_pos == -1)
8937 gtk_text_buffer_get_start_iter(buffer, &iter);
8938 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
8939 gtk_text_buffer_place_cursor(buffer, &iter);
8942 /* process the other fields */
8944 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
8945 compose_template_apply_fields(compose, tmpl);
8946 quote_fmt_reset_vartable();
8947 quote_fmtlex_destroy();
8949 compose_changed_cb(NULL, compose);
8952 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
8953 gtkaspell_highlight_all(compose->gtkaspell);
8957 static void compose_template_apply_fields_error(const gchar *header)
8962 tr = g_strdup(C_("'%s' stands for a header name",
8963 "Template '%s' format error."));
8964 text = g_strdup_printf(tr, prefs_common_translated_header_name(header));
8965 alertpanel_error("%s", text);
8971 static void compose_template_apply_fields(Compose *compose, Template *tmpl)
8973 MsgInfo* dummyinfo = NULL;
8974 MsgInfo *msginfo = NULL;
8977 if (compose->replyinfo != NULL)
8978 msginfo = compose->replyinfo;
8979 else if (compose->fwdinfo != NULL)
8980 msginfo = compose->fwdinfo;
8982 dummyinfo = compose_msginfo_new_from_compose(compose);
8983 msginfo = dummyinfo;
8986 if (tmpl->from && *tmpl->from != '\0') {
8988 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8989 compose->gtkaspell);
8991 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8993 quote_fmt_scan_string(tmpl->from);
8996 buf = quote_fmt_get_buffer();
8998 compose_template_apply_fields_error("From");
9000 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
9003 quote_fmt_reset_vartable();
9004 quote_fmtlex_destroy();
9007 if (tmpl->to && *tmpl->to != '\0') {
9009 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
9010 compose->gtkaspell);
9012 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
9014 quote_fmt_scan_string(tmpl->to);
9017 buf = quote_fmt_get_buffer();
9019 compose_template_apply_fields_error("To");
9021 compose_entry_append(compose, buf, COMPOSE_TO, PREF_TEMPLATE);
9024 quote_fmt_reset_vartable();
9025 quote_fmtlex_destroy();
9028 if (tmpl->cc && *tmpl->cc != '\0') {
9030 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
9031 compose->gtkaspell);
9033 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
9035 quote_fmt_scan_string(tmpl->cc);
9038 buf = quote_fmt_get_buffer();
9040 compose_template_apply_fields_error("Cc");
9042 compose_entry_append(compose, buf, COMPOSE_CC, PREF_TEMPLATE);
9045 quote_fmt_reset_vartable();
9046 quote_fmtlex_destroy();
9049 if (tmpl->bcc && *tmpl->bcc != '\0') {
9051 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
9052 compose->gtkaspell);
9054 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
9056 quote_fmt_scan_string(tmpl->bcc);
9059 buf = quote_fmt_get_buffer();
9061 compose_template_apply_fields_error("Bcc");
9063 compose_entry_append(compose, buf, COMPOSE_BCC, PREF_TEMPLATE);
9066 quote_fmt_reset_vartable();
9067 quote_fmtlex_destroy();
9070 if (tmpl->replyto && *tmpl->replyto != '\0') {
9072 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
9073 compose->gtkaspell);
9075 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
9077 quote_fmt_scan_string(tmpl->replyto);
9080 buf = quote_fmt_get_buffer();
9082 compose_template_apply_fields_error("Reply-To");
9084 compose_entry_append(compose, buf, COMPOSE_REPLYTO, PREF_TEMPLATE);
9087 quote_fmt_reset_vartable();
9088 quote_fmtlex_destroy();
9091 /* process the subject */
9092 if (tmpl->subject && *tmpl->subject != '\0') {
9094 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
9095 compose->gtkaspell);
9097 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
9099 quote_fmt_scan_string(tmpl->subject);
9102 buf = quote_fmt_get_buffer();
9104 compose_template_apply_fields_error("Subject");
9106 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
9109 quote_fmt_reset_vartable();
9110 quote_fmtlex_destroy();
9113 procmsg_msginfo_free( &dummyinfo );
9116 static void compose_destroy(Compose *compose)
9118 GtkAllocation allocation;
9119 GtkTextBuffer *buffer;
9120 GtkClipboard *clipboard;
9122 compose_list = g_list_remove(compose_list, compose);
9125 gboolean enable = TRUE;
9126 g_slist_foreach(compose->passworded_ldap_servers,
9127 _ldap_srv_func, &enable);
9128 g_slist_free(compose->passworded_ldap_servers);
9131 if (compose->updating) {
9132 debug_print("danger, not destroying anything now\n");
9133 compose->deferred_destroy = TRUE;
9137 /* NOTE: address_completion_end() does nothing with the window
9138 * however this may change. */
9139 address_completion_end(compose->window);
9141 slist_free_strings_full(compose->to_list);
9142 slist_free_strings_full(compose->newsgroup_list);
9143 slist_free_strings_full(compose->header_list);
9145 slist_free_strings_full(extra_headers);
9146 extra_headers = NULL;
9148 compose->header_list = compose->newsgroup_list = compose->to_list = NULL;
9150 g_hash_table_destroy(compose->email_hashtable);
9152 hooks_unregister_hook(FOLDER_UPDATE_HOOKLIST,
9153 compose->folder_update_callback_id);
9155 procmsg_msginfo_free(&(compose->targetinfo));
9156 procmsg_msginfo_free(&(compose->replyinfo));
9157 procmsg_msginfo_free(&(compose->fwdinfo));
9159 g_free(compose->replyto);
9160 g_free(compose->cc);
9161 g_free(compose->bcc);
9162 g_free(compose->newsgroups);
9163 g_free(compose->followup_to);
9165 g_free(compose->ml_post);
9167 g_free(compose->inreplyto);
9168 g_free(compose->references);
9169 g_free(compose->msgid);
9170 g_free(compose->boundary);
9172 g_free(compose->redirect_filename);
9173 if (compose->undostruct)
9174 undo_destroy(compose->undostruct);
9176 g_free(compose->sig_str);
9178 g_free(compose->exteditor_file);
9180 g_free(compose->orig_charset);
9182 g_free(compose->privacy_system);
9183 g_free(compose->encdata);
9185 #ifndef USE_ALT_ADDRBOOK
9186 if (addressbook_get_target_compose() == compose)
9187 addressbook_set_target_compose(NULL);
9190 if (compose->gtkaspell) {
9191 gtkaspell_delete(compose->gtkaspell);
9192 compose->gtkaspell = NULL;
9196 if (!compose->batch) {
9197 gtk_widget_get_allocation(compose->window, &allocation);
9198 prefs_common.compose_width = allocation.width;
9199 prefs_common.compose_height = allocation.height;
9202 if (!gtk_widget_get_parent(compose->paned))
9203 gtk_widget_destroy(compose->paned);
9204 gtk_widget_destroy(compose->popupmenu);
9206 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
9207 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
9208 gtk_text_buffer_remove_selection_clipboard(buffer, clipboard);
9210 message_search_close(compose);
9211 gtk_widget_destroy(compose->window);
9212 toolbar_destroy(compose->toolbar);
9213 g_free(compose->toolbar);
9214 cm_mutex_free(compose->mutex);
9218 static void compose_attach_info_free(AttachInfo *ainfo)
9220 g_free(ainfo->file);
9221 g_free(ainfo->content_type);
9222 g_free(ainfo->name);
9223 g_free(ainfo->charset);
9227 static void compose_attach_update_label(Compose *compose)
9232 GtkTreeModel *model;
9236 if (compose == NULL)
9239 model = gtk_tree_view_get_model(GTK_TREE_VIEW(compose->attach_clist));
9240 if (!gtk_tree_model_get_iter_first(model, &iter)) {
9241 gtk_label_set_text(GTK_LABEL(compose->attach_label), "");
9245 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
9246 total_size = ainfo->size;
9247 while(gtk_tree_model_iter_next(model, &iter)) {
9248 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
9249 total_size += ainfo->size;
9252 text = g_strdup_printf(" (%d/%s)", i, to_human_readable(total_size));
9253 gtk_label_set_text(GTK_LABEL(compose->attach_label), text);
9257 static void compose_attach_remove_selected(GtkAction *action, gpointer data)
9259 Compose *compose = (Compose *)data;
9260 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
9261 GtkTreeSelection *selection;
9263 GtkTreeModel *model;
9265 selection = gtk_tree_view_get_selection(tree_view);
9266 sel = gtk_tree_selection_get_selected_rows(selection, &model);
9267 cm_return_if_fail(sel);
9269 for (cur = sel; cur != NULL; cur = cur->next) {
9270 GtkTreePath *path = cur->data;
9271 GtkTreeRowReference *ref = gtk_tree_row_reference_new
9274 gtk_tree_path_free(path);
9277 for (cur = sel; cur != NULL; cur = cur->next) {
9278 GtkTreeRowReference *ref = cur->data;
9279 GtkTreePath *path = gtk_tree_row_reference_get_path(ref);
9282 if (gtk_tree_model_get_iter(model, &iter, path))
9283 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
9285 gtk_tree_path_free(path);
9286 gtk_tree_row_reference_free(ref);
9290 compose_attach_update_label(compose);
9293 static struct _AttachProperty
9296 GtkWidget *mimetype_entry;
9297 GtkWidget *encoding_optmenu;
9298 GtkWidget *path_entry;
9299 GtkWidget *filename_entry;
9301 GtkWidget *cancel_btn;
9304 static void gtk_tree_path_free_(gpointer ptr, gpointer data)
9306 gtk_tree_path_free((GtkTreePath *)ptr);
9309 static void compose_attach_property(GtkAction *action, gpointer data)
9311 Compose *compose = (Compose *)data;
9312 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
9314 GtkComboBox *optmenu;
9315 GtkTreeSelection *selection;
9317 GtkTreeModel *model;
9320 static gboolean cancelled;
9322 /* only if one selected */
9323 selection = gtk_tree_view_get_selection(tree_view);
9324 if (gtk_tree_selection_count_selected_rows(selection) != 1)
9327 sel = gtk_tree_selection_get_selected_rows(selection, &model);
9328 cm_return_if_fail(sel);
9330 path = (GtkTreePath *) sel->data;
9331 gtk_tree_model_get_iter(model, &iter, path);
9332 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
9335 g_list_foreach(sel, gtk_tree_path_free_, NULL);
9341 if (!attach_prop.window)
9342 compose_attach_property_create(&cancelled);
9343 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), TRUE);
9344 gtk_widget_grab_focus(attach_prop.ok_btn);
9345 gtk_widget_show(attach_prop.window);
9346 gtk_window_set_transient_for(GTK_WINDOW(attach_prop.window),
9347 GTK_WINDOW(compose->window));
9349 optmenu = GTK_COMBO_BOX(attach_prop.encoding_optmenu);
9350 if (ainfo->encoding == ENC_UNKNOWN)
9351 combobox_select_by_data(optmenu, ENC_BASE64);
9353 combobox_select_by_data(optmenu, ainfo->encoding);
9355 gtk_entry_set_text(GTK_ENTRY(attach_prop.mimetype_entry),
9356 ainfo->content_type ? ainfo->content_type : "");
9357 gtk_entry_set_text(GTK_ENTRY(attach_prop.path_entry),
9358 ainfo->file ? ainfo->file : "");
9359 gtk_entry_set_text(GTK_ENTRY(attach_prop.filename_entry),
9360 ainfo->name ? ainfo->name : "");
9363 const gchar *entry_text;
9365 gchar *cnttype = NULL;
9372 gtk_widget_hide(attach_prop.window);
9373 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), FALSE);
9378 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.mimetype_entry));
9379 if (*entry_text != '\0') {
9382 text = g_strstrip(g_strdup(entry_text));
9383 if ((p = strchr(text, '/')) && !strchr(p + 1, '/')) {
9384 cnttype = g_strdup(text);
9387 alertpanel_error(_("Invalid MIME type."));
9393 ainfo->encoding = combobox_get_active_data(optmenu);
9395 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.path_entry));
9396 if (*entry_text != '\0') {
9397 if (is_file_exist(entry_text) &&
9398 (size = get_file_size(entry_text)) > 0)
9399 file = g_strdup(entry_text);
9402 (_("File doesn't exist or is empty."));
9408 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.filename_entry));
9409 if (*entry_text != '\0') {
9410 g_free(ainfo->name);
9411 ainfo->name = g_strdup(entry_text);
9415 g_free(ainfo->content_type);
9416 ainfo->content_type = cnttype;
9419 g_free(ainfo->file);
9423 ainfo->size = (goffset)size;
9425 /* update tree store */
9426 text = to_human_readable(ainfo->size);
9427 gtk_tree_model_get_iter(model, &iter, path);
9428 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
9429 COL_MIMETYPE, ainfo->content_type,
9431 COL_NAME, ainfo->name,
9432 COL_CHARSET, ainfo->charset,
9438 gtk_tree_path_free(path);
9441 #define SET_LABEL_AND_ENTRY(str, entry, top) \
9443 label = gtk_label_new(str); \
9444 gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), \
9445 GTK_FILL, 0, 0, 0); \
9446 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); \
9448 entry = gtk_entry_new(); \
9449 gtk_table_attach(GTK_TABLE(table), entry, 1, 2, top, (top + 1), \
9450 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); \
9453 static void compose_attach_property_create(gboolean *cancelled)
9459 GtkWidget *mimetype_entry;
9462 GtkListStore *optmenu_menu;
9463 GtkWidget *path_entry;
9464 GtkWidget *filename_entry;
9467 GtkWidget *cancel_btn;
9468 GList *mime_type_list, *strlist;
9471 debug_print("Creating attach_property window...\n");
9473 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose_attach_property");
9474 gtk_widget_set_size_request(window, 480, -1);
9475 gtk_container_set_border_width(GTK_CONTAINER(window), 8);
9476 gtk_window_set_title(GTK_WINDOW(window), _("Properties"));
9477 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
9478 gtk_window_set_type_hint(GTK_WINDOW(window), GDK_WINDOW_TYPE_HINT_DIALOG);
9479 g_signal_connect(G_OBJECT(window), "delete_event",
9480 G_CALLBACK(attach_property_delete_event),
9482 g_signal_connect(G_OBJECT(window), "key_press_event",
9483 G_CALLBACK(attach_property_key_pressed),
9486 vbox = gtk_vbox_new(FALSE, 8);
9487 gtk_container_add(GTK_CONTAINER(window), vbox);
9489 table = gtk_table_new(4, 2, FALSE);
9490 gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
9491 gtk_table_set_row_spacings(GTK_TABLE(table), 8);
9492 gtk_table_set_col_spacings(GTK_TABLE(table), 8);
9494 label = gtk_label_new(_("MIME type"));
9495 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, (0 + 1),
9497 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
9498 mimetype_entry = gtk_combo_box_text_new_with_entry();
9499 gtk_table_attach(GTK_TABLE(table), mimetype_entry, 1, 2, 0, (0 + 1),
9500 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
9502 /* stuff with list */
9503 mime_type_list = procmime_get_mime_type_list();
9505 for (; mime_type_list != NULL; mime_type_list = mime_type_list->next) {
9506 MimeType *type = (MimeType *) mime_type_list->data;
9509 tmp = g_strdup_printf("%s/%s", type->type, type->sub_type);
9511 if (g_list_find_custom(strlist, tmp, (GCompareFunc)g_strcmp0))
9514 strlist = g_list_insert_sorted(strlist, (gpointer)tmp,
9515 (GCompareFunc)g_strcmp0);
9518 for (mime_type_list = strlist; mime_type_list != NULL;
9519 mime_type_list = mime_type_list->next) {
9520 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(mimetype_entry), mime_type_list->data);
9521 g_free(mime_type_list->data);
9523 g_list_free(strlist);
9524 gtk_combo_box_set_active(GTK_COMBO_BOX(mimetype_entry), 0);
9525 mimetype_entry = gtk_bin_get_child(GTK_BIN((mimetype_entry)));
9527 label = gtk_label_new(_("Encoding"));
9528 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2,
9530 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
9532 hbox = gtk_hbox_new(FALSE, 0);
9533 gtk_table_attach(GTK_TABLE(table), hbox, 1, 2, 1, 2,
9534 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
9536 optmenu = gtkut_sc_combobox_create(NULL, TRUE);
9537 optmenu_menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
9539 COMBOBOX_ADD(optmenu_menu, "7bit", ENC_7BIT);
9540 COMBOBOX_ADD(optmenu_menu, "8bit", ENC_8BIT);
9541 COMBOBOX_ADD(optmenu_menu, "quoted-printable", ENC_QUOTED_PRINTABLE);
9542 COMBOBOX_ADD(optmenu_menu, "base64", ENC_BASE64);
9543 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), 0);
9545 gtk_box_pack_start(GTK_BOX(hbox), optmenu, TRUE, TRUE, 0);
9547 SET_LABEL_AND_ENTRY(_("Path"), path_entry, 2);
9548 SET_LABEL_AND_ENTRY(_("File name"), filename_entry, 3);
9550 gtkut_stock_button_set_create(&hbbox, &cancel_btn, GTK_STOCK_CANCEL,
9551 &ok_btn, GTK_STOCK_OK,
9553 gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
9554 gtk_widget_grab_default(ok_btn);
9556 g_signal_connect(G_OBJECT(ok_btn), "clicked",
9557 G_CALLBACK(attach_property_ok),
9559 g_signal_connect(G_OBJECT(cancel_btn), "clicked",
9560 G_CALLBACK(attach_property_cancel),
9563 gtk_widget_show_all(vbox);
9565 attach_prop.window = window;
9566 attach_prop.mimetype_entry = mimetype_entry;
9567 attach_prop.encoding_optmenu = optmenu;
9568 attach_prop.path_entry = path_entry;
9569 attach_prop.filename_entry = filename_entry;
9570 attach_prop.ok_btn = ok_btn;
9571 attach_prop.cancel_btn = cancel_btn;
9574 #undef SET_LABEL_AND_ENTRY
9576 static void attach_property_ok(GtkWidget *widget, gboolean *cancelled)
9582 static void attach_property_cancel(GtkWidget *widget, gboolean *cancelled)
9588 static gint attach_property_delete_event(GtkWidget *widget, GdkEventAny *event,
9589 gboolean *cancelled)
9597 static gboolean attach_property_key_pressed(GtkWidget *widget,
9599 gboolean *cancelled)
9601 if (event && event->keyval == GDK_KEY_Escape) {
9605 if (event && event->keyval == GDK_KEY_Return) {
9613 static void compose_exec_ext_editor(Compose *compose)
9618 GdkNativeWindow socket_wid = 0;
9622 tmp = g_strdup_printf("%s%ctmpmsg.%p", get_tmp_dir(),
9623 G_DIR_SEPARATOR, compose);
9625 if (compose_get_ext_editor_uses_socket()) {
9626 /* Only allow one socket */
9627 if (compose->exteditor_socket != NULL) {
9628 if (gtk_widget_is_focus(compose->exteditor_socket)) {
9629 /* Move the focus off of the socket */
9630 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9635 /* Create the receiving GtkSocket */
9636 socket = gtk_socket_new ();
9637 g_signal_connect (GTK_OBJECT(socket), "plug-removed",
9638 G_CALLBACK(compose_ext_editor_plug_removed_cb),
9640 gtk_box_pack_start(GTK_BOX(compose->edit_vbox), socket, TRUE, TRUE, 0);
9641 gtk_widget_set_size_request(socket, prefs_common.compose_width, -1);
9642 /* Realize the socket so that we can use its ID */
9643 gtk_widget_realize(socket);
9644 socket_wid = gtk_socket_get_id(GTK_SOCKET (socket));
9645 compose->exteditor_socket = socket;
9648 if (pipe(pipe_fds) < 0) {
9654 if ((pid = fork()) < 0) {
9661 /* close the write side of the pipe */
9664 compose->exteditor_file = g_strdup(tmp);
9665 compose->exteditor_pid = pid;
9667 compose_set_ext_editor_sensitive(compose, FALSE);
9670 compose->exteditor_ch = g_io_channel_unix_new(pipe_fds[0]);
9672 compose->exteditor_ch = g_io_channel_win32_new_fd(pipe_fds[0]);
9674 compose->exteditor_tag = g_io_add_watch(compose->exteditor_ch,
9678 } else { /* process-monitoring process */
9684 /* close the read side of the pipe */
9687 if (compose_write_body_to_file(compose, tmp) < 0) {
9688 fd_write_all(pipe_fds[1], "2\n", 2);
9692 pid_ed = compose_exec_ext_editor_real(tmp, socket_wid);
9694 fd_write_all(pipe_fds[1], "1\n", 2);
9698 /* wait until editor is terminated */
9699 waitpid(pid_ed, NULL, 0);
9701 fd_write_all(pipe_fds[1], "0\n", 2);
9708 #endif /* G_OS_UNIX */
9711 static gboolean compose_can_autosave(Compose *compose)
9713 if (compose->privacy_system && compose->use_encryption)
9714 return prefs_common.autosave && prefs_common.autosave_encrypted;
9716 return prefs_common.autosave;
9720 static gboolean compose_get_ext_editor_cmd_valid()
9722 gboolean has_s = FALSE;
9723 gboolean has_w = FALSE;
9724 const gchar *p = prefs_common_get_ext_editor_cmd();
9727 while ((p = strchr(p, '%'))) {
9733 } else if (*p == 'w') {
9744 static gint compose_exec_ext_editor_real(const gchar *file, GdkNativeWindow socket_wid)
9751 cm_return_val_if_fail(file != NULL, -1);
9753 if ((pid = fork()) < 0) {
9758 if (pid != 0) return pid;
9760 /* grandchild process */
9762 if (setpgid(0, getppid()))
9765 if (compose_get_ext_editor_cmd_valid()) {
9766 if (compose_get_ext_editor_uses_socket()) {
9767 p = g_strdup(prefs_common_get_ext_editor_cmd());
9768 s = strstr(p, "%w");
9770 if (strstr(p, "%s") < s)
9771 buf = g_strdup_printf(p, file, socket_wid);
9773 buf = g_strdup_printf(p, socket_wid, file);
9776 buf = g_strdup_printf(prefs_common_get_ext_editor_cmd(), file);
9779 if (prefs_common_get_ext_editor_cmd())
9780 g_warning("External editor command-line is invalid: '%s'",
9781 prefs_common_get_ext_editor_cmd());
9782 buf = g_strdup_printf(DEFAULT_EDITOR_CMD, file);
9785 cmdline = strsplit_with_quote(buf, " ", 0);
9787 execvp(cmdline[0], cmdline);
9790 g_strfreev(cmdline);
9795 static gboolean compose_ext_editor_kill(Compose *compose)
9797 pid_t pgid = compose->exteditor_pid * -1;
9800 ret = kill(pgid, 0);
9802 if (ret == 0 || (ret == -1 && EPERM == errno)) {
9806 msg = g_strdup_printf
9807 (_("The external editor is still working.\n"
9808 "Force terminating the process?\n"
9809 "process group id: %d"), -pgid);
9810 val = alertpanel_full(_("Notice"), msg, GTK_STOCK_NO, GTK_STOCK_YES,
9811 NULL, ALERTFOCUS_FIRST, FALSE, NULL,
9816 if (val == G_ALERTALTERNATE) {
9817 g_source_remove(compose->exteditor_tag);
9818 g_io_channel_shutdown(compose->exteditor_ch,
9820 g_io_channel_unref(compose->exteditor_ch);
9822 if (kill(pgid, SIGTERM) < 0) perror("kill");
9823 waitpid(compose->exteditor_pid, NULL, 0);
9825 g_warning("Terminated process group id: %d. "
9826 "Temporary file: %s", -pgid, compose->exteditor_file);
9828 compose_set_ext_editor_sensitive(compose, TRUE);
9830 g_free(compose->exteditor_file);
9831 compose->exteditor_file = NULL;
9832 compose->exteditor_pid = -1;
9833 compose->exteditor_ch = NULL;
9834 compose->exteditor_tag = -1;
9842 static gboolean compose_input_cb(GIOChannel *source, GIOCondition condition,
9846 Compose *compose = (Compose *)data;
9849 debug_print("Compose: input from monitoring process\n");
9851 if (g_io_channel_read_chars(source, buf, sizeof(buf), &bytes_read, NULL) != G_IO_STATUS_NORMAL) {
9856 g_io_channel_shutdown(source, FALSE, NULL);
9857 g_io_channel_unref(source);
9859 waitpid(compose->exteditor_pid, NULL, 0);
9861 if (buf[0] == '0') { /* success */
9862 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9863 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
9864 GtkTextIter start, end;
9867 gtk_text_buffer_set_text(buffer, "", -1);
9868 compose_insert_file(compose, compose->exteditor_file);
9869 compose_changed_cb(NULL, compose);
9871 /* Check if we should save the draft or not */
9872 if (compose_can_autosave(compose))
9873 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
9875 if (claws_unlink(compose->exteditor_file) < 0)
9876 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9878 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
9879 gtk_text_buffer_get_start_iter(buffer, &start);
9880 gtk_text_buffer_get_end_iter(buffer, &end);
9881 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
9882 if (chars && strlen(chars) > 0)
9883 compose->modified = TRUE;
9885 } else if (buf[0] == '1') { /* failed */
9886 g_warning("Couldn't exec external editor");
9887 if (claws_unlink(compose->exteditor_file) < 0)
9888 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9889 } else if (buf[0] == '2') {
9890 g_warning("Couldn't write to file");
9891 } else if (buf[0] == '3') {
9892 g_warning("Pipe read failed");
9895 compose_set_ext_editor_sensitive(compose, TRUE);
9897 g_free(compose->exteditor_file);
9898 compose->exteditor_file = NULL;
9899 compose->exteditor_pid = -1;
9900 compose->exteditor_ch = NULL;
9901 compose->exteditor_tag = -1;
9902 if (compose->exteditor_socket) {
9903 gtk_widget_destroy(compose->exteditor_socket);
9904 compose->exteditor_socket = NULL;
9911 static char *ext_editor_menu_entries[] = {
9912 "Menu/Message/Send",
9913 "Menu/Message/SendLater",
9914 "Menu/Message/InsertFile",
9915 "Menu/Message/InsertSig",
9916 "Menu/Message/ReplaceSig",
9917 "Menu/Message/Save",
9918 "Menu/Message/Print",
9923 "Menu/Tools/ShowRuler",
9924 "Menu/Tools/Actions",
9929 static void compose_set_ext_editor_sensitive(Compose *compose,
9934 for (i = 0; ext_editor_menu_entries[i]; ++i) {
9935 cm_menu_set_sensitive_full(compose->ui_manager,
9936 ext_editor_menu_entries[i], sensitive);
9939 if (compose_get_ext_editor_uses_socket()) {
9941 if (compose->exteditor_socket)
9942 gtk_widget_hide(compose->exteditor_socket);
9943 gtk_widget_show(compose->scrolledwin);
9944 if (prefs_common.show_ruler)
9945 gtk_widget_show(compose->ruler_hbox);
9946 /* Fix the focus, as it doesn't go anywhere when the
9947 * socket is hidden or destroyed */
9948 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9950 g_assert (compose->exteditor_socket != NULL);
9951 /* Fix the focus, as it doesn't go anywhere when the
9952 * edit box is hidden */
9953 if (gtk_widget_is_focus(compose->text))
9954 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9955 gtk_widget_hide(compose->scrolledwin);
9956 gtk_widget_hide(compose->ruler_hbox);
9957 gtk_widget_show(compose->exteditor_socket);
9960 gtk_widget_set_sensitive(compose->text, sensitive);
9962 if (compose->toolbar->send_btn)
9963 gtk_widget_set_sensitive(compose->toolbar->send_btn, sensitive);
9964 if (compose->toolbar->sendl_btn)
9965 gtk_widget_set_sensitive(compose->toolbar->sendl_btn, sensitive);
9966 if (compose->toolbar->draft_btn)
9967 gtk_widget_set_sensitive(compose->toolbar->draft_btn, sensitive);
9968 if (compose->toolbar->insert_btn)
9969 gtk_widget_set_sensitive(compose->toolbar->insert_btn, sensitive);
9970 if (compose->toolbar->sig_btn)
9971 gtk_widget_set_sensitive(compose->toolbar->sig_btn, sensitive);
9972 if (compose->toolbar->exteditor_btn)
9973 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, sensitive);
9974 if (compose->toolbar->linewrap_current_btn)
9975 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, sensitive);
9976 if (compose->toolbar->linewrap_all_btn)
9977 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, sensitive);
9980 static gboolean compose_get_ext_editor_uses_socket()
9982 return (prefs_common_get_ext_editor_cmd() &&
9983 strstr(prefs_common_get_ext_editor_cmd(), "%w"));
9986 static gboolean compose_ext_editor_plug_removed_cb(GtkSocket *socket, Compose *compose)
9988 compose->exteditor_socket = NULL;
9989 /* returning FALSE allows destruction of the socket */
9992 #endif /* G_OS_UNIX */
9995 * compose_undo_state_changed:
9997 * Change the sensivity of the menuentries undo and redo
9999 static void compose_undo_state_changed(UndoMain *undostruct, gint undo_state,
10000 gint redo_state, gpointer data)
10002 Compose *compose = (Compose *)data;
10004 switch (undo_state) {
10005 case UNDO_STATE_TRUE:
10006 if (!undostruct->undo_state) {
10007 undostruct->undo_state = TRUE;
10008 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", TRUE);
10011 case UNDO_STATE_FALSE:
10012 if (undostruct->undo_state) {
10013 undostruct->undo_state = FALSE;
10014 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
10017 case UNDO_STATE_UNCHANGED:
10019 case UNDO_STATE_REFRESH:
10020 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", undostruct->undo_state);
10023 g_warning("Undo state not recognized");
10027 switch (redo_state) {
10028 case UNDO_STATE_TRUE:
10029 if (!undostruct->redo_state) {
10030 undostruct->redo_state = TRUE;
10031 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", TRUE);
10034 case UNDO_STATE_FALSE:
10035 if (undostruct->redo_state) {
10036 undostruct->redo_state = FALSE;
10037 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
10040 case UNDO_STATE_UNCHANGED:
10042 case UNDO_STATE_REFRESH:
10043 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", undostruct->redo_state);
10046 g_warning("Redo state not recognized");
10051 /* callback functions */
10053 static void compose_notebook_size_alloc(GtkNotebook *notebook,
10054 GtkAllocation *allocation,
10057 prefs_common.compose_notebook_height = gtk_paned_get_position(paned);
10060 /* compose_edit_size_alloc() - called when resized. don't know whether Gtk
10061 * includes "non-client" (windows-izm) in calculation, so this calculation
10062 * may not be accurate.
10064 static gboolean compose_edit_size_alloc(GtkEditable *widget,
10065 GtkAllocation *allocation,
10066 GtkSHRuler *shruler)
10068 if (prefs_common.show_ruler) {
10069 gint char_width = 0, char_height = 0;
10070 gint line_width_in_chars;
10072 gtkut_get_font_size(GTK_WIDGET(widget),
10073 &char_width, &char_height);
10074 line_width_in_chars =
10075 (allocation->width - allocation->x) / char_width;
10077 /* got the maximum */
10078 gtk_shruler_set_range(GTK_SHRULER(shruler),
10079 0.0, line_width_in_chars, 0);
10088 ComposePrefType type;
10089 gboolean entry_marked;
10090 } HeaderEntryState;
10092 static void account_activated(GtkComboBox *optmenu, gpointer data)
10094 Compose *compose = (Compose *)data;
10097 gchar *folderidentifier;
10098 gint account_id = 0;
10099 GtkTreeModel *menu;
10101 GSList *list, *saved_list = NULL;
10102 HeaderEntryState *state;
10104 /* Get ID of active account in the combo box */
10105 menu = gtk_combo_box_get_model(optmenu);
10106 cm_return_if_fail(gtk_combo_box_get_active_iter(optmenu, &iter));
10107 gtk_tree_model_get(menu, &iter, 1, &account_id, -1);
10109 ac = account_find_from_id(account_id);
10110 cm_return_if_fail(ac != NULL);
10112 if (ac != compose->account) {
10113 compose_select_account(compose, ac, FALSE);
10115 for (list = compose->header_list; list; list = list->next) {
10116 ComposeHeaderEntry *hentry=(ComposeHeaderEntry *)list->data;
10118 if (hentry->type == PREF_ACCOUNT || !list->next) {
10119 compose_destroy_headerentry(compose, hentry);
10122 state = g_malloc0(sizeof(HeaderEntryState));
10123 state->header = gtk_editable_get_chars(GTK_EDITABLE(
10124 gtk_bin_get_child(GTK_BIN(hentry->combo))), 0, -1);
10125 state->entry = gtk_editable_get_chars(
10126 GTK_EDITABLE(hentry->entry), 0, -1);
10127 state->type = hentry->type;
10129 saved_list = g_slist_append(saved_list, state);
10130 compose_destroy_headerentry(compose, hentry);
10133 compose->header_last = NULL;
10134 g_slist_free(compose->header_list);
10135 compose->header_list = NULL;
10136 compose->header_nextrow = 1;
10137 compose_create_header_entry(compose);
10139 if (ac->set_autocc && ac->auto_cc)
10140 compose_entry_append(compose, ac->auto_cc,
10141 COMPOSE_CC, PREF_ACCOUNT);
10142 if (ac->set_autobcc && ac->auto_bcc)
10143 compose_entry_append(compose, ac->auto_bcc,
10144 COMPOSE_BCC, PREF_ACCOUNT);
10145 if (ac->set_autoreplyto && ac->auto_replyto)
10146 compose_entry_append(compose, ac->auto_replyto,
10147 COMPOSE_REPLYTO, PREF_ACCOUNT);
10149 for (list = saved_list; list; list = list->next) {
10150 state = (HeaderEntryState *) list->data;
10152 compose_add_header_entry(compose, state->header,
10153 state->entry, state->type);
10155 g_free(state->header);
10156 g_free(state->entry);
10159 g_slist_free(saved_list);
10161 combobox_select_by_data(GTK_COMBO_BOX(compose->header_last->combo),
10162 (ac->protocol == A_NNTP) ?
10163 COMPOSE_NEWSGROUPS : COMPOSE_TO);
10166 /* Set message save folder */
10167 compose_set_save_to(compose, NULL);
10168 if (compose->folder && compose->folder->prefs && compose->folder->prefs->save_copy_to_folder) {
10169 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
10170 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo), TRUE);
10171 folderidentifier = folder_item_get_identifier(compose->folder);
10172 compose_set_save_to(compose, folderidentifier);
10173 g_free(folderidentifier);
10174 } else if (account_get_special_folder(compose->account, F_OUTBOX)) {
10175 if (compose->account->set_sent_folder || prefs_common.savemsg)
10176 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
10178 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), FALSE);
10179 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo), TRUE);
10180 folderidentifier = folder_item_get_identifier(account_get_special_folder
10181 (compose->account, F_OUTBOX));
10182 compose_set_save_to(compose, folderidentifier);
10183 g_free(folderidentifier);
10187 static void attach_selected(GtkTreeView *tree_view, GtkTreePath *tree_path,
10188 GtkTreeViewColumn *column, Compose *compose)
10190 compose_attach_property(NULL, compose);
10193 static gboolean attach_button_pressed(GtkWidget *widget, GdkEventButton *event,
10196 Compose *compose = (Compose *)data;
10197 GtkTreeSelection *attach_selection;
10198 gint attach_nr_selected;
10201 if (!event) return FALSE;
10203 if (event->button == 3) {
10204 attach_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
10205 attach_nr_selected = gtk_tree_selection_count_selected_rows(attach_selection);
10207 /* If no rows, or just one row is selected, right-click should
10208 * open menu relevant to the row being right-clicked on. We
10209 * achieve that by selecting the clicked row first. If more
10210 * than one row is selected, we shouldn't modify the selection,
10211 * as user may want to remove selected rows (attachments). */
10212 if (attach_nr_selected < 2) {
10213 gtk_tree_selection_unselect_all(attach_selection);
10214 attach_nr_selected = 0;
10215 gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget),
10216 event->x, event->y, &path, NULL, NULL, NULL);
10217 if (path != NULL) {
10218 gtk_tree_selection_select_path(attach_selection, path);
10219 gtk_tree_path_free(path);
10220 attach_nr_selected++;
10224 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", (attach_nr_selected > 0));
10225 /* Properties menu item makes no sense with more than one row
10226 * selected, the properties dialog can only edit one attachment. */
10227 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", (attach_nr_selected == 1));
10229 gtk_menu_popup(GTK_MENU(compose->popupmenu), NULL, NULL,
10230 NULL, NULL, event->button, event->time);
10237 static gboolean attach_key_pressed(GtkWidget *widget, GdkEventKey *event,
10240 Compose *compose = (Compose *)data;
10242 if (!event) return FALSE;
10244 switch (event->keyval) {
10245 case GDK_KEY_Delete:
10246 compose_attach_remove_selected(NULL, compose);
10252 static void compose_allow_user_actions (Compose *compose, gboolean allow)
10254 toolbar_comp_set_sensitive(compose, allow);
10255 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message", allow);
10256 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", allow);
10258 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", allow);
10260 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", allow);
10261 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools", allow);
10262 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Help", allow);
10264 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), allow);
10268 static void compose_send_cb(GtkAction *action, gpointer data)
10270 Compose *compose = (Compose *)data;
10273 if (compose->exteditor_tag != -1) {
10274 debug_print("ignoring send: external editor still open\n");
10278 if (prefs_common.work_offline &&
10279 !inc_offline_should_override(TRUE,
10280 _("Claws Mail needs network access in order "
10281 "to send this email.")))
10284 if (compose->draft_timeout_tag >= 0) { /* CLAWS: disable draft timeout */
10285 g_source_remove(compose->draft_timeout_tag);
10286 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
10289 compose_send(compose);
10292 static void compose_send_later_cb(GtkAction *action, gpointer data)
10294 Compose *compose = (Compose *)data;
10295 ComposeQueueResult val;
10298 compose_allow_user_actions(compose, FALSE);
10299 val = compose_queue_sub(compose, NULL, NULL, NULL, TRUE, TRUE);
10300 compose_allow_user_actions(compose, TRUE);
10303 if (val == COMPOSE_QUEUE_SUCCESS) {
10304 compose_close(compose);
10306 _display_queue_error(val);
10309 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
10312 #define DRAFTED_AT_EXIT "drafted_at_exit"
10313 static void compose_register_draft(MsgInfo *info)
10315 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10316 DRAFTED_AT_EXIT, NULL);
10317 FILE *fp = claws_fopen(filepath, "ab");
10320 fprintf(fp, "%s\t%d\n", folder_item_get_identifier(info->folder),
10328 gboolean compose_draft (gpointer data, guint action)
10330 Compose *compose = (Compose *)data;
10335 MsgFlags flag = {0, 0};
10336 static gboolean lock = FALSE;
10337 MsgInfo *newmsginfo;
10339 gboolean target_locked = FALSE;
10340 gboolean err = FALSE;
10342 if (lock) return FALSE;
10344 if (compose->sending)
10347 draft = account_get_special_folder(compose->account, F_DRAFT);
10348 cm_return_val_if_fail(draft != NULL, FALSE);
10350 if (!g_mutex_trylock(compose->mutex)) {
10351 /* we don't want to lock the mutex once it's available,
10352 * because as the only other part of compose.c locking
10353 * it is compose_close - which means once unlocked,
10354 * the compose struct will be freed */
10355 debug_print("couldn't lock mutex, probably sending\n");
10361 tmp = g_strdup_printf("%s%cdraft.%p", get_tmp_dir(),
10362 G_DIR_SEPARATOR, compose);
10363 if ((fp = claws_fopen(tmp, "wb")) == NULL) {
10364 FILE_OP_ERROR(tmp, "claws_fopen");
10368 /* chmod for security */
10369 if (change_file_mode_rw(fp, tmp) < 0) {
10370 FILE_OP_ERROR(tmp, "chmod");
10371 g_warning("can't change file mode");
10374 /* Save draft infos */
10375 err |= (fprintf(fp, "X-Claws-Account-Id:%d\n", compose->account->account_id) < 0);
10376 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
10378 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
10379 gchar *savefolderid;
10381 savefolderid = compose_get_save_to(compose);
10382 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
10383 g_free(savefolderid);
10385 if (compose->return_receipt) {
10386 err |= (fprintf(fp, "RRCPT:1\n") < 0);
10388 if (compose->privacy_system) {
10389 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
10390 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
10391 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
10394 /* Message-ID of message replying to */
10395 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
10396 gchar *folderid = NULL;
10398 if (compose->replyinfo->folder)
10399 folderid = folder_item_get_identifier(compose->replyinfo->folder);
10400 if (folderid == NULL)
10401 folderid = g_strdup("NULL");
10403 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
10406 /* Message-ID of message forwarding to */
10407 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
10408 gchar *folderid = NULL;
10410 if (compose->fwdinfo->folder)
10411 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
10412 if (folderid == NULL)
10413 folderid = g_strdup("NULL");
10415 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
10419 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
10420 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
10422 sheaders = compose_get_manual_headers_info(compose);
10423 err |= (fprintf(fp, "X-Claws-Manual-Headers:%s\n", sheaders) < 0);
10426 /* end of headers */
10427 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
10434 if (compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_STORE, action != COMPOSE_AUTO_SAVE) < 0) {
10438 if (claws_safe_fclose(fp) == EOF) {
10442 flag.perm_flags = MSG_NEW|MSG_UNREAD;
10443 if (compose->targetinfo) {
10444 target_locked = MSG_IS_LOCKED(compose->targetinfo->flags);
10446 flag.perm_flags |= MSG_LOCKED;
10448 flag.tmp_flags = MSG_DRAFT;
10450 folder_item_scan(draft);
10451 if ((msgnum = folder_item_add_msg(draft, tmp, &flag, TRUE)) < 0) {
10452 MsgInfo *tmpinfo = NULL;
10453 debug_print("didn't get msgnum after adding draft [%s]\n", compose->msgid?compose->msgid:"no msgid");
10454 if (compose->msgid) {
10455 tmpinfo = folder_item_get_msginfo_by_msgid(draft, compose->msgid);
10458 msgnum = tmpinfo->msgnum;
10459 procmsg_msginfo_free(&tmpinfo);
10460 debug_print("got draft msgnum %d from scanning\n", msgnum);
10462 debug_print("didn't get draft msgnum after scanning\n");
10465 debug_print("got draft msgnum %d from adding\n", msgnum);
10471 if (action != COMPOSE_AUTO_SAVE) {
10472 if (action != COMPOSE_DRAFT_FOR_EXIT)
10473 alertpanel_error(_("Could not save draft."));
10476 gtkut_window_popup(compose->window);
10477 val = alertpanel_full(_("Could not save draft"),
10478 _("Could not save draft.\n"
10479 "Do you want to cancel exit or discard this email?"),
10480 _("_Cancel exit"), _("_Discard email"), NULL, ALERTFOCUS_FIRST,
10481 FALSE, NULL, ALERT_QUESTION);
10482 if (val == G_ALERTALTERNATE) {
10484 g_mutex_unlock(compose->mutex); /* must be done before closing */
10485 compose_close(compose);
10489 g_mutex_unlock(compose->mutex); /* must be done before closing */
10498 if (compose->mode == COMPOSE_REEDIT) {
10499 compose_remove_reedit_target(compose, TRUE);
10502 newmsginfo = folder_item_get_msginfo(draft, msgnum);
10505 procmsg_msginfo_unset_flags(newmsginfo, ~0, ~0);
10507 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD|MSG_LOCKED, MSG_DRAFT);
10509 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD, MSG_DRAFT);
10510 if (compose_use_attach(compose) && action != COMPOSE_AUTO_SAVE)
10511 procmsg_msginfo_set_flags(newmsginfo, 0,
10512 MSG_HAS_ATTACHMENT);
10514 if (action == COMPOSE_DRAFT_FOR_EXIT) {
10515 compose_register_draft(newmsginfo);
10517 procmsg_msginfo_free(&newmsginfo);
10520 folder_item_scan(draft);
10522 if (action == COMPOSE_QUIT_EDITING || action == COMPOSE_DRAFT_FOR_EXIT) {
10524 g_mutex_unlock(compose->mutex); /* must be done before closing */
10525 compose_close(compose);
10532 GError *error = NULL;
10537 goffset size, mtime;
10539 path = folder_item_fetch_msg(draft, msgnum);
10540 if (path == NULL) {
10541 debug_print("can't fetch %s:%d\n", draft->path, msgnum);
10545 f = g_file_new_for_path(path);
10546 fi = g_file_query_info(f, "standard::size,time::modified",
10547 G_FILE_QUERY_INFO_NONE, NULL, &error);
10548 if (error != NULL) {
10549 debug_print("couldn't query file info for '%s': %s\n",
10550 path, error->message);
10551 g_error_free(error);
10556 size = g_file_info_get_size(fi);
10557 g_file_info_get_modification_time(fi, &tv);
10559 g_object_unref(fi);
10562 if (g_stat(path, &s) < 0) {
10563 FILE_OP_ERROR(path, "stat");
10568 mtime = s.st_mtime;
10572 procmsg_msginfo_free(&(compose->targetinfo));
10573 compose->targetinfo = procmsg_msginfo_new();
10574 compose->targetinfo->msgnum = msgnum;
10575 compose->targetinfo->size = size;
10576 compose->targetinfo->mtime = mtime;
10577 compose->targetinfo->folder = draft;
10579 procmsg_msginfo_set_flags(compose->targetinfo, MSG_LOCKED, 0);
10580 compose->mode = COMPOSE_REEDIT;
10582 if (action == COMPOSE_AUTO_SAVE) {
10583 compose->autosaved_draft = compose->targetinfo;
10585 compose->modified = FALSE;
10586 compose_set_title(compose);
10590 g_mutex_unlock(compose->mutex);
10594 void compose_clear_exit_drafts(void)
10596 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10597 DRAFTED_AT_EXIT, NULL);
10598 if (is_file_exist(filepath))
10599 claws_unlink(filepath);
10604 void compose_reopen_exit_drafts(void)
10606 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10607 DRAFTED_AT_EXIT, NULL);
10608 FILE *fp = claws_fopen(filepath, "rb");
10612 while (claws_fgets(buf, sizeof(buf), fp)) {
10613 gchar **parts = g_strsplit(buf, "\t", 2);
10614 const gchar *folder = parts[0];
10615 int msgnum = parts[1] ? atoi(parts[1]):-1;
10617 if (folder && *folder && msgnum > -1) {
10618 FolderItem *item = folder_find_item_from_identifier(folder);
10619 MsgInfo *info = folder_item_get_msginfo(item, msgnum);
10621 compose_reedit(info, FALSE);
10628 compose_clear_exit_drafts();
10631 static void compose_save_cb(GtkAction *action, gpointer data)
10633 Compose *compose = (Compose *)data;
10634 compose_draft(compose, COMPOSE_KEEP_EDITING);
10635 compose->rmode = COMPOSE_REEDIT;
10638 void compose_attach_from_list(Compose *compose, GList *file_list, gboolean free_data)
10640 if (compose && file_list) {
10643 for ( tmp = file_list; tmp; tmp = tmp->next) {
10644 gchar *file = (gchar *) tmp->data;
10645 gchar *utf8_filename = conv_filename_to_utf8(file);
10646 compose_attach_append(compose, file, utf8_filename, NULL, NULL);
10647 compose_changed_cb(NULL, compose);
10652 g_free(utf8_filename);
10657 static void compose_attach_cb(GtkAction *action, gpointer data)
10659 Compose *compose = (Compose *)data;
10662 if (compose->redirect_filename != NULL)
10665 /* Set focus_window properly, in case we were called via popup menu,
10666 * which unsets it (via focus_out_event callback on compose window). */
10667 manage_window_focus_in(compose->window, NULL, NULL);
10669 file_list = filesel_select_multiple_files_open(_("Select file"), NULL);
10672 compose_attach_from_list(compose, file_list, TRUE);
10673 g_list_free(file_list);
10677 static void compose_insert_file_cb(GtkAction *action, gpointer data)
10679 Compose *compose = (Compose *)data;
10681 gint files_inserted = 0;
10683 file_list = filesel_select_multiple_files_open(_("Select file"), NULL);
10688 for ( tmp = file_list; tmp; tmp = tmp->next) {
10689 gchar *file = (gchar *) tmp->data;
10690 gchar *filedup = g_strdup(file);
10691 gchar *shortfile = g_path_get_basename(filedup);
10692 ComposeInsertResult res;
10693 /* insert the file if the file is short or if the user confirmed that
10694 he/she wants to insert the large file */
10695 res = compose_insert_file(compose, file);
10696 if (res == COMPOSE_INSERT_READ_ERROR) {
10697 alertpanel_error(_("File '%s' could not be read."), shortfile);
10698 } else if (res == COMPOSE_INSERT_INVALID_CHARACTER) {
10699 alertpanel_error(_("File '%s' contained invalid characters\n"
10700 "for the current encoding, insertion may be incorrect."),
10702 } else if (res == COMPOSE_INSERT_SUCCESS)
10709 g_list_free(file_list);
10713 if (files_inserted > 0 && compose->gtkaspell &&
10714 compose->gtkaspell->check_while_typing)
10715 gtkaspell_highlight_all(compose->gtkaspell);
10719 static void compose_insert_sig_cb(GtkAction *action, gpointer data)
10721 Compose *compose = (Compose *)data;
10723 compose_insert_sig(compose, FALSE);
10726 static void compose_replace_sig_cb(GtkAction *action, gpointer data)
10728 Compose *compose = (Compose *)data;
10730 compose_insert_sig(compose, TRUE);
10733 static gint compose_delete_cb(GtkWidget *widget, GdkEventAny *event,
10737 Compose *compose = (Compose *)data;
10739 gtkut_widget_get_uposition(widget, &x, &y);
10740 if (!compose->batch) {
10741 prefs_common.compose_x = x;
10742 prefs_common.compose_y = y;
10744 if (compose->sending || compose->updating)
10746 compose_close_cb(NULL, compose);
10750 void compose_close_toolbar(Compose *compose)
10752 compose_close_cb(NULL, compose);
10755 static void compose_close_cb(GtkAction *action, gpointer data)
10757 Compose *compose = (Compose *)data;
10761 if (compose->exteditor_tag != -1) {
10762 if (!compose_ext_editor_kill(compose))
10767 if (compose->modified) {
10768 gboolean reedit = (compose->rmode == COMPOSE_REEDIT);
10769 if (!g_mutex_trylock(compose->mutex)) {
10770 /* we don't want to lock the mutex once it's available,
10771 * because as the only other part of compose.c locking
10772 * it is compose_close - which means once unlocked,
10773 * the compose struct will be freed */
10774 debug_print("couldn't lock mutex, probably sending\n");
10777 if (!reedit || compose->folder->stype == F_DRAFT) {
10778 val = alertpanel(_("Discard message"),
10779 _("This message has been modified. Discard it?"),
10780 _("_Discard"), _("_Save to Drafts"), GTK_STOCK_CANCEL,
10783 val = alertpanel(_("Save changes"),
10784 _("This message has been modified. Save the latest changes?"),
10785 _("_Don't save"), _("_Save to Drafts"), GTK_STOCK_CANCEL,
10786 ALERTFOCUS_SECOND);
10788 g_mutex_unlock(compose->mutex);
10790 case G_ALERTDEFAULT:
10791 if (compose_can_autosave(compose) && !reedit)
10792 compose_remove_draft(compose);
10794 case G_ALERTALTERNATE:
10795 compose_draft(data, COMPOSE_QUIT_EDITING);
10802 compose_close(compose);
10805 static void compose_print_cb(GtkAction *action, gpointer data)
10807 Compose *compose = (Compose *) data;
10809 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
10810 if (compose->targetinfo)
10811 messageview_print(compose->targetinfo, FALSE, -1, -1, 0);
10814 static void compose_set_encoding_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
10816 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
10817 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
10818 Compose *compose = (Compose *) data;
10821 compose->out_encoding = (CharSet)value;
10824 static void compose_address_cb(GtkAction *action, gpointer data)
10826 Compose *compose = (Compose *)data;
10828 #ifndef USE_ALT_ADDRBOOK
10829 addressbook_open(compose);
10831 GError* error = NULL;
10832 addressbook_connect_signals(compose);
10833 addressbook_dbus_open(TRUE, &error);
10835 g_warning("%s", error->message);
10836 g_error_free(error);
10841 static void about_show_cb(GtkAction *action, gpointer data)
10846 static void compose_template_activate_cb(GtkWidget *widget, gpointer data)
10848 Compose *compose = (Compose *)data;
10853 tmpl = g_object_get_data(G_OBJECT(widget), "template");
10854 cm_return_if_fail(tmpl != NULL);
10856 msg = g_strdup_printf(_("Do you want to apply the template '%s'?"),
10858 val = alertpanel(_("Apply template"), msg,
10859 _("_Replace"), _("_Insert"), GTK_STOCK_CANCEL, ALERTFOCUS_FIRST);
10862 if (val == G_ALERTDEFAULT)
10863 compose_template_apply(compose, tmpl, TRUE);
10864 else if (val == G_ALERTALTERNATE)
10865 compose_template_apply(compose, tmpl, FALSE);
10868 static void compose_ext_editor_cb(GtkAction *action, gpointer data)
10870 Compose *compose = (Compose *)data;
10873 if (compose->exteditor_tag != -1) {
10874 debug_print("ignoring open external editor: external editor still open\n");
10878 compose_exec_ext_editor(compose);
10881 static void compose_undo_cb(GtkAction *action, gpointer data)
10883 Compose *compose = (Compose *)data;
10884 gboolean prev_autowrap = compose->autowrap;
10886 compose->autowrap = FALSE;
10887 undo_undo(compose->undostruct);
10888 compose->autowrap = prev_autowrap;
10891 static void compose_redo_cb(GtkAction *action, gpointer data)
10893 Compose *compose = (Compose *)data;
10894 gboolean prev_autowrap = compose->autowrap;
10896 compose->autowrap = FALSE;
10897 undo_redo(compose->undostruct);
10898 compose->autowrap = prev_autowrap;
10901 static void entry_cut_clipboard(GtkWidget *entry)
10903 if (GTK_IS_EDITABLE(entry))
10904 gtk_editable_cut_clipboard (GTK_EDITABLE(entry));
10905 else if (GTK_IS_TEXT_VIEW(entry))
10906 gtk_text_buffer_cut_clipboard(
10907 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10908 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
10912 static void entry_copy_clipboard(GtkWidget *entry)
10914 if (GTK_IS_EDITABLE(entry))
10915 gtk_editable_copy_clipboard (GTK_EDITABLE(entry));
10916 else if (GTK_IS_TEXT_VIEW(entry))
10917 gtk_text_buffer_copy_clipboard(
10918 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10919 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
10922 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry,
10923 gboolean wrap, GdkAtom clip, GtkTextIter *insert_place)
10925 if (GTK_IS_TEXT_VIEW(entry)) {
10926 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10927 GtkTextMark *mark_start = gtk_text_buffer_get_insert(buffer);
10928 GtkTextIter start_iter, end_iter;
10930 gchar *contents = gtk_clipboard_wait_for_text(gtk_clipboard_get(clip));
10932 if (contents == NULL)
10935 /* we shouldn't delete the selection when middle-click-pasting, or we
10936 * can't mid-click-paste our own selection */
10937 if (clip != GDK_SELECTION_PRIMARY) {
10938 undo_paste_clipboard(GTK_TEXT_VIEW(compose->text), compose->undostruct);
10939 gtk_text_buffer_delete_selection(buffer, FALSE, TRUE);
10942 if (insert_place == NULL) {
10943 /* if insert_place isn't specified, insert at the cursor.
10944 * used for Ctrl-V pasting */
10945 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10946 start = gtk_text_iter_get_offset(&start_iter);
10947 gtk_text_buffer_insert(buffer, &start_iter, contents, strlen(contents));
10949 /* if insert_place is specified, paste here.
10950 * used for mid-click-pasting */
10951 start = gtk_text_iter_get_offset(insert_place);
10952 gtk_text_buffer_insert(buffer, insert_place, contents, strlen(contents));
10953 if (prefs_common.primary_paste_unselects)
10954 gtk_text_buffer_select_range(buffer, insert_place, insert_place);
10958 /* paste unwrapped: mark the paste so it's not wrapped later */
10959 end = start + strlen(contents);
10960 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, start);
10961 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, end);
10962 gtk_text_buffer_apply_tag_by_name(buffer, "no_wrap", &start_iter, &end_iter);
10963 } else if (wrap && clip == GDK_SELECTION_PRIMARY) {
10964 /* rewrap paragraph now (after a mid-click-paste) */
10965 mark_start = gtk_text_buffer_get_insert(buffer);
10966 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10967 gtk_text_iter_backward_char(&start_iter);
10968 compose_beautify_paragraph(compose, &start_iter, TRUE);
10970 } else if (GTK_IS_EDITABLE(entry))
10971 gtk_editable_paste_clipboard (GTK_EDITABLE(entry));
10973 compose->modified = TRUE;
10976 static void entry_allsel(GtkWidget *entry)
10978 if (GTK_IS_EDITABLE(entry))
10979 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
10980 else if (GTK_IS_TEXT_VIEW(entry)) {
10981 GtkTextIter startiter, enditer;
10982 GtkTextBuffer *textbuf;
10984 textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10985 gtk_text_buffer_get_start_iter(textbuf, &startiter);
10986 gtk_text_buffer_get_end_iter(textbuf, &enditer);
10988 gtk_text_buffer_move_mark_by_name(textbuf,
10989 "selection_bound", &startiter);
10990 gtk_text_buffer_move_mark_by_name(textbuf,
10991 "insert", &enditer);
10995 static void compose_cut_cb(GtkAction *action, gpointer data)
10997 Compose *compose = (Compose *)data;
10998 if (compose->focused_editable
10999 #ifndef GENERIC_UMPC
11000 && gtk_widget_has_focus(compose->focused_editable)
11003 entry_cut_clipboard(compose->focused_editable);
11006 static void compose_copy_cb(GtkAction *action, gpointer data)
11008 Compose *compose = (Compose *)data;
11009 if (compose->focused_editable
11010 #ifndef GENERIC_UMPC
11011 && gtk_widget_has_focus(compose->focused_editable)
11014 entry_copy_clipboard(compose->focused_editable);
11017 static void compose_paste_cb(GtkAction *action, gpointer data)
11019 Compose *compose = (Compose *)data;
11020 gint prev_autowrap;
11021 GtkTextBuffer *buffer;
11023 if (compose->focused_editable
11024 #ifndef GENERIC_UMPC
11025 && gtk_widget_has_focus(compose->focused_editable)
11028 entry_paste_clipboard(compose, compose->focused_editable,
11029 prefs_common.linewrap_pastes,
11030 GDK_SELECTION_CLIPBOARD, NULL);
11035 #ifndef GENERIC_UMPC
11036 gtk_widget_has_focus(compose->text) &&
11038 compose->gtkaspell &&
11039 compose->gtkaspell->check_while_typing)
11040 gtkaspell_highlight_all(compose->gtkaspell);
11044 static void compose_paste_as_quote_cb(GtkAction *action, gpointer data)
11046 Compose *compose = (Compose *)data;
11047 gint wrap_quote = prefs_common.linewrap_quote;
11048 if (compose->focused_editable
11049 #ifndef GENERIC_UMPC
11050 && gtk_widget_has_focus(compose->focused_editable)
11053 /* let text_insert() (called directly or at a later time
11054 * after the gtk_editable_paste_clipboard) know that
11055 * text is to be inserted as a quotation. implemented
11056 * by using a simple refcount... */
11057 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data(
11058 G_OBJECT(compose->focused_editable),
11059 "paste_as_quotation"));
11060 g_object_set_data(G_OBJECT(compose->focused_editable),
11061 "paste_as_quotation",
11062 GINT_TO_POINTER(paste_as_quotation + 1));
11063 prefs_common.linewrap_quote = prefs_common.linewrap_pastes;
11064 entry_paste_clipboard(compose, compose->focused_editable,
11065 prefs_common.linewrap_pastes,
11066 GDK_SELECTION_CLIPBOARD, NULL);
11067 prefs_common.linewrap_quote = wrap_quote;
11071 static void compose_paste_no_wrap_cb(GtkAction *action, gpointer data)
11073 Compose *compose = (Compose *)data;
11074 gint prev_autowrap;
11075 GtkTextBuffer *buffer;
11077 if (compose->focused_editable
11078 #ifndef GENERIC_UMPC
11079 && gtk_widget_has_focus(compose->focused_editable)
11082 entry_paste_clipboard(compose, compose->focused_editable, FALSE,
11083 GDK_SELECTION_CLIPBOARD, NULL);
11088 #ifndef GENERIC_UMPC
11089 gtk_widget_has_focus(compose->text) &&
11091 compose->gtkaspell &&
11092 compose->gtkaspell->check_while_typing)
11093 gtkaspell_highlight_all(compose->gtkaspell);
11097 static void compose_paste_wrap_cb(GtkAction *action, gpointer data)
11099 Compose *compose = (Compose *)data;
11100 gint prev_autowrap;
11101 GtkTextBuffer *buffer;
11103 if (compose->focused_editable
11104 #ifndef GENERIC_UMPC
11105 && gtk_widget_has_focus(compose->focused_editable)
11108 entry_paste_clipboard(compose, compose->focused_editable, TRUE,
11109 GDK_SELECTION_CLIPBOARD, NULL);
11114 #ifndef GENERIC_UMPC
11115 gtk_widget_has_focus(compose->text) &&
11117 compose->gtkaspell &&
11118 compose->gtkaspell->check_while_typing)
11119 gtkaspell_highlight_all(compose->gtkaspell);
11123 static void compose_allsel_cb(GtkAction *action, gpointer data)
11125 Compose *compose = (Compose *)data;
11126 if (compose->focused_editable
11127 #ifndef GENERIC_UMPC
11128 && gtk_widget_has_focus(compose->focused_editable)
11131 entry_allsel(compose->focused_editable);
11134 static void textview_move_beginning_of_line (GtkTextView *text)
11136 GtkTextBuffer *buffer;
11140 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11142 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11143 mark = gtk_text_buffer_get_insert(buffer);
11144 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11145 gtk_text_iter_set_line_offset(&ins, 0);
11146 gtk_text_buffer_place_cursor(buffer, &ins);
11149 static void textview_move_forward_character (GtkTextView *text)
11151 GtkTextBuffer *buffer;
11155 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11157 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11158 mark = gtk_text_buffer_get_insert(buffer);
11159 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11160 if (gtk_text_iter_forward_cursor_position(&ins))
11161 gtk_text_buffer_place_cursor(buffer, &ins);
11164 static void textview_move_backward_character (GtkTextView *text)
11166 GtkTextBuffer *buffer;
11170 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11172 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11173 mark = gtk_text_buffer_get_insert(buffer);
11174 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11175 if (gtk_text_iter_backward_cursor_position(&ins))
11176 gtk_text_buffer_place_cursor(buffer, &ins);
11179 static void textview_move_forward_word (GtkTextView *text)
11181 GtkTextBuffer *buffer;
11186 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11188 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11189 mark = gtk_text_buffer_get_insert(buffer);
11190 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11191 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
11192 if (gtk_text_iter_forward_word_ends(&ins, count)) {
11193 gtk_text_iter_backward_word_start(&ins);
11194 gtk_text_buffer_place_cursor(buffer, &ins);
11198 static void textview_move_backward_word (GtkTextView *text)
11200 GtkTextBuffer *buffer;
11204 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11206 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11207 mark = gtk_text_buffer_get_insert(buffer);
11208 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11209 if (gtk_text_iter_backward_word_starts(&ins, 1))
11210 gtk_text_buffer_place_cursor(buffer, &ins);
11213 static void textview_move_end_of_line (GtkTextView *text)
11215 GtkTextBuffer *buffer;
11219 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11221 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11222 mark = gtk_text_buffer_get_insert(buffer);
11223 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11224 if (gtk_text_iter_forward_to_line_end(&ins))
11225 gtk_text_buffer_place_cursor(buffer, &ins);
11228 static void textview_move_next_line (GtkTextView *text)
11230 GtkTextBuffer *buffer;
11235 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11237 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11238 mark = gtk_text_buffer_get_insert(buffer);
11239 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11240 offset = gtk_text_iter_get_line_offset(&ins);
11241 if (gtk_text_iter_forward_line(&ins)) {
11242 gtk_text_iter_set_line_offset(&ins, offset);
11243 gtk_text_buffer_place_cursor(buffer, &ins);
11247 static void textview_move_previous_line (GtkTextView *text)
11249 GtkTextBuffer *buffer;
11254 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11256 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11257 mark = gtk_text_buffer_get_insert(buffer);
11258 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11259 offset = gtk_text_iter_get_line_offset(&ins);
11260 if (gtk_text_iter_backward_line(&ins)) {
11261 gtk_text_iter_set_line_offset(&ins, offset);
11262 gtk_text_buffer_place_cursor(buffer, &ins);
11266 static void textview_delete_forward_character (GtkTextView *text)
11268 GtkTextBuffer *buffer;
11270 GtkTextIter ins, end_iter;
11272 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11274 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11275 mark = gtk_text_buffer_get_insert(buffer);
11276 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11278 if (gtk_text_iter_forward_char(&end_iter)) {
11279 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11283 static void textview_delete_backward_character (GtkTextView *text)
11285 GtkTextBuffer *buffer;
11287 GtkTextIter ins, end_iter;
11289 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11291 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11292 mark = gtk_text_buffer_get_insert(buffer);
11293 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11295 if (gtk_text_iter_backward_char(&end_iter)) {
11296 gtk_text_buffer_delete(buffer, &end_iter, &ins);
11300 static void textview_delete_forward_word (GtkTextView *text)
11302 GtkTextBuffer *buffer;
11304 GtkTextIter ins, end_iter;
11306 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11308 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11309 mark = gtk_text_buffer_get_insert(buffer);
11310 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11312 if (gtk_text_iter_forward_word_end(&end_iter)) {
11313 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11317 static void textview_delete_backward_word (GtkTextView *text)
11319 GtkTextBuffer *buffer;
11321 GtkTextIter ins, end_iter;
11323 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11325 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11326 mark = gtk_text_buffer_get_insert(buffer);
11327 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11329 if (gtk_text_iter_backward_word_start(&end_iter)) {
11330 gtk_text_buffer_delete(buffer, &end_iter, &ins);
11334 static void textview_delete_line (GtkTextView *text)
11336 GtkTextBuffer *buffer;
11338 GtkTextIter ins, start_iter, end_iter;
11340 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11342 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11343 mark = gtk_text_buffer_get_insert(buffer);
11344 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11347 gtk_text_iter_set_line_offset(&start_iter, 0);
11350 if (gtk_text_iter_ends_line(&end_iter)){
11351 if (!gtk_text_iter_forward_char(&end_iter))
11352 gtk_text_iter_backward_char(&start_iter);
11355 gtk_text_iter_forward_to_line_end(&end_iter);
11356 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
11359 static void textview_delete_to_line_end (GtkTextView *text)
11361 GtkTextBuffer *buffer;
11363 GtkTextIter ins, end_iter;
11365 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11367 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11368 mark = gtk_text_buffer_get_insert(buffer);
11369 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11371 if (gtk_text_iter_ends_line(&end_iter))
11372 gtk_text_iter_forward_char(&end_iter);
11374 gtk_text_iter_forward_to_line_end(&end_iter);
11375 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11378 #define DO_ACTION(name, act) { \
11379 if(!strcmp(name, a_name)) { \
11383 static ComposeCallAdvancedAction compose_call_advanced_action_from_path(GtkAction *action)
11385 const gchar *a_name = gtk_action_get_name(action);
11386 DO_ACTION("Edit/Advanced/BackChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER);
11387 DO_ACTION("Edit/Advanced/ForwChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER);
11388 DO_ACTION("Edit/Advanced/BackWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD);
11389 DO_ACTION("Edit/Advanced/ForwWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD);
11390 DO_ACTION("Edit/Advanced/BegLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE);
11391 DO_ACTION("Edit/Advanced/EndLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE);
11392 DO_ACTION("Edit/Advanced/PrevLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE);
11393 DO_ACTION("Edit/Advanced/NextLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE);
11394 DO_ACTION("Edit/Advanced/DelBackChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER);
11395 DO_ACTION("Edit/Advanced/DelForwChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER);
11396 DO_ACTION("Edit/Advanced/DelBackWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD);
11397 DO_ACTION("Edit/Advanced/DelForwWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD);
11398 DO_ACTION("Edit/Advanced/DelLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE);
11399 DO_ACTION("Edit/Advanced/DelEndLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END);
11400 return COMPOSE_CALL_ADVANCED_ACTION_UNDEFINED;
11403 static void compose_advanced_action_cb(GtkAction *gaction, gpointer data)
11405 Compose *compose = (Compose *)data;
11406 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11407 ComposeCallAdvancedAction action = COMPOSE_CALL_ADVANCED_ACTION_UNDEFINED;
11409 action = compose_call_advanced_action_from_path(gaction);
11412 void (*do_action) (GtkTextView *text);
11413 } action_table[] = {
11414 {textview_move_beginning_of_line},
11415 {textview_move_forward_character},
11416 {textview_move_backward_character},
11417 {textview_move_forward_word},
11418 {textview_move_backward_word},
11419 {textview_move_end_of_line},
11420 {textview_move_next_line},
11421 {textview_move_previous_line},
11422 {textview_delete_forward_character},
11423 {textview_delete_backward_character},
11424 {textview_delete_forward_word},
11425 {textview_delete_backward_word},
11426 {textview_delete_line},
11427 {textview_delete_to_line_end}
11430 if (!gtk_widget_has_focus(GTK_WIDGET(text))) return;
11432 if (action >= COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE &&
11433 action <= COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END) {
11434 if (action_table[action].do_action)
11435 action_table[action].do_action(text);
11437 g_warning("Not implemented yet.");
11441 static void compose_grab_focus_cb(GtkWidget *widget, Compose *compose)
11443 GtkAllocation allocation;
11447 if (GTK_IS_EDITABLE(widget)) {
11448 str = gtk_editable_get_chars(GTK_EDITABLE(widget), 0, -1);
11449 gtk_editable_set_position(GTK_EDITABLE(widget),
11452 if ((parent = gtk_widget_get_parent(widget))
11453 && (parent = gtk_widget_get_parent(parent))
11454 && (parent = gtk_widget_get_parent(parent))) {
11455 if (GTK_IS_SCROLLED_WINDOW(parent)) {
11456 gtk_widget_get_allocation(widget, &allocation);
11457 gint y = allocation.y;
11458 gint height = allocation.height;
11459 GtkAdjustment *shown = gtk_scrolled_window_get_vadjustment
11460 (GTK_SCROLLED_WINDOW(parent));
11462 gfloat value = gtk_adjustment_get_value(shown);
11463 gfloat upper = gtk_adjustment_get_upper(shown);
11464 gfloat page_size = gtk_adjustment_get_page_size(shown);
11465 if (y < (int)value) {
11466 gtk_adjustment_set_value(shown, y - 1);
11468 if ((y + height) > ((int)value + (int)page_size)) {
11469 if ((y - height - 1) < ((int)upper - (int)page_size)) {
11470 gtk_adjustment_set_value(shown,
11471 y + height - (int)page_size - 1);
11473 gtk_adjustment_set_value(shown,
11474 (int)upper - (int)page_size - 1);
11481 if (GTK_IS_EDITABLE(widget) || GTK_IS_TEXT_VIEW(widget))
11482 compose->focused_editable = widget;
11484 #ifdef GENERIC_UMPC
11485 if (GTK_IS_TEXT_VIEW(widget)
11486 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->edit_vbox) {
11487 g_object_ref(compose->notebook);
11488 g_object_ref(compose->edit_vbox);
11489 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
11490 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
11491 gtk_paned_add1(GTK_PANED(compose->paned), compose->edit_vbox);
11492 gtk_paned_add2(GTK_PANED(compose->paned), compose->notebook);
11493 g_object_unref(compose->notebook);
11494 g_object_unref(compose->edit_vbox);
11495 g_signal_handlers_block_by_func(G_OBJECT(widget),
11496 G_CALLBACK(compose_grab_focus_cb),
11498 gtk_widget_grab_focus(widget);
11499 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
11500 G_CALLBACK(compose_grab_focus_cb),
11502 } else if (!GTK_IS_TEXT_VIEW(widget)
11503 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->notebook) {
11504 g_object_ref(compose->notebook);
11505 g_object_ref(compose->edit_vbox);
11506 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
11507 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
11508 gtk_paned_add1(GTK_PANED(compose->paned), compose->notebook);
11509 gtk_paned_add2(GTK_PANED(compose->paned), compose->edit_vbox);
11510 g_object_unref(compose->notebook);
11511 g_object_unref(compose->edit_vbox);
11512 g_signal_handlers_block_by_func(G_OBJECT(widget),
11513 G_CALLBACK(compose_grab_focus_cb),
11515 gtk_widget_grab_focus(widget);
11516 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
11517 G_CALLBACK(compose_grab_focus_cb),
11523 static void compose_changed_cb(GtkTextBuffer *textbuf, Compose *compose)
11525 compose->modified = TRUE;
11526 /* compose_beautify_paragraph(compose, NULL, TRUE); */
11527 #ifndef GENERIC_UMPC
11528 compose_set_title(compose);
11532 static void compose_wrap_cb(GtkAction *action, gpointer data)
11534 Compose *compose = (Compose *)data;
11535 compose_beautify_paragraph(compose, NULL, TRUE);
11538 static void compose_wrap_all_cb(GtkAction *action, gpointer data)
11540 Compose *compose = (Compose *)data;
11541 compose_wrap_all_full(compose, TRUE);
11544 static void compose_find_cb(GtkAction *action, gpointer data)
11546 Compose *compose = (Compose *)data;
11548 message_search_compose(compose);
11551 static void compose_toggle_autowrap_cb(GtkToggleAction *action,
11554 Compose *compose = (Compose *)data;
11555 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11556 if (compose->autowrap)
11557 compose_wrap_all_full(compose, TRUE);
11558 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11561 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
11564 Compose *compose = (Compose *)data;
11565 compose->autoindent = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11568 static void compose_toggle_sign_cb(GtkToggleAction *action, gpointer data)
11570 Compose *compose = (Compose *)data;
11572 compose->use_signing = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11573 gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(compose->toolbar->privacy_sign_btn), compose->use_signing);
11576 static void compose_toggle_encrypt_cb(GtkToggleAction *action, gpointer data)
11578 Compose *compose = (Compose *)data;
11580 compose->use_encryption = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11581 gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(compose->toolbar->privacy_encrypt_btn), compose->use_encryption);
11584 static void compose_activate_privacy_system(Compose *compose, PrefsAccount *account, gboolean warn)
11586 g_free(compose->privacy_system);
11587 g_free(compose->encdata);
11589 compose->privacy_system = g_strdup(account->default_privacy_system);
11590 compose_update_privacy_system_menu_item(compose, warn);
11593 static void compose_apply_folder_privacy_settings(Compose *compose, FolderItem *folder_item)
11595 if (folder_item != NULL) {
11596 if (folder_item->prefs->always_sign != SIGN_OR_ENCRYPT_DEFAULT &&
11597 privacy_system_can_sign(compose->privacy_system)) {
11598 compose_use_signing(compose,
11599 (folder_item->prefs->always_sign == SIGN_OR_ENCRYPT_ALWAYS) ? TRUE : FALSE);
11601 if (folder_item->prefs->always_encrypt != SIGN_OR_ENCRYPT_DEFAULT &&
11602 privacy_system_can_encrypt(compose->privacy_system)) {
11603 compose_use_encryption(compose,
11604 (folder_item->prefs->always_encrypt == SIGN_OR_ENCRYPT_ALWAYS) ? TRUE : FALSE);
11609 static void compose_toggle_ruler_cb(GtkToggleAction *action, gpointer data)
11611 Compose *compose = (Compose *)data;
11613 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action))) {
11614 gtk_widget_show(compose->ruler_hbox);
11615 prefs_common.show_ruler = TRUE;
11617 gtk_widget_hide(compose->ruler_hbox);
11618 gtk_widget_queue_resize(compose->edit_vbox);
11619 prefs_common.show_ruler = FALSE;
11623 static void compose_attach_drag_received_cb (GtkWidget *widget,
11624 GdkDragContext *context,
11627 GtkSelectionData *data,
11630 gpointer user_data)
11632 Compose *compose = (Compose *)user_data;
11636 type = gtk_selection_data_get_data_type(data);
11637 if ((gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list"))
11638 && gtk_drag_get_source_widget(context) !=
11639 summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
11640 list = uri_list_extract_filenames(
11641 (const gchar *)gtk_selection_data_get_data(data));
11642 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11643 gchar *utf8_filename = conv_filename_to_utf8((const gchar *)tmp->data);
11644 compose_attach_append
11645 (compose, (const gchar *)tmp->data,
11646 utf8_filename, NULL, NULL);
11647 g_free(utf8_filename);
11650 compose_changed_cb(NULL, compose);
11651 list_free_strings_full(list);
11652 } else if (gtk_drag_get_source_widget(context)
11653 == summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
11654 /* comes from our summaryview */
11655 SummaryView * summaryview = NULL;
11656 GSList * list = NULL, *cur = NULL;
11658 if (mainwindow_get_mainwindow())
11659 summaryview = mainwindow_get_mainwindow()->summaryview;
11662 list = summary_get_selected_msg_list(summaryview);
11664 for (cur = list; cur; cur = cur->next) {
11665 MsgInfo *msginfo = (MsgInfo *)cur->data;
11666 gchar *file = NULL;
11668 file = procmsg_get_message_file_full(msginfo,
11671 compose_attach_append(compose, (const gchar *)file,
11672 (const gchar *)file, "message/rfc822", NULL);
11676 g_slist_free(list);
11680 static gboolean compose_drag_drop(GtkWidget *widget,
11681 GdkDragContext *drag_context,
11683 guint time, gpointer user_data)
11685 /* not handling this signal makes compose_insert_drag_received_cb
11690 static gboolean completion_set_focus_to_subject
11691 (GtkWidget *widget,
11692 GdkEventKey *event,
11695 cm_return_val_if_fail(compose != NULL, FALSE);
11697 /* make backtab move to subject field */
11698 if(event->keyval == GDK_KEY_ISO_Left_Tab) {
11699 gtk_widget_grab_focus(compose->subject_entry);
11705 static void compose_insert_drag_received_cb (GtkWidget *widget,
11706 GdkDragContext *drag_context,
11709 GtkSelectionData *data,
11712 gpointer user_data)
11714 Compose *compose = (Compose *)user_data;
11720 /* strangely, testing data->type == gdk_atom_intern("text/uri-list", TRUE)
11722 type = gtk_selection_data_get_data_type(data);
11723 if (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list")) {
11724 AlertValue val = G_ALERTDEFAULT;
11725 const gchar* ddata = (const gchar *)gtk_selection_data_get_data(data);
11727 list = uri_list_extract_filenames(ddata);
11728 num_files = g_list_length(list);
11729 if (list == NULL && strstr(ddata, "://")) {
11730 /* Assume a list of no files, and data has ://, is a remote link */
11731 gchar *tmpdata = g_strstrip(g_strdup(ddata));
11732 gchar *tmpfile = get_tmp_file();
11733 str_write_to_file(tmpdata, tmpfile, TRUE);
11735 compose_insert_file(compose, tmpfile);
11736 claws_unlink(tmpfile);
11738 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11739 compose_beautify_paragraph(compose, NULL, TRUE);
11742 switch (prefs_common.compose_dnd_mode) {
11743 case COMPOSE_DND_ASK:
11744 msg = g_strdup_printf(
11746 "Do you want to insert the contents of the file "
11747 "into the message body, or attach it to the email?",
11748 "Do you want to insert the contents of the %d files "
11749 "into the message body, or attach them to the email?",
11752 val = alertpanel_full(_("Insert or attach?"), msg,
11753 GTK_STOCK_CANCEL, _("_Insert"), _("_Attach"),
11755 TRUE, NULL, ALERT_QUESTION);
11758 case COMPOSE_DND_INSERT:
11759 val = G_ALERTALTERNATE;
11761 case COMPOSE_DND_ATTACH:
11762 val = G_ALERTOTHER;
11765 /* unexpected case */
11766 g_warning("error: unexpected compose_dnd_mode option value in compose_insert_drag_received_cb()");
11769 if (val & G_ALERTDISABLE) {
11770 val &= ~G_ALERTDISABLE;
11771 /* remember what action to perform by default, only if we don't click Cancel */
11772 if (val == G_ALERTALTERNATE)
11773 prefs_common.compose_dnd_mode = COMPOSE_DND_INSERT;
11774 else if (val == G_ALERTOTHER)
11775 prefs_common.compose_dnd_mode = COMPOSE_DND_ATTACH;
11778 if (val == G_ALERTDEFAULT || val == G_ALERTCANCEL) {
11779 gtk_drag_finish(drag_context, FALSE, FALSE, time);
11780 list_free_strings_full(list);
11782 } else if (val == G_ALERTOTHER) {
11783 compose_attach_drag_received_cb(widget, drag_context, x, y, data, info, time, user_data);
11784 list_free_strings_full(list);
11788 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11789 compose_insert_file(compose, (const gchar *)tmp->data);
11791 list_free_strings_full(list);
11792 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11797 static void compose_header_drag_received_cb (GtkWidget *widget,
11798 GdkDragContext *drag_context,
11801 GtkSelectionData *data,
11804 gpointer user_data)
11806 GtkEditable *entry = (GtkEditable *)user_data;
11807 const gchar *email = (const gchar *)gtk_selection_data_get_data(data);
11809 /* strangely, testing data->type == gdk_atom_intern("text/plain", TRUE)
11812 if (!strncmp(email, "mailto:", strlen("mailto:"))) {
11813 gchar *decoded=g_new(gchar, strlen(email));
11816 decode_uri(decoded, email + strlen("mailto:")); /* will fit */
11817 gtk_editable_delete_text(entry, 0, -1);
11818 gtk_editable_insert_text(entry, decoded, strlen(decoded), &start);
11819 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11823 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11826 static void compose_toggle_return_receipt_cb(GtkToggleAction *action, gpointer data)
11828 Compose *compose = (Compose *)data;
11830 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11831 compose->return_receipt = TRUE;
11833 compose->return_receipt = FALSE;
11836 static void compose_toggle_remove_refs_cb(GtkToggleAction *action, gpointer data)
11838 Compose *compose = (Compose *)data;
11840 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11841 compose->remove_references = TRUE;
11843 compose->remove_references = FALSE;
11846 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
11847 ComposeHeaderEntry *headerentry)
11849 gtk_entry_set_text(GTK_ENTRY(headerentry->entry), "");
11850 gtk_widget_modify_base(GTK_WIDGET(headerentry->entry), GTK_STATE_NORMAL, NULL);
11851 gtk_widget_modify_text(GTK_WIDGET(headerentry->entry), GTK_STATE_NORMAL, NULL);
11855 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
11856 GdkEventKey *event,
11857 ComposeHeaderEntry *headerentry)
11859 if ((g_slist_length(headerentry->compose->header_list) > 0) &&
11860 ((headerentry->headernum + 1) != headerentry->compose->header_nextrow) &&
11861 !(event->state & GDK_MODIFIER_MASK) &&
11862 (event->keyval == GDK_KEY_BackSpace) &&
11863 (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) == 0)) {
11864 gtk_container_remove
11865 (GTK_CONTAINER(headerentry->compose->header_table),
11866 headerentry->combo);
11867 gtk_container_remove
11868 (GTK_CONTAINER(headerentry->compose->header_table),
11869 headerentry->entry);
11870 headerentry->compose->header_list =
11871 g_slist_remove(headerentry->compose->header_list,
11873 g_free(headerentry);
11874 } else if (event->keyval == GDK_KEY_Tab) {
11875 if (headerentry->compose->header_last == headerentry) {
11876 /* Override default next focus, and give it to subject_entry
11877 * instead of notebook tabs
11879 g_signal_stop_emission_by_name(G_OBJECT(entry), "key-press-event");
11880 gtk_widget_grab_focus(headerentry->compose->subject_entry);
11887 static gboolean scroll_postpone(gpointer data)
11889 Compose *compose = (Compose *)data;
11891 if (compose->batch)
11894 GTK_EVENTS_FLUSH();
11895 compose_show_first_last_header(compose, FALSE);
11899 static void compose_headerentry_changed_cb(GtkWidget *entry,
11900 ComposeHeaderEntry *headerentry)
11902 if (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) != 0) {
11903 compose_create_header_entry(headerentry->compose);
11904 g_signal_handlers_disconnect_matched
11905 (G_OBJECT(entry), G_SIGNAL_MATCH_DATA,
11906 0, 0, NULL, NULL, headerentry);
11908 if (!headerentry->compose->batch)
11909 g_timeout_add(0, scroll_postpone, headerentry->compose);
11913 static gboolean compose_defer_auto_save_draft(Compose *compose)
11915 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
11916 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
11920 static void compose_show_first_last_header(Compose *compose, gboolean show_first)
11922 GtkAdjustment *vadj;
11924 cm_return_if_fail(compose);
11929 cm_return_if_fail(GTK_IS_WIDGET(compose->header_table));
11930 cm_return_if_fail(GTK_IS_VIEWPORT(gtk_widget_get_parent(compose->header_table)));
11931 vadj = gtk_viewport_get_vadjustment(GTK_VIEWPORT(
11932 gtk_widget_get_parent(compose->header_table)));
11933 gtk_adjustment_set_value(vadj, (show_first ?
11934 gtk_adjustment_get_lower(vadj) :
11935 (gtk_adjustment_get_upper(vadj) -
11936 gtk_adjustment_get_page_size(vadj))));
11937 gtk_adjustment_changed(vadj);
11940 static void text_inserted(GtkTextBuffer *buffer, GtkTextIter *iter,
11941 const gchar *text, gint len, Compose *compose)
11943 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data
11944 (G_OBJECT(compose->text), "paste_as_quotation"));
11947 cm_return_if_fail(text != NULL);
11949 g_signal_handlers_block_by_func(G_OBJECT(buffer),
11950 G_CALLBACK(text_inserted),
11952 if (paste_as_quotation) {
11954 const gchar *qmark;
11956 GtkTextIter start_iter;
11959 len = strlen(text);
11961 new_text = g_strndup(text, len);
11963 qmark = compose_quote_char_from_context(compose);
11965 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11966 gtk_text_buffer_place_cursor(buffer, iter);
11968 pos = gtk_text_iter_get_offset(iter);
11970 compose_quote_fmt(compose, NULL, "%Q", qmark, new_text, TRUE, FALSE,
11971 _("Quote format error at line %d."));
11972 quote_fmt_reset_vartable();
11974 g_object_set_data(G_OBJECT(compose->text), "paste_as_quotation",
11975 GINT_TO_POINTER(paste_as_quotation - 1));
11977 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11978 gtk_text_buffer_place_cursor(buffer, iter);
11979 gtk_text_buffer_delete_mark(buffer, mark);
11981 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, pos);
11982 mark = gtk_text_buffer_create_mark(buffer, NULL, &start_iter, FALSE);
11983 compose_beautify_paragraph(compose, &start_iter, FALSE);
11984 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark);
11985 gtk_text_buffer_delete_mark(buffer, mark);
11987 if (strcmp(text, "\n") || compose->automatic_break
11988 || gtk_text_iter_starts_line(iter)) {
11989 GtkTextIter before_ins;
11990 gtk_text_buffer_insert(buffer, iter, text, len);
11991 if (!strstr(text, "\n") && gtk_text_iter_has_tag(iter, compose->no_join_tag)) {
11992 before_ins = *iter;
11993 gtk_text_iter_backward_chars(&before_ins, len);
11994 gtk_text_buffer_remove_tag_by_name(buffer, "no_join", &before_ins, iter);
11997 /* check if the preceding is just whitespace or quote */
11998 GtkTextIter start_line;
11999 gchar *tmp = NULL, *quote = NULL;
12000 gint quote_len = 0, is_normal = 0;
12001 start_line = *iter;
12002 gtk_text_iter_set_line_offset(&start_line, 0);
12003 tmp = gtk_text_buffer_get_text(buffer, &start_line, iter, FALSE);
12006 if (*tmp == '\0') {
12009 quote = compose_get_quote_str(buffer, &start_line, "e_len);
12017 gtk_text_buffer_insert(buffer, iter, text, len);
12019 gtk_text_buffer_insert_with_tags_by_name(buffer,
12020 iter, text, len, "no_join", NULL);
12025 if (!paste_as_quotation) {
12026 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
12027 compose_beautify_paragraph(compose, iter, FALSE);
12028 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
12029 gtk_text_buffer_delete_mark(buffer, mark);
12032 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
12033 G_CALLBACK(text_inserted),
12035 g_signal_stop_emission_by_name(G_OBJECT(buffer), "insert-text");
12037 if (compose_can_autosave(compose) &&
12038 gtk_text_buffer_get_char_count(buffer) % prefs_common.autosave_length == 0 &&
12039 compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN /* disabled while loading */)
12040 compose->draft_timeout_tag = g_timeout_add
12041 (500, (GSourceFunc) compose_defer_auto_save_draft, compose);
12045 static void compose_check_all(GtkAction *action, gpointer data)
12047 Compose *compose = (Compose *)data;
12048 if (!compose->gtkaspell)
12051 if (gtk_widget_has_focus(compose->subject_entry))
12052 claws_spell_entry_check_all(
12053 CLAWS_SPELL_ENTRY(compose->subject_entry));
12055 gtkaspell_check_all(compose->gtkaspell);
12058 static void compose_highlight_all(GtkAction *action, gpointer data)
12060 Compose *compose = (Compose *)data;
12061 if (compose->gtkaspell) {
12062 claws_spell_entry_recheck_all(
12063 CLAWS_SPELL_ENTRY(compose->subject_entry));
12064 gtkaspell_highlight_all(compose->gtkaspell);
12068 static void compose_check_backwards(GtkAction *action, gpointer data)
12070 Compose *compose = (Compose *)data;
12071 if (!compose->gtkaspell) {
12072 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
12076 if (gtk_widget_has_focus(compose->subject_entry))
12077 claws_spell_entry_check_backwards(
12078 CLAWS_SPELL_ENTRY(compose->subject_entry));
12080 gtkaspell_check_backwards(compose->gtkaspell);
12083 static void compose_check_forwards_go(GtkAction *action, gpointer data)
12085 Compose *compose = (Compose *)data;
12086 if (!compose->gtkaspell) {
12087 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
12091 if (gtk_widget_has_focus(compose->subject_entry))
12092 claws_spell_entry_check_forwards_go(
12093 CLAWS_SPELL_ENTRY(compose->subject_entry));
12095 gtkaspell_check_forwards_go(compose->gtkaspell);
12100 *\brief Guess originating forward account from MsgInfo and several
12101 * "common preference" settings. Return NULL if no guess.
12103 static PrefsAccount *compose_find_account(MsgInfo *msginfo)
12105 PrefsAccount *account = NULL;
12107 cm_return_val_if_fail(msginfo, NULL);
12108 cm_return_val_if_fail(msginfo->folder, NULL);
12109 cm_return_val_if_fail(msginfo->folder->prefs, NULL);
12111 if (msginfo->folder->prefs->enable_default_account)
12112 account = account_find_from_id(msginfo->folder->prefs->default_account);
12114 if (!account && msginfo->to && prefs_common.forward_account_autosel) {
12116 Xstrdup_a(to, msginfo->to, return NULL);
12117 extract_address(to);
12118 account = account_find_from_address(to, FALSE);
12121 if (!account && prefs_common.forward_account_autosel) {
12123 if (!procheader_get_header_from_msginfo
12124 (msginfo, &cc, "Cc:")) {
12125 gchar *buf = cc + strlen("Cc:");
12126 extract_address(buf);
12127 account = account_find_from_address(buf, FALSE);
12132 if (!account && prefs_common.forward_account_autosel) {
12133 gchar *deliveredto = NULL;
12134 if (!procheader_get_header_from_msginfo
12135 (msginfo, &deliveredto, "Delivered-To:")) {
12136 gchar *buf = deliveredto + strlen("Delivered-To:");
12137 extract_address(buf);
12138 account = account_find_from_address(buf, FALSE);
12139 g_free(deliveredto);
12144 account = msginfo->folder->folder->account;
12149 gboolean compose_close(Compose *compose)
12153 cm_return_val_if_fail(compose, FALSE);
12155 if (!g_mutex_trylock(compose->mutex)) {
12156 /* we have to wait for the (possibly deferred by auto-save)
12157 * drafting to be done, before destroying the compose under
12159 debug_print("waiting for drafting to finish...\n");
12160 compose_allow_user_actions(compose, FALSE);
12161 if (compose->close_timeout_tag == 0) {
12162 compose->close_timeout_tag =
12163 g_timeout_add (500, (GSourceFunc) compose_close,
12169 if (compose->draft_timeout_tag >= 0) {
12170 g_source_remove(compose->draft_timeout_tag);
12171 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN;
12174 gtkut_widget_get_uposition(compose->window, &x, &y);
12175 if (!compose->batch) {
12176 prefs_common.compose_x = x;
12177 prefs_common.compose_y = y;
12179 g_mutex_unlock(compose->mutex);
12180 compose_destroy(compose);
12185 * Add entry field for each address in list.
12186 * \param compose E-Mail composition object.
12187 * \param listAddress List of (formatted) E-Mail addresses.
12189 static void compose_add_field_list( Compose *compose, GList *listAddress ) {
12192 node = listAddress;
12194 addr = ( gchar * ) node->data;
12195 compose_entry_append( compose, addr, COMPOSE_TO, PREF_NONE );
12196 node = g_list_next( node );
12200 static void compose_reply_from_messageview_real(MessageView *msgview, GSList *msginfo_list,
12201 guint action, gboolean opening_multiple)
12203 gchar *body = NULL;
12204 GSList *new_msglist = NULL;
12205 MsgInfo *tmp_msginfo = NULL;
12206 gboolean originally_enc = FALSE;
12207 gboolean originally_sig = FALSE;
12208 Compose *compose = NULL;
12209 gchar *s_system = NULL;
12211 cm_return_if_fail(msginfo_list != NULL);
12213 if (g_slist_length(msginfo_list) == 1 && !opening_multiple && msgview != NULL) {
12214 MimeInfo *mimeinfo = messageview_get_selected_mime_part(msgview);
12215 MsgInfo *orig_msginfo = (MsgInfo *)msginfo_list->data;
12217 if (mimeinfo != NULL && mimeinfo->type == MIMETYPE_MESSAGE &&
12218 !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
12219 tmp_msginfo = procmsg_msginfo_new_from_mimeinfo(
12220 orig_msginfo, mimeinfo);
12221 if (tmp_msginfo != NULL) {
12222 new_msglist = g_slist_append(NULL, tmp_msginfo);
12224 originally_enc = MSG_IS_ENCRYPTED(orig_msginfo->flags);
12225 privacy_msginfo_get_signed_state(orig_msginfo, &s_system);
12226 originally_sig = MSG_IS_SIGNED(orig_msginfo->flags);
12228 tmp_msginfo->folder = orig_msginfo->folder;
12229 tmp_msginfo->msgnum = orig_msginfo->msgnum;
12230 if (orig_msginfo->tags) {
12231 tmp_msginfo->tags = g_slist_copy(orig_msginfo->tags);
12232 tmp_msginfo->folder->tags_dirty = TRUE;
12238 if (!opening_multiple && msgview != NULL)
12239 body = messageview_get_selection(msgview);
12242 compose = compose_reply_mode((ComposeMode)action, new_msglist, body);
12243 procmsg_msginfo_free(&tmp_msginfo);
12244 g_slist_free(new_msglist);
12246 compose = compose_reply_mode((ComposeMode)action, msginfo_list, body);
12248 if (compose && originally_enc) {
12249 compose_force_encryption(compose, compose->account, FALSE, s_system);
12252 if (compose && originally_sig && compose->account->default_sign_reply) {
12253 compose_force_signing(compose, compose->account, s_system);
12257 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
12260 void compose_reply_from_messageview(MessageView *msgview, GSList *msginfo_list,
12263 if ((!prefs_common.forward_as_attachment || action != COMPOSE_FORWARD)
12264 && msginfo_list != NULL
12265 && action != COMPOSE_FORWARD_AS_ATTACH && g_slist_length(msginfo_list) > 1) {
12266 GSList *cur = msginfo_list;
12267 gchar *msg = g_strdup_printf(_("You are about to reply to %d "
12268 "messages. Opening the windows "
12269 "could take some time. Do you "
12270 "want to continue?"),
12271 g_slist_length(msginfo_list));
12272 if (g_slist_length(msginfo_list) > 9
12273 && alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, GTK_STOCK_YES, NULL,
12274 ALERTFOCUS_SECOND) != G_ALERTALTERNATE) {
12279 /* We'll open multiple compose windows */
12280 /* let the WM place the next windows */
12281 compose_force_window_origin = FALSE;
12282 for (; cur; cur = cur->next) {
12284 tmplist.data = cur->data;
12285 tmplist.next = NULL;
12286 compose_reply_from_messageview_real(msgview, &tmplist, action, TRUE);
12288 compose_force_window_origin = TRUE;
12290 /* forwarding multiple mails as attachments is done via a
12291 * single compose window */
12292 if (msginfo_list != NULL) {
12293 compose_reply_from_messageview_real(msgview, msginfo_list, action, FALSE);
12294 } else if (msgview != NULL) {
12296 tmplist.data = msgview->msginfo;
12297 tmplist.next = NULL;
12298 compose_reply_from_messageview_real(msgview, &tmplist, action, FALSE);
12300 debug_print("Nothing to reply to\n");
12305 void compose_check_for_email_account(Compose *compose)
12307 PrefsAccount *ac = NULL, *curr = NULL;
12313 if (compose->account && compose->account->protocol == A_NNTP) {
12314 ac = account_get_cur_account();
12315 if (ac->protocol == A_NNTP) {
12316 list = account_get_list();
12318 for( ; list != NULL ; list = g_list_next(list)) {
12319 curr = (PrefsAccount *) list->data;
12320 if (curr->protocol != A_NNTP) {
12326 combobox_select_by_data(GTK_COMBO_BOX(compose->account_combo),
12331 void compose_reply_to_address(MessageView *msgview, MsgInfo *msginfo,
12332 const gchar *address)
12334 GSList *msginfo_list = NULL;
12335 gchar *body = messageview_get_selection(msgview);
12338 msginfo_list = g_slist_prepend(msginfo_list, msginfo);
12340 compose = compose_reply_mode(COMPOSE_REPLY_TO_ADDRESS, msginfo_list, body);
12341 compose_check_for_email_account(compose);
12342 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
12343 compose_entry_append(compose, address, COMPOSE_TO, PREF_NONE);
12344 compose_reply_set_subject(compose, msginfo);
12347 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
12350 void compose_set_position(Compose *compose, gint pos)
12352 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
12354 gtkut_text_view_set_position(text, pos);
12357 gboolean compose_search_string(Compose *compose,
12358 const gchar *str, gboolean case_sens)
12360 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
12362 return gtkut_text_view_search_string(text, str, case_sens);
12365 gboolean compose_search_string_backward(Compose *compose,
12366 const gchar *str, gboolean case_sens)
12368 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
12370 return gtkut_text_view_search_string_backward(text, str, case_sens);
12373 /* allocate a msginfo structure and populate its data from a compose data structure */
12374 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose)
12376 MsgInfo *newmsginfo;
12378 gchar date[RFC822_DATE_BUFFSIZE];
12380 cm_return_val_if_fail( compose != NULL, NULL );
12382 newmsginfo = procmsg_msginfo_new();
12385 get_rfc822_date(date, sizeof(date));
12386 newmsginfo->date = g_strdup(date);
12389 if (compose->from_name) {
12390 newmsginfo->from = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
12391 newmsginfo->fromname = procheader_get_fromname(newmsginfo->from);
12395 if (compose->subject_entry)
12396 newmsginfo->subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
12398 /* to, cc, reply-to, newsgroups */
12399 for (list = compose->header_list; list; list = list->next) {
12400 gchar *header = gtk_editable_get_chars(
12402 gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
12403 gchar *entry = gtk_editable_get_chars(
12404 GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
12406 if ( strcasecmp(header, prefs_common_translated_header_name("To:")) == 0 ) {
12407 if ( newmsginfo->to == NULL ) {
12408 newmsginfo->to = g_strdup(entry);
12409 } else if (entry && *entry) {
12410 gchar *tmp = g_strconcat(newmsginfo->to, ", ", entry, NULL);
12411 g_free(newmsginfo->to);
12412 newmsginfo->to = tmp;
12415 if ( strcasecmp(header, prefs_common_translated_header_name("Cc:")) == 0 ) {
12416 if ( newmsginfo->cc == NULL ) {
12417 newmsginfo->cc = g_strdup(entry);
12418 } else if (entry && *entry) {
12419 gchar *tmp = g_strconcat(newmsginfo->cc, ", ", entry, NULL);
12420 g_free(newmsginfo->cc);
12421 newmsginfo->cc = tmp;
12424 if ( strcasecmp(header,
12425 prefs_common_translated_header_name("Newsgroups:")) == 0 ) {
12426 if ( newmsginfo->newsgroups == NULL ) {
12427 newmsginfo->newsgroups = g_strdup(entry);
12428 } else if (entry && *entry) {
12429 gchar *tmp = g_strconcat(newmsginfo->newsgroups, ", ", entry, NULL);
12430 g_free(newmsginfo->newsgroups);
12431 newmsginfo->newsgroups = tmp;
12439 /* other data is unset */
12445 /* update compose's dictionaries from folder dict settings */
12446 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
12447 FolderItem *folder_item)
12449 cm_return_if_fail(compose != NULL);
12451 if (compose->gtkaspell && folder_item && folder_item->prefs) {
12452 FolderItemPrefs *prefs = folder_item->prefs;
12454 if (prefs->enable_default_dictionary)
12455 gtkaspell_change_dict(compose->gtkaspell,
12456 prefs->default_dictionary, FALSE);
12457 if (folder_item->prefs->enable_default_alt_dictionary)
12458 gtkaspell_change_alt_dict(compose->gtkaspell,
12459 prefs->default_alt_dictionary);
12460 if (prefs->enable_default_dictionary
12461 || prefs->enable_default_alt_dictionary)
12462 compose_spell_menu_changed(compose);
12467 static void compose_subject_entry_activated(GtkWidget *widget, gpointer data)
12469 Compose *compose = (Compose *)data;
12471 cm_return_if_fail(compose != NULL);
12473 gtk_widget_grab_focus(compose->text);
12476 static void from_name_activate_cb(GtkWidget *widget, gpointer data)
12478 gtk_combo_box_popup(GTK_COMBO_BOX(data));