2 * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2016 Hiroyuki Yamamoto and the Claws Mail team
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"
121 #define N_ATTACH_COLS (N_COL_COLUMNS)
125 COMPOSE_CALL_ADVANCED_ACTION_UNDEFINED = -1,
126 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE = 0,
127 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER,
128 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER,
129 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD,
130 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD,
131 COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE,
132 COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE,
133 COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE,
134 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER,
135 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER,
136 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD,
137 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD,
138 COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE,
139 COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END
140 } ComposeCallAdvancedAction;
144 PRIORITY_HIGHEST = 1,
153 COMPOSE_INSERT_SUCCESS,
154 COMPOSE_INSERT_READ_ERROR,
155 COMPOSE_INSERT_INVALID_CHARACTER,
156 COMPOSE_INSERT_NO_FILE
157 } ComposeInsertResult;
161 COMPOSE_WRITE_FOR_SEND,
162 COMPOSE_WRITE_FOR_STORE
167 COMPOSE_QUOTE_FORCED,
174 SUBJECT_FIELD_PRESENT,
179 #define B64_LINE_SIZE 57
180 #define B64_BUFFSIZE 77
182 #define MAX_REFERENCES_LEN 999
184 #define COMPOSE_DRAFT_TIMEOUT_UNSET -1
185 #define COMPOSE_DRAFT_TIMEOUT_FORBIDDEN -2
187 static GdkColor default_header_bgcolor = {
194 static GdkColor default_header_color = {
201 static GList *compose_list = NULL;
202 static GSList *extra_headers = NULL;
204 static Compose *compose_generic_new (PrefsAccount *account,
208 GList *listAddress );
210 static Compose *compose_create (PrefsAccount *account,
215 static void compose_entry_indicate (Compose *compose,
216 const gchar *address);
217 static Compose *compose_followup_and_reply_to (MsgInfo *msginfo,
218 ComposeQuoteMode quote_mode,
222 static Compose *compose_forward_multiple (PrefsAccount *account,
223 GSList *msginfo_list);
224 static Compose *compose_reply (MsgInfo *msginfo,
225 ComposeQuoteMode quote_mode,
230 static Compose *compose_reply_mode (ComposeMode mode,
231 GSList *msginfo_list,
233 static void compose_template_apply_fields(Compose *compose, Template *tmpl);
234 static void compose_update_privacy_systems_menu(Compose *compose);
236 static GtkWidget *compose_account_option_menu_create
238 static void compose_set_out_encoding (Compose *compose);
239 static void compose_set_template_menu (Compose *compose);
240 static void compose_destroy (Compose *compose);
242 static MailField compose_entries_set (Compose *compose,
244 ComposeEntryType to_type);
245 static gint compose_parse_header (Compose *compose,
247 static gint compose_parse_manual_headers (Compose *compose,
249 HeaderEntry *entries);
250 static gchar *compose_parse_references (const gchar *ref,
253 static gchar *compose_quote_fmt (Compose *compose,
259 gboolean need_unescape,
260 const gchar *err_msg);
262 static void compose_reply_set_entry (Compose *compose,
268 followup_and_reply_to);
269 static void compose_reedit_set_entry (Compose *compose,
272 static void compose_insert_sig (Compose *compose,
274 static ComposeInsertResult compose_insert_file (Compose *compose,
277 static gboolean compose_attach_append (Compose *compose,
280 const gchar *content_type,
281 const gchar *charset);
282 static void compose_attach_parts (Compose *compose,
285 static gboolean compose_beautify_paragraph (Compose *compose,
286 GtkTextIter *par_iter,
288 static void compose_wrap_all (Compose *compose);
289 static void compose_wrap_all_full (Compose *compose,
292 static void compose_set_title (Compose *compose);
293 static void compose_select_account (Compose *compose,
294 PrefsAccount *account,
297 static PrefsAccount *compose_current_mail_account(void);
298 /* static gint compose_send (Compose *compose); */
299 static gboolean compose_check_for_valid_recipient
301 static gboolean compose_check_entries (Compose *compose,
302 gboolean check_everything);
303 static gint compose_write_to_file (Compose *compose,
306 gboolean attach_parts);
307 static gint compose_write_body_to_file (Compose *compose,
309 static gint compose_remove_reedit_target (Compose *compose,
311 static void compose_remove_draft (Compose *compose);
312 static gint compose_queue_sub (Compose *compose,
316 gboolean perform_checks,
317 gboolean remove_reedit_target);
318 static int compose_add_attachments (Compose *compose,
320 static gchar *compose_get_header (Compose *compose);
321 static gchar *compose_get_manual_headers_info (Compose *compose);
323 static void compose_convert_header (Compose *compose,
328 gboolean addr_field);
330 static void compose_attach_info_free (AttachInfo *ainfo);
331 static void compose_attach_remove_selected (GtkAction *action,
334 static void compose_template_apply (Compose *compose,
337 static void compose_attach_property (GtkAction *action,
339 static void compose_attach_property_create (gboolean *cancelled);
340 static void attach_property_ok (GtkWidget *widget,
341 gboolean *cancelled);
342 static void attach_property_cancel (GtkWidget *widget,
343 gboolean *cancelled);
344 static gint attach_property_delete_event (GtkWidget *widget,
346 gboolean *cancelled);
347 static gboolean attach_property_key_pressed (GtkWidget *widget,
349 gboolean *cancelled);
351 static void compose_exec_ext_editor (Compose *compose);
353 static gint compose_exec_ext_editor_real (const gchar *file,
354 GdkNativeWindow socket_wid);
355 static gboolean compose_ext_editor_kill (Compose *compose);
356 static gboolean compose_input_cb (GIOChannel *source,
357 GIOCondition condition,
359 static void compose_set_ext_editor_sensitive (Compose *compose,
361 static gboolean compose_get_ext_editor_cmd_valid();
362 static gboolean compose_get_ext_editor_uses_socket();
363 static gboolean compose_ext_editor_plug_removed_cb
366 #endif /* G_OS_UNIX */
368 static void compose_undo_state_changed (UndoMain *undostruct,
373 static void compose_create_header_entry (Compose *compose);
374 static void compose_add_header_entry (Compose *compose, const gchar *header,
375 gchar *text, ComposePrefType pref_type);
376 static void compose_remove_header_entries(Compose *compose);
378 static void compose_update_priority_menu_item(Compose * compose);
380 static void compose_spell_menu_changed (void *data);
381 static void compose_dict_changed (void *data);
383 static void compose_add_field_list ( Compose *compose,
384 GList *listAddress );
386 /* callback functions */
388 static void compose_notebook_size_alloc (GtkNotebook *notebook,
389 GtkAllocation *allocation,
391 static gboolean compose_edit_size_alloc (GtkEditable *widget,
392 GtkAllocation *allocation,
393 GtkSHRuler *shruler);
394 static void account_activated (GtkComboBox *optmenu,
396 static void attach_selected (GtkTreeView *tree_view,
397 GtkTreePath *tree_path,
398 GtkTreeViewColumn *column,
400 static gboolean attach_button_pressed (GtkWidget *widget,
401 GdkEventButton *event,
403 static gboolean attach_key_pressed (GtkWidget *widget,
406 static void compose_send_cb (GtkAction *action, gpointer data);
407 static void compose_send_later_cb (GtkAction *action, gpointer data);
409 static void compose_save_cb (GtkAction *action,
412 static void compose_attach_cb (GtkAction *action,
414 static void compose_insert_file_cb (GtkAction *action,
416 static void compose_insert_sig_cb (GtkAction *action,
418 static void compose_replace_sig_cb (GtkAction *action,
421 static void compose_close_cb (GtkAction *action,
423 static void compose_print_cb (GtkAction *action,
426 static void compose_set_encoding_cb (GtkAction *action, GtkRadioAction *current, gpointer data);
428 static void compose_address_cb (GtkAction *action,
430 static void about_show_cb (GtkAction *action,
432 static void compose_template_activate_cb(GtkWidget *widget,
435 static void compose_ext_editor_cb (GtkAction *action,
438 static gint compose_delete_cb (GtkWidget *widget,
442 static void compose_undo_cb (GtkAction *action,
444 static void compose_redo_cb (GtkAction *action,
446 static void compose_cut_cb (GtkAction *action,
448 static void compose_copy_cb (GtkAction *action,
450 static void compose_paste_cb (GtkAction *action,
452 static void compose_paste_as_quote_cb (GtkAction *action,
454 static void compose_paste_no_wrap_cb (GtkAction *action,
456 static void compose_paste_wrap_cb (GtkAction *action,
458 static void compose_allsel_cb (GtkAction *action,
461 static void compose_advanced_action_cb (GtkAction *action,
464 static void compose_grab_focus_cb (GtkWidget *widget,
467 static void compose_changed_cb (GtkTextBuffer *textbuf,
470 static void compose_wrap_cb (GtkAction *action,
472 static void compose_wrap_all_cb (GtkAction *action,
474 static void compose_find_cb (GtkAction *action,
476 static void compose_toggle_autowrap_cb (GtkToggleAction *action,
478 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
481 static void compose_toggle_ruler_cb (GtkToggleAction *action,
483 static void compose_toggle_sign_cb (GtkToggleAction *action,
485 static void compose_toggle_encrypt_cb (GtkToggleAction *action,
487 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data);
488 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn);
489 static void activate_privacy_system (Compose *compose,
490 PrefsAccount *account,
492 static void compose_use_signing(Compose *compose, gboolean use_signing);
493 static void compose_use_encryption(Compose *compose, gboolean use_encryption);
494 static void compose_toggle_return_receipt_cb(GtkToggleAction *action,
496 static void compose_toggle_remove_refs_cb(GtkToggleAction *action,
498 static void compose_set_priority_cb (GtkAction *action, GtkRadioAction *current, gpointer data);
499 static void compose_reply_change_mode (Compose *compose, ComposeMode action);
500 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data);
502 static void compose_attach_drag_received_cb (GtkWidget *widget,
503 GdkDragContext *drag_context,
506 GtkSelectionData *data,
510 static void compose_insert_drag_received_cb (GtkWidget *widget,
511 GdkDragContext *drag_context,
514 GtkSelectionData *data,
518 static void compose_header_drag_received_cb (GtkWidget *widget,
519 GdkDragContext *drag_context,
522 GtkSelectionData *data,
527 static gboolean compose_drag_drop (GtkWidget *widget,
528 GdkDragContext *drag_context,
530 guint time, gpointer user_data);
531 static gboolean completion_set_focus_to_subject
536 static void text_inserted (GtkTextBuffer *buffer,
541 static Compose *compose_generic_reply(MsgInfo *msginfo,
542 ComposeQuoteMode quote_mode,
546 gboolean followup_and_reply_to,
549 static void compose_headerentry_changed_cb (GtkWidget *entry,
550 ComposeHeaderEntry *headerentry);
551 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
553 ComposeHeaderEntry *headerentry);
554 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
555 ComposeHeaderEntry *headerentry);
557 static void compose_show_first_last_header (Compose *compose, gboolean show_first);
559 static void compose_allow_user_actions (Compose *compose, gboolean allow);
561 static void compose_nothing_cb (GtkAction *action, gpointer data)
567 static void compose_check_all (GtkAction *action, gpointer data);
568 static void compose_highlight_all (GtkAction *action, gpointer data);
569 static void compose_check_backwards (GtkAction *action, gpointer data);
570 static void compose_check_forwards_go (GtkAction *action, gpointer data);
573 static PrefsAccount *compose_find_account (MsgInfo *msginfo);
575 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose);
578 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
579 FolderItem *folder_item);
581 static void compose_attach_update_label(Compose *compose);
582 static void compose_set_folder_prefs(Compose *compose, FolderItem *folder,
583 gboolean respect_default_to);
584 static void compose_subject_entry_activated(GtkWidget *widget, gpointer data);
585 static void from_name_activate_cb(GtkWidget *widget, gpointer data);
587 static GtkActionEntry compose_popup_entries[] =
589 {"Compose", NULL, "Compose", NULL, NULL, NULL },
590 {"Compose/Add", NULL, N_("_Add..."), NULL, NULL, G_CALLBACK(compose_attach_cb) },
591 {"Compose/Remove", NULL, N_("_Remove"), NULL, NULL, G_CALLBACK(compose_attach_remove_selected) },
592 {"Compose/---", NULL, "---", NULL, NULL, NULL },
593 {"Compose/Properties", NULL, N_("_Properties..."), NULL, NULL, G_CALLBACK(compose_attach_property) },
596 static GtkActionEntry compose_entries[] =
598 {"Menu", NULL, "Menu", NULL, NULL, NULL },
600 {"Message", NULL, N_("_Message"), NULL, NULL, NULL },
601 {"Edit", NULL, N_("_Edit"), NULL, NULL, NULL },
603 {"Spelling", NULL, N_("_Spelling"), NULL, NULL, NULL },
605 {"Options", NULL, N_("_Options"), NULL, NULL, NULL },
606 {"Tools", NULL, N_("_Tools"), NULL, NULL, NULL },
607 {"Help", NULL, N_("_Help"), NULL, NULL, NULL },
609 {"Message/Send", NULL, N_("S_end"), "<control>Return", NULL, G_CALLBACK(compose_send_cb) },
610 {"Message/SendLater", NULL, N_("Send _later"), "<shift><control>S", NULL, G_CALLBACK(compose_send_later_cb) },
611 {"Message/---", NULL, "---", NULL, NULL, NULL },
613 {"Message/AttachFile", NULL, N_("_Attach file"), "<control>M", NULL, G_CALLBACK(compose_attach_cb) },
614 {"Message/InsertFile", NULL, N_("_Insert file"), "<control>I", NULL, G_CALLBACK(compose_insert_file_cb) },
615 {"Message/InsertSig", NULL, N_("Insert si_gnature"), "<control>G", NULL, G_CALLBACK(compose_insert_sig_cb) },
616 {"Message/ReplaceSig", NULL, N_("_Replace signature"), NULL, NULL, G_CALLBACK(compose_replace_sig_cb) },
617 /* {"Message/---", NULL, "---", NULL, NULL, NULL }, */
618 {"Message/Save", NULL, N_("_Save"), "<control>S", NULL, G_CALLBACK(compose_save_cb) }, /*COMPOSE_KEEP_EDITING*/
619 /* {"Message/---", NULL, "---", NULL, NULL, NULL }, */
620 {"Message/Print", NULL, N_("_Print"), NULL, NULL, G_CALLBACK(compose_print_cb) },
621 /* {"Message/---", NULL, "---", NULL, NULL, NULL }, */
622 {"Message/Close", NULL, N_("_Close"), "<control>W", NULL, G_CALLBACK(compose_close_cb) },
625 {"Edit/Undo", NULL, N_("_Undo"), "<control>Z", NULL, G_CALLBACK(compose_undo_cb) },
626 {"Edit/Redo", NULL, N_("_Redo"), "<control>Y", NULL, G_CALLBACK(compose_redo_cb) },
627 {"Edit/---", NULL, "---", NULL, NULL, NULL },
629 {"Edit/Cut", NULL, N_("Cu_t"), "<control>X", NULL, G_CALLBACK(compose_cut_cb) },
630 {"Edit/Copy", NULL, N_("_Copy"), "<control>C", NULL, G_CALLBACK(compose_copy_cb) },
631 {"Edit/Paste", NULL, N_("_Paste"), "<control>V", NULL, G_CALLBACK(compose_paste_cb) },
633 {"Edit/SpecialPaste", NULL, N_("_Special paste"), NULL, NULL, NULL },
634 {"Edit/SpecialPaste/AsQuotation", NULL, N_("As _quotation"), NULL, NULL, G_CALLBACK(compose_paste_as_quote_cb) },
635 {"Edit/SpecialPaste/Wrapped", NULL, N_("_Wrapped"), NULL, NULL, G_CALLBACK(compose_paste_wrap_cb) },
636 {"Edit/SpecialPaste/Unwrapped", NULL, N_("_Unwrapped"), NULL, NULL, G_CALLBACK(compose_paste_no_wrap_cb) },
638 {"Edit/SelectAll", NULL, N_("Select _all"), "<control>A", NULL, G_CALLBACK(compose_allsel_cb) },
640 {"Edit/Advanced", NULL, N_("A_dvanced"), NULL, NULL, NULL },
641 {"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*/
642 {"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*/
643 {"Edit/Advanced/BackWord", NULL, N_("Move a word backward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD*/
644 {"Edit/Advanced/ForwWord", NULL, N_("Move a word forward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD*/
645 {"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*/
646 {"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*/
647 {"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*/
648 {"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*/
649 {"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*/
650 {"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*/
651 {"Edit/Advanced/DelBackWord", NULL, N_("Delete a word backward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD*/
652 {"Edit/Advanced/DelForwWord", NULL, N_("Delete a word forward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD*/
653 {"Edit/Advanced/DelLine", NULL, N_("Delete line"), "<control>U", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE*/
654 {"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*/
656 /* {"Edit/---", NULL, "---", NULL, NULL, NULL }, */
657 {"Edit/Find", NULL, N_("_Find"), "<control>F", NULL, G_CALLBACK(compose_find_cb) },
659 /* {"Edit/---", NULL, "---", NULL, NULL, NULL }, */
660 {"Edit/WrapPara", NULL, N_("_Wrap current paragraph"), "<control>L", NULL, G_CALLBACK(compose_wrap_cb) }, /* 0 */
661 {"Edit/WrapAllLines", NULL, N_("Wrap all long _lines"), "<control><alt>L", NULL, G_CALLBACK(compose_wrap_all_cb) }, /* 1 */
662 /* {"Edit/---", NULL, "---", NULL, NULL, NULL }, */
663 {"Edit/ExtEditor", NULL, N_("Edit with e_xternal editor"), "<shift><control>X", NULL, G_CALLBACK(compose_ext_editor_cb) },
666 {"Spelling/CheckAllSel", NULL, N_("_Check all or check selection"), NULL, NULL, G_CALLBACK(compose_check_all) },
667 {"Spelling/HighlightAll", NULL, N_("_Highlight all misspelled words"), NULL, NULL, G_CALLBACK(compose_highlight_all) },
668 {"Spelling/CheckBackwards", NULL, N_("Check _backwards misspelled word"), NULL, NULL, G_CALLBACK(compose_check_backwards) },
669 {"Spelling/ForwardNext", NULL, N_("_Forward to next misspelled word"), NULL, NULL, G_CALLBACK(compose_check_forwards_go) },
671 {"Spelling/---", NULL, "---", NULL, NULL, NULL },
672 {"Spelling/Options", NULL, N_("_Options"), NULL, NULL, NULL },
676 {"Options/ReplyMode", NULL, N_("Reply _mode"), NULL, NULL, NULL },
677 {"Options/---", NULL, "---", NULL, NULL, NULL },
678 {"Options/PrivacySystem", NULL, N_("Privacy _System"), NULL, NULL, NULL },
679 {"Options/PrivacySystem/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
681 /* {"Options/---", NULL, "---", NULL, NULL, NULL }, */
682 {"Options/Priority", NULL, N_("_Priority"), NULL, NULL, NULL },
684 {"Options/Encoding", NULL, N_("Character _encoding"), NULL, NULL, NULL },
685 {"Options/Encoding/---", NULL, "---", NULL, NULL, NULL },
686 #define ENC_ACTION(cs_char,c_char,string) \
687 {"Options/Encoding/" cs_char, NULL, N_(string), NULL, NULL, c_char }
689 {"Options/Encoding/Western", NULL, N_("Western European"), NULL, NULL, NULL },
690 {"Options/Encoding/Baltic", NULL, N_("Baltic"), NULL, NULL, NULL },
691 {"Options/Encoding/Hebrew", NULL, N_("Hebrew"), NULL, NULL, NULL },
692 {"Options/Encoding/Arabic", NULL, N_("Arabic"), NULL, NULL, NULL },
693 {"Options/Encoding/Cyrillic", NULL, N_("Cyrillic"), NULL, NULL, NULL },
694 {"Options/Encoding/Japanese", NULL, N_("Japanese"), NULL, NULL, NULL },
695 {"Options/Encoding/Chinese", NULL, N_("Chinese"), NULL, NULL, NULL },
696 {"Options/Encoding/Korean", NULL, N_("Korean"), NULL, NULL, NULL },
697 {"Options/Encoding/Thai", NULL, N_("Thai"), NULL, NULL, NULL },
700 {"Tools/AddressBook", NULL, N_("_Address book"), NULL, NULL, G_CALLBACK(compose_address_cb) },
702 {"Tools/Template", NULL, N_("_Template"), NULL, NULL, NULL },
703 {"Tools/Template/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
704 {"Tools/Actions", NULL, N_("Actio_ns"), NULL, NULL, NULL },
705 {"Tools/Actions/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
708 {"Help/About", NULL, N_("_About"), NULL, NULL, G_CALLBACK(about_show_cb) },
711 static GtkToggleActionEntry compose_toggle_entries[] =
713 {"Edit/AutoWrap", NULL, N_("Aut_o wrapping"), "<shift><control>L", NULL, G_CALLBACK(compose_toggle_autowrap_cb), FALSE }, /* Toggle */
714 {"Edit/AutoIndent", NULL, N_("Auto _indent"), NULL, NULL, G_CALLBACK(compose_toggle_autoindent_cb), FALSE }, /* Toggle */
715 {"Options/Sign", NULL, N_("Si_gn"), NULL, NULL, G_CALLBACK(compose_toggle_sign_cb), FALSE }, /* Toggle */
716 {"Options/Encrypt", NULL, N_("_Encrypt"), NULL, NULL, G_CALLBACK(compose_toggle_encrypt_cb), FALSE }, /* Toggle */
717 {"Options/RequestRetRcpt", NULL, N_("_Request Return Receipt"), NULL, NULL, G_CALLBACK(compose_toggle_return_receipt_cb), FALSE }, /* Toggle */
718 {"Options/RemoveReferences", NULL, N_("Remo_ve references"), NULL, NULL, G_CALLBACK(compose_toggle_remove_refs_cb), FALSE }, /* Toggle */
719 {"Tools/ShowRuler", NULL, N_("Show _ruler"), NULL, NULL, G_CALLBACK(compose_toggle_ruler_cb), FALSE }, /* Toggle */
722 static GtkRadioActionEntry compose_radio_rm_entries[] =
724 {"Options/ReplyMode/Normal", NULL, N_("_Normal"), NULL, NULL, COMPOSE_REPLY }, /* RADIO compose_reply_change_mode_cb */
725 {"Options/ReplyMode/All", NULL, N_("_All"), NULL, NULL, COMPOSE_REPLY_TO_ALL }, /* RADIO compose_reply_change_mode_cb */
726 {"Options/ReplyMode/Sender", NULL, N_("_Sender"), NULL, NULL, COMPOSE_REPLY_TO_SENDER }, /* RADIO compose_reply_change_mode_cb */
727 {"Options/ReplyMode/List", NULL, N_("_Mailing-list"), NULL, NULL, COMPOSE_REPLY_TO_LIST }, /* RADIO compose_reply_change_mode_cb */
730 static GtkRadioActionEntry compose_radio_prio_entries[] =
732 {"Options/Priority/Highest", NULL, N_("_Highest"), NULL, NULL, PRIORITY_HIGHEST }, /* RADIO compose_set_priority_cb */
733 {"Options/Priority/High", NULL, N_("Hi_gh"), NULL, NULL, PRIORITY_HIGH }, /* RADIO compose_set_priority_cb */
734 {"Options/Priority/Normal", NULL, N_("_Normal"), NULL, NULL, PRIORITY_NORMAL }, /* RADIO compose_set_priority_cb */
735 {"Options/Priority/Low", NULL, N_("Lo_w"), NULL, NULL, PRIORITY_LOW }, /* RADIO compose_set_priority_cb */
736 {"Options/Priority/Lowest", NULL, N_("_Lowest"), NULL, NULL, PRIORITY_LOWEST }, /* RADIO compose_set_priority_cb */
739 static GtkRadioActionEntry compose_radio_enc_entries[] =
741 ENC_ACTION(CS_AUTO, C_AUTO, N_("_Automatic")), /* RADIO compose_set_encoding_cb */
742 ENC_ACTION(CS_US_ASCII, C_US_ASCII, N_("7bit ASCII (US-ASC_II)")), /* RADIO compose_set_encoding_cb */
743 ENC_ACTION(CS_UTF_8, C_UTF_8, N_("Unicode (_UTF-8)")), /* RADIO compose_set_encoding_cb */
744 ENC_ACTION("Western/"CS_ISO_8859_1, C_ISO_8859_1, "ISO-8859-_1"), /* RADIO compose_set_encoding_cb */
745 ENC_ACTION("Western/"CS_ISO_8859_15, C_ISO_8859_15, "ISO-8859-15"), /* RADIO compose_set_encoding_cb */
746 ENC_ACTION("Western/"CS_WINDOWS_1252, C_WINDOWS_1252, "Windows-1252"), /* RADIO compose_set_encoding_cb */
747 ENC_ACTION(CS_ISO_8859_2, C_ISO_8859_2, N_("Central European (ISO-8859-_2)")), /* RADIO compose_set_encoding_cb */
748 ENC_ACTION("Baltic/"CS_ISO_8859_13, C_ISO_8859_13, "ISO-8859-13"), /* RADIO compose_set_encoding_cb */
749 ENC_ACTION("Baltic/"CS_ISO_8859_4, C_ISO_8859_14, "ISO-8859-_4"), /* RADIO compose_set_encoding_cb */
750 ENC_ACTION(CS_ISO_8859_7, C_ISO_8859_7, N_("Greek (ISO-8859-_7)")), /* RADIO compose_set_encoding_cb */
751 ENC_ACTION("Hebrew/"CS_ISO_8859_8, C_ISO_8859_8, "ISO-8859-_8"), /* RADIO compose_set_encoding_cb */
752 ENC_ACTION("Hebrew/"CS_WINDOWS_1255, C_WINDOWS_1255, "Windows-1255"), /* RADIO compose_set_encoding_cb */
753 ENC_ACTION("Arabic/"CS_ISO_8859_6, C_ISO_8859_6, "ISO-8859-_6"), /* RADIO compose_set_encoding_cb */
754 ENC_ACTION("Arabic/"CS_WINDOWS_1256, C_WINDOWS_1256, "Windows-1256"), /* RADIO compose_set_encoding_cb */
755 ENC_ACTION(CS_ISO_8859_9, C_ISO_8859_9, N_("Turkish (ISO-8859-_9)")), /* RADIO compose_set_encoding_cb */
756 ENC_ACTION("Cyrillic/"CS_ISO_8859_5, C_ISO_8859_5, "ISO-8859-_5"), /* RADIO compose_set_encoding_cb */
757 ENC_ACTION("Cyrillic/"CS_KOI8_R, C_KOI8_R, "KOI8-_R"), /* RADIO compose_set_encoding_cb */
758 ENC_ACTION("Cyrillic/"CS_MACCYR, C_MACCYR, "_Mac-Cyrillic"), /* RADIO compose_set_encoding_cb */
759 ENC_ACTION("Cyrillic/"CS_KOI8_U, C_KOI8_U, "KOI8-_U"), /* RADIO compose_set_encoding_cb */
760 ENC_ACTION("Cyrillic/"CS_WINDOWS_1251, C_WINDOWS_1251, "Windows-1251"), /* RADIO compose_set_encoding_cb */
761 ENC_ACTION("Japanese/"CS_ISO_2022_JP, C_ISO_2022_JP, "ISO-2022-_JP"), /* RADIO compose_set_encoding_cb */
762 ENC_ACTION("Japanese/"CS_ISO_2022_JP_2, C_ISO_2022_JP_2, "ISO-2022-JP-_2"), /* RADIO compose_set_encoding_cb */
763 ENC_ACTION("Japanese/"CS_EUC_JP, C_EUC_JP, "_EUC-JP"), /* RADIO compose_set_encoding_cb */
764 ENC_ACTION("Japanese/"CS_SHIFT_JIS, C_SHIFT_JIS, "_Shift-JIS"), /* RADIO compose_set_encoding_cb */
765 ENC_ACTION("Chinese/"CS_GB18030, C_GB18030, "_GB18030"), /* RADIO compose_set_encoding_cb */
766 ENC_ACTION("Chinese/"CS_GB2312, C_GB2312, "_GB2312"), /* RADIO compose_set_encoding_cb */
767 ENC_ACTION("Chinese/"CS_GBK, C_GBK, "GB_K"), /* RADIO compose_set_encoding_cb */
768 ENC_ACTION("Chinese/"CS_BIG5, C_BIG5, "_Big5-JP"), /* RADIO compose_set_encoding_cb */
769 ENC_ACTION("Chinese/"CS_EUC_TW, C_EUC_TW, "EUC-_TW"), /* RADIO compose_set_encoding_cb */
770 ENC_ACTION("Korean/"CS_EUC_KR, C_EUC_KR, "_EUC-KR"), /* RADIO compose_set_encoding_cb */
771 ENC_ACTION("Korean/"CS_ISO_2022_KR, C_ISO_2022_KR, "_ISO-2022-KR"), /* RADIO compose_set_encoding_cb */
772 ENC_ACTION("Thai/"CS_TIS_620, C_TIS_620, "_TIS-620-KR"), /* RADIO compose_set_encoding_cb */
773 ENC_ACTION("Thai/"CS_WINDOWS_874, C_WINDOWS_874, "_Windows-874"), /* RADIO compose_set_encoding_cb */
776 static GtkTargetEntry compose_mime_types[] =
778 {"text/uri-list", 0, 0},
779 {"UTF8_STRING", 0, 0},
783 static gboolean compose_put_existing_to_front(MsgInfo *info)
785 const GList *compose_list = compose_get_compose_list();
786 const GList *elem = NULL;
789 for (elem = compose_list; elem != NULL && elem->data != NULL;
791 Compose *c = (Compose*)elem->data;
793 if (!c->targetinfo || !c->targetinfo->msgid ||
797 if (!strcmp(c->targetinfo->msgid, info->msgid)) {
798 gtkut_window_popup(c->window);
806 static GdkColor quote_color1 =
807 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
808 static GdkColor quote_color2 =
809 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
810 static GdkColor quote_color3 =
811 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
813 static GdkColor quote_bgcolor1 =
814 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
815 static GdkColor quote_bgcolor2 =
816 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
817 static GdkColor quote_bgcolor3 =
818 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
820 static GdkColor signature_color = {
827 static GdkColor uri_color = {
834 static void compose_create_tags(GtkTextView *text, Compose *compose)
836 GtkTextBuffer *buffer;
837 GdkColor black = {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
838 #if !GTK_CHECK_VERSION(2, 24, 0)
845 buffer = gtk_text_view_get_buffer(text);
847 if (prefs_common.enable_color) {
848 /* grab the quote colors, converting from an int to a GdkColor */
849 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_col,
851 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_col,
853 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_col,
855 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_bgcol,
857 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_bgcol,
859 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_bgcol,
861 gtkut_convert_int_to_gdk_color(prefs_common.signature_col,
863 gtkut_convert_int_to_gdk_color(prefs_common.uri_col,
866 signature_color = quote_color1 = quote_color2 = quote_color3 =
867 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 = uri_color = black;
870 if (prefs_common.enable_color && prefs_common.enable_bgcolor) {
871 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
872 "foreground-gdk", "e_color1,
873 "paragraph-background-gdk", "e_bgcolor1,
875 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
876 "foreground-gdk", "e_color2,
877 "paragraph-background-gdk", "e_bgcolor2,
879 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
880 "foreground-gdk", "e_color3,
881 "paragraph-background-gdk", "e_bgcolor3,
884 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
885 "foreground-gdk", "e_color1,
887 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
888 "foreground-gdk", "e_color2,
890 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
891 "foreground-gdk", "e_color3,
895 compose->signature_tag = gtk_text_buffer_create_tag(buffer, "signature",
896 "foreground-gdk", &signature_color,
899 compose->uri_tag = gtk_text_buffer_create_tag(buffer, "link",
900 "foreground-gdk", &uri_color,
902 compose->no_wrap_tag = gtk_text_buffer_create_tag(buffer, "no_wrap", NULL);
903 compose->no_join_tag = gtk_text_buffer_create_tag(buffer, "no_join", NULL);
905 #if !GTK_CHECK_VERSION(2, 24, 0)
906 color[0] = quote_color1;
907 color[1] = quote_color2;
908 color[2] = quote_color3;
909 color[3] = quote_bgcolor1;
910 color[4] = quote_bgcolor2;
911 color[5] = quote_bgcolor3;
912 color[6] = signature_color;
913 color[7] = uri_color;
915 cmap = gdk_drawable_get_colormap(gtk_widget_get_window(compose->window));
916 gdk_colormap_alloc_colors(cmap, color, 8, FALSE, TRUE, success);
918 for (i = 0; i < 8; i++) {
919 if (success[i] == FALSE) {
920 g_warning("Compose: color allocation failed.");
921 quote_color1 = quote_color2 = quote_color3 =
922 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 =
923 signature_color = uri_color = black;
929 Compose *compose_new(PrefsAccount *account, const gchar *mailto,
932 return compose_generic_new(account, mailto, NULL, attach_files, NULL);
935 Compose *compose_new_with_folderitem(PrefsAccount *account, FolderItem *item, const gchar *mailto)
937 return compose_generic_new(account, mailto, item, NULL, NULL);
940 Compose *compose_new_with_list( PrefsAccount *account, GList *listAddress )
942 return compose_generic_new( account, NULL, NULL, NULL, listAddress );
945 #define SCROLL_TO_CURSOR(compose) { \
946 GtkTextMark *cmark = gtk_text_buffer_get_insert( \
947 gtk_text_view_get_buffer( \
948 GTK_TEXT_VIEW(compose->text))); \
949 gtk_text_view_scroll_mark_onscreen( \
950 GTK_TEXT_VIEW(compose->text), \
954 static void compose_set_save_to(Compose *compose, const gchar *folderidentifier)
957 if (folderidentifier) {
958 #if !GTK_CHECK_VERSION(2, 24, 0)
959 combobox_unset_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo));
961 combobox_unset_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo));
963 prefs_common.compose_save_to_history = add_history(
964 prefs_common.compose_save_to_history, folderidentifier);
965 #if !GTK_CHECK_VERSION(2, 24, 0)
966 combobox_set_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo),
967 prefs_common.compose_save_to_history);
969 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo),
970 prefs_common.compose_save_to_history);
974 entry = GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(compose->savemsg_combo)));
975 if (folderidentifier)
976 gtk_entry_set_text(GTK_ENTRY(entry), folderidentifier);
978 gtk_entry_set_text(GTK_ENTRY(entry), "");
981 static gchar *compose_get_save_to(Compose *compose)
984 gchar *result = NULL;
985 entry = GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(compose->savemsg_combo)));
986 result = gtk_editable_get_chars(entry, 0, -1);
989 #if !GTK_CHECK_VERSION(2, 24, 0)
990 combobox_unset_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo));
992 combobox_unset_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo));
994 prefs_common.compose_save_to_history = add_history(
995 prefs_common.compose_save_to_history, result);
996 #if !GTK_CHECK_VERSION(2, 24, 0)
997 combobox_set_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo),
998 prefs_common.compose_save_to_history);
1000 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo),
1001 prefs_common.compose_save_to_history);
1007 Compose *compose_generic_new(PrefsAccount *account, const gchar *mailto, FolderItem *item,
1008 GList *attach_files, GList *listAddress )
1011 GtkTextView *textview;
1012 GtkTextBuffer *textbuf;
1014 const gchar *subject_format = NULL;
1015 const gchar *body_format = NULL;
1016 gchar *mailto_from = NULL;
1017 PrefsAccount *mailto_account = NULL;
1018 MsgInfo* dummyinfo = NULL;
1019 gint cursor_pos = -1;
1020 MailField mfield = NO_FIELD_PRESENT;
1024 /* check if mailto defines a from */
1025 if (mailto && *mailto != '\0') {
1026 scan_mailto_url(mailto, &mailto_from, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
1027 /* mailto defines a from, check if we can get account prefs from it,
1028 if not, the account prefs will be guessed using other ways, but we'll keep
1031 mailto_account = account_find_from_address(mailto_from, TRUE);
1032 if (mailto_account == NULL) {
1034 Xstrdup_a(tmp_from, mailto_from, return NULL);
1035 extract_address(tmp_from);
1036 mailto_account = account_find_from_address(tmp_from, TRUE);
1040 account = mailto_account;
1043 /* if no account prefs set from mailto, set if from folder prefs (if any) */
1044 if (!mailto_account && item && item->prefs && item->prefs->enable_default_account)
1045 account = account_find_from_id(item->prefs->default_account);
1047 /* if no account prefs set, fallback to the current one */
1048 if (!account) account = cur_account;
1049 cm_return_val_if_fail(account != NULL, NULL);
1051 compose = compose_create(account, item, COMPOSE_NEW, FALSE);
1053 /* override from name if mailto asked for it */
1055 gtk_entry_set_text(GTK_ENTRY(compose->from_name), mailto_from);
1056 g_free(mailto_from);
1058 /* override from name according to folder properties */
1059 if (item && item->prefs &&
1060 item->prefs->compose_with_format &&
1061 item->prefs->compose_override_from_format &&
1062 *item->prefs->compose_override_from_format != '\0') {
1067 dummyinfo = compose_msginfo_new_from_compose(compose);
1069 /* decode \-escape sequences in the internal representation of the quote format */
1070 tmp = g_malloc(strlen(item->prefs->compose_override_from_format)+1);
1071 pref_get_unescaped_pref(tmp, item->prefs->compose_override_from_format);
1074 quote_fmt_init(dummyinfo, NULL, NULL, FALSE, compose->account, FALSE,
1075 compose->gtkaspell);
1077 quote_fmt_init(dummyinfo, NULL, NULL, FALSE, compose->account, FALSE);
1079 quote_fmt_scan_string(tmp);
1082 buf = quote_fmt_get_buffer();
1084 alertpanel_error(_("New message From format error."));
1086 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1087 quote_fmt_reset_vartable();
1092 compose->replyinfo = NULL;
1093 compose->fwdinfo = NULL;
1095 textview = GTK_TEXT_VIEW(compose->text);
1096 textbuf = gtk_text_view_get_buffer(textview);
1097 compose_create_tags(textview, compose);
1099 undo_block(compose->undostruct);
1101 compose_set_dictionaries_from_folder_prefs(compose, item);
1104 if (account->auto_sig)
1105 compose_insert_sig(compose, FALSE);
1106 gtk_text_buffer_get_start_iter(textbuf, &iter);
1107 gtk_text_buffer_place_cursor(textbuf, &iter);
1109 if (account->protocol != A_NNTP) {
1110 if (mailto && *mailto != '\0') {
1111 mfield = compose_entries_set(compose, mailto, COMPOSE_TO);
1114 compose_set_folder_prefs(compose, item, TRUE);
1116 if (item && item->ret_rcpt) {
1117 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
1120 if (mailto && *mailto != '\0') {
1121 if (!strchr(mailto, '@'))
1122 mfield = compose_entries_set(compose, mailto, COMPOSE_NEWSGROUPS);
1124 mfield = compose_entries_set(compose, mailto, COMPOSE_TO);
1125 } else if (item && FOLDER_CLASS(item->folder) == news_get_class()) {
1126 compose_entry_append(compose, item->path, COMPOSE_NEWSGROUPS, PREF_FOLDER);
1127 mfield = TO_FIELD_PRESENT;
1130 * CLAWS: just don't allow return receipt request, even if the user
1131 * may want to send an email. simple but foolproof.
1133 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", FALSE);
1135 compose_add_field_list( compose, listAddress );
1137 if (item && item->prefs && item->prefs->compose_with_format) {
1138 subject_format = item->prefs->compose_subject_format;
1139 body_format = item->prefs->compose_body_format;
1140 } else if (account->compose_with_format) {
1141 subject_format = account->compose_subject_format;
1142 body_format = account->compose_body_format;
1143 } else if (prefs_common.compose_with_format) {
1144 subject_format = prefs_common.compose_subject_format;
1145 body_format = prefs_common.compose_body_format;
1148 if (subject_format || body_format) {
1151 && *subject_format != '\0' )
1153 gchar *subject = NULL;
1158 dummyinfo = compose_msginfo_new_from_compose(compose);
1160 /* decode \-escape sequences in the internal representation of the quote format */
1161 tmp = g_malloc(strlen(subject_format)+1);
1162 pref_get_unescaped_pref(tmp, subject_format);
1164 subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1166 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account, FALSE,
1167 compose->gtkaspell);
1169 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account, FALSE);
1171 quote_fmt_scan_string(tmp);
1174 buf = quote_fmt_get_buffer();
1176 alertpanel_error(_("New message subject format error."));
1178 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1179 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1180 quote_fmt_reset_vartable();
1184 mfield = SUBJECT_FIELD_PRESENT;
1188 && *body_format != '\0' )
1191 GtkTextBuffer *buffer;
1192 GtkTextIter start, end;
1196 dummyinfo = compose_msginfo_new_from_compose(compose);
1198 text = GTK_TEXT_VIEW(compose->text);
1199 buffer = gtk_text_view_get_buffer(text);
1200 gtk_text_buffer_get_start_iter(buffer, &start);
1201 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
1202 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
1204 compose_quote_fmt(compose, dummyinfo,
1206 NULL, tmp, FALSE, TRUE,
1207 _("The body of the \"New message\" template has an error at line %d."));
1208 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1209 quote_fmt_reset_vartable();
1213 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1214 gtkaspell_highlight_all(compose->gtkaspell);
1216 mfield = BODY_FIELD_PRESENT;
1220 procmsg_msginfo_free( &dummyinfo );
1226 for (curr = attach_files ; curr != NULL ; curr = curr->next) {
1227 ainfo = (AttachInfo *) curr->data;
1228 compose_attach_append(compose, ainfo->file, ainfo->file,
1229 ainfo->content_type, ainfo->charset);
1233 compose_show_first_last_header(compose, TRUE);
1235 /* Set save folder */
1236 if (item && item->prefs && item->prefs->save_copy_to_folder) {
1237 gchar *folderidentifier;
1239 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
1240 folderidentifier = folder_item_get_identifier(item);
1241 compose_set_save_to(compose, folderidentifier);
1242 g_free(folderidentifier);
1245 /* Place cursor according to provided input (mfield) */
1247 case NO_FIELD_PRESENT:
1248 if (compose->header_last)
1249 gtk_widget_grab_focus(compose->header_last->entry);
1251 case TO_FIELD_PRESENT:
1252 buf = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1254 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1257 gtk_widget_grab_focus(compose->subject_entry);
1259 case SUBJECT_FIELD_PRESENT:
1260 textview = GTK_TEXT_VIEW(compose->text);
1263 textbuf = gtk_text_view_get_buffer(textview);
1266 mark = gtk_text_buffer_get_insert(textbuf);
1267 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
1268 gtk_text_buffer_insert(textbuf, &iter, "", -1);
1270 * SUBJECT_FIELD_PRESENT and BODY_FIELD_PRESENT
1271 * only defers where it comes to the variable body
1272 * is not null. If no body is present compose->text
1273 * will be null in which case you cannot place the
1274 * cursor inside the component so. An empty component
1275 * is therefore created before placing the cursor
1277 case BODY_FIELD_PRESENT:
1278 cursor_pos = quote_fmt_get_cursor_pos();
1279 if (cursor_pos == -1)
1280 gtk_widget_grab_focus(compose->header_last->entry);
1282 gtk_widget_grab_focus(compose->text);
1286 undo_unblock(compose->undostruct);
1288 if (prefs_common.auto_exteditor)
1289 compose_exec_ext_editor(compose);
1291 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
1293 SCROLL_TO_CURSOR(compose);
1295 compose->modified = FALSE;
1296 compose_set_title(compose);
1298 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
1303 static void compose_force_encryption(Compose *compose, PrefsAccount *account,
1304 gboolean override_pref, const gchar *system)
1306 const gchar *privacy = NULL;
1308 cm_return_if_fail(compose != NULL);
1309 cm_return_if_fail(account != NULL);
1311 if (override_pref == FALSE && account->default_encrypt_reply == FALSE)
1314 if (account->default_privacy_system && strlen(account->default_privacy_system))
1315 privacy = account->default_privacy_system;
1319 GSList *privacy_avail = privacy_get_system_ids();
1320 if (privacy_avail && g_slist_length(privacy_avail)) {
1321 privacy = (gchar *)(privacy_avail->data);
1324 if (privacy != NULL) {
1326 g_free(compose->privacy_system);
1327 compose->privacy_system = NULL;
1328 g_free(compose->encdata);
1329 compose->encdata = NULL;
1331 if (compose->privacy_system == NULL)
1332 compose->privacy_system = g_strdup(privacy);
1333 else if (*(compose->privacy_system) == '\0') {
1334 g_free(compose->privacy_system);
1335 g_free(compose->encdata);
1336 compose->encdata = NULL;
1337 compose->privacy_system = g_strdup(privacy);
1339 compose_update_privacy_system_menu_item(compose, FALSE);
1340 compose_use_encryption(compose, TRUE);
1344 static void compose_force_signing(Compose *compose, PrefsAccount *account, const gchar *system)
1346 const gchar *privacy = NULL;
1348 if (account->default_privacy_system && strlen(account->default_privacy_system))
1349 privacy = account->default_privacy_system;
1353 GSList *privacy_avail = privacy_get_system_ids();
1354 if (privacy_avail && g_slist_length(privacy_avail)) {
1355 privacy = (gchar *)(privacy_avail->data);
1359 if (privacy != NULL) {
1361 g_free(compose->privacy_system);
1362 compose->privacy_system = NULL;
1363 g_free(compose->encdata);
1364 compose->encdata = NULL;
1366 if (compose->privacy_system == NULL)
1367 compose->privacy_system = g_strdup(privacy);
1368 compose_update_privacy_system_menu_item(compose, FALSE);
1369 compose_use_signing(compose, TRUE);
1373 static Compose *compose_reply_mode(ComposeMode mode, GSList *msginfo_list, gchar *body)
1377 Compose *compose = NULL;
1379 cm_return_val_if_fail(msginfo_list != NULL, NULL);
1381 msginfo = (MsgInfo*)g_slist_nth_data(msginfo_list, 0);
1382 cm_return_val_if_fail(msginfo != NULL, NULL);
1384 list_len = g_slist_length(msginfo_list);
1388 case COMPOSE_REPLY_TO_ADDRESS:
1389 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1390 FALSE, prefs_common.default_reply_list, FALSE, body);
1392 case COMPOSE_REPLY_WITH_QUOTE:
1393 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1394 FALSE, prefs_common.default_reply_list, FALSE, body);
1396 case COMPOSE_REPLY_WITHOUT_QUOTE:
1397 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1398 FALSE, prefs_common.default_reply_list, FALSE, NULL);
1400 case COMPOSE_REPLY_TO_SENDER:
1401 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1402 FALSE, FALSE, TRUE, body);
1404 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1405 compose = compose_followup_and_reply_to(msginfo,
1406 COMPOSE_QUOTE_CHECK,
1407 FALSE, FALSE, body);
1409 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1410 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1411 FALSE, FALSE, TRUE, body);
1413 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1414 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1415 FALSE, FALSE, TRUE, NULL);
1417 case COMPOSE_REPLY_TO_ALL:
1418 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1419 TRUE, FALSE, FALSE, body);
1421 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1422 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1423 TRUE, FALSE, FALSE, body);
1425 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1426 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1427 TRUE, FALSE, FALSE, NULL);
1429 case COMPOSE_REPLY_TO_LIST:
1430 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1431 FALSE, TRUE, FALSE, body);
1433 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1434 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1435 FALSE, TRUE, FALSE, body);
1437 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1438 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1439 FALSE, TRUE, FALSE, NULL);
1441 case COMPOSE_FORWARD:
1442 if (prefs_common.forward_as_attachment) {
1443 compose = compose_reply_mode(COMPOSE_FORWARD_AS_ATTACH, msginfo_list, body);
1446 compose = compose_reply_mode(COMPOSE_FORWARD_INLINE, msginfo_list, body);
1450 case COMPOSE_FORWARD_INLINE:
1451 /* check if we reply to more than one Message */
1452 if (list_len == 1) {
1453 compose = compose_forward(NULL, msginfo, FALSE, body, FALSE, FALSE);
1456 /* more messages FALL THROUGH */
1457 case COMPOSE_FORWARD_AS_ATTACH:
1458 compose = compose_forward_multiple(NULL, msginfo_list);
1460 case COMPOSE_REDIRECT:
1461 compose = compose_redirect(NULL, msginfo, FALSE);
1464 g_warning("compose_reply_mode(): invalid Compose Mode: %d", mode);
1467 if (compose == NULL) {
1468 alertpanel_error(_("Unable to reply. The original email probably doesn't exist."));
1472 compose->rmode = mode;
1473 switch (compose->rmode) {
1475 case COMPOSE_REPLY_WITH_QUOTE:
1476 case COMPOSE_REPLY_WITHOUT_QUOTE:
1477 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1478 debug_print("reply mode Normal\n");
1479 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/Normal", TRUE);
1480 compose_reply_change_mode(compose, COMPOSE_REPLY); /* force update */
1482 case COMPOSE_REPLY_TO_SENDER:
1483 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1484 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1485 debug_print("reply mode Sender\n");
1486 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/Sender", TRUE);
1488 case COMPOSE_REPLY_TO_ALL:
1489 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1490 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1491 debug_print("reply mode All\n");
1492 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/All", TRUE);
1494 case COMPOSE_REPLY_TO_LIST:
1495 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1496 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1497 debug_print("reply mode List\n");
1498 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/List", TRUE);
1500 case COMPOSE_REPLY_TO_ADDRESS:
1501 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", FALSE);
1509 static Compose *compose_reply(MsgInfo *msginfo,
1510 ComposeQuoteMode quote_mode,
1516 return compose_generic_reply(msginfo, quote_mode, to_all, to_ml,
1517 to_sender, FALSE, body);
1520 static Compose *compose_followup_and_reply_to(MsgInfo *msginfo,
1521 ComposeQuoteMode quote_mode,
1526 return compose_generic_reply(msginfo, quote_mode, to_all, FALSE,
1527 to_sender, TRUE, body);
1530 static void compose_extract_original_charset(Compose *compose)
1532 MsgInfo *info = NULL;
1533 if (compose->replyinfo) {
1534 info = compose->replyinfo;
1535 } else if (compose->fwdinfo) {
1536 info = compose->fwdinfo;
1537 } else if (compose->targetinfo) {
1538 info = compose->targetinfo;
1541 MimeInfo *mimeinfo = procmime_scan_message_short(info);
1542 MimeInfo *partinfo = mimeinfo;
1543 while (partinfo && partinfo->type != MIMETYPE_TEXT)
1544 partinfo = procmime_mimeinfo_next(partinfo);
1546 compose->orig_charset =
1547 g_strdup(procmime_mimeinfo_get_parameter(
1548 partinfo, "charset"));
1550 procmime_mimeinfo_free_all(&mimeinfo);
1554 #define SIGNAL_BLOCK(buffer) { \
1555 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1556 G_CALLBACK(compose_changed_cb), \
1558 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1559 G_CALLBACK(text_inserted), \
1563 #define SIGNAL_UNBLOCK(buffer) { \
1564 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1565 G_CALLBACK(compose_changed_cb), \
1567 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1568 G_CALLBACK(text_inserted), \
1572 static Compose *compose_generic_reply(MsgInfo *msginfo,
1573 ComposeQuoteMode quote_mode,
1574 gboolean to_all, gboolean to_ml,
1576 gboolean followup_and_reply_to,
1580 PrefsAccount *account = NULL;
1581 GtkTextView *textview;
1582 GtkTextBuffer *textbuf;
1583 gboolean quote = FALSE;
1584 const gchar *qmark = NULL;
1585 const gchar *body_fmt = NULL;
1586 gchar *s_system = NULL;
1588 cm_return_val_if_fail(msginfo != NULL, NULL);
1589 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
1591 account = account_get_reply_account(msginfo, prefs_common.reply_account_autosel);
1593 cm_return_val_if_fail(account != NULL, NULL);
1595 compose = compose_create(account, msginfo->folder, COMPOSE_REPLY, FALSE);
1597 compose->updating = TRUE;
1599 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
1600 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", TRUE);
1602 compose->replyinfo = procmsg_msginfo_get_full_info(msginfo);
1603 if (!compose->replyinfo)
1604 compose->replyinfo = procmsg_msginfo_copy(msginfo);
1606 compose_extract_original_charset(compose);
1608 if (msginfo->folder && msginfo->folder->ret_rcpt)
1609 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
1611 /* Set save folder */
1612 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1613 gchar *folderidentifier;
1615 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1616 folderidentifier = folder_item_get_identifier(msginfo->folder);
1617 compose_set_save_to(compose, folderidentifier);
1618 g_free(folderidentifier);
1621 if (compose_parse_header(compose, msginfo) < 0) {
1622 compose->updating = FALSE;
1623 compose_destroy(compose);
1627 /* override from name according to folder properties */
1628 if (msginfo->folder && msginfo->folder->prefs &&
1629 msginfo->folder->prefs->reply_with_format &&
1630 msginfo->folder->prefs->reply_override_from_format &&
1631 *msginfo->folder->prefs->reply_override_from_format != '\0') {
1636 /* decode \-escape sequences in the internal representation of the quote format */
1637 tmp = g_malloc(strlen(msginfo->folder->prefs->reply_override_from_format)+1);
1638 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->reply_override_from_format);
1641 quote_fmt_init(compose->replyinfo, NULL, NULL, FALSE, compose->account, FALSE,
1642 compose->gtkaspell);
1644 quote_fmt_init(compose->replyinfo, NULL, NULL, FALSE, compose->account, FALSE);
1646 quote_fmt_scan_string(tmp);
1649 buf = quote_fmt_get_buffer();
1651 alertpanel_error(_("The \"From\" field of the \"Reply\" template contains an invalid email address."));
1653 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1654 quote_fmt_reset_vartable();
1659 textview = (GTK_TEXT_VIEW(compose->text));
1660 textbuf = gtk_text_view_get_buffer(textview);
1661 compose_create_tags(textview, compose);
1663 undo_block(compose->undostruct);
1665 compose_set_dictionaries_from_folder_prefs(compose, msginfo->folder);
1666 gtkaspell_block_check(compose->gtkaspell);
1669 if (quote_mode == COMPOSE_QUOTE_FORCED ||
1670 (quote_mode == COMPOSE_QUOTE_CHECK && prefs_common.reply_with_quote)) {
1671 /* use the reply format of folder (if enabled), or the account's one
1672 (if enabled) or fallback to the global reply format, which is always
1673 enabled (even if empty), and use the relevant quotemark */
1675 if (msginfo->folder && msginfo->folder->prefs &&
1676 msginfo->folder->prefs->reply_with_format) {
1677 qmark = msginfo->folder->prefs->reply_quotemark;
1678 body_fmt = msginfo->folder->prefs->reply_body_format;
1680 } else if (account->reply_with_format) {
1681 qmark = account->reply_quotemark;
1682 body_fmt = account->reply_body_format;
1685 qmark = prefs_common.quotemark;
1686 if (prefs_common.quotefmt && *prefs_common.quotefmt)
1687 body_fmt = gettext(prefs_common.quotefmt);
1694 /* empty quotemark is not allowed */
1695 if (qmark == NULL || *qmark == '\0')
1697 compose_quote_fmt(compose, compose->replyinfo,
1698 body_fmt, qmark, body, FALSE, TRUE,
1699 _("The body of the \"Reply\" template has an error at line %d."));
1700 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1701 quote_fmt_reset_vartable();
1704 if (MSG_IS_ENCRYPTED(compose->replyinfo->flags)) {
1705 compose_force_encryption(compose, account, FALSE, s_system);
1708 privacy_msginfo_get_signed_state(compose->replyinfo, &s_system);
1709 if (MSG_IS_SIGNED(compose->replyinfo->flags) && account->default_sign_reply) {
1710 compose_force_signing(compose, account, s_system);
1714 SIGNAL_BLOCK(textbuf);
1716 if (account->auto_sig)
1717 compose_insert_sig(compose, FALSE);
1719 compose_wrap_all(compose);
1722 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1723 gtkaspell_highlight_all(compose->gtkaspell);
1724 gtkaspell_unblock_check(compose->gtkaspell);
1726 SIGNAL_UNBLOCK(textbuf);
1728 gtk_widget_grab_focus(compose->text);
1730 undo_unblock(compose->undostruct);
1732 if (prefs_common.auto_exteditor)
1733 compose_exec_ext_editor(compose);
1735 compose->modified = FALSE;
1736 compose_set_title(compose);
1738 compose->updating = FALSE;
1739 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
1740 SCROLL_TO_CURSOR(compose);
1742 if (compose->deferred_destroy) {
1743 compose_destroy(compose);
1751 #define INSERT_FW_HEADER(var, hdr) \
1752 if (msginfo->var && *msginfo->var) { \
1753 gtk_stext_insert(text, NULL, NULL, NULL, hdr, -1); \
1754 gtk_stext_insert(text, NULL, NULL, NULL, msginfo->var, -1); \
1755 gtk_stext_insert(text, NULL, NULL, NULL, "\n", 1); \
1758 Compose *compose_forward(PrefsAccount *account, MsgInfo *msginfo,
1759 gboolean as_attach, const gchar *body,
1760 gboolean no_extedit,
1764 GtkTextView *textview;
1765 GtkTextBuffer *textbuf;
1766 gint cursor_pos = -1;
1769 cm_return_val_if_fail(msginfo != NULL, NULL);
1770 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
1772 if (!account && !(account = compose_find_account(msginfo)))
1773 account = cur_account;
1775 if (!prefs_common.forward_as_attachment)
1776 mode = COMPOSE_FORWARD_INLINE;
1778 mode = COMPOSE_FORWARD;
1779 compose = compose_create(account, msginfo->folder, mode, batch);
1781 compose->updating = TRUE;
1782 compose->fwdinfo = procmsg_msginfo_get_full_info(msginfo);
1783 if (!compose->fwdinfo)
1784 compose->fwdinfo = procmsg_msginfo_copy(msginfo);
1786 compose_extract_original_charset(compose);
1788 if (msginfo->subject && *msginfo->subject) {
1789 gchar *buf, *buf2, *p;
1791 buf = p = g_strdup(msginfo->subject);
1792 p += subject_get_prefix_length(p);
1793 memmove(buf, p, strlen(p) + 1);
1795 buf2 = g_strdup_printf("Fw: %s", buf);
1796 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
1802 /* override from name according to folder properties */
1803 if (msginfo->folder && msginfo->folder->prefs &&
1804 msginfo->folder->prefs->forward_with_format &&
1805 msginfo->folder->prefs->forward_override_from_format &&
1806 *msginfo->folder->prefs->forward_override_from_format != '\0') {
1810 MsgInfo *full_msginfo = NULL;
1813 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1815 full_msginfo = procmsg_msginfo_copy(msginfo);
1817 /* decode \-escape sequences in the internal representation of the quote format */
1818 tmp = g_malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
1819 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
1822 gtkaspell_block_check(compose->gtkaspell);
1823 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE,
1824 compose->gtkaspell);
1826 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE);
1828 quote_fmt_scan_string(tmp);
1831 buf = quote_fmt_get_buffer();
1833 alertpanel_error(_("The \"From\" field of the \"Forward\" template contains an invalid email address."));
1835 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1836 quote_fmt_reset_vartable();
1839 procmsg_msginfo_free(&full_msginfo);
1842 textview = GTK_TEXT_VIEW(compose->text);
1843 textbuf = gtk_text_view_get_buffer(textview);
1844 compose_create_tags(textview, compose);
1846 undo_block(compose->undostruct);
1850 msgfile = procmsg_get_message_file(msginfo);
1851 if (!is_file_exist(msgfile))
1852 g_warning("%s: file does not exist", msgfile);
1854 compose_attach_append(compose, msgfile, msgfile,
1855 "message/rfc822", NULL);
1859 const gchar *qmark = NULL;
1860 const gchar *body_fmt = NULL;
1861 MsgInfo *full_msginfo;
1863 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1865 full_msginfo = procmsg_msginfo_copy(msginfo);
1867 /* use the forward format of folder (if enabled), or the account's one
1868 (if enabled) or fallback to the global forward format, which is always
1869 enabled (even if empty), and use the relevant quotemark */
1870 if (msginfo->folder && msginfo->folder->prefs &&
1871 msginfo->folder->prefs->forward_with_format) {
1872 qmark = msginfo->folder->prefs->forward_quotemark;
1873 body_fmt = msginfo->folder->prefs->forward_body_format;
1875 } else if (account->forward_with_format) {
1876 qmark = account->forward_quotemark;
1877 body_fmt = account->forward_body_format;
1880 qmark = prefs_common.fw_quotemark;
1881 if (prefs_common.fw_quotefmt && *prefs_common.fw_quotefmt)
1882 body_fmt = gettext(prefs_common.fw_quotefmt);
1887 /* empty quotemark is not allowed */
1888 if (qmark == NULL || *qmark == '\0')
1891 compose_quote_fmt(compose, full_msginfo,
1892 body_fmt, qmark, body, FALSE, TRUE,
1893 _("The body of the \"Forward\" template has an error at line %d."));
1894 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1895 quote_fmt_reset_vartable();
1896 compose_attach_parts(compose, msginfo);
1898 procmsg_msginfo_free(&full_msginfo);
1901 SIGNAL_BLOCK(textbuf);
1903 if (account->auto_sig)
1904 compose_insert_sig(compose, FALSE);
1906 compose_wrap_all(compose);
1909 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1910 gtkaspell_highlight_all(compose->gtkaspell);
1911 gtkaspell_unblock_check(compose->gtkaspell);
1913 SIGNAL_UNBLOCK(textbuf);
1915 cursor_pos = quote_fmt_get_cursor_pos();
1916 if (cursor_pos == -1)
1917 gtk_widget_grab_focus(compose->header_last->entry);
1919 gtk_widget_grab_focus(compose->text);
1921 if (!no_extedit && prefs_common.auto_exteditor)
1922 compose_exec_ext_editor(compose);
1925 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1926 gchar *folderidentifier;
1928 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1929 folderidentifier = folder_item_get_identifier(msginfo->folder);
1930 compose_set_save_to(compose, folderidentifier);
1931 g_free(folderidentifier);
1934 undo_unblock(compose->undostruct);
1936 compose->modified = FALSE;
1937 compose_set_title(compose);
1939 compose->updating = FALSE;
1940 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
1941 SCROLL_TO_CURSOR(compose);
1943 if (compose->deferred_destroy) {
1944 compose_destroy(compose);
1948 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
1953 #undef INSERT_FW_HEADER
1955 static Compose *compose_forward_multiple(PrefsAccount *account, GSList *msginfo_list)
1958 GtkTextView *textview;
1959 GtkTextBuffer *textbuf;
1963 gboolean single_mail = TRUE;
1965 cm_return_val_if_fail(msginfo_list != NULL, NULL);
1967 if (g_slist_length(msginfo_list) > 1)
1968 single_mail = FALSE;
1970 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next)
1971 if (((MsgInfo *)msginfo->data)->folder == NULL)
1974 /* guess account from first selected message */
1976 !(account = compose_find_account(msginfo_list->data)))
1977 account = cur_account;
1979 cm_return_val_if_fail(account != NULL, NULL);
1981 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
1982 if (msginfo->data) {
1983 MSG_UNSET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_REPLIED);
1984 MSG_SET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_FORWARDED);
1988 if (msginfo_list == NULL || msginfo_list->data == NULL) {
1989 g_warning("no msginfo_list");
1993 compose = compose_create(account, ((MsgInfo *)msginfo_list->data)->folder, COMPOSE_FORWARD, FALSE);
1995 compose->updating = TRUE;
1997 /* override from name according to folder properties */
1998 if (msginfo_list->data) {
1999 MsgInfo *msginfo = msginfo_list->data;
2001 if (msginfo->folder && msginfo->folder->prefs &&
2002 msginfo->folder->prefs->forward_with_format &&
2003 msginfo->folder->prefs->forward_override_from_format &&
2004 *msginfo->folder->prefs->forward_override_from_format != '\0') {
2009 /* decode \-escape sequences in the internal representation of the quote format */
2010 tmp = g_malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
2011 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
2014 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
2015 compose->gtkaspell);
2017 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
2019 quote_fmt_scan_string(tmp);
2022 buf = quote_fmt_get_buffer();
2024 alertpanel_error(_("The \"From\" field of the \"Forward\" template contains an invalid email address."));
2026 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
2027 quote_fmt_reset_vartable();
2033 textview = GTK_TEXT_VIEW(compose->text);
2034 textbuf = gtk_text_view_get_buffer(textview);
2035 compose_create_tags(textview, compose);
2037 undo_block(compose->undostruct);
2038 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
2039 msgfile = procmsg_get_message_file((MsgInfo *)msginfo->data);
2041 if (!is_file_exist(msgfile))
2042 g_warning("%s: file does not exist", msgfile);
2044 compose_attach_append(compose, msgfile, msgfile,
2045 "message/rfc822", NULL);
2050 MsgInfo *info = (MsgInfo *)msginfo_list->data;
2051 if (info->subject && *info->subject) {
2052 gchar *buf, *buf2, *p;
2054 buf = p = g_strdup(info->subject);
2055 p += subject_get_prefix_length(p);
2056 memmove(buf, p, strlen(p) + 1);
2058 buf2 = g_strdup_printf("Fw: %s", buf);
2059 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
2065 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2066 _("Fw: multiple emails"));
2069 SIGNAL_BLOCK(textbuf);
2071 if (account->auto_sig)
2072 compose_insert_sig(compose, FALSE);
2074 compose_wrap_all(compose);
2076 SIGNAL_UNBLOCK(textbuf);
2078 gtk_text_buffer_get_start_iter(textbuf, &iter);
2079 gtk_text_buffer_place_cursor(textbuf, &iter);
2081 if (prefs_common.auto_exteditor)
2082 compose_exec_ext_editor(compose);
2084 gtk_widget_grab_focus(compose->header_last->entry);
2085 undo_unblock(compose->undostruct);
2086 compose->modified = FALSE;
2087 compose_set_title(compose);
2089 compose->updating = FALSE;
2090 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2091 SCROLL_TO_CURSOR(compose);
2093 if (compose->deferred_destroy) {
2094 compose_destroy(compose);
2098 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2103 static gboolean compose_is_sig_separator(Compose *compose, GtkTextBuffer *textbuf, GtkTextIter *iter)
2105 GtkTextIter start = *iter;
2106 GtkTextIter end_iter;
2107 int start_pos = gtk_text_iter_get_offset(&start);
2109 if (!compose->account->sig_sep)
2112 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
2113 start_pos+strlen(compose->account->sig_sep));
2115 /* check sig separator */
2116 str = gtk_text_iter_get_text(&start, &end_iter);
2117 if (!strcmp(str, compose->account->sig_sep)) {
2119 /* check end of line (\n) */
2120 gtk_text_buffer_get_iter_at_offset(textbuf, &start,
2121 start_pos+strlen(compose->account->sig_sep));
2122 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
2123 start_pos+strlen(compose->account->sig_sep)+1);
2124 tmp = gtk_text_iter_get_text(&start, &end_iter);
2125 if (!strcmp(tmp,"\n")) {
2137 static gboolean compose_update_folder_hook(gpointer source, gpointer data)
2139 FolderUpdateData *hookdata = (FolderUpdateData *)source;
2140 Compose *compose = (Compose *)data;
2141 FolderItem *old_item = NULL;
2142 FolderItem *new_item = NULL;
2143 gchar *old_id, *new_id;
2145 if (!(hookdata->update_flags & FOLDER_REMOVE_FOLDERITEM)
2146 && !(hookdata->update_flags & FOLDER_MOVE_FOLDERITEM))
2149 old_item = hookdata->item;
2150 new_item = hookdata->item2;
2152 old_id = folder_item_get_identifier(old_item);
2153 new_id = new_item ? folder_item_get_identifier(new_item) : g_strdup("NULL");
2155 if (compose->targetinfo && compose->targetinfo->folder == old_item) {
2156 debug_print("updating targetinfo folder: %s -> %s\n", old_id, new_id);
2157 compose->targetinfo->folder = new_item;
2160 if (compose->replyinfo && compose->replyinfo->folder == old_item) {
2161 debug_print("updating replyinfo folder: %s -> %s\n", old_id, new_id);
2162 compose->replyinfo->folder = new_item;
2165 if (compose->fwdinfo && compose->fwdinfo->folder == old_item) {
2166 debug_print("updating fwdinfo folder: %s -> %s\n", old_id, new_id);
2167 compose->fwdinfo->folder = new_item;
2175 static void compose_colorize_signature(Compose *compose)
2177 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
2179 GtkTextIter end_iter;
2180 gtk_text_buffer_get_start_iter(buffer, &iter);
2181 while (gtk_text_iter_forward_line(&iter))
2182 if (compose_is_sig_separator(compose, buffer, &iter)) {
2183 gtk_text_buffer_get_end_iter(buffer, &end_iter);
2184 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &end_iter);
2188 #define BLOCK_WRAP() { \
2189 prev_autowrap = compose->autowrap; \
2190 buffer = gtk_text_view_get_buffer( \
2191 GTK_TEXT_VIEW(compose->text)); \
2192 compose->autowrap = FALSE; \
2194 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2195 G_CALLBACK(compose_changed_cb), \
2197 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2198 G_CALLBACK(text_inserted), \
2201 #define UNBLOCK_WRAP() { \
2202 compose->autowrap = prev_autowrap; \
2203 if (compose->autowrap) { \
2204 gint old = compose->draft_timeout_tag; \
2205 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN; \
2206 compose_wrap_all(compose); \
2207 compose->draft_timeout_tag = old; \
2210 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2211 G_CALLBACK(compose_changed_cb), \
2213 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2214 G_CALLBACK(text_inserted), \
2218 Compose *compose_reedit(MsgInfo *msginfo, gboolean batch)
2220 Compose *compose = NULL;
2221 PrefsAccount *account = NULL;
2222 GtkTextView *textview;
2223 GtkTextBuffer *textbuf;
2227 gchar buf[BUFFSIZE];
2228 gboolean use_signing = FALSE;
2229 gboolean use_encryption = FALSE;
2230 gchar *privacy_system = NULL;
2231 int priority = PRIORITY_NORMAL;
2232 MsgInfo *replyinfo = NULL, *fwdinfo = NULL;
2233 gboolean autowrap = prefs_common.autowrap;
2234 gboolean autoindent = prefs_common.auto_indent;
2235 HeaderEntry *manual_headers = NULL;
2237 cm_return_val_if_fail(msginfo != NULL, NULL);
2238 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
2240 if (compose_put_existing_to_front(msginfo)) {
2244 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2245 folder_has_parent_of_type(msginfo->folder, F_DRAFT) ||
2246 folder_has_parent_of_type(msginfo->folder, F_OUTBOX)) {
2247 gchar queueheader_buf[BUFFSIZE];
2250 /* Select Account from queue headers */
2251 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2252 sizeof(queueheader_buf), "X-Claws-Account-Id:")) {
2253 id = atoi(&queueheader_buf[strlen("X-Claws-Account-Id:")]);
2254 account = account_find_from_id(id);
2256 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2257 sizeof(queueheader_buf), "X-Sylpheed-Account-Id:")) {
2258 id = atoi(&queueheader_buf[strlen("X-Sylpheed-Account-Id:")]);
2259 account = account_find_from_id(id);
2261 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2262 sizeof(queueheader_buf), "NAID:")) {
2263 id = atoi(&queueheader_buf[strlen("NAID:")]);
2264 account = account_find_from_id(id);
2266 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2267 sizeof(queueheader_buf), "MAID:")) {
2268 id = atoi(&queueheader_buf[strlen("MAID:")]);
2269 account = account_find_from_id(id);
2271 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2272 sizeof(queueheader_buf), "S:")) {
2273 account = account_find_from_address(queueheader_buf, FALSE);
2275 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2276 sizeof(queueheader_buf), "X-Claws-Sign:")) {
2277 param = atoi(&queueheader_buf[strlen("X-Claws-Sign:")]);
2278 use_signing = param;
2281 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2282 sizeof(queueheader_buf), "X-Sylpheed-Sign:")) {
2283 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Sign:")]);
2284 use_signing = param;
2287 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2288 sizeof(queueheader_buf), "X-Claws-Encrypt:")) {
2289 param = atoi(&queueheader_buf[strlen("X-Claws-Encrypt:")]);
2290 use_encryption = param;
2292 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2293 sizeof(queueheader_buf), "X-Sylpheed-Encrypt:")) {
2294 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Encrypt:")]);
2295 use_encryption = param;
2297 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2298 sizeof(queueheader_buf), "X-Claws-Auto-Wrapping:")) {
2299 param = atoi(&queueheader_buf[strlen("X-Claws-Auto-Wrapping:")]);
2302 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2303 sizeof(queueheader_buf), "X-Claws-Auto-Indent:")) {
2304 param = atoi(&queueheader_buf[strlen("X-Claws-Auto-Indent:")]);
2307 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2308 sizeof(queueheader_buf), "X-Claws-Privacy-System:")) {
2309 privacy_system = g_strdup(&queueheader_buf[strlen("X-Claws-Privacy-System:")]);
2311 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2312 sizeof(queueheader_buf), "X-Sylpheed-Privacy-System:")) {
2313 privacy_system = g_strdup(&queueheader_buf[strlen("X-Sylpheed-Privacy-System:")]);
2315 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2316 sizeof(queueheader_buf), "X-Priority: ")) {
2317 param = atoi(&queueheader_buf[strlen("X-Priority: ")]); /* mind the space */
2320 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2321 sizeof(queueheader_buf), "RMID:")) {
2322 gchar **tokens = g_strsplit(&queueheader_buf[strlen("RMID:")], "\t", 0);
2323 if (tokens[0] && tokens[1] && tokens[2]) {
2324 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2325 if (orig_item != NULL) {
2326 replyinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2331 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2332 sizeof(queueheader_buf), "FMID:")) {
2333 gchar **tokens = g_strsplit(&queueheader_buf[strlen("FMID:")], "\t", 0);
2334 if (tokens[0] && tokens[1] && tokens[2]) {
2335 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2336 if (orig_item != NULL) {
2337 fwdinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2342 /* Get manual headers */
2343 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "X-Claws-Manual-Headers:")) {
2344 gchar *listmh = g_strdup(&queueheader_buf[strlen("X-Claws-Manual-Headers:")]);
2345 if (*listmh != '\0') {
2346 debug_print("Got manual headers: %s\n", listmh);
2347 manual_headers = procheader_entries_from_str(listmh);
2352 account = msginfo->folder->folder->account;
2355 if (!account && prefs_common.reedit_account_autosel) {
2356 gchar from[BUFFSIZE];
2357 if (!procheader_get_header_from_msginfo(msginfo, from, sizeof(from), "FROM:")) {
2358 extract_address(from);
2359 account = account_find_from_address(from, FALSE);
2363 account = cur_account;
2365 cm_return_val_if_fail(account != NULL, NULL);
2367 compose = compose_create(account, msginfo->folder, COMPOSE_REEDIT, batch);
2369 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", autowrap);
2370 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", autoindent);
2371 compose->autowrap = autowrap;
2372 compose->replyinfo = replyinfo;
2373 compose->fwdinfo = fwdinfo;
2375 compose->updating = TRUE;
2376 compose->priority = priority;
2378 if (privacy_system != NULL) {
2379 compose->privacy_system = privacy_system;
2380 compose_use_signing(compose, use_signing);
2381 compose_use_encryption(compose, use_encryption);
2382 compose_update_privacy_system_menu_item(compose, FALSE);
2384 activate_privacy_system(compose, account, FALSE);
2387 compose->targetinfo = procmsg_msginfo_copy(msginfo);
2389 compose_extract_original_charset(compose);
2391 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2392 folder_has_parent_of_type(msginfo->folder, F_DRAFT) ||
2393 folder_has_parent_of_type(msginfo->folder, F_OUTBOX)) {
2394 gchar queueheader_buf[BUFFSIZE];
2396 /* Set message save folder */
2397 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "SCF:")) {
2398 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
2399 compose_set_save_to(compose, &queueheader_buf[4]);
2401 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "RRCPT:")) {
2402 gint active = atoi(&queueheader_buf[strlen("RRCPT:")]);
2404 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
2409 if (compose_parse_header(compose, msginfo) < 0) {
2410 compose->updating = FALSE;
2411 compose_destroy(compose);
2414 compose_reedit_set_entry(compose, msginfo);
2416 textview = GTK_TEXT_VIEW(compose->text);
2417 textbuf = gtk_text_view_get_buffer(textview);
2418 compose_create_tags(textview, compose);
2420 mark = gtk_text_buffer_get_insert(textbuf);
2421 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
2423 g_signal_handlers_block_by_func(G_OBJECT(textbuf),
2424 G_CALLBACK(compose_changed_cb),
2427 if (MSG_IS_ENCRYPTED(msginfo->flags)) {
2428 fp = procmime_get_first_encrypted_text_content(msginfo);
2430 compose_force_encryption(compose, account, TRUE, NULL);
2433 fp = procmime_get_first_text_content(msginfo);
2436 g_warning("Can't get text part");
2440 gboolean prev_autowrap;
2441 GtkTextBuffer *buffer;
2443 while (fgets(buf, sizeof(buf), fp) != NULL) {
2445 gtk_text_buffer_insert(textbuf, &iter, buf, -1);
2451 compose_attach_parts(compose, msginfo);
2453 compose_colorize_signature(compose);
2455 g_signal_handlers_unblock_by_func(G_OBJECT(textbuf),
2456 G_CALLBACK(compose_changed_cb),
2459 if (manual_headers != NULL) {
2460 if (compose_parse_manual_headers(compose, msginfo, manual_headers) < 0) {
2461 procheader_entries_free(manual_headers);
2462 compose->updating = FALSE;
2463 compose_destroy(compose);
2466 procheader_entries_free(manual_headers);
2469 gtk_widget_grab_focus(compose->text);
2471 if (prefs_common.auto_exteditor) {
2472 compose_exec_ext_editor(compose);
2474 compose->modified = FALSE;
2475 compose_set_title(compose);
2477 compose->updating = FALSE;
2478 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2479 SCROLL_TO_CURSOR(compose);
2481 if (compose->deferred_destroy) {
2482 compose_destroy(compose);
2486 compose->sig_str = account_get_signature_str(compose->account);
2488 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2493 Compose *compose_redirect(PrefsAccount *account, MsgInfo *msginfo,
2500 cm_return_val_if_fail(msginfo != NULL, NULL);
2503 account = account_get_reply_account(msginfo,
2504 prefs_common.reply_account_autosel);
2505 cm_return_val_if_fail(account != NULL, NULL);
2507 compose = compose_create(account, msginfo->folder, COMPOSE_REDIRECT, batch);
2509 compose->updating = TRUE;
2511 compose_create_tags(GTK_TEXT_VIEW(compose->text), compose);
2512 compose->replyinfo = NULL;
2513 compose->fwdinfo = NULL;
2515 compose_show_first_last_header(compose, TRUE);
2517 gtk_widget_grab_focus(compose->header_last->entry);
2519 filename = procmsg_get_message_file(msginfo);
2521 if (filename == NULL) {
2522 compose->updating = FALSE;
2523 compose_destroy(compose);
2528 compose->redirect_filename = filename;
2530 /* Set save folder */
2531 item = msginfo->folder;
2532 if (item && item->prefs && item->prefs->save_copy_to_folder) {
2533 gchar *folderidentifier;
2535 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
2536 folderidentifier = folder_item_get_identifier(item);
2537 compose_set_save_to(compose, folderidentifier);
2538 g_free(folderidentifier);
2541 compose_attach_parts(compose, msginfo);
2543 if (msginfo->subject)
2544 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2546 gtk_editable_set_editable(GTK_EDITABLE(compose->subject_entry), FALSE);
2548 compose_quote_fmt(compose, msginfo, "%M", NULL, NULL, FALSE, FALSE,
2549 _("The body of the \"Redirect\" template has an error at line %d."));
2550 quote_fmt_reset_vartable();
2551 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), FALSE);
2553 compose_colorize_signature(compose);
2556 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Add", FALSE);
2557 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", FALSE);
2558 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", FALSE);
2560 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/Save", FALSE);
2561 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertFile", FALSE);
2562 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/AttachFile", FALSE);
2563 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertSig", FALSE);
2564 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/ReplaceSig", FALSE);
2565 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", FALSE);
2566 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", FALSE);
2567 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/ShowRuler", FALSE);
2568 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Actions", FALSE);
2570 if (compose->toolbar->draft_btn)
2571 gtk_widget_set_sensitive(compose->toolbar->draft_btn, FALSE);
2572 if (compose->toolbar->insert_btn)
2573 gtk_widget_set_sensitive(compose->toolbar->insert_btn, FALSE);
2574 if (compose->toolbar->attach_btn)
2575 gtk_widget_set_sensitive(compose->toolbar->attach_btn, FALSE);
2576 if (compose->toolbar->sig_btn)
2577 gtk_widget_set_sensitive(compose->toolbar->sig_btn, FALSE);
2578 if (compose->toolbar->exteditor_btn)
2579 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, FALSE);
2580 if (compose->toolbar->linewrap_current_btn)
2581 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, FALSE);
2582 if (compose->toolbar->linewrap_all_btn)
2583 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, FALSE);
2585 compose->modified = FALSE;
2586 compose_set_title(compose);
2587 compose->updating = FALSE;
2588 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2589 SCROLL_TO_CURSOR(compose);
2591 if (compose->deferred_destroy) {
2592 compose_destroy(compose);
2596 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2601 const GList *compose_get_compose_list(void)
2603 return compose_list;
2606 void compose_entry_append(Compose *compose, const gchar *address,
2607 ComposeEntryType type, ComposePrefType pref_type)
2609 const gchar *header;
2611 gboolean in_quote = FALSE;
2612 if (!address || *address == '\0') return;
2619 header = N_("Bcc:");
2621 case COMPOSE_REPLYTO:
2622 header = N_("Reply-To:");
2624 case COMPOSE_NEWSGROUPS:
2625 header = N_("Newsgroups:");
2627 case COMPOSE_FOLLOWUPTO:
2628 header = N_( "Followup-To:");
2630 case COMPOSE_INREPLYTO:
2631 header = N_( "In-Reply-To:");
2638 header = prefs_common_translated_header_name(header);
2640 cur = begin = (gchar *)address;
2642 /* we separate the line by commas, but not if we're inside a quoted
2644 while (*cur != '\0') {
2646 in_quote = !in_quote;
2647 if (*cur == ',' && !in_quote) {
2648 gchar *tmp = g_strdup(begin);
2650 tmp[cur-begin]='\0';
2653 while (*tmp == ' ' || *tmp == '\t')
2655 compose_add_header_entry(compose, header, tmp, pref_type);
2656 compose_entry_indicate(compose, tmp);
2663 gchar *tmp = g_strdup(begin);
2665 tmp[cur-begin]='\0';
2666 while (*tmp == ' ' || *tmp == '\t')
2668 compose_add_header_entry(compose, header, tmp, pref_type);
2669 compose_entry_indicate(compose, tmp);
2674 static void compose_entry_indicate(Compose *compose, const gchar *mailto)
2679 for (h_list = compose->header_list; h_list != NULL; h_list = h_list->next) {
2680 entry = GTK_ENTRY(((ComposeHeaderEntry *)h_list->data)->entry);
2681 if (gtk_entry_get_text(entry) &&
2682 !g_utf8_collate(gtk_entry_get_text(entry), mailto)) {
2683 gtk_widget_modify_base(
2684 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2685 GTK_STATE_NORMAL, &default_header_bgcolor);
2686 gtk_widget_modify_text(
2687 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2688 GTK_STATE_NORMAL, &default_header_color);
2693 void compose_toolbar_cb(gint action, gpointer data)
2695 ToolbarItem *toolbar_item = (ToolbarItem*)data;
2696 Compose *compose = (Compose*)toolbar_item->parent;
2698 cm_return_if_fail(compose != NULL);
2702 compose_send_cb(NULL, compose);
2705 compose_send_later_cb(NULL, compose);
2708 compose_draft(compose, COMPOSE_QUIT_EDITING);
2711 compose_insert_file_cb(NULL, compose);
2714 compose_attach_cb(NULL, compose);
2717 compose_insert_sig(compose, FALSE);
2720 compose_insert_sig(compose, TRUE);
2723 compose_ext_editor_cb(NULL, compose);
2725 case A_LINEWRAP_CURRENT:
2726 compose_beautify_paragraph(compose, NULL, TRUE);
2728 case A_LINEWRAP_ALL:
2729 compose_wrap_all_full(compose, TRUE);
2732 compose_address_cb(NULL, compose);
2735 case A_CHECK_SPELLING:
2736 compose_check_all(NULL, compose);
2744 static MailField compose_entries_set(Compose *compose, const gchar *mailto, ComposeEntryType to_type)
2749 gchar *subject = NULL;
2753 gchar **attach = NULL;
2754 gchar *inreplyto = NULL;
2755 MailField mfield = NO_FIELD_PRESENT;
2757 /* get mailto parts but skip from */
2758 scan_mailto_url(mailto, NULL, &to, &cc, &bcc, &subject, &body, &attach, &inreplyto);
2761 compose_entry_append(compose, to, to_type, PREF_MAILTO);
2762 mfield = TO_FIELD_PRESENT;
2765 compose_entry_append(compose, cc, COMPOSE_CC, PREF_MAILTO);
2767 compose_entry_append(compose, bcc, COMPOSE_BCC, PREF_MAILTO);
2769 if (!g_utf8_validate (subject, -1, NULL)) {
2770 temp = g_locale_to_utf8 (subject, -1, NULL, &len, NULL);
2771 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), temp);
2774 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), subject);
2776 mfield = SUBJECT_FIELD_PRESENT;
2779 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2780 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2783 gboolean prev_autowrap = compose->autowrap;
2785 compose->autowrap = FALSE;
2787 mark = gtk_text_buffer_get_insert(buffer);
2788 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2790 if (!g_utf8_validate (body, -1, NULL)) {
2791 temp = g_locale_to_utf8 (body, -1, NULL, &len, NULL);
2792 gtk_text_buffer_insert(buffer, &iter, temp, -1);
2795 gtk_text_buffer_insert(buffer, &iter, body, -1);
2797 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
2799 compose->autowrap = prev_autowrap;
2800 if (compose->autowrap)
2801 compose_wrap_all(compose);
2802 mfield = BODY_FIELD_PRESENT;
2806 gint i = 0, att = 0;
2807 gchar *warn_files = NULL;
2808 while (attach[i] != NULL) {
2809 gchar *utf8_filename = conv_filename_to_utf8(attach[i]);
2810 if (utf8_filename) {
2811 if (compose_attach_append(compose, attach[i], utf8_filename, NULL, NULL)) {
2812 gchar *tmp = g_strdup_printf("%s%s\n",
2813 warn_files?warn_files:"",
2819 g_free(utf8_filename);
2821 alertpanel_error(_("Couldn't attach a file (charset conversion failed)."));
2826 alertpanel_notice(ngettext(
2827 "The following file has been attached: \n%s",
2828 "The following files have been attached: \n%s", att), warn_files);
2833 compose_entry_append(compose, inreplyto, COMPOSE_INREPLYTO, PREF_MAILTO);
2846 static gint compose_parse_header(Compose *compose, MsgInfo *msginfo)
2848 static HeaderEntry hentry[] = {
2849 {"Reply-To:", NULL, TRUE },
2850 {"Cc:", NULL, TRUE },
2851 {"References:", NULL, FALSE },
2852 {"Bcc:", NULL, TRUE },
2853 {"Newsgroups:", NULL, TRUE },
2854 {"Followup-To:", NULL, TRUE },
2855 {"List-Post:", NULL, FALSE },
2856 {"X-Priority:", NULL, FALSE },
2857 {NULL, NULL, FALSE }
2874 cm_return_val_if_fail(msginfo != NULL, -1);
2876 if ((fp = procmsg_open_message(msginfo)) == NULL) return -1;
2877 procheader_get_header_fields(fp, hentry);
2880 if (hentry[H_REPLY_TO].body != NULL) {
2881 if (hentry[H_REPLY_TO].body[0] != '\0') {
2883 conv_unmime_header(hentry[H_REPLY_TO].body,
2886 g_free(hentry[H_REPLY_TO].body);
2887 hentry[H_REPLY_TO].body = NULL;
2889 if (hentry[H_CC].body != NULL) {
2890 compose->cc = conv_unmime_header(hentry[H_CC].body, NULL, TRUE);
2891 g_free(hentry[H_CC].body);
2892 hentry[H_CC].body = NULL;
2894 if (hentry[H_REFERENCES].body != NULL) {
2895 if (compose->mode == COMPOSE_REEDIT)
2896 compose->references = hentry[H_REFERENCES].body;
2898 compose->references = compose_parse_references
2899 (hentry[H_REFERENCES].body, msginfo->msgid);
2900 g_free(hentry[H_REFERENCES].body);
2902 hentry[H_REFERENCES].body = NULL;
2904 if (hentry[H_BCC].body != NULL) {
2905 if (compose->mode == COMPOSE_REEDIT)
2907 conv_unmime_header(hentry[H_BCC].body, NULL, TRUE);
2908 g_free(hentry[H_BCC].body);
2909 hentry[H_BCC].body = NULL;
2911 if (hentry[H_NEWSGROUPS].body != NULL) {
2912 compose->newsgroups = hentry[H_NEWSGROUPS].body;
2913 hentry[H_NEWSGROUPS].body = NULL;
2915 if (hentry[H_FOLLOWUP_TO].body != NULL) {
2916 if (hentry[H_FOLLOWUP_TO].body[0] != '\0') {
2917 compose->followup_to =
2918 conv_unmime_header(hentry[H_FOLLOWUP_TO].body,
2921 g_free(hentry[H_FOLLOWUP_TO].body);
2922 hentry[H_FOLLOWUP_TO].body = NULL;
2924 if (hentry[H_LIST_POST].body != NULL) {
2925 gchar *to = NULL, *start = NULL;
2927 extract_address(hentry[H_LIST_POST].body);
2928 if (hentry[H_LIST_POST].body[0] != '\0') {
2929 start = strstr(hentry[H_LIST_POST].body, "mailto:");
2931 scan_mailto_url(start ? start : hentry[H_LIST_POST].body,
2932 NULL, &to, NULL, NULL, NULL, NULL, NULL, NULL);
2935 g_free(compose->ml_post);
2936 compose->ml_post = to;
2939 g_free(hentry[H_LIST_POST].body);
2940 hentry[H_LIST_POST].body = NULL;
2943 /* CLAWS - X-Priority */
2944 if (compose->mode == COMPOSE_REEDIT)
2945 if (hentry[H_X_PRIORITY].body != NULL) {
2948 priority = atoi(hentry[H_X_PRIORITY].body);
2949 g_free(hentry[H_X_PRIORITY].body);
2951 hentry[H_X_PRIORITY].body = NULL;
2953 if (priority < PRIORITY_HIGHEST ||
2954 priority > PRIORITY_LOWEST)
2955 priority = PRIORITY_NORMAL;
2957 compose->priority = priority;
2960 if (compose->mode == COMPOSE_REEDIT) {
2961 if (msginfo->inreplyto && *msginfo->inreplyto)
2962 compose->inreplyto = g_strdup(msginfo->inreplyto);
2964 if (msginfo->msgid && *msginfo->msgid)
2965 compose->msgid = g_strdup(msginfo->msgid);
2967 if (msginfo->msgid && *msginfo->msgid)
2968 compose->inreplyto = g_strdup(msginfo->msgid);
2970 if (!compose->references) {
2971 if (msginfo->msgid && *msginfo->msgid) {
2972 if (msginfo->inreplyto && *msginfo->inreplyto)
2973 compose->references =
2974 g_strdup_printf("<%s>\n\t<%s>",
2978 compose->references =
2979 g_strconcat("<", msginfo->msgid, ">",
2981 } else if (msginfo->inreplyto && *msginfo->inreplyto) {
2982 compose->references =
2983 g_strconcat("<", msginfo->inreplyto, ">",
2992 static gint compose_parse_manual_headers(Compose *compose, MsgInfo *msginfo, HeaderEntry *entries)
2997 cm_return_val_if_fail(msginfo != NULL, -1);
2999 if ((fp = procmsg_open_message(msginfo)) == NULL) return -1;
3000 procheader_get_header_fields(fp, entries);
3004 while (he != NULL && he->name != NULL) {
3006 GtkListStore *model = NULL;
3008 debug_print("Adding manual header: %s with value %s\n", he->name, he->body);
3009 model = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(compose->header_last->combo)));
3010 COMBOBOX_ADD(model, he->name, COMPOSE_TO);
3011 gtk_combo_box_set_active_iter(GTK_COMBO_BOX(compose->header_last->combo), &iter);
3012 gtk_entry_set_text(GTK_ENTRY(compose->header_last->entry), he->body);
3019 static gchar *compose_parse_references(const gchar *ref, const gchar *msgid)
3021 GSList *ref_id_list, *cur;
3025 ref_id_list = references_list_append(NULL, ref);
3026 if (!ref_id_list) return NULL;
3027 if (msgid && *msgid)
3028 ref_id_list = g_slist_append(ref_id_list, g_strdup(msgid));
3033 for (cur = ref_id_list; cur != NULL; cur = cur->next)
3034 /* "<" + Message-ID + ">" + CR+LF+TAB */
3035 len += strlen((gchar *)cur->data) + 5;
3037 if (len > MAX_REFERENCES_LEN) {
3038 /* remove second message-ID */
3039 if (ref_id_list && ref_id_list->next &&
3040 ref_id_list->next->next) {
3041 g_free(ref_id_list->next->data);
3042 ref_id_list = g_slist_remove
3043 (ref_id_list, ref_id_list->next->data);
3045 slist_free_strings_full(ref_id_list);
3052 new_ref = g_string_new("");
3053 for (cur = ref_id_list; cur != NULL; cur = cur->next) {
3054 if (new_ref->len > 0)
3055 g_string_append(new_ref, "\n\t");
3056 g_string_append_printf(new_ref, "<%s>", (gchar *)cur->data);
3059 slist_free_strings_full(ref_id_list);
3061 new_ref_str = new_ref->str;
3062 g_string_free(new_ref, FALSE);
3067 static gchar *compose_quote_fmt(Compose *compose, MsgInfo *msginfo,
3068 const gchar *fmt, const gchar *qmark,
3069 const gchar *body, gboolean rewrap,
3070 gboolean need_unescape,
3071 const gchar *err_msg)
3073 MsgInfo* dummyinfo = NULL;
3074 gchar *quote_str = NULL;
3076 gboolean prev_autowrap;
3077 const gchar *trimmed_body = body;
3078 gint cursor_pos = -1;
3079 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3080 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3085 SIGNAL_BLOCK(buffer);
3088 dummyinfo = compose_msginfo_new_from_compose(compose);
3089 msginfo = dummyinfo;
3092 if (qmark != NULL) {
3094 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
3095 compose->gtkaspell);
3097 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
3099 quote_fmt_scan_string(qmark);
3102 buf = quote_fmt_get_buffer();
3104 alertpanel_error(_("The \"Quotation mark\" of the template is invalid."));
3106 Xstrdup_a(quote_str, buf, goto error)
3109 if (fmt && *fmt != '\0') {
3112 while (*trimmed_body == '\n')
3116 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE,
3117 compose->gtkaspell);
3119 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE);
3121 if (need_unescape) {
3124 /* decode \-escape sequences in the internal representation of the quote format */
3125 tmp = g_malloc(strlen(fmt)+1);
3126 pref_get_unescaped_pref(tmp, fmt);
3127 quote_fmt_scan_string(tmp);
3131 quote_fmt_scan_string(fmt);
3135 buf = quote_fmt_get_buffer();
3137 gint line = quote_fmt_get_line();
3138 alertpanel_error(err_msg, line);
3144 prev_autowrap = compose->autowrap;
3145 compose->autowrap = FALSE;
3147 mark = gtk_text_buffer_get_insert(buffer);
3148 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3149 if (g_utf8_validate(buf, -1, NULL)) {
3150 gtk_text_buffer_insert(buffer, &iter, buf, -1);
3152 gchar *tmpout = NULL;
3153 tmpout = conv_codeset_strdup
3154 (buf, conv_get_locale_charset_str_no_utf8(),
3156 if (!tmpout || !g_utf8_validate(tmpout, -1, NULL)) {
3158 tmpout = g_malloc(strlen(buf)*2+1);
3159 conv_localetodisp(tmpout, strlen(buf)*2+1, buf);
3161 gtk_text_buffer_insert(buffer, &iter, tmpout, -1);
3165 cursor_pos = quote_fmt_get_cursor_pos();
3166 if (cursor_pos == -1)
3167 cursor_pos = gtk_text_iter_get_offset(&iter);
3168 compose->set_cursor_pos = cursor_pos;
3170 gtk_text_buffer_get_start_iter(buffer, &iter);
3171 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
3172 gtk_text_buffer_place_cursor(buffer, &iter);
3174 compose->autowrap = prev_autowrap;
3175 if (compose->autowrap && rewrap)
3176 compose_wrap_all(compose);
3183 SIGNAL_UNBLOCK(buffer);
3185 procmsg_msginfo_free( &dummyinfo );
3190 /* if ml_post is of type addr@host and from is of type
3191 * addr-anything@host, return TRUE
3193 static gboolean is_subscription(const gchar *ml_post, const gchar *from)
3195 gchar *left_ml = NULL;
3196 gchar *right_ml = NULL;
3197 gchar *left_from = NULL;
3198 gchar *right_from = NULL;
3199 gboolean result = FALSE;
3201 if (!ml_post || !from)
3204 left_ml = g_strdup(ml_post);
3205 if (strstr(left_ml, "@")) {
3206 right_ml = strstr(left_ml, "@")+1;
3207 *(strstr(left_ml, "@")) = '\0';
3210 left_from = g_strdup(from);
3211 if (strstr(left_from, "@")) {
3212 right_from = strstr(left_from, "@")+1;
3213 *(strstr(left_from, "@")) = '\0';
3216 if (right_ml && right_from
3217 && !strncmp(left_from, left_ml, strlen(left_ml))
3218 && !strcmp(right_from, right_ml)) {
3227 static void compose_set_folder_prefs(Compose *compose, FolderItem *folder,
3228 gboolean respect_default_to)
3232 if (!folder || !folder->prefs)
3235 if (respect_default_to && folder->prefs->enable_default_to) {
3236 compose_entry_append(compose, folder->prefs->default_to,
3237 COMPOSE_TO, PREF_FOLDER);
3238 compose_entry_indicate(compose, folder->prefs->default_to);
3240 if (folder->prefs->enable_default_cc) {
3241 compose_entry_append(compose, folder->prefs->default_cc,
3242 COMPOSE_CC, PREF_FOLDER);
3243 compose_entry_indicate(compose, folder->prefs->default_cc);
3245 if (folder->prefs->enable_default_bcc) {
3246 compose_entry_append(compose, folder->prefs->default_bcc,
3247 COMPOSE_BCC, PREF_FOLDER);
3248 compose_entry_indicate(compose, folder->prefs->default_bcc);
3250 if (folder->prefs->enable_default_replyto) {
3251 compose_entry_append(compose, folder->prefs->default_replyto,
3252 COMPOSE_REPLYTO, PREF_FOLDER);
3253 compose_entry_indicate(compose, folder->prefs->default_replyto);
3257 static void compose_reply_set_subject(Compose *compose, MsgInfo *msginfo)
3262 if (!compose || !msginfo)
3265 if (msginfo->subject && *msginfo->subject) {
3266 buf = p = g_strdup(msginfo->subject);
3267 p += subject_get_prefix_length(p);
3268 memmove(buf, p, strlen(p) + 1);
3270 buf2 = g_strdup_printf("Re: %s", buf);
3271 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
3276 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), "Re: ");
3279 static void compose_reply_set_entry(Compose *compose, MsgInfo *msginfo,
3280 gboolean to_all, gboolean to_ml,
3282 gboolean followup_and_reply_to)
3284 GSList *cc_list = NULL;
3287 gchar *replyto = NULL;
3288 gchar *ac_email = NULL;
3290 gboolean reply_to_ml = FALSE;
3291 gboolean default_reply_to = FALSE;
3293 cm_return_if_fail(compose->account != NULL);
3294 cm_return_if_fail(msginfo != NULL);
3296 reply_to_ml = to_ml && compose->ml_post;
3298 default_reply_to = msginfo->folder &&
3299 msginfo->folder->prefs->enable_default_reply_to;
3301 if (compose->account->protocol != A_NNTP) {
3302 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
3304 if (reply_to_ml && !default_reply_to) {
3306 gboolean is_subscr = is_subscription(compose->ml_post,
3309 /* normal answer to ml post with a reply-to */
3310 compose_entry_append(compose,
3312 COMPOSE_TO, PREF_ML);
3313 if (compose->replyto)
3314 compose_entry_append(compose,
3316 COMPOSE_CC, PREF_ML);
3318 /* answer to subscription confirmation */
3319 if (compose->replyto)
3320 compose_entry_append(compose,
3322 COMPOSE_TO, PREF_ML);
3323 else if (msginfo->from)
3324 compose_entry_append(compose,
3326 COMPOSE_TO, PREF_ML);
3329 else if (!(to_all || to_sender) && default_reply_to) {
3330 compose_entry_append(compose,
3331 msginfo->folder->prefs->default_reply_to,
3332 COMPOSE_TO, PREF_FOLDER);
3333 compose_entry_indicate(compose,
3334 msginfo->folder->prefs->default_reply_to);
3340 compose_entry_append(compose, msginfo->from,
3341 COMPOSE_TO, PREF_NONE);
3343 Xstrdup_a(tmp1, msginfo->from, return);
3344 extract_address(tmp1);
3345 compose_entry_append(compose,
3346 (!account_find_from_address(tmp1, FALSE))
3349 COMPOSE_TO, PREF_NONE);
3350 if (compose->replyto)
3351 compose_entry_append(compose,
3353 COMPOSE_CC, PREF_NONE);
3355 if (!folder_has_parent_of_type(msginfo->folder, F_QUEUE) &&
3356 !folder_has_parent_of_type(msginfo->folder, F_OUTBOX) &&
3357 !folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
3358 if (compose->replyto) {
3359 compose_entry_append(compose,
3361 COMPOSE_TO, PREF_NONE);
3363 compose_entry_append(compose,
3364 msginfo->from ? msginfo->from : "",
3365 COMPOSE_TO, PREF_NONE);
3368 /* replying to own mail, use original recp */
3369 compose_entry_append(compose,
3370 msginfo->to ? msginfo->to : "",
3371 COMPOSE_TO, PREF_NONE);
3372 compose_entry_append(compose,
3373 msginfo->cc ? msginfo->cc : "",
3374 COMPOSE_CC, PREF_NONE);
3379 if (to_sender || (compose->followup_to &&
3380 !strncmp(compose->followup_to, "poster", 6)))
3381 compose_entry_append
3383 (compose->replyto ? compose->replyto :
3384 msginfo->from ? msginfo->from : ""),
3385 COMPOSE_TO, PREF_NONE);
3387 else if (followup_and_reply_to || to_all) {
3388 compose_entry_append
3390 (compose->replyto ? compose->replyto :
3391 msginfo->from ? msginfo->from : ""),
3392 COMPOSE_TO, PREF_NONE);
3394 compose_entry_append
3396 compose->followup_to ? compose->followup_to :
3397 compose->newsgroups ? compose->newsgroups : "",
3398 COMPOSE_NEWSGROUPS, PREF_NONE);
3400 compose_entry_append
3402 msginfo->cc ? msginfo->cc : "",
3403 COMPOSE_CC, PREF_NONE);
3406 compose_entry_append
3408 compose->followup_to ? compose->followup_to :
3409 compose->newsgroups ? compose->newsgroups : "",
3410 COMPOSE_NEWSGROUPS, PREF_NONE);
3412 compose_reply_set_subject(compose, msginfo);
3414 if (to_ml && compose->ml_post) return;
3415 if (!to_all || compose->account->protocol == A_NNTP) return;
3417 if (compose->replyto) {
3418 Xstrdup_a(replyto, compose->replyto, return);
3419 extract_address(replyto);
3421 if (msginfo->from) {
3422 Xstrdup_a(from, msginfo->from, return);
3423 extract_address(from);
3426 if (replyto && from)
3427 cc_list = address_list_append_with_comments(cc_list, from);
3428 if (to_all && msginfo->folder &&
3429 msginfo->folder->prefs->enable_default_reply_to)
3430 cc_list = address_list_append_with_comments(cc_list,
3431 msginfo->folder->prefs->default_reply_to);
3432 cc_list = address_list_append_with_comments(cc_list, msginfo->to);
3433 cc_list = address_list_append_with_comments(cc_list, compose->cc);
3435 ac_email = g_utf8_strdown(compose->account->address, -1);
3438 for (cur = cc_list; cur != NULL; cur = cur->next) {
3439 gchar *addr = g_utf8_strdown(cur->data, -1);
3440 extract_address(addr);
3442 if (strcmp(ac_email, addr))
3443 compose_entry_append(compose, (gchar *)cur->data,
3444 COMPOSE_CC, PREF_NONE);
3446 debug_print("Cc address same as compose account's, ignoring\n");
3451 slist_free_strings_full(cc_list);
3457 #define SET_ENTRY(entry, str) \
3460 gtk_entry_set_text(GTK_ENTRY(compose->entry), str); \
3463 #define SET_ADDRESS(type, str) \
3466 compose_entry_append(compose, str, type, PREF_NONE); \
3469 static void compose_reedit_set_entry(Compose *compose, MsgInfo *msginfo)
3471 cm_return_if_fail(msginfo != NULL);
3473 SET_ENTRY(subject_entry, msginfo->subject);
3474 SET_ENTRY(from_name, msginfo->from);
3475 SET_ADDRESS(COMPOSE_TO, msginfo->to);
3476 SET_ADDRESS(COMPOSE_CC, compose->cc);
3477 SET_ADDRESS(COMPOSE_BCC, compose->bcc);
3478 SET_ADDRESS(COMPOSE_REPLYTO, compose->replyto);
3479 SET_ADDRESS(COMPOSE_NEWSGROUPS, compose->newsgroups);
3480 SET_ADDRESS(COMPOSE_FOLLOWUPTO, compose->followup_to);
3482 compose_update_priority_menu_item(compose);
3483 compose_update_privacy_system_menu_item(compose, FALSE);
3484 compose_show_first_last_header(compose, TRUE);
3490 static void compose_insert_sig(Compose *compose, gboolean replace)
3492 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3493 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3495 GtkTextIter iter, iter_end;
3496 gint cur_pos, ins_pos;
3497 gboolean prev_autowrap;
3498 gboolean found = FALSE;
3499 gboolean exists = FALSE;
3501 cm_return_if_fail(compose->account != NULL);
3505 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3506 G_CALLBACK(compose_changed_cb),
3509 mark = gtk_text_buffer_get_insert(buffer);
3510 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3511 cur_pos = gtk_text_iter_get_offset (&iter);
3514 gtk_text_buffer_get_end_iter(buffer, &iter);
3516 exists = (compose->sig_str != NULL);
3519 GtkTextIter first_iter, start_iter, end_iter;
3521 gtk_text_buffer_get_start_iter(buffer, &first_iter);
3523 if (!exists || compose->sig_str[0] == '\0')
3526 found = gtk_text_iter_forward_to_tag_toggle(&first_iter,
3527 compose->signature_tag);
3530 /* include previous \n\n */
3531 gtk_text_iter_backward_chars(&first_iter, 1);
3532 start_iter = first_iter;
3533 end_iter = first_iter;
3535 found = gtk_text_iter_forward_to_tag_toggle(&end_iter,
3536 compose->signature_tag);
3537 found &= gtk_text_iter_forward_to_tag_toggle(&end_iter,
3538 compose->signature_tag);
3540 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
3546 g_free(compose->sig_str);
3547 compose->sig_str = account_get_signature_str(compose->account);
3549 cur_pos = gtk_text_iter_get_offset(&iter);
3551 if (!compose->sig_str || (replace && !compose->account->auto_sig)) {
3552 g_free(compose->sig_str);
3553 compose->sig_str = NULL;
3555 if (compose->sig_inserted == FALSE)
3556 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
3557 compose->sig_inserted = TRUE;
3559 cur_pos = gtk_text_iter_get_offset(&iter);
3560 gtk_text_buffer_insert(buffer, &iter, compose->sig_str, -1);
3562 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cur_pos);
3563 gtk_text_iter_forward_chars(&iter, 1);
3564 gtk_text_buffer_get_end_iter(buffer, &iter_end);
3565 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &iter_end);
3567 if (cur_pos > gtk_text_buffer_get_char_count (buffer))
3568 cur_pos = gtk_text_buffer_get_char_count (buffer);
3571 /* put the cursor where it should be
3572 * either where the quote_fmt says, either where it was */
3573 if (compose->set_cursor_pos < 0)
3574 gtk_text_buffer_get_iter_at_offset(buffer, &iter, ins_pos);
3576 gtk_text_buffer_get_iter_at_offset(buffer, &iter,
3577 compose->set_cursor_pos);
3579 compose->set_cursor_pos = -1;
3580 gtk_text_buffer_place_cursor(buffer, &iter);
3581 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3582 G_CALLBACK(compose_changed_cb),
3588 static ComposeInsertResult compose_insert_file(Compose *compose, const gchar *file)
3591 GtkTextBuffer *buffer;
3594 const gchar *cur_encoding;
3595 gchar buf[BUFFSIZE];
3598 gboolean prev_autowrap;
3601 GString *file_contents = NULL;
3602 ComposeInsertResult result = COMPOSE_INSERT_SUCCESS;
3604 cm_return_val_if_fail(file != NULL, COMPOSE_INSERT_NO_FILE);
3606 /* get the size of the file we are about to insert */
3607 ret = g_stat(file, &file_stat);
3609 gchar *shortfile = g_path_get_basename(file);
3610 alertpanel_error(_("Could not get size of file '%s'."), shortfile);
3612 return COMPOSE_INSERT_NO_FILE;
3613 } else if (prefs_common.warn_large_insert == TRUE) {
3615 /* ask user for confirmation if the file is large */
3616 if (prefs_common.warn_large_insert_size < 0 ||
3617 file_stat.st_size > (prefs_common.warn_large_insert_size * 1024)) {
3621 msg = g_strdup_printf(_("You are about to insert a file of %s "
3622 "in the message body. Are you sure you want to do that?"),
3623 to_human_readable(file_stat.st_size));
3624 aval = alertpanel_full(_("Are you sure?"), msg, GTK_STOCK_CANCEL,
3625 g_strconcat("+", _("_Insert"), NULL), NULL, TRUE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
3628 /* do we ask for confirmation next time? */
3629 if (aval & G_ALERTDISABLE) {
3630 /* no confirmation next time, disable feature in preferences */
3631 aval &= ~G_ALERTDISABLE;
3632 prefs_common.warn_large_insert = FALSE;
3635 /* abort file insertion if user canceled action */
3636 if (aval != G_ALERTALTERNATE) {
3637 return COMPOSE_INSERT_NO_FILE;
3643 if ((fp = g_fopen(file, "rb")) == NULL) {
3644 FILE_OP_ERROR(file, "fopen");
3645 return COMPOSE_INSERT_READ_ERROR;
3648 prev_autowrap = compose->autowrap;
3649 compose->autowrap = FALSE;
3651 text = GTK_TEXT_VIEW(compose->text);
3652 buffer = gtk_text_view_get_buffer(text);
3653 mark = gtk_text_buffer_get_insert(buffer);
3654 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3656 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3657 G_CALLBACK(text_inserted),
3660 cur_encoding = conv_get_locale_charset_str_no_utf8();
3662 file_contents = g_string_new("");
3663 while (fgets(buf, sizeof(buf), fp) != NULL) {
3666 if (g_utf8_validate(buf, -1, NULL) == TRUE)
3667 str = g_strdup(buf);
3669 codeconv_set_strict(TRUE);
3670 str = conv_codeset_strdup
3671 (buf, cur_encoding, CS_INTERNAL);
3672 codeconv_set_strict(FALSE);
3675 result = COMPOSE_INSERT_INVALID_CHARACTER;
3681 /* strip <CR> if DOS/Windows file,
3682 replace <CR> with <LF> if Macintosh file. */
3685 if (len > 0 && str[len - 1] != '\n') {
3687 if (str[len] == '\r') str[len] = '\n';
3690 file_contents = g_string_append(file_contents, str);
3694 if (result == COMPOSE_INSERT_SUCCESS) {
3695 gtk_text_buffer_insert(buffer, &iter, file_contents->str, -1);
3697 compose_changed_cb(NULL, compose);
3698 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3699 G_CALLBACK(text_inserted),
3701 compose->autowrap = prev_autowrap;
3702 if (compose->autowrap)
3703 compose_wrap_all(compose);
3706 g_string_free(file_contents, TRUE);
3712 static gboolean compose_attach_append(Compose *compose, const gchar *file,
3713 const gchar *filename,
3714 const gchar *content_type,
3715 const gchar *charset)
3723 GtkListStore *store;
3725 gboolean has_binary = FALSE;
3727 if (!is_file_exist(file)) {
3728 gchar *file_from_uri = g_filename_from_uri(file, NULL, NULL);
3729 gboolean result = FALSE;
3730 if (file_from_uri && is_file_exist(file_from_uri)) {
3731 result = compose_attach_append(
3732 compose, file_from_uri,
3733 filename, content_type,
3736 g_free(file_from_uri);
3739 alertpanel_error("File %s doesn't exist\n", filename);
3742 if ((size = get_file_size(file)) < 0) {
3743 alertpanel_error("Can't get file size of %s\n", filename);
3747 /* In batch mode, we allow 0-length files to be attached no questions asked */
3748 if (size == 0 && !compose->batch) {
3749 gchar * msg = g_strdup_printf(_("File %s is empty."), filename);
3750 AlertValue aval = alertpanel_full(_("Empty file"), msg,
3751 GTK_STOCK_CANCEL, g_strconcat("+", _("_Attach anyway"), NULL), NULL, FALSE,
3752 NULL, ALERT_WARNING, G_ALERTDEFAULT);
3755 if (aval != G_ALERTALTERNATE) {
3759 if ((fp = g_fopen(file, "rb")) == NULL) {
3760 alertpanel_error(_("Can't read %s."), filename);
3765 ainfo = g_new0(AttachInfo, 1);
3766 auto_ainfo = g_auto_pointer_new_with_free
3767 (ainfo, (GFreeFunc) compose_attach_info_free);
3768 ainfo->file = g_strdup(file);
3771 ainfo->content_type = g_strdup(content_type);
3772 if (!g_ascii_strcasecmp(content_type, "message/rfc822")) {
3774 MsgFlags flags = {0, 0};
3776 if (procmime_get_encoding_for_text_file(file, &has_binary) == ENC_7BIT)
3777 ainfo->encoding = ENC_7BIT;
3779 ainfo->encoding = ENC_8BIT;
3781 msginfo = procheader_parse_file(file, flags, FALSE, FALSE);
3782 if (msginfo && msginfo->subject)
3783 name = g_strdup(msginfo->subject);
3785 name = g_path_get_basename(filename ? filename : file);
3787 ainfo->name = g_strdup_printf(_("Message: %s"), name);
3789 procmsg_msginfo_free(&msginfo);
3791 if (!g_ascii_strncasecmp(content_type, "text/", 5)) {
3792 ainfo->charset = g_strdup(charset);
3793 ainfo->encoding = procmime_get_encoding_for_text_file(file, &has_binary);
3795 ainfo->encoding = ENC_BASE64;
3797 name = g_path_get_basename(filename ? filename : file);
3798 ainfo->name = g_strdup(name);
3802 ainfo->content_type = procmime_get_mime_type(file);
3803 if (!ainfo->content_type) {
3804 ainfo->content_type =
3805 g_strdup("application/octet-stream");
3806 ainfo->encoding = ENC_BASE64;
3807 } else if (!g_ascii_strncasecmp(ainfo->content_type, "text/", 5))
3809 procmime_get_encoding_for_text_file(file, &has_binary);
3811 ainfo->encoding = ENC_BASE64;
3812 name = g_path_get_basename(filename ? filename : file);
3813 ainfo->name = g_strdup(name);
3817 if (ainfo->name != NULL
3818 && !strcmp(ainfo->name, ".")) {
3819 g_free(ainfo->name);
3823 if (!strcmp(ainfo->content_type, "unknown") || has_binary) {
3824 g_free(ainfo->content_type);
3825 ainfo->content_type = g_strdup("application/octet-stream");
3826 g_free(ainfo->charset);
3827 ainfo->charset = NULL;
3830 ainfo->size = (goffset)size;
3831 size_text = to_human_readable((goffset)size);
3833 store = GTK_LIST_STORE(gtk_tree_view_get_model
3834 (GTK_TREE_VIEW(compose->attach_clist)));
3836 gtk_list_store_append(store, &iter);
3837 gtk_list_store_set(store, &iter,
3838 COL_MIMETYPE, ainfo->content_type,
3839 COL_SIZE, size_text,
3840 COL_NAME, ainfo->name,
3841 COL_CHARSET, ainfo->charset,
3843 COL_AUTODATA, auto_ainfo,
3846 g_auto_pointer_free(auto_ainfo);
3847 compose_attach_update_label(compose);
3851 static void compose_use_signing(Compose *compose, gboolean use_signing)
3853 compose->use_signing = use_signing;
3854 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", use_signing);
3857 static void compose_use_encryption(Compose *compose, gboolean use_encryption)
3859 compose->use_encryption = use_encryption;
3860 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", use_encryption);
3863 #define NEXT_PART_NOT_CHILD(info) \
3865 node = info->node; \
3866 while (node->children) \
3867 node = g_node_last_child(node); \
3868 info = procmime_mimeinfo_next((MimeInfo *)node->data); \
3871 static void compose_attach_parts(Compose *compose, MsgInfo *msginfo)
3875 MimeInfo *firsttext = NULL;
3876 MimeInfo *encrypted = NULL;
3879 const gchar *partname = NULL;
3881 mimeinfo = procmime_scan_message(msginfo);
3882 if (!mimeinfo) return;
3884 if (mimeinfo->node->children == NULL) {
3885 procmime_mimeinfo_free_all(&mimeinfo);
3889 /* find first content part */
3890 child = (MimeInfo *) mimeinfo->node->children->data;
3891 while (child && child->node->children && (child->type == MIMETYPE_MULTIPART))
3892 child = (MimeInfo *)child->node->children->data;
3895 if (child->type == MIMETYPE_TEXT) {
3897 debug_print("First text part found\n");
3898 } else if (compose->mode == COMPOSE_REEDIT &&
3899 child->type == MIMETYPE_APPLICATION &&
3900 !g_ascii_strcasecmp(child->subtype, "pgp-encrypted")) {
3901 encrypted = (MimeInfo *)child->node->parent->data;
3904 child = (MimeInfo *) mimeinfo->node->children->data;
3905 while (child != NULL) {
3908 if (child == encrypted) {
3909 /* skip this part of tree */
3910 NEXT_PART_NOT_CHILD(child);
3914 if (child->type == MIMETYPE_MULTIPART) {
3915 /* get the actual content */
3916 child = procmime_mimeinfo_next(child);
3920 if (child == firsttext) {
3921 child = procmime_mimeinfo_next(child);
3925 outfile = procmime_get_tmp_file_name(child);
3926 if ((err = procmime_get_part(outfile, child)) < 0)
3927 g_warning("Can't get the part of multipart message. (%s)", g_strerror(-err));
3929 gchar *content_type;
3931 content_type = procmime_get_content_type_str(child->type, child->subtype);
3933 /* if we meet a pgp signature, we don't attach it, but
3934 * we force signing. */
3935 if ((strcmp(content_type, "application/pgp-signature") &&
3936 strcmp(content_type, "application/pkcs7-signature") &&
3937 strcmp(content_type, "application/x-pkcs7-signature"))
3938 || compose->mode == COMPOSE_REDIRECT) {
3939 partname = procmime_mimeinfo_get_parameter(child, "filename");
3940 if (partname == NULL)
3941 partname = procmime_mimeinfo_get_parameter(child, "name");
3942 if (partname == NULL)
3944 compose_attach_append(compose, outfile,
3945 partname, content_type,
3946 procmime_mimeinfo_get_parameter(child, "charset"));
3948 compose_force_signing(compose, compose->account, NULL);
3950 g_free(content_type);
3953 NEXT_PART_NOT_CHILD(child);
3955 procmime_mimeinfo_free_all(&mimeinfo);
3958 #undef NEXT_PART_NOT_CHILD
3963 WAIT_FOR_INDENT_CHAR,
3964 WAIT_FOR_INDENT_CHAR_OR_SPACE,
3967 /* return indent length, we allow:
3968 indent characters followed by indent characters or spaces/tabs,
3969 alphabets and numbers immediately followed by indent characters,
3970 and the repeating sequences of the above
3971 If quote ends with multiple spaces, only the first one is included. */
3972 static gchar *compose_get_quote_str(GtkTextBuffer *buffer,
3973 const GtkTextIter *start, gint *len)
3975 GtkTextIter iter = *start;
3979 IndentState state = WAIT_FOR_INDENT_CHAR;
3982 gint alnum_count = 0;
3983 gint space_count = 0;
3986 if (prefs_common.quote_chars == NULL) {
3990 while (!gtk_text_iter_ends_line(&iter)) {
3991 wc = gtk_text_iter_get_char(&iter);
3992 if (g_unichar_iswide(wc))
3994 clen = g_unichar_to_utf8(wc, ch);
3998 is_indent = strchr(prefs_common.quote_chars, ch[0]) ? TRUE : FALSE;
3999 is_space = g_unichar_isspace(wc);
4001 if (state == WAIT_FOR_INDENT_CHAR) {
4002 if (!is_indent && !g_unichar_isalnum(wc))
4005 quote_len += alnum_count + space_count + 1;
4006 alnum_count = space_count = 0;
4007 state = WAIT_FOR_INDENT_CHAR_OR_SPACE;
4010 } else if (state == WAIT_FOR_INDENT_CHAR_OR_SPACE) {
4011 if (!is_indent && !is_space && !g_unichar_isalnum(wc))
4015 else if (is_indent) {
4016 quote_len += alnum_count + space_count + 1;
4017 alnum_count = space_count = 0;
4020 state = WAIT_FOR_INDENT_CHAR;
4024 gtk_text_iter_forward_char(&iter);
4027 if (quote_len > 0 && space_count > 0)
4033 if (quote_len > 0) {
4035 gtk_text_iter_forward_chars(&iter, quote_len);
4036 return gtk_text_buffer_get_text(buffer, start, &iter, FALSE);
4042 /* return >0 if the line is itemized */
4043 static int compose_itemized_length(GtkTextBuffer *buffer,
4044 const GtkTextIter *start)
4046 GtkTextIter iter = *start;
4051 if (gtk_text_iter_ends_line(&iter))
4056 wc = gtk_text_iter_get_char(&iter);
4057 if (!g_unichar_isspace(wc))
4059 gtk_text_iter_forward_char(&iter);
4060 if (gtk_text_iter_ends_line(&iter))
4064 clen = g_unichar_to_utf8(wc, ch);
4068 if (!strchr("*-+", ch[0]))
4071 gtk_text_iter_forward_char(&iter);
4072 if (gtk_text_iter_ends_line(&iter))
4074 wc = gtk_text_iter_get_char(&iter);
4075 if (g_unichar_isspace(wc)) {
4081 /* return the string at the start of the itemization */
4082 static gchar * compose_get_itemized_chars(GtkTextBuffer *buffer,
4083 const GtkTextIter *start)
4085 GtkTextIter iter = *start;
4088 GString *item_chars = g_string_new("");
4091 if (gtk_text_iter_ends_line(&iter))
4096 wc = gtk_text_iter_get_char(&iter);
4097 if (!g_unichar_isspace(wc))
4099 gtk_text_iter_forward_char(&iter);
4100 if (gtk_text_iter_ends_line(&iter))
4102 g_string_append_unichar(item_chars, wc);
4105 str = item_chars->str;
4106 g_string_free(item_chars, FALSE);
4110 /* return the number of spaces at a line's start */
4111 static int compose_left_offset_length(GtkTextBuffer *buffer,
4112 const GtkTextIter *start)
4114 GtkTextIter iter = *start;
4117 if (gtk_text_iter_ends_line(&iter))
4121 wc = gtk_text_iter_get_char(&iter);
4122 if (!g_unichar_isspace(wc))
4125 gtk_text_iter_forward_char(&iter);
4126 if (gtk_text_iter_ends_line(&iter))
4130 gtk_text_iter_forward_char(&iter);
4131 if (gtk_text_iter_ends_line(&iter))
4136 static gboolean compose_get_line_break_pos(GtkTextBuffer *buffer,
4137 const GtkTextIter *start,
4138 GtkTextIter *break_pos,
4142 GtkTextIter iter = *start, line_end = *start;
4143 PangoLogAttr *attrs;
4150 gboolean can_break = FALSE;
4151 gboolean do_break = FALSE;
4152 gboolean was_white = FALSE;
4153 gboolean prev_dont_break = FALSE;
4155 gtk_text_iter_forward_to_line_end(&line_end);
4156 str = gtk_text_buffer_get_text(buffer, &iter, &line_end, FALSE);
4157 len = g_utf8_strlen(str, -1);
4161 g_warning("compose_get_line_break_pos: len = 0!");
4165 /* g_print("breaking line: %d: %s (len = %d)\n",
4166 gtk_text_iter_get_line(&iter), str, len); */
4168 attrs = g_new(PangoLogAttr, len + 1);
4170 pango_default_break(str, -1, NULL, attrs, len + 1);
4174 /* skip quote and leading spaces */
4175 for (i = 0; *p != '\0' && i < len; i++) {
4178 wc = g_utf8_get_char(p);
4179 if (i >= quote_len && !g_unichar_isspace(wc))
4181 if (g_unichar_iswide(wc))
4183 else if (*p == '\t')
4187 p = g_utf8_next_char(p);
4190 for (; *p != '\0' && i < len; i++) {
4191 PangoLogAttr *attr = attrs + i;
4195 if (attr->is_line_break && can_break && was_white && !prev_dont_break)
4198 was_white = attr->is_white;
4200 /* don't wrap URI */
4201 if ((uri_len = get_uri_len(p)) > 0) {
4203 if (pos > 0 && col > max_col) {
4213 wc = g_utf8_get_char(p);
4214 if (g_unichar_iswide(wc)) {
4216 if (prev_dont_break && can_break && attr->is_line_break)
4218 } else if (*p == '\t')
4222 if (pos > 0 && col > max_col) {
4227 if (*p == '-' || *p == '/')
4228 prev_dont_break = TRUE;
4230 prev_dont_break = FALSE;
4232 p = g_utf8_next_char(p);
4236 /* debug_print("compose_get_line_break_pos(): do_break = %d, pos = %d, col = %d\n", do_break, pos, col); */
4241 *break_pos = *start;
4242 gtk_text_iter_set_line_offset(break_pos, pos);
4247 static gboolean compose_join_next_line(Compose *compose,
4248 GtkTextBuffer *buffer,
4250 const gchar *quote_str)
4252 GtkTextIter iter_ = *iter, cur, prev, next, end;
4253 PangoLogAttr attrs[3];
4255 gchar *next_quote_str;
4258 gboolean keep_cursor = FALSE;
4260 if (!gtk_text_iter_forward_line(&iter_) ||
4261 gtk_text_iter_ends_line(&iter_)) {
4264 next_quote_str = compose_get_quote_str(buffer, &iter_, "e_len);
4266 if ((quote_str || next_quote_str) &&
4267 strcmp2(quote_str, next_quote_str) != 0) {
4268 g_free(next_quote_str);
4271 g_free(next_quote_str);
4274 if (quote_len > 0) {
4275 gtk_text_iter_forward_chars(&end, quote_len);
4276 if (gtk_text_iter_ends_line(&end)) {
4281 /* don't join itemized lines */
4282 if (compose_itemized_length(buffer, &end) > 0) {
4286 /* don't join signature separator */
4287 if (compose_is_sig_separator(compose, buffer, &iter_)) {
4290 /* delete quote str */
4292 gtk_text_buffer_delete(buffer, &iter_, &end);
4294 /* don't join line breaks put by the user */
4296 gtk_text_iter_backward_char(&cur);
4297 if (gtk_text_iter_has_tag(&cur, compose->no_join_tag)) {
4298 gtk_text_iter_forward_char(&cur);
4302 gtk_text_iter_forward_char(&cur);
4303 /* delete linebreak and extra spaces */
4304 while (gtk_text_iter_backward_char(&cur)) {
4305 wc1 = gtk_text_iter_get_char(&cur);
4306 if (!g_unichar_isspace(wc1))
4311 while (!gtk_text_iter_ends_line(&cur)) {
4312 wc1 = gtk_text_iter_get_char(&cur);
4313 if (!g_unichar_isspace(wc1))
4315 gtk_text_iter_forward_char(&cur);
4318 if (!gtk_text_iter_equal(&prev, &next)) {
4321 mark = gtk_text_buffer_get_insert(buffer);
4322 gtk_text_buffer_get_iter_at_mark(buffer, &cur, mark);
4323 if (gtk_text_iter_equal(&prev, &cur))
4325 gtk_text_buffer_delete(buffer, &prev, &next);
4329 /* insert space if required */
4330 gtk_text_iter_backward_char(&prev);
4331 wc1 = gtk_text_iter_get_char(&prev);
4332 wc2 = gtk_text_iter_get_char(&next);
4333 gtk_text_iter_forward_char(&next);
4334 str = gtk_text_buffer_get_text(buffer, &prev, &next, FALSE);
4335 pango_default_break(str, -1, NULL, attrs, 3);
4336 if (!attrs[1].is_line_break ||
4337 (!g_unichar_iswide(wc1) || !g_unichar_iswide(wc2))) {
4338 gtk_text_buffer_insert(buffer, &iter_, " ", 1);
4340 gtk_text_iter_backward_char(&iter_);
4341 gtk_text_buffer_place_cursor(buffer, &iter_);
4350 #define ADD_TXT_POS(bp_, ep_, pti_) \
4351 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
4352 last = last->next; \
4353 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
4354 last->next = NULL; \
4356 g_warning("alloc error scanning URIs"); \
4359 static gboolean compose_beautify_paragraph(Compose *compose, GtkTextIter *par_iter, gboolean force)
4361 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4362 GtkTextBuffer *buffer;
4363 GtkTextIter iter, break_pos, end_of_line;
4364 gchar *quote_str = NULL;
4366 gboolean wrap_quote = force || prefs_common.linewrap_quote;
4367 gboolean prev_autowrap = compose->autowrap;
4368 gint startq_offset = -1, noq_offset = -1;
4369 gint uri_start = -1, uri_stop = -1;
4370 gint nouri_start = -1, nouri_stop = -1;
4371 gint num_blocks = 0;
4372 gint quotelevel = -1;
4373 gboolean modified = force;
4374 gboolean removed = FALSE;
4375 gboolean modified_before_remove = FALSE;
4377 gboolean start = TRUE;
4378 gint itemized_len = 0, rem_item_len = 0;
4379 gchar *itemized_chars = NULL;
4380 gboolean item_continuation = FALSE;
4385 if (compose->draft_timeout_tag == COMPOSE_DRAFT_TIMEOUT_FORBIDDEN) {
4389 compose->autowrap = FALSE;
4391 buffer = gtk_text_view_get_buffer(text);
4392 undo_wrapping(compose->undostruct, TRUE);
4397 mark = gtk_text_buffer_get_insert(buffer);
4398 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
4402 if (compose->draft_timeout_tag == COMPOSE_DRAFT_TIMEOUT_FORBIDDEN) {
4403 if (gtk_text_iter_ends_line(&iter)) {
4404 while (gtk_text_iter_ends_line(&iter) &&
4405 gtk_text_iter_forward_line(&iter))
4408 while (gtk_text_iter_backward_line(&iter)) {
4409 if (gtk_text_iter_ends_line(&iter)) {
4410 gtk_text_iter_forward_line(&iter);
4416 /* move to line start */
4417 gtk_text_iter_set_line_offset(&iter, 0);
4420 itemized_len = compose_itemized_length(buffer, &iter);
4422 if (!itemized_len) {
4423 itemized_len = compose_left_offset_length(buffer, &iter);
4424 item_continuation = TRUE;
4428 itemized_chars = compose_get_itemized_chars(buffer, &iter);
4430 /* go until paragraph end (empty line) */
4431 while (start || !gtk_text_iter_ends_line(&iter)) {
4432 gchar *scanpos = NULL;
4433 /* parse table - in order of priority */
4435 const gchar *needle; /* token */
4437 /* token search function */
4438 gchar *(*search) (const gchar *haystack,
4439 const gchar *needle);
4440 /* part parsing function */
4441 gboolean (*parse) (const gchar *start,
4442 const gchar *scanpos,
4446 /* part to URI function */
4447 gchar *(*build_uri) (const gchar *bp,
4451 static struct table parser[] = {
4452 {"http://", strcasestr, get_uri_part, make_uri_string},
4453 {"https://", strcasestr, get_uri_part, make_uri_string},
4454 {"ftp://", strcasestr, get_uri_part, make_uri_string},
4455 {"sftp://", strcasestr, get_uri_part, make_uri_string},
4456 {"gopher://",strcasestr, get_uri_part, make_uri_string},
4457 {"www.", strcasestr, get_uri_part, make_http_string},
4458 {"mailto:", strcasestr, get_uri_part, make_uri_string},
4459 {"@", strcasestr, get_email_part, make_email_string}
4461 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
4462 gint last_index = PARSE_ELEMS;
4464 gchar *o_walk = NULL, *walk = NULL, *bp = NULL, *ep = NULL;
4468 if (!prev_autowrap && num_blocks == 0) {
4470 g_signal_handlers_block_by_func(G_OBJECT(buffer),
4471 G_CALLBACK(text_inserted),
4474 if (gtk_text_iter_has_tag(&iter, compose->no_wrap_tag) && !force)
4477 uri_start = uri_stop = -1;
4479 quote_str = compose_get_quote_str(buffer, &iter, "e_len);
4482 /* debug_print("compose_beautify_paragraph(): quote_str = '%s'\n", quote_str); */
4483 if (startq_offset == -1)
4484 startq_offset = gtk_text_iter_get_offset(&iter);
4485 quotelevel = get_quote_level(quote_str, prefs_common.quote_chars);
4486 if (quotelevel > 2) {
4487 /* recycle colors */
4488 if (prefs_common.recycle_quote_colors)
4497 if (startq_offset == -1)
4498 noq_offset = gtk_text_iter_get_offset(&iter);
4502 if (prev_autowrap == FALSE && !force && !wrap_quote) {
4505 if (gtk_text_iter_ends_line(&iter)) {
4507 } else if (compose_get_line_break_pos(buffer, &iter, &break_pos,
4508 prefs_common.linewrap_len,
4510 GtkTextIter prev, next, cur;
4511 if (prev_autowrap != FALSE || force) {
4512 compose->automatic_break = TRUE;
4514 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4515 compose->automatic_break = FALSE;
4516 if (itemized_len && compose->autoindent) {
4517 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4518 if (!item_continuation)
4519 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4521 } else if (quote_str && wrap_quote) {
4522 compose->automatic_break = TRUE;
4524 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4525 compose->automatic_break = FALSE;
4526 if (itemized_len && compose->autoindent) {
4527 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4528 if (!item_continuation)
4529 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4533 /* remove trailing spaces */
4535 rem_item_len = itemized_len;
4536 while (compose->autoindent && rem_item_len-- > 0)
4537 gtk_text_iter_backward_char(&cur);
4538 gtk_text_iter_backward_char(&cur);
4541 while (!gtk_text_iter_starts_line(&cur)) {
4544 gtk_text_iter_backward_char(&cur);
4545 wc = gtk_text_iter_get_char(&cur);
4546 if (!g_unichar_isspace(wc))
4550 if (!gtk_text_iter_equal(&prev, &next)) {
4551 gtk_text_buffer_delete(buffer, &prev, &next);
4553 gtk_text_iter_forward_char(&break_pos);
4557 gtk_text_buffer_insert(buffer, &break_pos,
4561 modified |= compose_join_next_line(compose, buffer, &iter, quote_str);
4563 /* move iter to current line start */
4564 gtk_text_iter_set_line_offset(&iter, 0);
4571 /* move iter to next line start */
4577 if (!prev_autowrap && num_blocks > 0) {
4579 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
4580 G_CALLBACK(text_inserted),
4584 while (!gtk_text_iter_ends_line(&end_of_line)) {
4585 gtk_text_iter_forward_char(&end_of_line);
4587 o_walk = walk = gtk_text_buffer_get_text(buffer, &iter, &end_of_line, FALSE);
4589 nouri_start = gtk_text_iter_get_offset(&iter);
4590 nouri_stop = gtk_text_iter_get_offset(&end_of_line);
4592 walk_pos = gtk_text_iter_get_offset(&iter);
4593 /* FIXME: this looks phony. scanning for anything in the parse table */
4594 for (n = 0; n < PARSE_ELEMS; n++) {
4597 tmp = parser[n].search(walk, parser[n].needle);
4599 if (scanpos == NULL || tmp < scanpos) {
4608 /* check if URI can be parsed */
4609 if (parser[last_index].parse(walk, scanpos, (const gchar **)&bp,
4610 (const gchar **)&ep, FALSE)
4611 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
4615 strlen(parser[last_index].needle);
4618 uri_start = walk_pos + (bp - o_walk);
4619 uri_stop = walk_pos + (ep - o_walk);
4623 gtk_text_iter_forward_line(&iter);
4626 if (startq_offset != -1) {
4627 GtkTextIter startquote, endquote;
4628 gtk_text_buffer_get_iter_at_offset(
4629 buffer, &startquote, startq_offset);
4632 switch (quotelevel) {
4634 if (!gtk_text_iter_has_tag(&startquote, compose->quote0_tag) ||
4635 !gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) {
4636 gtk_text_buffer_apply_tag_by_name(
4637 buffer, "quote0", &startquote, &endquote);
4638 gtk_text_buffer_remove_tag_by_name(
4639 buffer, "quote1", &startquote, &endquote);
4640 gtk_text_buffer_remove_tag_by_name(
4641 buffer, "quote2", &startquote, &endquote);
4646 if (!gtk_text_iter_has_tag(&startquote, compose->quote1_tag) ||
4647 !gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) {
4648 gtk_text_buffer_apply_tag_by_name(
4649 buffer, "quote1", &startquote, &endquote);
4650 gtk_text_buffer_remove_tag_by_name(
4651 buffer, "quote0", &startquote, &endquote);
4652 gtk_text_buffer_remove_tag_by_name(
4653 buffer, "quote2", &startquote, &endquote);
4658 if (!gtk_text_iter_has_tag(&startquote, compose->quote2_tag) ||
4659 !gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag)) {
4660 gtk_text_buffer_apply_tag_by_name(
4661 buffer, "quote2", &startquote, &endquote);
4662 gtk_text_buffer_remove_tag_by_name(
4663 buffer, "quote0", &startquote, &endquote);
4664 gtk_text_buffer_remove_tag_by_name(
4665 buffer, "quote1", &startquote, &endquote);
4671 } else if (noq_offset != -1) {
4672 GtkTextIter startnoquote, endnoquote;
4673 gtk_text_buffer_get_iter_at_offset(
4674 buffer, &startnoquote, noq_offset);
4677 if ((gtk_text_iter_has_tag(&startnoquote, compose->quote0_tag)
4678 && gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) ||
4679 (gtk_text_iter_has_tag(&startnoquote, compose->quote1_tag)
4680 && gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) ||
4681 (gtk_text_iter_has_tag(&startnoquote, compose->quote2_tag)
4682 && gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag))) {
4683 gtk_text_buffer_remove_tag_by_name(
4684 buffer, "quote0", &startnoquote, &endnoquote);
4685 gtk_text_buffer_remove_tag_by_name(
4686 buffer, "quote1", &startnoquote, &endnoquote);
4687 gtk_text_buffer_remove_tag_by_name(
4688 buffer, "quote2", &startnoquote, &endnoquote);
4694 if (uri_start != nouri_start && uri_stop != nouri_stop) {
4695 GtkTextIter nouri_start_iter, nouri_end_iter;
4696 gtk_text_buffer_get_iter_at_offset(
4697 buffer, &nouri_start_iter, nouri_start);
4698 gtk_text_buffer_get_iter_at_offset(
4699 buffer, &nouri_end_iter, nouri_stop);
4700 if (gtk_text_iter_has_tag(&nouri_start_iter, compose->uri_tag) &&
4701 gtk_text_iter_has_tag(&nouri_end_iter, compose->uri_tag)) {
4702 gtk_text_buffer_remove_tag_by_name(
4703 buffer, "link", &nouri_start_iter, &nouri_end_iter);
4704 modified_before_remove = modified;
4709 if (uri_start >= 0 && uri_stop > 0) {
4710 GtkTextIter uri_start_iter, uri_end_iter, back;
4711 gtk_text_buffer_get_iter_at_offset(
4712 buffer, &uri_start_iter, uri_start);
4713 gtk_text_buffer_get_iter_at_offset(
4714 buffer, &uri_end_iter, uri_stop);
4715 back = uri_end_iter;
4716 gtk_text_iter_backward_char(&back);
4717 if (!gtk_text_iter_has_tag(&uri_start_iter, compose->uri_tag) ||
4718 !gtk_text_iter_has_tag(&back, compose->uri_tag)) {
4719 gtk_text_buffer_apply_tag_by_name(
4720 buffer, "link", &uri_start_iter, &uri_end_iter);
4722 if (removed && !modified_before_remove) {
4728 /* debug_print("not modified, out after %d lines\n", lines); */
4732 /* debug_print("modified, out after %d lines\n", lines); */
4734 g_free(itemized_chars);
4737 undo_wrapping(compose->undostruct, FALSE);
4738 compose->autowrap = prev_autowrap;
4743 void compose_action_cb(void *data)
4745 Compose *compose = (Compose *)data;
4746 compose_wrap_all(compose);
4749 static void compose_wrap_all(Compose *compose)
4751 compose_wrap_all_full(compose, FALSE);
4754 static void compose_wrap_all_full(Compose *compose, gboolean force)
4756 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4757 GtkTextBuffer *buffer;
4759 gboolean modified = TRUE;
4761 buffer = gtk_text_view_get_buffer(text);
4763 gtk_text_buffer_get_start_iter(buffer, &iter);
4765 undo_wrapping(compose->undostruct, TRUE);
4767 while (!gtk_text_iter_is_end(&iter) && modified)
4768 modified = compose_beautify_paragraph(compose, &iter, force);
4770 undo_wrapping(compose->undostruct, FALSE);
4774 static void compose_set_title(Compose *compose)
4780 edited = compose->modified ? _(" [Edited]") : "";
4782 subject = gtk_editable_get_chars(
4783 GTK_EDITABLE(compose->subject_entry), 0, -1);
4785 #ifndef GENERIC_UMPC
4786 if (subject && strlen(subject))
4787 str = g_strdup_printf(_("%s - Compose message%s"),
4790 str = g_strdup_printf(_("[no subject] - Compose message%s"), edited);
4792 str = g_strdup(_("Compose message"));
4795 gtk_window_set_title(GTK_WINDOW(compose->window), str);
4801 * compose_current_mail_account:
4803 * Find a current mail account (the currently selected account, or the
4804 * default account, if a news account is currently selected). If a
4805 * mail account cannot be found, display an error message.
4807 * Return value: Mail account, or NULL if not found.
4809 static PrefsAccount *
4810 compose_current_mail_account(void)
4814 if (cur_account && cur_account->protocol != A_NNTP)
4817 ac = account_get_default();
4818 if (!ac || ac->protocol == A_NNTP) {
4819 alertpanel_error(_("Account for sending mail is not specified.\n"
4820 "Please select a mail account before sending."));
4827 #define QUOTE_IF_REQUIRED(out, str) \
4829 if (*str != '"' && strpbrk(str, ",.:;[]<>()@\\\"")) { \
4833 len = strlen(str) + 3; \
4834 if ((__tmp = alloca(len)) == NULL) { \
4835 g_warning("can't allocate memory"); \
4836 g_string_free(header, TRUE); \
4839 g_snprintf(__tmp, len, "\"%s\"", str); \
4844 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4845 g_warning("can't allocate memory"); \
4846 g_string_free(header, TRUE); \
4849 strcpy(__tmp, str); \
4855 #define QUOTE_IF_REQUIRED_NORMAL(out, str, errret) \
4857 if (*str != '"' && strpbrk(str, ",.:;[]<>()@\\\"")) { \
4861 len = strlen(str) + 3; \
4862 if ((__tmp = alloca(len)) == NULL) { \
4863 g_warning("can't allocate memory"); \
4866 g_snprintf(__tmp, len, "\"%s\"", str); \
4871 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4872 g_warning("can't allocate memory"); \
4875 strcpy(__tmp, str); \
4881 static void compose_select_account(Compose *compose, PrefsAccount *account,
4884 gchar *from = NULL, *header = NULL;
4885 ComposeHeaderEntry *header_entry;
4886 #if GTK_CHECK_VERSION(2, 24, 0)
4890 cm_return_if_fail(account != NULL);
4892 compose->account = account;
4893 if (account->name && *account->name) {
4895 QUOTE_IF_REQUIRED_NORMAL(buf, account->name, return);
4896 qbuf = escape_internal_quotes(buf, '"');
4897 from = g_strdup_printf("%s <%s>",
4898 qbuf, account->address);
4901 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4903 from = g_strdup_printf("<%s>",
4905 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4910 compose_set_title(compose);
4912 if (account->default_sign && compose->mode != COMPOSE_REDIRECT)
4913 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", TRUE);
4915 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", FALSE);
4916 if (account->default_encrypt && compose->mode != COMPOSE_REDIRECT)
4917 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", TRUE);
4919 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", FALSE);
4921 activate_privacy_system(compose, account, FALSE);
4923 if (!init && compose->mode != COMPOSE_REDIRECT) {
4924 undo_block(compose->undostruct);
4925 compose_insert_sig(compose, TRUE);
4926 undo_unblock(compose->undostruct);
4929 header_entry = (ComposeHeaderEntry *) compose->header_list->data;
4930 #if !GTK_CHECK_VERSION(2, 24, 0)
4931 header = gtk_combo_box_get_active_text(GTK_COMBO_BOX(header_entry->combo));
4933 if (gtk_combo_box_get_active_iter(GTK_COMBO_BOX(header_entry->combo), &iter))
4934 gtk_tree_model_get(gtk_combo_box_get_model(GTK_COMBO_BOX(
4935 header_entry->combo)), &iter, COMBOBOX_TEXT, &header, -1);
4938 if (header && !strlen(gtk_entry_get_text(GTK_ENTRY(header_entry->entry)))) {
4939 if (account->protocol == A_NNTP) {
4940 if (!strcmp(header, _("To:")))
4941 combobox_select_by_text(
4942 GTK_COMBO_BOX(header_entry->combo),
4945 if (!strcmp(header, _("Newsgroups:")))
4946 combobox_select_by_text(
4947 GTK_COMBO_BOX(header_entry->combo),
4955 /* use account's dict info if set */
4956 if (compose->gtkaspell) {
4957 if (account->enable_default_dictionary)
4958 gtkaspell_change_dict(compose->gtkaspell,
4959 account->default_dictionary, FALSE);
4960 if (account->enable_default_alt_dictionary)
4961 gtkaspell_change_alt_dict(compose->gtkaspell,
4962 account->default_alt_dictionary);
4963 if (account->enable_default_dictionary
4964 || account->enable_default_alt_dictionary)
4965 compose_spell_menu_changed(compose);
4970 gboolean compose_check_for_valid_recipient(Compose *compose) {
4971 gchar *recipient_headers_mail[] = {"To:", "Cc:", "Bcc:", NULL};
4972 gchar *recipient_headers_news[] = {"Newsgroups:", NULL};
4973 gboolean recipient_found = FALSE;
4977 /* free to and newsgroup list */
4978 slist_free_strings_full(compose->to_list);
4979 compose->to_list = NULL;
4981 slist_free_strings_full(compose->newsgroup_list);
4982 compose->newsgroup_list = NULL;
4984 /* search header entries for to and newsgroup entries */
4985 for (list = compose->header_list; list; list = list->next) {
4988 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
4989 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4992 if (entry[0] != '\0') {
4993 for (strptr = recipient_headers_mail; *strptr != NULL; strptr++) {
4994 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
4995 compose->to_list = address_list_append(compose->to_list, entry);
4996 recipient_found = TRUE;
4999 for (strptr = recipient_headers_news; *strptr != NULL; strptr++) {
5000 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
5001 compose->newsgroup_list = newsgroup_list_append(compose->newsgroup_list, entry);
5002 recipient_found = TRUE;
5009 return recipient_found;
5012 static gboolean compose_check_for_set_recipients(Compose *compose)
5014 if (compose->account->set_autocc && compose->account->auto_cc) {
5015 gboolean found_other = FALSE;
5017 /* search header entries for to and newsgroup entries */
5018 for (list = compose->header_list; list; list = list->next) {
5021 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5022 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5025 if (strcmp(entry, compose->account->auto_cc)
5026 || strcmp(header, prefs_common_translated_header_name("Cc:"))) {
5037 if (compose->batch) {
5038 gtk_widget_show_all(compose->window);
5040 text = g_strdup_printf(_("The only recipient is the default '%s' address. Send anyway?"),
5041 prefs_common_translated_header_name("Cc"));
5042 aval = alertpanel(_("Send"),
5044 GTK_STOCK_CANCEL, g_strconcat("+", _("_Send"), NULL), NULL);
5046 if (aval != G_ALERTALTERNATE)
5050 if (compose->account->set_autobcc && compose->account->auto_bcc) {
5051 gboolean found_other = FALSE;
5053 /* search header entries for to and newsgroup entries */
5054 for (list = compose->header_list; list; list = list->next) {
5057 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5058 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5061 if (strcmp(entry, compose->account->auto_bcc)
5062 || strcmp(header, prefs_common_translated_header_name("Bcc:"))) {
5074 if (compose->batch) {
5075 gtk_widget_show_all(compose->window);
5077 text = g_strdup_printf(_("The only recipient is the default '%s' address. Send anyway?"),
5078 prefs_common_translated_header_name("Bcc"));
5079 aval = alertpanel(_("Send"),
5081 GTK_STOCK_CANCEL, g_strconcat("+", _("_Send"), NULL), NULL);
5083 if (aval != G_ALERTALTERNATE)
5090 static gboolean compose_check_entries(Compose *compose, gboolean check_everything)
5094 if (compose_check_for_valid_recipient(compose) == FALSE) {
5095 if (compose->batch) {
5096 gtk_widget_show_all(compose->window);
5098 alertpanel_error(_("Recipient is not specified."));
5102 if (compose_check_for_set_recipients(compose) == FALSE) {
5106 if (!compose->batch && prefs_common.warn_empty_subj == TRUE) {
5107 str = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5108 if (*str == '\0' && check_everything == TRUE &&
5109 compose->mode != COMPOSE_REDIRECT) {
5111 gchar *button_label;
5114 if (compose->sending)
5115 button_label = g_strconcat("+", _("_Send"), NULL);
5117 button_label = g_strconcat("+", _("_Queue"), NULL);
5118 message = g_strdup_printf(_("Subject is empty. %s"),
5119 compose->sending?_("Send it anyway?"):
5120 _("Queue it anyway?"));
5122 aval = alertpanel_full(compose->sending?_("Send"):_("Send later"), message,
5123 GTK_STOCK_CANCEL, button_label, NULL, TRUE, NULL,
5124 ALERT_QUESTION, G_ALERTDEFAULT);
5126 if (aval & G_ALERTDISABLE) {
5127 aval &= ~G_ALERTDISABLE;
5128 prefs_common.warn_empty_subj = FALSE;
5130 if (aval != G_ALERTALTERNATE)
5135 if (!compose->batch && prefs_common.warn_sending_many_recipients == TRUE
5136 && check_everything == TRUE) {
5140 /* count To and Cc recipients */
5141 for (list = compose->header_list; list; list = list->next) {
5145 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5146 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5149 if ((entry[0] != '\0')
5150 && (strcmp(header, prefs_common_translated_header_name("To:"))
5151 || strcmp(header, prefs_common_translated_header_name("Cc:")))) {
5157 if (cnt > prefs_common.warn_sending_many_recipients_num) {
5159 gchar *button_label;
5162 if (compose->sending)
5163 button_label = g_strconcat("+", _("_Send"), NULL);
5165 button_label = g_strconcat("+", _("_Queue"), NULL);
5166 message = g_strdup_printf(_("Sending to %d recipients. %s"), cnt,
5167 compose->sending?_("Send it anyway?"):
5168 _("Queue it anyway?"));
5170 aval = alertpanel_full(compose->sending?_("Send"):_("Send later"), message,
5171 GTK_STOCK_CANCEL, button_label, NULL, TRUE, NULL,
5172 ALERT_QUESTION, G_ALERTDEFAULT);
5174 if (aval & G_ALERTDISABLE) {
5175 aval &= ~G_ALERTDISABLE;
5176 prefs_common.warn_empty_subj = FALSE;
5178 if (aval != G_ALERTALTERNATE)
5183 if (check_everything && hooks_invoke(COMPOSE_CHECK_BEFORE_SEND_HOOKLIST, compose))
5189 gint compose_send(Compose *compose)
5192 FolderItem *folder = NULL;
5194 gchar *msgpath = NULL;
5195 gboolean discard_window = FALSE;
5196 gchar *errstr = NULL;
5197 gchar *tmsgid = NULL;
5198 MainWindow *mainwin = mainwindow_get_mainwindow();
5199 gboolean queued_removed = FALSE;
5201 if (prefs_common.send_dialog_invisible
5202 || compose->batch == TRUE)
5203 discard_window = TRUE;
5205 compose_allow_user_actions (compose, FALSE);
5206 compose->sending = TRUE;
5208 if (compose_check_entries(compose, TRUE) == FALSE) {
5209 if (compose->batch) {
5210 gtk_widget_show_all(compose->window);
5216 val = compose_queue(compose, &msgnum, &folder, &msgpath, TRUE);
5219 if (compose->batch) {
5220 gtk_widget_show_all(compose->window);
5223 alertpanel_error(_("Could not queue message for sending:\n\n"
5224 "Charset conversion failed."));
5225 } else if (val == -5) {
5226 alertpanel_error(_("Could not queue message for sending:\n\n"
5227 "Couldn't get recipient encryption key."));
5228 } else if (val == -6) {
5230 } else if (val == -3) {
5231 if (privacy_peek_error())
5232 alertpanel_error(_("Could not queue message for sending:\n\n"
5233 "Signature failed: %s"), privacy_get_error());
5234 } else if (val == -2 && errno != 0) {
5235 alertpanel_error(_("Could not queue message for sending:\n\n%s."), g_strerror(errno));
5237 alertpanel_error(_("Could not queue message for sending."));
5242 tmsgid = compose->msgid ? g_strdup(compose->msgid) : NULL;
5243 if (discard_window) {
5244 compose->sending = FALSE;
5245 compose_close(compose);
5246 /* No more compose access in the normal codepath
5247 * after this point! */
5252 alertpanel_error(_("The message was queued but could not be "
5253 "sent.\nUse \"Send queued messages\" from "
5254 "the main window to retry."));
5255 if (!discard_window) {
5262 if (msgpath == NULL) {
5263 msgpath = folder_item_fetch_msg(folder, msgnum);
5264 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5267 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5268 claws_unlink(msgpath);
5271 if (!discard_window) {
5273 if (!queued_removed)
5274 folder_item_remove_msg(folder, msgnum);
5275 folder_item_scan(folder);
5277 /* make sure we delete that */
5278 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5280 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5281 folder_item_remove_msg(folder, tmp->msgnum);
5282 procmsg_msginfo_free(&tmp);
5289 if (!queued_removed)
5290 folder_item_remove_msg(folder, msgnum);
5291 folder_item_scan(folder);
5293 /* make sure we delete that */
5294 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5296 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5297 folder_item_remove_msg(folder, tmp->msgnum);
5298 procmsg_msginfo_free(&tmp);
5301 if (!discard_window) {
5302 compose->sending = FALSE;
5303 compose_allow_user_actions (compose, TRUE);
5304 compose_close(compose);
5308 alertpanel_error_log(_("%s\nUse \"Send queued messages\" from "
5309 "the main window to retry."), errstr);
5312 alertpanel_error_log(_("The message was queued but could not be "
5313 "sent.\nUse \"Send queued messages\" from "
5314 "the main window to retry."));
5316 if (!discard_window) {
5325 toolbar_main_set_sensitive(mainwin);
5326 main_window_set_menu_sensitive(mainwin);
5332 compose_allow_user_actions (compose, TRUE);
5333 compose->sending = FALSE;
5334 compose->modified = TRUE;
5335 toolbar_main_set_sensitive(mainwin);
5336 main_window_set_menu_sensitive(mainwin);
5341 static gboolean compose_use_attach(Compose *compose)
5343 GtkTreeModel *model = gtk_tree_view_get_model
5344 (GTK_TREE_VIEW(compose->attach_clist));
5345 return gtk_tree_model_iter_n_children(model, NULL) > 0;
5348 static gint compose_redirect_write_headers_from_headerlist(Compose *compose,
5351 gchar buf[BUFFSIZE];
5353 gboolean first_to_address;
5354 gboolean first_cc_address;
5356 ComposeHeaderEntry *headerentry;
5357 const gchar *headerentryname;
5358 const gchar *cc_hdr;
5359 const gchar *to_hdr;
5360 gboolean err = FALSE;
5362 debug_print("Writing redirect header\n");
5364 cc_hdr = prefs_common_translated_header_name("Cc:");
5365 to_hdr = prefs_common_translated_header_name("To:");
5367 first_to_address = TRUE;
5368 for (list = compose->header_list; list; list = list->next) {
5369 headerentry = ((ComposeHeaderEntry *)list->data);
5370 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5372 if (g_utf8_collate(headerentryname, to_hdr) == 0) {
5373 const gchar *entstr = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5374 Xstrdup_a(str, entstr, return -1);
5376 if (str[0] != '\0') {
5377 compose_convert_header
5378 (compose, buf, sizeof(buf), str,
5379 strlen("Resent-To") + 2, TRUE);
5381 if (first_to_address) {
5382 err |= (fprintf(fp, "Resent-To: ") < 0);
5383 first_to_address = FALSE;
5385 err |= (fprintf(fp, ",") < 0);
5387 err |= (fprintf(fp, "%s", buf) < 0);
5391 if (!first_to_address) {
5392 err |= (fprintf(fp, "\n") < 0);
5395 first_cc_address = TRUE;
5396 for (list = compose->header_list; list; list = list->next) {
5397 headerentry = ((ComposeHeaderEntry *)list->data);
5398 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5400 if (g_utf8_collate(headerentryname, cc_hdr) == 0) {
5401 const gchar *strg = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5402 Xstrdup_a(str, strg, return -1);
5404 if (str[0] != '\0') {
5405 compose_convert_header
5406 (compose, buf, sizeof(buf), str,
5407 strlen("Resent-Cc") + 2, TRUE);
5409 if (first_cc_address) {
5410 err |= (fprintf(fp, "Resent-Cc: ") < 0);
5411 first_cc_address = FALSE;
5413 err |= (fprintf(fp, ",") < 0);
5415 err |= (fprintf(fp, "%s", buf) < 0);
5419 if (!first_cc_address) {
5420 err |= (fprintf(fp, "\n") < 0);
5423 return (err ? -1:0);
5426 static gint compose_redirect_write_headers(Compose *compose, FILE *fp)
5428 gchar buf[BUFFSIZE];
5430 const gchar *entstr;
5431 /* struct utsname utsbuf; */
5432 gboolean err = FALSE;
5434 cm_return_val_if_fail(fp != NULL, -1);
5435 cm_return_val_if_fail(compose->account != NULL, -1);
5436 cm_return_val_if_fail(compose->account->address != NULL, -1);
5439 if (prefs_common.hide_timezone)
5440 get_rfc822_date_hide_tz(buf, sizeof(buf));
5442 get_rfc822_date(buf, sizeof(buf));
5443 err |= (fprintf(fp, "Resent-Date: %s\n", buf) < 0);
5446 if (compose->account->name && *compose->account->name) {
5447 compose_convert_header
5448 (compose, buf, sizeof(buf), compose->account->name,
5449 strlen("From: "), TRUE);
5450 err |= (fprintf(fp, "Resent-From: %s <%s>\n",
5451 buf, compose->account->address) < 0);
5453 err |= (fprintf(fp, "Resent-From: %s\n", compose->account->address) < 0);
5456 entstr = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5457 if (*entstr != '\0') {
5458 Xstrdup_a(str, entstr, return -1);
5461 compose_convert_header(compose, buf, sizeof(buf), str,
5462 strlen("Subject: "), FALSE);
5463 err |= (fprintf(fp, "Subject: %s\n", buf) < 0);
5467 /* Resent-Message-ID */
5468 if (compose->account->gen_msgid) {
5469 gchar *addr = prefs_account_generate_msgid(compose->account);
5470 err |= (fprintf(fp, "Resent-Message-ID: <%s>\n", addr) < 0);
5472 g_free(compose->msgid);
5473 compose->msgid = addr;
5475 compose->msgid = NULL;
5478 if (compose_redirect_write_headers_from_headerlist(compose, fp))
5481 /* separator between header and body */
5482 err |= (fputs("\n", fp) == EOF);
5484 return (err ? -1:0);
5487 static gint compose_redirect_write_to_file(Compose *compose, FILE *fdest)
5491 gchar buf[BUFFSIZE];
5493 gboolean skip = FALSE;
5494 gboolean err = FALSE;
5495 gchar *not_included[]={
5496 "Return-Path:", "Delivered-To:", "Received:",
5497 "Subject:", "X-UIDL:", "AF:",
5498 "NF:", "PS:", "SRH:",
5499 "SFN:", "DSR:", "MID:",
5500 "CFG:", "PT:", "S:",
5501 "RQ:", "SSV:", "NSV:",
5502 "SSH:", "R:", "MAID:",
5503 "NAID:", "RMID:", "FMID:",
5504 "SCF:", "RRCPT:", "NG:",
5505 "X-Claws-Privacy", "X-Claws-Sign:", "X-Claws-Encrypt",
5506 "X-Claws-End-Special-Headers:", "X-Claws-Account-Id:",
5507 "X-Sylpheed-Privacy", "X-Sylpheed-Sign:", "X-Sylpheed-Encrypt",
5508 "X-Sylpheed-End-Special-Headers:", "X-Sylpheed-Account-Id:",
5509 "X-Claws-Auto-Wrapping:", "X-Claws-Auto-Indent:",
5512 if ((fp = g_fopen(compose->redirect_filename, "rb")) == NULL) {
5513 FILE_OP_ERROR(compose->redirect_filename, "fopen");
5517 while (procheader_get_one_field_asis(buf, sizeof(buf), fp) != -1) {
5519 for (i = 0; not_included[i] != NULL; i++) {
5520 if (g_ascii_strncasecmp(buf, not_included[i],
5521 strlen(not_included[i])) == 0) {
5528 if (fputs(buf, fdest) == -1)
5531 if (!prefs_common.redirect_keep_from) {
5532 if (g_ascii_strncasecmp(buf, "From:",
5533 strlen("From:")) == 0) {
5534 err |= (fputs(" (by way of ", fdest) == EOF);
5535 if (compose->account->name
5536 && *compose->account->name) {
5537 compose_convert_header
5538 (compose, buf, sizeof(buf),
5539 compose->account->name,
5542 err |= (fprintf(fdest, "%s <%s>",
5544 compose->account->address) < 0);
5546 err |= (fprintf(fdest, "%s",
5547 compose->account->address) < 0);
5548 err |= (fputs(")", fdest) == EOF);
5552 if (fputs("\n", fdest) == -1)
5559 if (compose_redirect_write_headers(compose, fdest))
5562 while ((len = fread(buf, sizeof(gchar), sizeof(buf), fp)) > 0) {
5563 if (fwrite(buf, sizeof(gchar), len, fdest) != len)
5576 static gint compose_write_to_file(Compose *compose, FILE *fp, gint action, gboolean attach_parts)
5578 GtkTextBuffer *buffer;
5579 GtkTextIter start, end;
5580 gchar *chars, *tmp_enc_file, *content;
5582 const gchar *out_codeset;
5583 EncodingType encoding = ENC_UNKNOWN;
5584 MimeInfo *mimemsg, *mimetext;
5586 const gchar *src_codeset = CS_INTERNAL;
5587 gchar *from_addr = NULL;
5588 gchar *from_name = NULL;
5591 if (action == COMPOSE_WRITE_FOR_SEND) {
5592 attach_parts = TRUE;
5594 /* We're sending the message, generate a Message-ID
5596 if (compose->msgid == NULL &&
5597 compose->account->gen_msgid) {
5598 compose->msgid = prefs_account_generate_msgid(compose->account);
5602 /* create message MimeInfo */
5603 mimemsg = procmime_mimeinfo_new();
5604 mimemsg->type = MIMETYPE_MESSAGE;
5605 mimemsg->subtype = g_strdup("rfc822");
5606 mimemsg->content = MIMECONTENT_MEM;
5607 mimemsg->tmp = TRUE; /* must free content later */
5608 mimemsg->data.mem = compose_get_header(compose);
5610 /* Create text part MimeInfo */
5611 /* get all composed text */
5612 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5613 gtk_text_buffer_get_start_iter(buffer, &start);
5614 gtk_text_buffer_get_end_iter(buffer, &end);
5615 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5617 out_codeset = conv_get_charset_str(compose->out_encoding);
5619 if (!out_codeset && is_ascii_str(chars)) {
5620 out_codeset = CS_US_ASCII;
5621 } else if (prefs_common.outgoing_fallback_to_ascii &&
5622 is_ascii_str(chars)) {
5623 out_codeset = CS_US_ASCII;
5624 encoding = ENC_7BIT;
5628 gchar *test_conv_global_out = NULL;
5629 gchar *test_conv_reply = NULL;
5631 /* automatic mode. be automatic. */
5632 codeconv_set_strict(TRUE);
5634 out_codeset = conv_get_outgoing_charset_str();
5636 debug_print("trying to convert to %s\n", out_codeset);
5637 test_conv_global_out = conv_codeset_strdup(chars, src_codeset, out_codeset);
5640 if (!test_conv_global_out && compose->orig_charset
5641 && strcmp(compose->orig_charset, CS_US_ASCII)) {
5642 out_codeset = compose->orig_charset;
5643 debug_print("failure; trying to convert to %s\n", out_codeset);
5644 test_conv_reply = conv_codeset_strdup(chars, src_codeset, out_codeset);
5647 if (!test_conv_global_out && !test_conv_reply) {
5649 out_codeset = CS_INTERNAL;
5650 debug_print("failure; finally using %s\n", out_codeset);
5652 g_free(test_conv_global_out);
5653 g_free(test_conv_reply);
5654 codeconv_set_strict(FALSE);
5657 if (encoding == ENC_UNKNOWN) {
5658 if (prefs_common.encoding_method == CTE_BASE64)
5659 encoding = ENC_BASE64;
5660 else if (prefs_common.encoding_method == CTE_QUOTED_PRINTABLE)
5661 encoding = ENC_QUOTED_PRINTABLE;
5662 else if (prefs_common.encoding_method == CTE_8BIT)
5663 encoding = ENC_8BIT;
5665 encoding = procmime_get_encoding_for_charset(out_codeset);
5668 debug_print("src encoding = %s, out encoding = %s, transfer encoding = %s\n",
5669 src_codeset, out_codeset, procmime_get_encoding_str(encoding));
5671 if (action == COMPOSE_WRITE_FOR_SEND) {
5672 codeconv_set_strict(TRUE);
5673 buf = conv_codeset_strdup(chars, src_codeset, out_codeset);
5674 codeconv_set_strict(FALSE);
5679 msg = g_strdup_printf(_("Can't convert the character encoding of the message \n"
5680 "to the specified %s charset.\n"
5681 "Send it as %s?"), out_codeset, src_codeset);
5682 aval = alertpanel_full(_("Error"), msg, GTK_STOCK_CANCEL,
5683 g_strconcat("+", _("_Send"), NULL), NULL, FALSE,
5684 NULL, ALERT_ERROR, G_ALERTDEFAULT);
5687 if (aval != G_ALERTALTERNATE) {
5692 out_codeset = src_codeset;
5698 out_codeset = src_codeset;
5703 if (prefs_common.rewrite_first_from && (encoding == ENC_8BIT || encoding == ENC_7BIT)) {
5704 if (!strncmp(buf, "From ", sizeof("From ")-1) ||
5705 strstr(buf, "\nFrom ") != NULL) {
5706 encoding = ENC_QUOTED_PRINTABLE;
5710 mimetext = procmime_mimeinfo_new();
5711 mimetext->content = MIMECONTENT_MEM;
5712 mimetext->tmp = TRUE; /* must free content later */
5713 /* dup'ed because procmime_encode_content can turn it into a tmpfile
5714 * and free the data, which we need later. */
5715 mimetext->data.mem = g_strdup(buf);
5716 mimetext->type = MIMETYPE_TEXT;
5717 mimetext->subtype = g_strdup("plain");
5718 g_hash_table_insert(mimetext->typeparameters, g_strdup("charset"),
5719 g_strdup(out_codeset));
5721 /* protect trailing spaces when signing message */
5722 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5723 privacy_system_can_sign(compose->privacy_system)) {
5724 encoding = ENC_QUOTED_PRINTABLE;
5728 debug_print("main text: %Id bytes encoded as %s in %d\n",
5730 debug_print("main text: %zd bytes encoded as %s in %d\n",
5732 strlen(buf), out_codeset, encoding);
5734 /* check for line length limit */
5735 if (action == COMPOSE_WRITE_FOR_SEND &&
5736 encoding != ENC_QUOTED_PRINTABLE && encoding != ENC_BASE64 &&
5737 check_line_length(buf, 1000, &line) < 0) {
5740 msg = g_strdup_printf
5741 (_("Line %d exceeds the line length limit (998 bytes).\n"
5742 "The contents of the message might be broken on the way to the delivery.\n"
5744 "Send it anyway?"), line + 1);
5745 aval = alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, GTK_STOCK_OK, NULL);
5747 if (aval != G_ALERTALTERNATE) {
5753 if (encoding != ENC_UNKNOWN)
5754 procmime_encode_content(mimetext, encoding);
5756 /* append attachment parts */
5757 if (compose_use_attach(compose) && attach_parts) {
5758 MimeInfo *mimempart;
5759 gchar *boundary = NULL;
5760 mimempart = procmime_mimeinfo_new();
5761 mimempart->content = MIMECONTENT_EMPTY;
5762 mimempart->type = MIMETYPE_MULTIPART;
5763 mimempart->subtype = g_strdup("mixed");
5767 boundary = generate_mime_boundary(NULL);
5768 } while (strstr(buf, boundary) != NULL);
5770 g_hash_table_insert(mimempart->typeparameters, g_strdup("boundary"),
5773 mimetext->disposition = DISPOSITIONTYPE_INLINE;
5775 g_node_append(mimempart->node, mimetext->node);
5776 g_node_append(mimemsg->node, mimempart->node);
5778 if (compose_add_attachments(compose, mimempart) < 0)
5781 g_node_append(mimemsg->node, mimetext->node);
5785 if (strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) != 0) {
5786 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
5787 /* extract name and address */
5788 if (strstr(spec, " <") && strstr(spec, ">")) {
5789 from_addr = g_strdup(strrchr(spec, '<')+1);
5790 *(strrchr(from_addr, '>')) = '\0';
5791 from_name = g_strdup(spec);
5792 *(strrchr(from_name, '<')) = '\0';
5799 /* sign message if sending */
5800 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5801 privacy_system_can_sign(compose->privacy_system))
5802 if (!privacy_sign(compose->privacy_system, mimemsg,
5803 compose->account, from_addr)) {
5811 if (compose->use_encryption) {
5812 if (compose->encdata != NULL &&
5813 strcmp(compose->encdata, "_DONT_ENCRYPT_")) {
5815 /* First, write an unencrypted copy and save it to outbox, if
5816 * user wants that. */
5817 if (compose->account->save_encrypted_as_clear_text) {
5818 debug_print("saving sent message unencrypted...\n");
5819 FILE *tmpfp = get_tmpfile_in_dir(get_mime_tmp_dir(), &tmp_enc_file);
5823 /* fp now points to a file with headers written,
5824 * let's make a copy. */
5826 content = file_read_stream_to_str(fp);
5828 str_write_to_file(content, tmp_enc_file);
5831 /* Now write the unencrypted body. */
5832 if ((tmpfp = g_fopen(tmp_enc_file, "a")) != NULL) {
5833 procmime_write_mimeinfo(mimemsg, tmpfp);
5836 outbox = folder_find_item_from_identifier(compose_get_save_to(compose));
5838 outbox = folder_get_default_outbox();
5840 procmsg_save_to_outbox(outbox, tmp_enc_file, TRUE);
5841 claws_unlink(tmp_enc_file);
5843 g_warning("Can't open file '%s'", tmp_enc_file);
5846 g_warning("couldn't get tempfile");
5849 if (!privacy_encrypt(compose->privacy_system, mimemsg, compose->encdata)) {
5850 debug_print("Couldn't encrypt mime structure: %s.\n",
5851 privacy_get_error());
5852 alertpanel_error(_("Couldn't encrypt the email: %s"),
5853 privacy_get_error());
5858 procmime_write_mimeinfo(mimemsg, fp);
5860 procmime_mimeinfo_free_all(&mimemsg);
5865 static gint compose_write_body_to_file(Compose *compose, const gchar *file)
5867 GtkTextBuffer *buffer;
5868 GtkTextIter start, end;
5873 if ((fp = g_fopen(file, "wb")) == NULL) {
5874 FILE_OP_ERROR(file, "fopen");
5878 /* chmod for security */
5879 if (change_file_mode_rw(fp, file) < 0) {
5880 FILE_OP_ERROR(file, "chmod");
5881 g_warning("can't change file mode");
5884 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5885 gtk_text_buffer_get_start_iter(buffer, &start);
5886 gtk_text_buffer_get_end_iter(buffer, &end);
5887 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5889 chars = conv_codeset_strdup
5890 (tmp, CS_INTERNAL, conv_get_locale_charset_str());
5899 len = strlen(chars);
5900 if (fwrite(chars, sizeof(gchar), len, fp) != len) {
5901 FILE_OP_ERROR(file, "fwrite");
5910 if (fclose(fp) == EOF) {
5911 FILE_OP_ERROR(file, "fclose");
5918 static gint compose_remove_reedit_target(Compose *compose, gboolean force)
5921 MsgInfo *msginfo = compose->targetinfo;
5923 cm_return_val_if_fail(compose->mode == COMPOSE_REEDIT, -1);
5924 if (!msginfo) return -1;
5926 if (!force && MSG_IS_LOCKED(msginfo->flags))
5929 item = msginfo->folder;
5930 cm_return_val_if_fail(item != NULL, -1);
5932 if (procmsg_msg_exist(msginfo) &&
5933 (folder_has_parent_of_type(item, F_QUEUE) ||
5934 folder_has_parent_of_type(item, F_DRAFT)
5935 || msginfo == compose->autosaved_draft)) {
5936 if (folder_item_remove_msg(item, msginfo->msgnum) < 0) {
5937 g_warning("can't remove the old message");
5940 debug_print("removed reedit target %d\n", msginfo->msgnum);
5947 static void compose_remove_draft(Compose *compose)
5950 MsgInfo *msginfo = compose->targetinfo;
5951 drafts = account_get_special_folder(compose->account, F_DRAFT);
5953 if (procmsg_msg_exist(msginfo)) {
5954 folder_item_remove_msg(drafts, msginfo->msgnum);
5959 gint compose_queue(Compose *compose, gint *msgnum, FolderItem **item, gchar **msgpath,
5960 gboolean remove_reedit_target)
5962 return compose_queue_sub (compose, msgnum, item, msgpath, FALSE, remove_reedit_target);
5965 static gboolean compose_warn_encryption(Compose *compose)
5967 const gchar *warning = privacy_get_encrypt_warning(compose->privacy_system);
5968 AlertValue val = G_ALERTALTERNATE;
5970 if (warning == NULL)
5973 val = alertpanel_full(_("Encryption warning"), warning,
5974 GTK_STOCK_CANCEL, g_strconcat("+", _("C_ontinue"), NULL), NULL,
5975 TRUE, NULL, ALERT_WARNING, G_ALERTALTERNATE);
5976 if (val & G_ALERTDISABLE) {
5977 val &= ~G_ALERTDISABLE;
5978 if (val == G_ALERTALTERNATE)
5979 privacy_inhibit_encrypt_warning(compose->privacy_system,
5983 if (val == G_ALERTALTERNATE) {
5990 static gint compose_queue_sub(Compose *compose, gint *msgnum, FolderItem **item,
5991 gchar **msgpath, gboolean perform_checks,
5992 gboolean remove_reedit_target)
5999 PrefsAccount *mailac = NULL, *newsac = NULL;
6000 gboolean err = FALSE;
6002 debug_print("queueing message...\n");
6003 cm_return_val_if_fail(compose->account != NULL, -1);
6005 if (compose_check_entries(compose, perform_checks) == FALSE) {
6006 if (compose->batch) {
6007 gtk_widget_show_all(compose->window);
6012 if (!compose->to_list && !compose->newsgroup_list) {
6013 g_warning("can't get recipient list.");
6017 if (compose->to_list) {
6018 if (compose->account->protocol != A_NNTP)
6019 mailac = compose->account;
6020 else if (cur_account && cur_account->protocol != A_NNTP)
6021 mailac = cur_account;
6022 else if (!(mailac = compose_current_mail_account())) {
6023 alertpanel_error(_("No account for sending mails available!"));
6028 if (compose->newsgroup_list) {
6029 if (compose->account->protocol == A_NNTP)
6030 newsac = compose->account;
6032 alertpanel_error(_("Selected account isn't NNTP: Posting is impossible."));
6037 /* write queue header */
6038 tmp = g_strdup_printf("%s%cqueue.%p%08x", get_tmp_dir(),
6039 G_DIR_SEPARATOR, compose, (guint) rand());
6040 debug_print("queuing to %s\n", tmp);
6041 if ((fp = g_fopen(tmp, "w+b")) == NULL) {
6042 FILE_OP_ERROR(tmp, "fopen");
6047 if (change_file_mode_rw(fp, tmp) < 0) {
6048 FILE_OP_ERROR(tmp, "chmod");
6049 g_warning("can't change file mode");
6052 /* queueing variables */
6053 err |= (fprintf(fp, "AF:\n") < 0);
6054 err |= (fprintf(fp, "NF:0\n") < 0);
6055 err |= (fprintf(fp, "PS:10\n") < 0);
6056 err |= (fprintf(fp, "SRH:1\n") < 0);
6057 err |= (fprintf(fp, "SFN:\n") < 0);
6058 err |= (fprintf(fp, "DSR:\n") < 0);
6060 err |= (fprintf(fp, "MID:<%s>\n", compose->msgid) < 0);
6062 err |= (fprintf(fp, "MID:\n") < 0);
6063 err |= (fprintf(fp, "CFG:\n") < 0);
6064 err |= (fprintf(fp, "PT:0\n") < 0);
6065 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
6066 err |= (fprintf(fp, "RQ:\n") < 0);
6068 err |= (fprintf(fp, "SSV:%s\n", mailac->smtp_server) < 0);
6070 err |= (fprintf(fp, "SSV:\n") < 0);
6072 err |= (fprintf(fp, "NSV:%s\n", newsac->nntp_server) < 0);
6074 err |= (fprintf(fp, "NSV:\n") < 0);
6075 err |= (fprintf(fp, "SSH:\n") < 0);
6076 /* write recepient list */
6077 if (compose->to_list) {
6078 err |= (fprintf(fp, "R:<%s>", (gchar *)compose->to_list->data) < 0);
6079 for (cur = compose->to_list->next; cur != NULL;
6081 err |= (fprintf(fp, ",<%s>", (gchar *)cur->data) < 0);
6082 err |= (fprintf(fp, "\n") < 0);
6084 /* write newsgroup list */
6085 if (compose->newsgroup_list) {
6086 err |= (fprintf(fp, "NG:") < 0);
6087 err |= (fprintf(fp, "%s", (gchar *)compose->newsgroup_list->data) < 0);
6088 for (cur = compose->newsgroup_list->next; cur != NULL; cur = cur->next)
6089 err |= (fprintf(fp, ",%s", (gchar *)cur->data) < 0);
6090 err |= (fprintf(fp, "\n") < 0);
6092 /* Sylpheed account IDs */
6094 err |= (fprintf(fp, "MAID:%d\n", mailac->account_id) < 0);
6096 err |= (fprintf(fp, "NAID:%d\n", newsac->account_id) < 0);
6099 if (compose->privacy_system != NULL) {
6100 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
6101 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
6102 if (compose->use_encryption) {
6103 if (!compose_warn_encryption(compose)) {
6109 if (mailac && mailac->encrypt_to_self) {
6110 GSList *tmp_list = g_slist_copy(compose->to_list);
6111 tmp_list = g_slist_append(tmp_list, compose->account->address);
6112 compose->encdata = privacy_get_encrypt_data(compose->privacy_system, tmp_list);
6113 g_slist_free(tmp_list);
6115 compose->encdata = privacy_get_encrypt_data(compose->privacy_system, compose->to_list);
6117 if (compose->encdata != NULL) {
6118 if (strcmp(compose->encdata, "_DONT_ENCRYPT_")) {
6119 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
6120 err |= (fprintf(fp, "X-Claws-Encrypt-Data:%s\n",
6121 compose->encdata) < 0);
6122 } /* else we finally dont want to encrypt */
6124 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
6125 /* and if encdata was null, it means there's been a problem in
6128 g_warning("failed to write queue message");
6137 /* Save copy folder */
6138 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
6139 gchar *savefolderid;
6141 savefolderid = compose_get_save_to(compose);
6142 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
6143 g_free(savefolderid);
6145 /* Save copy folder */
6146 if (compose->return_receipt) {
6147 err |= (fprintf(fp, "RRCPT:1\n") < 0);
6149 /* Message-ID of message replying to */
6150 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
6151 gchar *folderid = NULL;
6153 if (compose->replyinfo->folder)
6154 folderid = folder_item_get_identifier(compose->replyinfo->folder);
6155 if (folderid == NULL)
6156 folderid = g_strdup("NULL");
6158 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
6161 /* Message-ID of message forwarding to */
6162 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
6163 gchar *folderid = NULL;
6165 if (compose->fwdinfo->folder)
6166 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
6167 if (folderid == NULL)
6168 folderid = g_strdup("NULL");
6170 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
6174 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
6175 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
6177 /* end of headers */
6178 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
6180 if (compose->redirect_filename != NULL) {
6181 if (compose_redirect_write_to_file(compose, fp) < 0) {
6189 if ((result = compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_SEND, TRUE)) < 0) {
6193 return result - 1; /* -2 for a generic error, -3 for signing error, -4 for encoding */
6197 g_warning("failed to write queue message");
6203 if (fclose(fp) == EOF) {
6204 FILE_OP_ERROR(tmp, "fclose");
6210 if (item && *item) {
6213 queue = account_get_special_folder(compose->account, F_QUEUE);
6216 g_warning("can't find queue folder");
6221 folder_item_scan(queue);
6222 if ((num = folder_item_add_msg(queue, tmp, NULL, FALSE)) < 0) {
6223 g_warning("can't queue the message");
6229 if (msgpath == NULL) {
6235 if (compose->mode == COMPOSE_REEDIT && remove_reedit_target) {
6236 compose_remove_reedit_target(compose, FALSE);
6239 if ((msgnum != NULL) && (item != NULL)) {
6247 static int compose_add_attachments(Compose *compose, MimeInfo *parent)
6250 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
6253 gchar *type, *subtype;
6254 GtkTreeModel *model;
6257 model = gtk_tree_view_get_model(tree_view);
6259 if (!gtk_tree_model_get_iter_first(model, &iter))
6262 gtk_tree_model_get(model, &iter,
6266 if (!is_file_exist(ainfo->file)) {
6267 gchar *msg = g_strdup_printf(_("Attachment %s doesn't exist anymore. Ignore?"), ainfo->file);
6268 AlertValue val = alertpanel_full(_("Warning"), msg, _("Cancel sending"), _("Ignore attachment"),
6269 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
6271 if (val == G_ALERTDEFAULT) {
6276 if (g_stat(ainfo->file, &statbuf) < 0)
6279 mimepart = procmime_mimeinfo_new();
6280 mimepart->content = MIMECONTENT_FILE;
6281 mimepart->data.filename = g_strdup(ainfo->file);
6282 mimepart->tmp = FALSE; /* or we destroy our attachment */
6283 mimepart->offset = 0;
6284 mimepart->length = statbuf.st_size;
6286 type = g_strdup(ainfo->content_type);
6288 if (!strchr(type, '/')) {
6290 type = g_strdup("application/octet-stream");
6293 subtype = strchr(type, '/') + 1;
6294 *(subtype - 1) = '\0';
6295 mimepart->type = procmime_get_media_type(type);
6296 mimepart->subtype = g_strdup(subtype);
6299 if (mimepart->type == MIMETYPE_MESSAGE &&
6300 !g_ascii_strcasecmp(mimepart->subtype, "rfc822")) {
6301 mimepart->disposition = DISPOSITIONTYPE_INLINE;
6302 } else if (mimepart->type == MIMETYPE_TEXT) {
6303 if (!ainfo->name && g_ascii_strcasecmp(mimepart->subtype, "plain")) {
6304 /* Text parts with no name come from multipart/alternative
6305 * forwards. Make sure the recipient won't look at the
6306 * original HTML part by mistake. */
6307 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6308 ainfo->name = g_strdup_printf(_("Original %s part"),
6312 g_hash_table_insert(mimepart->typeparameters,
6313 g_strdup("charset"), g_strdup(ainfo->charset));
6315 if (ainfo->name && mimepart->type != MIMETYPE_MESSAGE) {
6316 if (mimepart->type == MIMETYPE_APPLICATION &&
6317 !strcmp2(mimepart->subtype, "octet-stream"))
6318 g_hash_table_insert(mimepart->typeparameters,
6319 g_strdup("name"), g_strdup(ainfo->name));
6320 g_hash_table_insert(mimepart->dispositionparameters,
6321 g_strdup("filename"), g_strdup(ainfo->name));
6322 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6325 if (mimepart->type == MIMETYPE_MESSAGE
6326 || mimepart->type == MIMETYPE_MULTIPART)
6327 ainfo->encoding = ENC_BINARY;
6328 else if (compose->use_signing) {
6329 if (ainfo->encoding == ENC_7BIT)
6330 ainfo->encoding = ENC_QUOTED_PRINTABLE;
6331 else if (ainfo->encoding == ENC_8BIT)
6332 ainfo->encoding = ENC_BASE64;
6337 procmime_encode_content(mimepart, ainfo->encoding);
6339 g_node_append(parent->node, mimepart->node);
6340 } while (gtk_tree_model_iter_next(model, &iter));
6345 static gchar *compose_quote_list_of_addresses(gchar *str)
6347 GSList *list = NULL, *item = NULL;
6348 gchar *qname = NULL, *faddr = NULL, *result = NULL;
6350 list = address_list_append_with_comments(list, str);
6351 for (item = list; item != NULL; item = item->next) {
6352 gchar *spec = item->data;
6353 gchar *endofname = strstr(spec, " <");
6354 if (endofname != NULL) {
6357 QUOTE_IF_REQUIRED_NORMAL(qname, spec, return NULL);
6358 qqname = escape_internal_quotes(qname, '"');
6360 if (*qname != *spec || qqname != qname) { /* has been quoted, compute new */
6361 gchar *addr = g_strdup(endofname);
6362 gchar *name = (qqname != qname)? qqname: g_strdup(qname);
6363 faddr = g_strconcat(name, addr, NULL);
6366 debug_print("new auto-quoted address: '%s'\n", faddr);
6370 result = g_strdup((faddr != NULL)? faddr: spec);
6372 result = g_strconcat(result,
6374 (faddr != NULL)? faddr: spec,
6377 if (faddr != NULL) {
6382 slist_free_strings_full(list);
6387 #define IS_IN_CUSTOM_HEADER(header) \
6388 (compose->account->add_customhdr && \
6389 custom_header_find(compose->account->customhdr_list, header) != NULL)
6391 static void compose_add_headerfield_from_headerlist(Compose *compose,
6393 const gchar *fieldname,
6394 const gchar *seperator)
6396 gchar *str, *fieldname_w_colon;
6397 gboolean add_field = FALSE;
6399 ComposeHeaderEntry *headerentry;
6400 const gchar *headerentryname;
6401 const gchar *trans_fieldname;
6404 if (IS_IN_CUSTOM_HEADER(fieldname))
6407 debug_print("Adding %s-fields\n", fieldname);
6409 fieldstr = g_string_sized_new(64);
6411 fieldname_w_colon = g_strconcat(fieldname, ":", NULL);
6412 trans_fieldname = prefs_common_translated_header_name(fieldname_w_colon);
6414 for (list = compose->header_list; list; list = list->next) {
6415 headerentry = ((ComposeHeaderEntry *)list->data);
6416 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
6418 if (!g_utf8_collate(trans_fieldname, headerentryname)) {
6419 gchar * ustr = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
6421 str = compose_quote_list_of_addresses(ustr);
6423 if (str != NULL && str[0] != '\0') {
6425 g_string_append(fieldstr, seperator);
6426 g_string_append(fieldstr, str);
6435 buf = g_new0(gchar, fieldstr->len * 4 + 256);
6436 compose_convert_header
6437 (compose, buf, fieldstr->len * 4 + 256, fieldstr->str,
6438 strlen(fieldname) + 2, TRUE);
6439 g_string_append_printf(header, "%s: %s\n", fieldname, buf);
6443 g_free(fieldname_w_colon);
6444 g_string_free(fieldstr, TRUE);
6449 static gchar *compose_get_manual_headers_info(Compose *compose)
6451 GString *sh_header = g_string_new(" ");
6453 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6455 for (list = compose->header_list; list; list = list->next) {
6456 ComposeHeaderEntry *headerentry;
6459 gchar *headername_wcolon;
6460 const gchar *headername_trans;
6462 gboolean standard_header = FALSE;
6464 headerentry = ((ComposeHeaderEntry *)list->data);
6466 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6468 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6473 if (!strstr(tmp, ":")) {
6474 headername_wcolon = g_strconcat(tmp, ":", NULL);
6475 headername = g_strdup(tmp);
6477 headername_wcolon = g_strdup(tmp);
6478 headername = g_strdup(strtok(tmp, ":"));
6482 string = std_headers;
6483 while (*string != NULL) {
6484 headername_trans = prefs_common_translated_header_name(*string);
6485 if (!strcmp(headername_trans, headername_wcolon))
6486 standard_header = TRUE;
6489 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
6490 g_string_append_printf(sh_header, "%s ", headername);
6492 g_free(headername_wcolon);
6494 g_string_truncate(sh_header, strlen(sh_header->str) - 1); /* remove last space */
6495 return g_string_free(sh_header, FALSE);
6498 static gchar *compose_get_header(Compose *compose)
6500 gchar buf[BUFFSIZE];
6501 const gchar *entry_str;
6505 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6507 gchar *from_name = NULL, *from_address = NULL;
6510 cm_return_val_if_fail(compose->account != NULL, NULL);
6511 cm_return_val_if_fail(compose->account->address != NULL, NULL);
6513 header = g_string_sized_new(64);
6516 if (prefs_common.hide_timezone)
6517 get_rfc822_date_hide_tz(buf, sizeof(buf));
6519 get_rfc822_date(buf, sizeof(buf));
6520 g_string_append_printf(header, "Date: %s\n", buf);
6524 if (compose->account->name && *compose->account->name) {
6526 QUOTE_IF_REQUIRED(buf, compose->account->name);
6527 tmp = g_strdup_printf("%s <%s>",
6528 buf, compose->account->address);
6530 tmp = g_strdup_printf("%s",
6531 compose->account->address);
6533 if (!strcmp(gtk_entry_get_text(GTK_ENTRY(compose->from_name)), tmp)
6534 || strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) == 0) {
6536 from_name = compose->account->name ? g_strdup(compose->account->name):NULL;
6537 from_address = g_strdup(compose->account->address);
6539 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
6540 /* extract name and address */
6541 if (strstr(spec, " <") && strstr(spec, ">")) {
6542 from_address = g_strdup(strrchr(spec, '<')+1);
6543 *(strrchr(from_address, '>')) = '\0';
6544 from_name = g_strdup(spec);
6545 *(strrchr(from_name, '<')) = '\0';
6548 from_address = g_strdup(spec);
6555 if (from_name && *from_name) {
6557 compose_convert_header
6558 (compose, buf, sizeof(buf), from_name,
6559 strlen("From: "), TRUE);
6560 QUOTE_IF_REQUIRED(name, buf);
6561 qname = escape_internal_quotes(name, '"');
6563 g_string_append_printf(header, "From: %s <%s>\n",
6564 qname, from_address);
6565 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To") &&
6566 compose->return_receipt) {
6567 compose_convert_header(compose, buf, sizeof(buf), from_name,
6568 strlen("Disposition-Notification-To: "),
6570 g_string_append_printf(header, "Disposition-Notification-To: %s <%s>\n", buf, from_address);
6575 g_string_append_printf(header, "From: %s\n", from_address);
6576 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To") &&
6577 compose->return_receipt)
6578 g_string_append_printf(header, "Disposition-Notification-To: %s\n", from_address);
6582 g_free(from_address);
6585 compose_add_headerfield_from_headerlist(compose, header, "To", ", ");
6588 compose_add_headerfield_from_headerlist(compose, header, "Newsgroups", ",");
6591 compose_add_headerfield_from_headerlist(compose, header, "Cc", ", ");
6595 * If this account is a NNTP account remove Bcc header from
6596 * message body since it otherwise will be publicly shown
6598 if (compose->account->protocol != A_NNTP)
6599 compose_add_headerfield_from_headerlist(compose, header, "Bcc", ", ");
6602 str = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
6604 if (*str != '\0' && !IS_IN_CUSTOM_HEADER("Subject")) {
6607 compose_convert_header(compose, buf, sizeof(buf), str,
6608 strlen("Subject: "), FALSE);
6609 g_string_append_printf(header, "Subject: %s\n", buf);
6615 if (compose->msgid != NULL && strlen(compose->msgid) > 0) {
6616 g_string_append_printf(header, "Message-ID: <%s>\n",
6620 if (compose->remove_references == FALSE) {
6622 if (compose->inreplyto && compose->to_list)
6623 g_string_append_printf(header, "In-Reply-To: <%s>\n", compose->inreplyto);
6626 if (compose->references)
6627 g_string_append_printf(header, "References: %s\n", compose->references);
6631 compose_add_headerfield_from_headerlist(compose, header, "Followup-To", ",");
6634 compose_add_headerfield_from_headerlist(compose, header, "Reply-To", ", ");
6637 if (compose->account->organization &&
6638 strlen(compose->account->organization) &&
6639 !IS_IN_CUSTOM_HEADER("Organization")) {
6640 compose_convert_header(compose, buf, sizeof(buf),
6641 compose->account->organization,
6642 strlen("Organization: "), FALSE);
6643 g_string_append_printf(header, "Organization: %s\n", buf);
6646 /* Program version and system info */
6647 if (compose->account->gen_xmailer &&
6648 g_slist_length(compose->to_list) && !IS_IN_CUSTOM_HEADER("X-Mailer") &&
6649 !compose->newsgroup_list) {
6650 g_string_append_printf(header, "X-Mailer: %s (GTK+ %d.%d.%d; %s)\n",
6652 gtk_major_version, gtk_minor_version, gtk_micro_version,
6655 if (compose->account->gen_xmailer &&
6656 g_slist_length(compose->newsgroup_list) && !IS_IN_CUSTOM_HEADER("X-Newsreader")) {
6657 g_string_append_printf(header, "X-Newsreader: %s (GTK+ %d.%d.%d; %s)\n",
6659 gtk_major_version, gtk_minor_version, gtk_micro_version,
6663 /* custom headers */
6664 if (compose->account->add_customhdr) {
6667 for (cur = compose->account->customhdr_list; cur != NULL;
6669 CustomHeader *chdr = (CustomHeader *)cur->data;
6671 if (custom_header_is_allowed(chdr->name)
6672 && chdr->value != NULL
6673 && *(chdr->value) != '\0') {
6674 compose_convert_header
6675 (compose, buf, sizeof(buf),
6677 strlen(chdr->name) + 2, FALSE);
6678 g_string_append_printf(header, "%s: %s\n", chdr->name, buf);
6683 /* Automatic Faces and X-Faces */
6684 if (get_account_xface (buf, sizeof(buf), compose->account->account_name) == 0) {
6685 g_string_append_printf(header, "X-Face: %s\n", buf);
6687 else if (get_default_xface (buf, sizeof(buf)) == 0) {
6688 g_string_append_printf(header, "X-Face: %s\n", buf);
6690 if (get_account_face (buf, sizeof(buf), compose->account->account_name) == 0) {
6691 g_string_append_printf(header, "Face: %s\n", buf);
6693 else if (get_default_face (buf, sizeof(buf)) == 0) {
6694 g_string_append_printf(header, "Face: %s\n", buf);
6698 switch (compose->priority) {
6699 case PRIORITY_HIGHEST: g_string_append_printf(header, "Importance: high\n"
6700 "X-Priority: 1 (Highest)\n");
6702 case PRIORITY_HIGH: g_string_append_printf(header, "Importance: high\n"
6703 "X-Priority: 2 (High)\n");
6705 case PRIORITY_NORMAL: break;
6706 case PRIORITY_LOW: g_string_append_printf(header, "Importance: low\n"
6707 "X-Priority: 4 (Low)\n");
6709 case PRIORITY_LOWEST: g_string_append_printf(header, "Importance: low\n"
6710 "X-Priority: 5 (Lowest)\n");
6712 default: debug_print("compose: priority unknown : %d\n",
6716 /* get special headers */
6717 for (list = compose->header_list; list; list = list->next) {
6718 ComposeHeaderEntry *headerentry;
6721 gchar *headername_wcolon;
6722 const gchar *headername_trans;
6725 gboolean standard_header = FALSE;
6727 headerentry = ((ComposeHeaderEntry *)list->data);
6729 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6731 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6736 if (!strstr(tmp, ":")) {
6737 headername_wcolon = g_strconcat(tmp, ":", NULL);
6738 headername = g_strdup(tmp);
6740 headername_wcolon = g_strdup(tmp);
6741 headername = g_strdup(strtok(tmp, ":"));
6745 entry_str = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
6746 Xstrdup_a(headervalue, entry_str, return NULL);
6747 subst_char(headervalue, '\r', ' ');
6748 subst_char(headervalue, '\n', ' ');
6749 string = std_headers;
6750 while (*string != NULL) {
6751 headername_trans = prefs_common_translated_header_name(*string);
6752 if (!strcmp(headername_trans, headername_wcolon))
6753 standard_header = TRUE;
6756 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
6757 g_string_append_printf(header, "%s %s\n", headername_wcolon, headervalue);
6760 g_free(headername_wcolon);
6764 g_string_free(header, FALSE);
6769 #undef IS_IN_CUSTOM_HEADER
6771 static void compose_convert_header(Compose *compose, gchar *dest, gint len, gchar *src,
6772 gint header_len, gboolean addr_field)
6774 gchar *tmpstr = NULL;
6775 const gchar *out_codeset = NULL;
6777 cm_return_if_fail(src != NULL);
6778 cm_return_if_fail(dest != NULL);
6780 if (len < 1) return;
6782 tmpstr = g_strdup(src);
6784 subst_char(tmpstr, '\n', ' ');
6785 subst_char(tmpstr, '\r', ' ');
6788 if (!g_utf8_validate(tmpstr, -1, NULL)) {
6789 gchar *mybuf = g_malloc(strlen(tmpstr)*2 +1);
6790 conv_localetodisp(mybuf, strlen(tmpstr)*2 +1, tmpstr);
6795 codeconv_set_strict(TRUE);
6796 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6797 conv_get_charset_str(compose->out_encoding));
6798 codeconv_set_strict(FALSE);
6800 if (!dest || *dest == '\0') {
6801 gchar *test_conv_global_out = NULL;
6802 gchar *test_conv_reply = NULL;
6804 /* automatic mode. be automatic. */
6805 codeconv_set_strict(TRUE);
6807 out_codeset = conv_get_outgoing_charset_str();
6809 debug_print("trying to convert to %s\n", out_codeset);
6810 test_conv_global_out = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6813 if (!test_conv_global_out && compose->orig_charset
6814 && strcmp(compose->orig_charset, CS_US_ASCII)) {
6815 out_codeset = compose->orig_charset;
6816 debug_print("failure; trying to convert to %s\n", out_codeset);
6817 test_conv_reply = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6820 if (!test_conv_global_out && !test_conv_reply) {
6822 out_codeset = CS_INTERNAL;
6823 debug_print("finally using %s\n", out_codeset);
6825 g_free(test_conv_global_out);
6826 g_free(test_conv_reply);
6827 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6829 codeconv_set_strict(FALSE);
6834 static void compose_add_to_addressbook_cb(GtkMenuItem *menuitem, gpointer user_data)
6838 cm_return_if_fail(user_data != NULL);
6840 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(user_data)));
6841 g_strstrip(address);
6842 if (*address != '\0') {
6843 gchar *name = procheader_get_fromname(address);
6844 extract_address(address);
6845 #ifndef USE_ALT_ADDRBOOK
6846 addressbook_add_contact(name, address, NULL, NULL);
6848 debug_print("%s: %s\n", name, address);
6849 if (addressadd_selection(name, address, NULL, NULL)) {
6850 debug_print( "addressbook_add_contact - added\n" );
6857 static void compose_entry_popup_extend(GtkEntry *entry, GtkMenu *menu, gpointer user_data)
6859 GtkWidget *menuitem;
6862 cm_return_if_fail(menu != NULL);
6863 cm_return_if_fail(GTK_IS_MENU_SHELL(menu));
6865 menuitem = gtk_separator_menu_item_new();
6866 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6867 gtk_widget_show(menuitem);
6869 menuitem = gtk_menu_item_new_with_mnemonic(_("Add to address _book"));
6870 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6872 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
6873 g_strstrip(address);
6874 if (*address == '\0') {
6875 gtk_widget_set_sensitive(GTK_WIDGET(menuitem), FALSE);
6878 g_signal_connect(G_OBJECT(menuitem), "activate",
6879 G_CALLBACK(compose_add_to_addressbook_cb), entry);
6880 gtk_widget_show(menuitem);
6883 void compose_add_extra_header(gchar *header, GtkListStore *model)
6886 if (strcmp(header, "")) {
6887 COMBOBOX_ADD(model, header, COMPOSE_TO);
6891 void compose_add_extra_header_entries(GtkListStore *model)
6895 gchar buf[BUFFSIZE];
6898 if (extra_headers == NULL) {
6899 exhrc = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, "extraheaderrc", NULL);
6900 if ((exh = g_fopen(exhrc, "rb")) == NULL) {
6901 debug_print("extra headers file not found\n");
6902 goto extra_headers_done;
6904 while (fgets(buf, BUFFSIZE, exh) != NULL) {
6905 lastc = strlen(buf) - 1; /* remove trailing control chars */
6906 while (lastc >= 0 && buf[lastc] != ':')
6907 buf[lastc--] = '\0';
6908 if (lastc > 0 && buf[0] != '#' && buf[lastc] == ':') {
6909 buf[lastc] = '\0'; /* remove trailing : for comparison */
6910 if (custom_header_is_allowed(buf)) {
6912 extra_headers = g_slist_prepend(extra_headers, g_strdup(buf));
6915 g_message("disallowed extra header line: %s\n", buf);
6919 g_message("invalid extra header line: %s\n", buf);
6925 extra_headers = g_slist_prepend(extra_headers, g_strdup("")); /* end of list */
6926 extra_headers = g_slist_reverse(extra_headers);
6928 g_slist_foreach(extra_headers, (GFunc)compose_add_extra_header, (gpointer)model);
6931 static void compose_create_header_entry(Compose *compose)
6933 gchar *headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6940 const gchar *header = NULL;
6941 ComposeHeaderEntry *headerentry;
6942 gboolean standard_header = FALSE;
6943 GtkListStore *model;
6946 headerentry = g_new0(ComposeHeaderEntry, 1);
6948 /* Combo box model */
6949 model = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_BOOLEAN);
6950 #if !GTK_CHECK_VERSION(2, 24, 0)
6951 combo = gtk_combo_box_entry_new_with_model(GTK_TREE_MODEL(model), 0);
6953 COMBOBOX_ADD(model, prefs_common_translated_header_name("To:"),
6955 COMBOBOX_ADD(model, prefs_common_translated_header_name("Cc:"),
6957 COMBOBOX_ADD(model, prefs_common_translated_header_name("Bcc:"),
6959 COMBOBOX_ADD(model, prefs_common_translated_header_name("Newsgroups:"),
6960 COMPOSE_NEWSGROUPS);
6961 COMBOBOX_ADD(model, prefs_common_translated_header_name("Reply-To:"),
6963 COMBOBOX_ADD(model, prefs_common_translated_header_name("Followup-To:"),
6964 COMPOSE_FOLLOWUPTO);
6965 compose_add_extra_header_entries(model);
6968 #if GTK_CHECK_VERSION(2, 24, 0)
6969 combo = gtk_combo_box_new_with_model_and_entry(GTK_TREE_MODEL(model));
6970 GtkCellRenderer *cell = gtk_cell_renderer_text_new();
6971 gtk_cell_renderer_set_alignment(cell, 0.0, 0.5);
6972 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), cell, TRUE);
6973 gtk_combo_box_set_entry_text_column(GTK_COMBO_BOX(combo), 0);
6975 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
6976 g_signal_connect(G_OBJECT(gtk_bin_get_child(GTK_BIN(combo))), "grab_focus",
6977 G_CALLBACK(compose_grab_focus_cb), compose);
6978 gtk_widget_show(combo);
6980 /* Putting only the combobox child into focus chain of its parent causes
6981 * the parent to be skipped when changing focus via Tab or Shift+Tab.
6982 * This eliminates need to pres Tab twice in order to really get from the
6983 * combobox to next widget. */
6985 l = g_list_prepend(l, gtk_bin_get_child(GTK_BIN(combo)));
6986 gtk_container_set_focus_chain(GTK_CONTAINER(combo), l);
6989 gtk_table_attach(GTK_TABLE(compose->header_table), combo, 0, 1,
6990 compose->header_nextrow, compose->header_nextrow+1,
6991 GTK_SHRINK, GTK_FILL, 0, 0);
6992 if (compose->header_last && (compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN)) {
6993 const gchar *last_header_entry = gtk_entry_get_text(
6994 GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
6996 while (*string != NULL) {
6997 if (!strcmp(prefs_common_translated_header_name(*string), last_header_entry))
6998 standard_header = TRUE;
7001 if (standard_header)
7002 header = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
7004 if (!compose->header_last || !standard_header) {
7005 switch(compose->account->protocol) {
7007 header = prefs_common_translated_header_name("Newsgroups:");
7010 header = prefs_common_translated_header_name("To:");
7015 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((combo)))), header);
7017 gtk_editable_set_editable(
7018 GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((combo)))),
7019 prefs_common.type_any_header);
7021 g_signal_connect_after(G_OBJECT(gtk_bin_get_child(GTK_BIN((combo)))), "grab_focus",
7022 G_CALLBACK(compose_grab_focus_cb), compose);
7024 /* Entry field with cleanup button */
7025 button = gtk_button_new();
7026 gtk_button_set_image(GTK_BUTTON(button),
7027 gtk_image_new_from_stock(GTK_STOCK_CLEAR, GTK_ICON_SIZE_MENU));
7028 gtk_widget_show(button);
7029 CLAWS_SET_TIP(button,
7030 _("Delete entry contents"));
7031 entry = gtk_entry_new();
7032 gtk_widget_show(entry);
7033 CLAWS_SET_TIP(entry,
7034 _("Use <tab> to autocomplete from addressbook"));
7035 hbox = gtk_hbox_new (FALSE, 0);
7036 gtk_widget_show(hbox);
7037 gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, TRUE, 0);
7038 gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
7039 gtk_table_attach(GTK_TABLE(compose->header_table), hbox, 1, 2,
7040 compose->header_nextrow, compose->header_nextrow+1,
7041 GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
7043 g_signal_connect(G_OBJECT(entry), "key-press-event",
7044 G_CALLBACK(compose_headerentry_key_press_event_cb),
7046 g_signal_connect(G_OBJECT(entry), "changed",
7047 G_CALLBACK(compose_headerentry_changed_cb),
7049 g_signal_connect_after(G_OBJECT(entry), "grab_focus",
7050 G_CALLBACK(compose_grab_focus_cb), compose);
7052 g_signal_connect(G_OBJECT(button), "clicked",
7053 G_CALLBACK(compose_headerentry_button_clicked_cb),
7057 gtk_drag_dest_set(entry, GTK_DEST_DEFAULT_ALL, compose_mime_types,
7058 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7059 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7060 g_signal_connect(G_OBJECT(entry), "drag_data_received",
7061 G_CALLBACK(compose_header_drag_received_cb),
7063 g_signal_connect(G_OBJECT(entry), "drag-drop",
7064 G_CALLBACK(compose_drag_drop),
7066 g_signal_connect(G_OBJECT(entry), "populate-popup",
7067 G_CALLBACK(compose_entry_popup_extend),
7070 address_completion_register_entry(GTK_ENTRY(entry), TRUE);
7072 headerentry->compose = compose;
7073 headerentry->combo = combo;
7074 headerentry->entry = entry;
7075 headerentry->button = button;
7076 headerentry->hbox = hbox;
7077 headerentry->headernum = compose->header_nextrow;
7078 headerentry->type = PREF_NONE;
7080 compose->header_nextrow++;
7081 compose->header_last = headerentry;
7082 compose->header_list =
7083 g_slist_append(compose->header_list,
7087 static void compose_add_header_entry(Compose *compose, const gchar *header,
7088 gchar *text, ComposePrefType pref_type)
7090 ComposeHeaderEntry *last_header = compose->header_last;
7091 gchar *tmp = g_strdup(text), *email;
7092 gboolean replyto_hdr;
7094 replyto_hdr = (!strcasecmp(header,
7095 prefs_common_translated_header_name("Reply-To:")) ||
7097 prefs_common_translated_header_name("Followup-To:")) ||
7099 prefs_common_translated_header_name("In-Reply-To:")));
7101 extract_address(tmp);
7102 email = g_utf8_strdown(tmp, -1);
7104 if (replyto_hdr == FALSE &&
7105 g_hash_table_lookup(compose->email_hashtable, email) != NULL)
7107 debug_print("Ignoring duplicate address - %s %s, pref_type: %d\n",
7108 header, text, (gint) pref_type);
7114 if (!strcasecmp(header, prefs_common_translated_header_name("In-Reply-To:")))
7115 gtk_entry_set_text(GTK_ENTRY(
7116 gtk_bin_get_child(GTK_BIN(last_header->combo))), header);
7118 combobox_select_by_text(GTK_COMBO_BOX(last_header->combo), header);
7119 gtk_entry_set_text(GTK_ENTRY(last_header->entry), text);
7120 last_header->type = pref_type;
7122 if (replyto_hdr == FALSE)
7123 g_hash_table_insert(compose->email_hashtable, email,
7124 GUINT_TO_POINTER(1));
7131 static void compose_destroy_headerentry(Compose *compose,
7132 ComposeHeaderEntry *headerentry)
7134 gchar *text = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
7137 extract_address(text);
7138 email = g_utf8_strdown(text, -1);
7139 g_hash_table_remove(compose->email_hashtable, email);
7143 gtk_widget_destroy(headerentry->combo);
7144 gtk_widget_destroy(headerentry->entry);
7145 gtk_widget_destroy(headerentry->button);
7146 gtk_widget_destroy(headerentry->hbox);
7147 g_free(headerentry);
7150 static void compose_remove_header_entries(Compose *compose)
7153 for (list = compose->header_list; list; list = list->next)
7154 compose_destroy_headerentry(compose, (ComposeHeaderEntry *)list->data);
7156 compose->header_last = NULL;
7157 g_slist_free(compose->header_list);
7158 compose->header_list = NULL;
7159 compose->header_nextrow = 1;
7160 compose_create_header_entry(compose);
7163 static GtkWidget *compose_create_header(Compose *compose)
7165 GtkWidget *from_optmenu_hbox;
7166 GtkWidget *header_table_main;
7167 GtkWidget *header_scrolledwin;
7168 GtkWidget *header_table;
7170 /* parent with account selection and from header */
7171 header_table_main = gtk_table_new(2, 2, FALSE);
7172 gtk_widget_show(header_table_main);
7173 gtk_container_set_border_width(GTK_CONTAINER(header_table_main), BORDER_WIDTH);
7175 from_optmenu_hbox = compose_account_option_menu_create(compose);
7176 gtk_table_attach(GTK_TABLE(header_table_main), from_optmenu_hbox,
7177 0, 2, 0, 1, GTK_EXPAND | GTK_FILL, GTK_SHRINK, 0, 0);
7179 /* child with header labels and entries */
7180 header_scrolledwin = gtk_scrolled_window_new(NULL, NULL);
7181 gtk_widget_show(header_scrolledwin);
7182 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
7184 header_table = gtk_table_new(2, 2, FALSE);
7185 gtk_widget_show(header_table);
7186 gtk_container_set_border_width(GTK_CONTAINER(header_table), 0);
7187 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin), header_table);
7188 gtk_container_set_focus_vadjustment(GTK_CONTAINER(header_table),
7189 gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(header_scrolledwin)));
7190 gtk_viewport_set_shadow_type(GTK_VIEWPORT(gtk_bin_get_child(GTK_BIN(header_scrolledwin))), GTK_SHADOW_NONE);
7192 gtk_table_attach(GTK_TABLE(header_table_main), header_scrolledwin,
7193 0, 2, 1, 2, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 2);
7195 compose->header_table = header_table;
7196 compose->header_list = NULL;
7197 compose->header_nextrow = 0;
7199 compose_create_header_entry(compose);
7201 compose->table = NULL;
7203 return header_table_main;
7206 static gboolean popup_attach_button_pressed(GtkWidget *widget, gpointer data)
7208 Compose *compose = (Compose *)data;
7209 GdkEventButton event;
7212 event.time = gtk_get_current_event_time();
7214 return attach_button_pressed(compose->attach_clist, &event, compose);
7217 static GtkWidget *compose_create_attach(Compose *compose)
7219 GtkWidget *attach_scrwin;
7220 GtkWidget *attach_clist;
7222 GtkListStore *store;
7223 GtkCellRenderer *renderer;
7224 GtkTreeViewColumn *column;
7225 GtkTreeSelection *selection;
7227 /* attachment list */
7228 attach_scrwin = gtk_scrolled_window_new(NULL, NULL);
7229 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(attach_scrwin),
7230 GTK_POLICY_AUTOMATIC,
7231 GTK_POLICY_AUTOMATIC);
7232 gtk_widget_set_size_request(attach_scrwin, -1, 80);
7234 store = gtk_list_store_new(N_ATTACH_COLS,
7240 G_TYPE_AUTO_POINTER,
7242 attach_clist = GTK_WIDGET(gtk_tree_view_new_with_model
7243 (GTK_TREE_MODEL(store)));
7244 gtk_container_add(GTK_CONTAINER(attach_scrwin), attach_clist);
7245 g_object_unref(store);
7247 renderer = gtk_cell_renderer_text_new();
7248 column = gtk_tree_view_column_new_with_attributes
7249 (_("Mime type"), renderer, "text",
7250 COL_MIMETYPE, NULL);
7251 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7253 renderer = gtk_cell_renderer_text_new();
7254 column = gtk_tree_view_column_new_with_attributes
7255 (_("Size"), renderer, "text",
7257 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7259 renderer = gtk_cell_renderer_text_new();
7260 column = gtk_tree_view_column_new_with_attributes
7261 (_("Name"), renderer, "text",
7263 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7265 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(attach_clist),
7266 prefs_common.use_stripes_everywhere);
7267 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(attach_clist));
7268 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
7270 g_signal_connect(G_OBJECT(attach_clist), "row_activated",
7271 G_CALLBACK(attach_selected), compose);
7272 g_signal_connect(G_OBJECT(attach_clist), "button_press_event",
7273 G_CALLBACK(attach_button_pressed), compose);
7274 g_signal_connect(G_OBJECT(attach_clist), "popup-menu",
7275 G_CALLBACK(popup_attach_button_pressed), compose);
7276 g_signal_connect(G_OBJECT(attach_clist), "key_press_event",
7277 G_CALLBACK(attach_key_pressed), compose);
7280 gtk_drag_dest_set(attach_clist,
7281 GTK_DEST_DEFAULT_ALL, compose_mime_types,
7282 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7283 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7284 g_signal_connect(G_OBJECT(attach_clist), "drag_data_received",
7285 G_CALLBACK(compose_attach_drag_received_cb),
7287 g_signal_connect(G_OBJECT(attach_clist), "drag-drop",
7288 G_CALLBACK(compose_drag_drop),
7291 compose->attach_scrwin = attach_scrwin;
7292 compose->attach_clist = attach_clist;
7294 return attach_scrwin;
7297 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose);
7298 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose);
7300 static GtkWidget *compose_create_others(Compose *compose)
7303 GtkWidget *savemsg_checkbtn;
7304 GtkWidget *savemsg_combo;
7305 GtkWidget *savemsg_select;
7308 gchar *folderidentifier;
7310 /* Table for settings */
7311 table = gtk_table_new(3, 1, FALSE);
7312 gtk_container_set_border_width(GTK_CONTAINER(table), BORDER_WIDTH);
7313 gtk_widget_show(table);
7314 gtk_table_set_row_spacings(GTK_TABLE(table), VSPACING_NARROW);
7317 /* Save Message to folder */
7318 savemsg_checkbtn = gtk_check_button_new_with_label(_("Save Message to "));
7319 gtk_widget_show(savemsg_checkbtn);
7320 gtk_table_attach(GTK_TABLE(table), savemsg_checkbtn, 0, 1, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7321 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7322 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), prefs_common.savemsg);
7324 g_signal_connect(G_OBJECT(savemsg_checkbtn), "toggled",
7325 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
7327 #if !GTK_CHECK_VERSION(2, 24, 0)
7328 savemsg_combo = gtk_combo_box_entry_new_text();
7330 savemsg_combo = gtk_combo_box_text_new_with_entry();
7332 compose->savemsg_checkbtn = savemsg_checkbtn;
7333 compose->savemsg_combo = savemsg_combo;
7334 gtk_widget_show(savemsg_combo);
7336 if (prefs_common.compose_save_to_history)
7337 #if !GTK_CHECK_VERSION(2, 24, 0)
7338 combobox_set_popdown_strings(GTK_COMBO_BOX(savemsg_combo),
7339 prefs_common.compose_save_to_history);
7341 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(savemsg_combo),
7342 prefs_common.compose_save_to_history);
7344 gtk_table_attach(GTK_TABLE(table), savemsg_combo, 1, 2, rowcount, rowcount + 1, GTK_FILL|GTK_EXPAND, GTK_SHRINK, 0, 0);
7345 gtk_widget_set_sensitive(GTK_WIDGET(savemsg_combo), prefs_common.savemsg);
7346 g_signal_connect_after(G_OBJECT(savemsg_combo), "grab_focus",
7347 G_CALLBACK(compose_grab_focus_cb), compose);
7348 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7349 folderidentifier = folder_item_get_identifier(account_get_special_folder
7350 (compose->account, F_OUTBOX));
7351 compose_set_save_to(compose, folderidentifier);
7352 g_free(folderidentifier);
7355 savemsg_select = gtkut_get_browse_file_btn(_("_Browse"));
7356 gtk_widget_show(savemsg_select);
7357 gtk_table_attach(GTK_TABLE(table), savemsg_select, 2, 3, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7358 g_signal_connect(G_OBJECT(savemsg_select), "clicked",
7359 G_CALLBACK(compose_savemsg_select_cb),
7365 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose)
7367 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo),
7368 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn)));
7371 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose)
7376 dest = foldersel_folder_sel(NULL, FOLDER_SEL_COPY, NULL, FALSE);
7379 path = folder_item_get_identifier(dest);
7381 compose_set_save_to(compose, path);
7385 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry, gboolean wrap,
7386 GdkAtom clip, GtkTextIter *insert_place);
7389 static gboolean text_clicked(GtkWidget *text, GdkEventButton *event,
7393 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
7395 if (event->button == 3) {
7397 GtkTextIter sel_start, sel_end;
7398 gboolean stuff_selected;
7400 /* move the cursor to allow GtkAspell to check the word
7401 * under the mouse */
7402 if (event->x && event->y) {
7403 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7404 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7406 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7409 GtkTextMark *mark = gtk_text_buffer_get_insert(buffer);
7410 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7413 stuff_selected = gtk_text_buffer_get_selection_bounds(
7415 &sel_start, &sel_end);
7417 gtk_text_buffer_place_cursor (buffer, &iter);
7418 /* reselect stuff */
7420 && gtk_text_iter_in_range(&iter, &sel_start, &sel_end)) {
7421 gtk_text_buffer_select_range(buffer,
7422 &sel_start, &sel_end);
7424 return FALSE; /* pass the event so that the right-click goes through */
7427 if (event->button == 2) {
7432 /* get the middle-click position to paste at the correct place */
7433 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7434 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7436 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7439 entry_paste_clipboard(compose, text,
7440 prefs_common.linewrap_pastes,
7441 GDK_SELECTION_PRIMARY, &iter);
7449 static void compose_spell_menu_changed(void *data)
7451 Compose *compose = (Compose *)data;
7453 GtkWidget *menuitem;
7454 GtkWidget *parent_item;
7455 GtkMenu *menu = GTK_MENU(gtk_menu_new());
7458 if (compose->gtkaspell == NULL)
7461 parent_item = gtk_ui_manager_get_widget(compose->ui_manager,
7462 "/Menu/Spelling/Options");
7464 /* setting the submenu removes /Spelling/Options from the factory
7465 * so we need to save it */
7467 if (parent_item == NULL) {
7468 parent_item = compose->aspell_options_menu;
7469 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), NULL);
7471 compose->aspell_options_menu = parent_item;
7473 spell_menu = gtkaspell_make_config_menu(compose->gtkaspell);
7475 spell_menu = g_slist_reverse(spell_menu);
7476 for (items = spell_menu;
7477 items; items = items->next) {
7478 menuitem = GTK_WIDGET(GTK_MENU_ITEM(items->data));
7479 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), GTK_WIDGET(menuitem));
7480 gtk_widget_show(GTK_WIDGET(menuitem));
7482 g_slist_free(spell_menu);
7484 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), GTK_WIDGET(menu));
7485 gtk_widget_show(parent_item);
7488 static void compose_dict_changed(void *data)
7490 Compose *compose = (Compose *) data;
7492 if(!compose->gtkaspell)
7494 if(compose->gtkaspell->recheck_when_changing_dict == FALSE)
7497 gtkaspell_highlight_all(compose->gtkaspell);
7498 claws_spell_entry_recheck_all(CLAWS_SPELL_ENTRY(compose->subject_entry));
7502 static gboolean compose_popup_menu(GtkWidget *widget, gpointer data)
7504 Compose *compose = (Compose *)data;
7505 GdkEventButton event;
7508 event.time = gtk_get_current_event_time();
7512 return text_clicked(compose->text, &event, compose);
7515 static gboolean compose_force_window_origin = TRUE;
7516 static Compose *compose_create(PrefsAccount *account,
7525 GtkWidget *handlebox;
7527 GtkWidget *notebook;
7529 GtkWidget *attach_hbox;
7530 GtkWidget *attach_lab1;
7531 GtkWidget *attach_lab2;
7536 GtkWidget *subject_hbox;
7537 GtkWidget *subject_frame;
7538 GtkWidget *subject_entry;
7542 GtkWidget *edit_vbox;
7543 GtkWidget *ruler_hbox;
7545 GtkWidget *scrolledwin;
7547 GtkTextBuffer *buffer;
7548 GtkClipboard *clipboard;
7550 UndoMain *undostruct;
7552 GtkWidget *popupmenu;
7553 GtkWidget *tmpl_menu;
7554 GtkActionGroup *action_group = NULL;
7557 GtkAspell * gtkaspell = NULL;
7560 static GdkGeometry geometry;
7562 cm_return_val_if_fail(account != NULL, NULL);
7564 gtkut_convert_int_to_gdk_color(prefs_common.default_header_bgcolor,
7565 &default_header_bgcolor);
7566 gtkut_convert_int_to_gdk_color(prefs_common.default_header_color,
7567 &default_header_color);
7569 debug_print("Creating compose window...\n");
7570 compose = g_new0(Compose, 1);
7572 compose->batch = batch;
7573 compose->account = account;
7574 compose->folder = folder;
7576 compose->mutex = cm_mutex_new();
7577 compose->set_cursor_pos = -1;
7579 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose");
7581 gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
7582 gtk_widget_set_size_request(window, prefs_common.compose_width,
7583 prefs_common.compose_height);
7585 if (!geometry.max_width) {
7586 geometry.max_width = gdk_screen_width();
7587 geometry.max_height = gdk_screen_height();
7590 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7591 &geometry, GDK_HINT_MAX_SIZE);
7592 if (!geometry.min_width) {
7593 geometry.min_width = 600;
7594 geometry.min_height = 440;
7596 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7597 &geometry, GDK_HINT_MIN_SIZE);
7599 #ifndef GENERIC_UMPC
7600 if (compose_force_window_origin)
7601 gtk_window_move(GTK_WINDOW(window), prefs_common.compose_x,
7602 prefs_common.compose_y);
7604 g_signal_connect(G_OBJECT(window), "delete_event",
7605 G_CALLBACK(compose_delete_cb), compose);
7606 MANAGE_WINDOW_SIGNALS_CONNECT(window);
7607 gtk_widget_realize(window);
7609 gtkut_widget_set_composer_icon(window);
7611 vbox = gtk_vbox_new(FALSE, 0);
7612 gtk_container_add(GTK_CONTAINER(window), vbox);
7614 compose->ui_manager = gtk_ui_manager_new();
7615 action_group = cm_menu_create_action_group_full(compose->ui_manager,"Menu", compose_entries,
7616 G_N_ELEMENTS(compose_entries), (gpointer)compose);
7617 gtk_action_group_add_toggle_actions(action_group, compose_toggle_entries,
7618 G_N_ELEMENTS(compose_toggle_entries), (gpointer)compose);
7619 gtk_action_group_add_radio_actions(action_group, compose_radio_rm_entries,
7620 G_N_ELEMENTS(compose_radio_rm_entries), COMPOSE_REPLY, G_CALLBACK(compose_reply_change_mode_cb), (gpointer)compose);
7621 gtk_action_group_add_radio_actions(action_group, compose_radio_prio_entries,
7622 G_N_ELEMENTS(compose_radio_prio_entries), PRIORITY_NORMAL, G_CALLBACK(compose_set_priority_cb), (gpointer)compose);
7623 gtk_action_group_add_radio_actions(action_group, compose_radio_enc_entries,
7624 G_N_ELEMENTS(compose_radio_enc_entries), C_AUTO, G_CALLBACK(compose_set_encoding_cb), (gpointer)compose);
7626 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Menu", NULL, GTK_UI_MANAGER_MENUBAR)
7628 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Message", "Message", GTK_UI_MANAGER_MENU)
7629 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Edit", "Edit", GTK_UI_MANAGER_MENU)
7631 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Spelling", "Spelling", GTK_UI_MANAGER_MENU)
7633 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Options", "Options", GTK_UI_MANAGER_MENU)
7634 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Tools", "Tools", GTK_UI_MANAGER_MENU)
7635 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Help", "Help", GTK_UI_MANAGER_MENU)
7638 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Send", "Message/Send", GTK_UI_MANAGER_MENUITEM)
7639 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "SendLater", "Message/SendLater", GTK_UI_MANAGER_MENUITEM)
7640 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator1", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7641 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "AttachFile", "Message/AttachFile", GTK_UI_MANAGER_MENUITEM)
7642 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertFile", "Message/InsertFile", GTK_UI_MANAGER_MENUITEM)
7643 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertSig", "Message/InsertSig", GTK_UI_MANAGER_MENUITEM)
7644 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "ReplaceSig", "Message/ReplaceSig", GTK_UI_MANAGER_MENUITEM)
7645 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator2", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7646 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Save", "Message/Save", GTK_UI_MANAGER_MENUITEM)
7647 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator3", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7648 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Print", "Message/Print", GTK_UI_MANAGER_MENUITEM)
7649 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator4", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7650 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Close", "Message/Close", GTK_UI_MANAGER_MENUITEM)
7653 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Undo", "Edit/Undo", GTK_UI_MANAGER_MENUITEM)
7654 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Redo", "Edit/Redo", GTK_UI_MANAGER_MENUITEM)
7655 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator1", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7657 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Cut", "Edit/Cut", GTK_UI_MANAGER_MENUITEM)
7658 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Copy", "Edit/Copy", GTK_UI_MANAGER_MENUITEM)
7659 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Paste", "Edit/Paste", GTK_UI_MANAGER_MENUITEM)
7661 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SpecialPaste", "Edit/SpecialPaste", GTK_UI_MANAGER_MENU)
7662 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "AsQuotation", "Edit/SpecialPaste/AsQuotation", GTK_UI_MANAGER_MENUITEM)
7663 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Wrapped", "Edit/SpecialPaste/Wrapped", GTK_UI_MANAGER_MENUITEM)
7664 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Unwrapped", "Edit/SpecialPaste/Unwrapped", GTK_UI_MANAGER_MENUITEM)
7666 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SelectAll", "Edit/SelectAll", GTK_UI_MANAGER_MENUITEM)
7668 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Advanced", "Edit/Advanced", GTK_UI_MANAGER_MENU)
7669 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackChar", "Edit/Advanced/BackChar", GTK_UI_MANAGER_MENUITEM)
7670 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwChar", "Edit/Advanced/ForwChar", GTK_UI_MANAGER_MENUITEM)
7671 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackWord", "Edit/Advanced/BackWord", GTK_UI_MANAGER_MENUITEM)
7672 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwWord", "Edit/Advanced/ForwWord", GTK_UI_MANAGER_MENUITEM)
7673 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BegLine", "Edit/Advanced/BegLine", GTK_UI_MANAGER_MENUITEM)
7674 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "EndLine", "Edit/Advanced/EndLine", GTK_UI_MANAGER_MENUITEM)
7675 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "PrevLine", "Edit/Advanced/PrevLine", GTK_UI_MANAGER_MENUITEM)
7676 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "NextLine", "Edit/Advanced/NextLine", GTK_UI_MANAGER_MENUITEM)
7677 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackChar", "Edit/Advanced/DelBackChar", GTK_UI_MANAGER_MENUITEM)
7678 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwChar", "Edit/Advanced/DelForwChar", GTK_UI_MANAGER_MENUITEM)
7679 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackWord", "Edit/Advanced/DelBackWord", GTK_UI_MANAGER_MENUITEM)
7680 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwWord", "Edit/Advanced/DelForwWord", GTK_UI_MANAGER_MENUITEM)
7681 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelLine", "Edit/Advanced/DelLine", GTK_UI_MANAGER_MENUITEM)
7682 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelEndLine", "Edit/Advanced/DelEndLine", GTK_UI_MANAGER_MENUITEM)
7684 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator2", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7686 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Find", "Edit/Find", GTK_UI_MANAGER_MENUITEM)
7687 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapPara", "Edit/WrapPara", GTK_UI_MANAGER_MENUITEM)
7688 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapAllLines", "Edit/WrapAllLines", GTK_UI_MANAGER_MENUITEM)
7689 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoWrap", "Edit/AutoWrap", GTK_UI_MANAGER_MENUITEM)
7690 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoIndent", "Edit/AutoIndent", GTK_UI_MANAGER_MENUITEM)
7692 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator3", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7694 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "ExtEditor", "Edit/ExtEditor", GTK_UI_MANAGER_MENUITEM)
7698 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckAllSel", "Spelling/CheckAllSel", GTK_UI_MANAGER_MENUITEM)
7699 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "HighlightAll", "Spelling/HighlightAll", GTK_UI_MANAGER_MENUITEM)
7700 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckBackwards", "Spelling/CheckBackwards", GTK_UI_MANAGER_MENUITEM)
7701 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "ForwardNext", "Spelling/ForwardNext", GTK_UI_MANAGER_MENUITEM)
7702 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Separator1", "Spelling/---", GTK_UI_MANAGER_SEPARATOR)
7703 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Options", "Spelling/Options", GTK_UI_MANAGER_MENU)
7707 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "ReplyMode", "Options/ReplyMode", GTK_UI_MANAGER_MENU)
7708 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Normal", "Options/ReplyMode/Normal", GTK_UI_MANAGER_MENUITEM)
7709 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "All", "Options/ReplyMode/All", GTK_UI_MANAGER_MENUITEM)
7710 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Sender", "Options/ReplyMode/Sender", GTK_UI_MANAGER_MENUITEM)
7711 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "List", "Options/ReplyMode/List", GTK_UI_MANAGER_MENUITEM)
7713 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator1", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7714 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "PrivacySystem", "Options/PrivacySystem", GTK_UI_MANAGER_MENU)
7715 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/PrivacySystem", "PlaceHolder", "Options/PrivacySystem/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7716 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Sign", "Options/Sign", GTK_UI_MANAGER_MENUITEM)
7717 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encrypt", "Options/Encrypt", GTK_UI_MANAGER_MENUITEM)
7720 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator2", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7721 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Priority", "Options/Priority", GTK_UI_MANAGER_MENU)
7722 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Highest", "Options/Priority/Highest", GTK_UI_MANAGER_MENUITEM)
7723 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "High", "Options/Priority/High", GTK_UI_MANAGER_MENUITEM)
7724 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Normal", "Options/Priority/Normal", GTK_UI_MANAGER_MENUITEM)
7725 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Low", "Options/Priority/Low", GTK_UI_MANAGER_MENUITEM)
7726 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Lowest", "Options/Priority/Lowest", GTK_UI_MANAGER_MENUITEM)
7728 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator3", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7729 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RequestRetRcpt", "Options/RequestRetRcpt", GTK_UI_MANAGER_MENUITEM)
7730 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator4", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7731 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RemoveReferences", "Options/RemoveReferences", GTK_UI_MANAGER_MENUITEM)
7732 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator5", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7734 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encoding", "Options/Encoding", GTK_UI_MANAGER_MENU)
7736 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_AUTO, "Options/Encoding/"CS_AUTO, GTK_UI_MANAGER_MENUITEM)
7737 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator1", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7738 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_US_ASCII, "Options/Encoding/"CS_US_ASCII, GTK_UI_MANAGER_MENUITEM)
7739 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_UTF_8, "Options/Encoding/"CS_UTF_8, GTK_UI_MANAGER_MENUITEM)
7740 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator2", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7742 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Western", "Options/Encoding/Western", GTK_UI_MANAGER_MENU)
7743 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)
7744 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)
7745 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Western", CS_WINDOWS_1252, "Options/Encoding/Western/"CS_WINDOWS_1252, GTK_UI_MANAGER_MENUITEM)
7747 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_2, "Options/Encoding/"CS_ISO_8859_2, GTK_UI_MANAGER_MENUITEM)
7749 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Baltic", "Options/Encoding/Baltic", GTK_UI_MANAGER_MENU)
7750 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)
7751 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)
7753 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_7, "Options/Encoding/"CS_ISO_8859_7, GTK_UI_MANAGER_MENUITEM)
7755 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Hebrew", "Options/Encoding/Hebrew", GTK_UI_MANAGER_MENU)
7756 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)
7757 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Hebrew", CS_WINDOWS_1255, "Options/Encoding/Hebrew/"CS_WINDOWS_1255, GTK_UI_MANAGER_MENUITEM)
7759 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Arabic", "Options/Encoding/Arabic", GTK_UI_MANAGER_MENU)
7760 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)
7761 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Arabic", CS_WINDOWS_1256, "Options/Encoding/Arabic/"CS_WINDOWS_1256, GTK_UI_MANAGER_MENUITEM)
7763 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_9, "Options/Encoding/"CS_ISO_8859_9, GTK_UI_MANAGER_MENUITEM)
7765 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Cyrillic", "Options/Encoding/Cyrillic", GTK_UI_MANAGER_MENU)
7766 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)
7767 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_R, "Options/Encoding/Cyrillic/"CS_KOI8_R, GTK_UI_MANAGER_MENUITEM)
7768 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_MACCYR, "Options/Encoding/Cyrillic/"CS_MACCYR, GTK_UI_MANAGER_MENUITEM)
7769 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_U, "Options/Encoding/Cyrillic/"CS_KOI8_U, GTK_UI_MANAGER_MENUITEM)
7770 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_WINDOWS_1251, "Options/Encoding/Cyrillic/"CS_WINDOWS_1251, GTK_UI_MANAGER_MENUITEM)
7772 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Japanese", "Options/Encoding/Japanese", GTK_UI_MANAGER_MENU)
7773 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)
7774 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)
7775 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_EUC_JP, "Options/Encoding/Japanese/"CS_EUC_JP, GTK_UI_MANAGER_MENUITEM)
7776 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_SHIFT_JIS, "Options/Encoding/Japanese/"CS_SHIFT_JIS, GTK_UI_MANAGER_MENUITEM)
7778 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Chinese", "Options/Encoding/Chinese", GTK_UI_MANAGER_MENU)
7779 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB18030, "Options/Encoding/Chinese/"CS_GB18030, GTK_UI_MANAGER_MENUITEM)
7780 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB2312, "Options/Encoding/Chinese/"CS_GB2312, GTK_UI_MANAGER_MENUITEM)
7781 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GBK, "Options/Encoding/Chinese/"CS_GBK, GTK_UI_MANAGER_MENUITEM)
7782 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_BIG5, "Options/Encoding/Chinese/"CS_BIG5, GTK_UI_MANAGER_MENUITEM)
7783 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_EUC_TW, "Options/Encoding/Chinese/"CS_EUC_TW, GTK_UI_MANAGER_MENUITEM)
7785 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Korean", "Options/Encoding/Korean", GTK_UI_MANAGER_MENU)
7786 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Korean", CS_EUC_KR, "Options/Encoding/Korean/"CS_EUC_KR, GTK_UI_MANAGER_MENUITEM)
7787 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)
7789 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Thai", "Options/Encoding/Thai", GTK_UI_MANAGER_MENU)
7790 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_TIS_620, "Options/Encoding/Thai/"CS_TIS_620, GTK_UI_MANAGER_MENUITEM)
7791 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_WINDOWS_874, "Options/Encoding/Thai/"CS_WINDOWS_874, GTK_UI_MANAGER_MENUITEM)
7795 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "ShowRuler", "Tools/ShowRuler", GTK_UI_MANAGER_MENUITEM)
7796 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "AddressBook", "Tools/AddressBook", GTK_UI_MANAGER_MENUITEM)
7797 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Template", "Tools/Template", GTK_UI_MANAGER_MENU)
7798 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Template", "PlaceHolder", "Tools/Template/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7799 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Actions", "Tools/Actions", GTK_UI_MANAGER_MENU)
7800 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Actions", "PlaceHolder", "Tools/Actions/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7803 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Help", "About", "Help/About", GTK_UI_MANAGER_MENUITEM)
7805 menubar = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu");
7806 gtk_widget_show_all(menubar);
7808 gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(compose->ui_manager));
7809 gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, TRUE, 0);
7811 if (prefs_common.toolbar_detachable) {
7812 handlebox = gtk_handle_box_new();
7814 handlebox = gtk_hbox_new(FALSE, 0);
7816 gtk_box_pack_start(GTK_BOX(vbox), handlebox, FALSE, FALSE, 0);
7818 gtk_widget_realize(handlebox);
7819 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, handlebox,
7822 vbox2 = gtk_vbox_new(FALSE, 2);
7823 gtk_box_pack_start(GTK_BOX(vbox), vbox2, TRUE, TRUE, 0);
7824 gtk_container_set_border_width(GTK_CONTAINER(vbox2), 0);
7827 notebook = gtk_notebook_new();
7828 gtk_widget_show(notebook);
7830 /* header labels and entries */
7831 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7832 compose_create_header(compose),
7833 gtk_label_new_with_mnemonic(_("Hea_der")));
7834 /* attachment list */
7835 attach_hbox = gtk_hbox_new(FALSE, 0);
7836 gtk_widget_show(attach_hbox);
7838 attach_lab1 = gtk_label_new_with_mnemonic(_("_Attachments"));
7839 gtk_widget_show(attach_lab1);
7840 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab1, TRUE, TRUE, 0);
7842 attach_lab2 = gtk_label_new("");
7843 gtk_widget_show(attach_lab2);
7844 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab2, FALSE, FALSE, 0);
7846 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7847 compose_create_attach(compose),
7850 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7851 compose_create_others(compose),
7852 gtk_label_new_with_mnemonic(_("Othe_rs")));
7855 subject_hbox = gtk_hbox_new(FALSE, 0);
7856 gtk_widget_show(subject_hbox);
7858 subject_frame = gtk_frame_new(NULL);
7859 gtk_frame_set_shadow_type(GTK_FRAME(subject_frame), GTK_SHADOW_NONE);
7860 gtk_box_pack_start(GTK_BOX(subject_hbox), subject_frame, TRUE, TRUE, 0);
7861 gtk_widget_show(subject_frame);
7863 subject = gtk_hbox_new(FALSE, HSPACING_NARROW);
7864 gtk_container_set_border_width(GTK_CONTAINER(subject), 0);
7865 gtk_widget_show(subject);
7867 label = gtk_label_new_with_mnemonic(_("S_ubject:"));
7868 gtk_box_pack_start(GTK_BOX(subject), label, FALSE, FALSE, 0);
7869 gtk_widget_show(label);
7872 subject_entry = claws_spell_entry_new();
7874 subject_entry = gtk_entry_new();
7876 gtk_box_pack_start(GTK_BOX(subject), subject_entry, TRUE, TRUE, 0);
7877 g_signal_connect_after(G_OBJECT(subject_entry), "grab_focus",
7878 G_CALLBACK(compose_grab_focus_cb), compose);
7879 gtk_label_set_mnemonic_widget(GTK_LABEL(label), subject_entry);
7880 gtk_widget_show(subject_entry);
7881 compose->subject_entry = subject_entry;
7882 gtk_container_add(GTK_CONTAINER(subject_frame), subject);
7884 edit_vbox = gtk_vbox_new(FALSE, 0);
7886 gtk_box_pack_start(GTK_BOX(edit_vbox), subject_hbox, FALSE, FALSE, 0);
7889 ruler_hbox = gtk_hbox_new(FALSE, 0);
7890 gtk_box_pack_start(GTK_BOX(edit_vbox), ruler_hbox, FALSE, FALSE, 0);
7892 ruler = gtk_shruler_new(GTK_ORIENTATION_HORIZONTAL);
7893 gtk_shruler_set_range(GTK_SHRULER(ruler), 0.0, 100.0, 1.0);
7894 gtk_box_pack_start(GTK_BOX(ruler_hbox), ruler, TRUE, TRUE,
7898 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
7899 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
7900 GTK_POLICY_AUTOMATIC,
7901 GTK_POLICY_AUTOMATIC);
7902 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
7904 gtk_box_pack_start(GTK_BOX(edit_vbox), scrolledwin, TRUE, TRUE, 0);
7906 text = gtk_text_view_new();
7907 if (prefs_common.show_compose_margin) {
7908 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text), 6);
7909 gtk_text_view_set_right_margin(GTK_TEXT_VIEW(text), 6);
7911 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
7912 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
7913 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), TRUE);
7914 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
7915 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
7917 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
7918 g_signal_connect_after(G_OBJECT(text), "size_allocate",
7919 G_CALLBACK(compose_edit_size_alloc),
7921 g_signal_connect(G_OBJECT(buffer), "changed",
7922 G_CALLBACK(compose_changed_cb), compose);
7923 g_signal_connect(G_OBJECT(text), "grab_focus",
7924 G_CALLBACK(compose_grab_focus_cb), compose);
7925 g_signal_connect(G_OBJECT(buffer), "insert_text",
7926 G_CALLBACK(text_inserted), compose);
7927 g_signal_connect(G_OBJECT(text), "button_press_event",
7928 G_CALLBACK(text_clicked), compose);
7929 g_signal_connect(G_OBJECT(text), "popup-menu",
7930 G_CALLBACK(compose_popup_menu), compose);
7931 g_signal_connect(G_OBJECT(subject_entry), "changed",
7932 G_CALLBACK(compose_changed_cb), compose);
7933 g_signal_connect(G_OBJECT(subject_entry), "activate",
7934 G_CALLBACK(compose_subject_entry_activated), compose);
7937 gtk_drag_dest_set(text, GTK_DEST_DEFAULT_ALL, compose_mime_types,
7938 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7939 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7940 g_signal_connect(G_OBJECT(text), "drag_data_received",
7941 G_CALLBACK(compose_insert_drag_received_cb),
7943 g_signal_connect(G_OBJECT(text), "drag-drop",
7944 G_CALLBACK(compose_drag_drop),
7946 g_signal_connect(G_OBJECT(text), "key-press-event",
7947 G_CALLBACK(completion_set_focus_to_subject),
7949 gtk_widget_show_all(vbox);
7951 /* pane between attach clist and text */
7952 paned = gtk_vpaned_new();
7953 gtk_container_add(GTK_CONTAINER(vbox2), paned);
7954 gtk_paned_pack1(GTK_PANED(paned), notebook, FALSE, FALSE);
7955 gtk_paned_pack2(GTK_PANED(paned), edit_vbox, TRUE, FALSE);
7956 gtk_paned_set_position(GTK_PANED(paned), prefs_common.compose_notebook_height);
7957 g_signal_connect(G_OBJECT(notebook), "size_allocate",
7958 G_CALLBACK(compose_notebook_size_alloc), paned);
7960 gtk_widget_show_all(paned);
7963 if (prefs_common.textfont) {
7964 PangoFontDescription *font_desc;
7966 font_desc = pango_font_description_from_string
7967 (prefs_common.textfont);
7969 gtk_widget_modify_font(text, font_desc);
7970 pango_font_description_free(font_desc);
7974 gtk_action_group_add_actions(action_group, compose_popup_entries,
7975 G_N_ELEMENTS(compose_popup_entries), (gpointer)compose);
7976 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Popup", NULL, GTK_UI_MANAGER_MENUBAR)
7977 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup", "Compose", "Compose", GTK_UI_MANAGER_MENU)
7978 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Add", "Compose/Add", GTK_UI_MANAGER_MENUITEM)
7979 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Remove", "Compose/Remove", GTK_UI_MANAGER_MENUITEM)
7980 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Separator1", "Compose/---", GTK_UI_MANAGER_SEPARATOR)
7981 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Properties", "Compose/Properties", GTK_UI_MANAGER_MENUITEM)
7983 popupmenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(gtk_ui_manager_get_widget(compose->ui_manager, "/Popup/Compose")));
7985 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
7986 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
7987 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
7989 tmpl_menu = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu/Tools/Template");
7991 undostruct = undo_init(text);
7992 undo_set_change_state_func(undostruct, &compose_undo_state_changed,
7995 address_completion_start(window);
7997 compose->window = window;
7998 compose->vbox = vbox;
7999 compose->menubar = menubar;
8000 compose->handlebox = handlebox;
8002 compose->vbox2 = vbox2;
8004 compose->paned = paned;
8006 compose->attach_label = attach_lab2;
8008 compose->notebook = notebook;
8009 compose->edit_vbox = edit_vbox;
8010 compose->ruler_hbox = ruler_hbox;
8011 compose->ruler = ruler;
8012 compose->scrolledwin = scrolledwin;
8013 compose->text = text;
8015 compose->focused_editable = NULL;
8017 compose->popupmenu = popupmenu;
8019 compose->tmpl_menu = tmpl_menu;
8021 compose->mode = mode;
8022 compose->rmode = mode;
8024 compose->targetinfo = NULL;
8025 compose->replyinfo = NULL;
8026 compose->fwdinfo = NULL;
8028 compose->email_hashtable = g_hash_table_new_full(g_str_hash,
8029 g_str_equal, (GDestroyNotify) g_free, NULL);
8031 compose->replyto = NULL;
8033 compose->bcc = NULL;
8034 compose->followup_to = NULL;
8036 compose->ml_post = NULL;
8038 compose->inreplyto = NULL;
8039 compose->references = NULL;
8040 compose->msgid = NULL;
8041 compose->boundary = NULL;
8043 compose->autowrap = prefs_common.autowrap;
8044 compose->autoindent = prefs_common.auto_indent;
8045 compose->use_signing = FALSE;
8046 compose->use_encryption = FALSE;
8047 compose->privacy_system = NULL;
8048 compose->encdata = NULL;
8050 compose->modified = FALSE;
8052 compose->return_receipt = FALSE;
8054 compose->to_list = NULL;
8055 compose->newsgroup_list = NULL;
8057 compose->undostruct = undostruct;
8059 compose->sig_str = NULL;
8061 compose->exteditor_file = NULL;
8062 compose->exteditor_pid = -1;
8063 compose->exteditor_tag = -1;
8064 compose->exteditor_socket = NULL;
8065 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN; /* inhibit auto-drafting while loading */
8067 compose->folder_update_callback_id =
8068 hooks_register_hook(FOLDER_UPDATE_HOOKLIST,
8069 compose_update_folder_hook,
8070 (gpointer) compose);
8073 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
8074 if (mode != COMPOSE_REDIRECT) {
8075 if (prefs_common.enable_aspell && prefs_common.dictionary &&
8076 strcmp(prefs_common.dictionary, "")) {
8077 gtkaspell = gtkaspell_new(prefs_common.dictionary,
8078 prefs_common.alt_dictionary,
8079 conv_get_locale_charset_str(),
8080 prefs_common.misspelled_col,
8081 prefs_common.check_while_typing,
8082 prefs_common.recheck_when_changing_dict,
8083 prefs_common.use_alternate,
8084 prefs_common.use_both_dicts,
8085 GTK_TEXT_VIEW(text),
8086 GTK_WINDOW(compose->window),
8087 compose_dict_changed,
8088 compose_spell_menu_changed,
8091 alertpanel_error(_("Spell checker could not "
8093 gtkaspell_checkers_strerror());
8094 gtkaspell_checkers_reset_error();
8096 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", TRUE);
8100 compose->gtkaspell = gtkaspell;
8101 compose_spell_menu_changed(compose);
8102 claws_spell_entry_set_gtkaspell(CLAWS_SPELL_ENTRY(subject_entry), gtkaspell);
8105 compose_select_account(compose, account, TRUE);
8107 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", prefs_common.autowrap);
8108 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", prefs_common.auto_indent);
8110 if (account->set_autocc && account->auto_cc && mode != COMPOSE_REEDIT)
8111 compose_entry_append(compose, account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8113 if (account->set_autobcc && account->auto_bcc && mode != COMPOSE_REEDIT)
8114 compose_entry_append(compose, account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8116 if (account->set_autoreplyto && account->auto_replyto && mode != COMPOSE_REEDIT)
8117 compose_entry_append(compose, account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8119 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", compose->mode == COMPOSE_REPLY);
8120 if (account->protocol != A_NNTP)
8121 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
8122 prefs_common_translated_header_name("To:"));
8124 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
8125 prefs_common_translated_header_name("Newsgroups:"));
8127 #ifndef USE_ALT_ADDRBOOK
8128 addressbook_set_target_compose(compose);
8130 if (mode != COMPOSE_REDIRECT)
8131 compose_set_template_menu(compose);
8133 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Template", FALSE);
8136 compose_list = g_list_append(compose_list, compose);
8138 if (!prefs_common.show_ruler)
8139 gtk_widget_hide(ruler_hbox);
8141 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Tools/ShowRuler", prefs_common.show_ruler);
8144 compose->priority = PRIORITY_NORMAL;
8145 compose_update_priority_menu_item(compose);
8147 compose_set_out_encoding(compose);
8150 compose_update_actions_menu(compose);
8152 /* Privacy Systems menu */
8153 compose_update_privacy_systems_menu(compose);
8155 activate_privacy_system(compose, account, TRUE);
8156 toolbar_set_style(compose->toolbar->toolbar, compose->handlebox, prefs_common.toolbar_style);
8158 gtk_widget_realize(window);
8160 gtk_widget_show(window);
8166 static GtkWidget *compose_account_option_menu_create(Compose *compose)
8171 GtkWidget *optmenubox;
8172 GtkWidget *fromlabel;
8175 GtkWidget *from_name = NULL;
8177 gint num = 0, def_menu = 0;
8179 accounts = account_get_list();
8180 cm_return_val_if_fail(accounts != NULL, NULL);
8182 optmenubox = gtk_event_box_new();
8183 optmenu = gtkut_sc_combobox_create(optmenubox, FALSE);
8184 menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
8186 hbox = gtk_hbox_new(FALSE, 4);
8187 from_name = gtk_entry_new();
8189 g_signal_connect_after(G_OBJECT(from_name), "grab_focus",
8190 G_CALLBACK(compose_grab_focus_cb), compose);
8191 g_signal_connect_after(G_OBJECT(from_name), "activate",
8192 G_CALLBACK(from_name_activate_cb), optmenu);
8194 for (; accounts != NULL; accounts = accounts->next, num++) {
8195 PrefsAccount *ac = (PrefsAccount *)accounts->data;
8196 gchar *name, *from = NULL;
8198 if (ac == compose->account) def_menu = num;
8200 name = g_markup_printf_escaped("<i>%s</i>",
8203 if (ac == compose->account) {
8204 if (ac->name && *ac->name) {
8206 QUOTE_IF_REQUIRED_NORMAL(buf, ac->name, return NULL);
8207 from = g_strdup_printf("%s <%s>",
8209 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8211 from = g_strdup_printf("%s",
8213 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8215 if (cur_account != compose->account) {
8216 gtk_widget_modify_base(
8217 GTK_WIDGET(from_name),
8218 GTK_STATE_NORMAL, &default_header_bgcolor);
8219 gtk_widget_modify_text(
8220 GTK_WIDGET(from_name),
8221 GTK_STATE_NORMAL, &default_header_color);
8224 COMBOBOX_ADD(menu, name, ac->account_id);
8229 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), def_menu);
8231 g_signal_connect(G_OBJECT(optmenu), "changed",
8232 G_CALLBACK(account_activated),
8234 g_signal_connect(G_OBJECT(from_name), "populate-popup",
8235 G_CALLBACK(compose_entry_popup_extend),
8238 fromlabel = gtk_label_new_with_mnemonic(_("_From:"));
8239 gtk_label_set_mnemonic_widget(GTK_LABEL(fromlabel), from_name);
8241 gtk_box_pack_start(GTK_BOX(hbox), fromlabel, FALSE, FALSE, 4);
8242 gtk_box_pack_start(GTK_BOX(hbox), optmenubox, FALSE, FALSE, 0);
8243 gtk_box_pack_start(GTK_BOX(hbox), from_name, TRUE, TRUE, 0);
8245 /* Putting only the GtkEntry into focus chain of parent hbox causes
8246 * the account selector combobox next to it to be unreachable when
8247 * navigating widgets in GtkTable with up/down arrow keys.
8248 * Note: gtk_widget_set_can_focus() was not enough. */
8250 l = g_list_prepend(l, from_name);
8251 gtk_container_set_focus_chain(GTK_CONTAINER(hbox), l);
8254 CLAWS_SET_TIP(optmenubox,
8255 _("Account to use for this email"));
8256 CLAWS_SET_TIP(from_name,
8257 _("Sender address to be used"));
8259 compose->account_combo = optmenu;
8260 compose->from_name = from_name;
8265 static void compose_set_priority_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8267 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8268 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8269 Compose *compose = (Compose *) data;
8271 compose->priority = value;
8275 static void compose_reply_change_mode(Compose *compose,
8278 gboolean was_modified = compose->modified;
8280 gboolean all = FALSE, ml = FALSE, sender = FALSE, followup = FALSE;
8282 cm_return_if_fail(compose->replyinfo != NULL);
8284 if (action == COMPOSE_REPLY && prefs_common.default_reply_list)
8286 if (action == COMPOSE_REPLY && compose->rmode == COMPOSE_FOLLOWUP_AND_REPLY_TO)
8288 if (action == COMPOSE_REPLY_TO_ALL)
8290 if (action == COMPOSE_REPLY_TO_SENDER)
8292 if (action == COMPOSE_REPLY_TO_LIST)
8295 compose_remove_header_entries(compose);
8296 compose_reply_set_entry(compose, compose->replyinfo, all, ml, sender, followup);
8297 if (compose->account->set_autocc && compose->account->auto_cc)
8298 compose_entry_append(compose, compose->account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8300 if (compose->account->set_autobcc && compose->account->auto_bcc)
8301 compose_entry_append(compose, compose->account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8303 if (compose->account->set_autoreplyto && compose->account->auto_replyto)
8304 compose_entry_append(compose, compose->account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8305 compose_show_first_last_header(compose, TRUE);
8306 compose->modified = was_modified;
8307 compose_set_title(compose);
8310 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8312 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8313 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8314 Compose *compose = (Compose *) data;
8317 compose_reply_change_mode(compose, value);
8320 static void compose_update_priority_menu_item(Compose * compose)
8322 GtkWidget *menuitem = NULL;
8323 switch (compose->priority) {
8324 case PRIORITY_HIGHEST:
8325 menuitem = gtk_ui_manager_get_widget
8326 (compose->ui_manager, "/Menu/Options/Priority/Highest");
8329 menuitem = gtk_ui_manager_get_widget
8330 (compose->ui_manager, "/Menu/Options/Priority/High");
8332 case PRIORITY_NORMAL:
8333 menuitem = gtk_ui_manager_get_widget
8334 (compose->ui_manager, "/Menu/Options/Priority/Normal");
8337 menuitem = gtk_ui_manager_get_widget
8338 (compose->ui_manager, "/Menu/Options/Priority/Low");
8340 case PRIORITY_LOWEST:
8341 menuitem = gtk_ui_manager_get_widget
8342 (compose->ui_manager, "/Menu/Options/Priority/Lowest");
8345 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8348 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data)
8350 Compose *compose = (Compose *) data;
8352 gboolean can_sign = FALSE, can_encrypt = FALSE;
8354 cm_return_if_fail(GTK_IS_CHECK_MENU_ITEM(widget));
8356 if (!gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget)))
8359 systemid = g_object_get_data(G_OBJECT(widget), "privacy_system");
8360 g_free(compose->privacy_system);
8361 compose->privacy_system = NULL;
8362 g_free(compose->encdata);
8363 compose->encdata = NULL;
8364 if (systemid != NULL) {
8365 compose->privacy_system = g_strdup(systemid);
8367 can_sign = privacy_system_can_sign(systemid);
8368 can_encrypt = privacy_system_can_encrypt(systemid);
8371 debug_print("activated privacy system: %s\n", systemid != NULL ? systemid : "None");
8373 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8374 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8377 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn)
8379 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8380 GtkWidget *menuitem = NULL;
8381 GList *children, *amenu;
8382 gboolean can_sign = FALSE, can_encrypt = FALSE;
8383 gboolean found = FALSE;
8385 if (compose->privacy_system != NULL) {
8387 menuitem = gtk_menu_item_get_submenu(GTK_MENU_ITEM(
8388 gtk_ui_manager_get_widget(compose->ui_manager, branch_path)));
8389 cm_return_if_fail(menuitem != NULL);
8391 children = gtk_container_get_children(GTK_CONTAINER(GTK_MENU_SHELL(menuitem)));
8394 while (amenu != NULL) {
8395 systemid = g_object_get_data(G_OBJECT(amenu->data), "privacy_system");
8396 if (systemid != NULL) {
8397 if (strcmp(systemid, compose->privacy_system) == 0 &&
8398 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8399 menuitem = GTK_WIDGET(amenu->data);
8401 can_sign = privacy_system_can_sign(systemid);
8402 can_encrypt = privacy_system_can_encrypt(systemid);
8406 } else if (strlen(compose->privacy_system) == 0 &&
8407 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8408 menuitem = GTK_WIDGET(amenu->data);
8411 can_encrypt = FALSE;
8416 amenu = amenu->next;
8418 g_list_free(children);
8419 if (menuitem != NULL)
8420 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8422 if (warn && !found && strlen(compose->privacy_system)) {
8423 alertpanel_warning(_("The privacy system '%s' cannot be loaded. You "
8424 "will not be able to sign or encrypt this message."),
8425 compose->privacy_system);
8429 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8430 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8433 static void compose_set_out_encoding(Compose *compose)
8435 CharSet out_encoding;
8436 const gchar *branch = NULL;
8437 out_encoding = conv_get_charset_from_str(prefs_common.outgoing_charset);
8439 switch(out_encoding) {
8440 case C_AUTO: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8441 case C_US_ASCII: branch = "Menu/Options/Encoding/" CS_US_ASCII; break;
8442 case C_UTF_8: branch = "Menu/Options/Encoding/" CS_UTF_8; break;
8443 case C_ISO_8859_2: branch = "Menu/Options/Encoding/" CS_ISO_8859_2; break;
8444 case C_ISO_8859_7: branch = "Menu/Options/Encoding/" CS_ISO_8859_7; break;
8445 case C_ISO_8859_9: branch = "Menu/Options/Encoding/" CS_ISO_8859_9; break;
8446 case C_ISO_8859_1: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_1; break;
8447 case C_ISO_8859_15: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_15; break;
8448 case C_WINDOWS_1252: branch = "Menu/Options/Encoding/Western/" CS_WINDOWS_1252; break;
8449 case C_ISO_8859_13: branch = "Menu/Options/Encoding/Baltic/" CS_ISO_8859_13; break;
8450 case C_ISO_8859_4: branch = "Menu/Options/Encoding/Baltic" CS_ISO_8859_4; break;
8451 case C_ISO_8859_8: branch = "Menu/Options/Encoding/Hebrew/" CS_ISO_8859_8; break;
8452 case C_WINDOWS_1255: branch = "Menu/Options/Encoding/Hebrew/" CS_WINDOWS_1255; break;
8453 case C_ISO_8859_6: branch = "Menu/Options/Encoding/Arabic/" CS_ISO_8859_6; break;
8454 case C_WINDOWS_1256: branch = "Menu/Options/Encoding/Arabic/" CS_WINDOWS_1256; break;
8455 case C_ISO_8859_5: branch = "Menu/Options/Encoding/Cyrillic/" CS_ISO_8859_5; break;
8456 case C_KOI8_R: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_R; break;
8457 case C_MACCYR: branch = "Menu/Options/Encoding/Cyrillic/" CS_MACCYR; break;
8458 case C_KOI8_U: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_U; break;
8459 case C_WINDOWS_1251: branch = "Menu/Options/Encoding/Cyrillic/" CS_WINDOWS_1251; break;
8460 case C_ISO_2022_JP: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP; break;
8461 case C_ISO_2022_JP_2: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP_2; break;
8462 case C_EUC_JP: branch = "Menu/Options/Encoding/Japanese/" CS_EUC_JP; break;
8463 case C_SHIFT_JIS: branch = "Menu/Options/Encoding/Japanese/" CS_SHIFT_JIS; break;
8464 case C_GB18030: branch = "Menu/Options/Encoding/Chinese/" CS_GB18030; break;
8465 case C_GB2312: branch = "Menu/Options/Encoding/Chinese/" CS_GB2312; break;
8466 case C_GBK: branch = "Menu/Options/Encoding/Chinese/" CS_GBK; break;
8467 case C_BIG5: branch = "Menu/Options/Encoding/Chinese/" CS_BIG5; break;
8468 case C_EUC_TW: branch = "Menu/Options/Encoding/Chinese/" CS_EUC_TW; break;
8469 case C_EUC_KR: branch = "Menu/Options/Encoding/Korean/" CS_EUC_KR; break;
8470 case C_ISO_2022_KR: branch = "Menu/Options/Encoding/Korean/" CS_ISO_2022_KR; break;
8471 case C_TIS_620: branch = "Menu/Options/Encoding/Thai/" CS_TIS_620; break;
8472 case C_WINDOWS_874: branch = "Menu/Options/Encoding/Thai/" CS_WINDOWS_874; break;
8473 default: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8475 cm_toggle_menu_set_active_full(compose->ui_manager, (gchar *)branch, TRUE);
8478 static void compose_set_template_menu(Compose *compose)
8480 GSList *tmpl_list, *cur;
8484 tmpl_list = template_get_config();
8486 menu = gtk_menu_new();
8488 gtk_menu_set_accel_group (GTK_MENU (menu),
8489 gtk_ui_manager_get_accel_group(compose->ui_manager));
8490 for (cur = tmpl_list; cur != NULL; cur = cur->next) {
8491 Template *tmpl = (Template *)cur->data;
8492 gchar *accel_path = NULL;
8493 item = gtk_menu_item_new_with_label(tmpl->name);
8494 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
8495 g_signal_connect(G_OBJECT(item), "activate",
8496 G_CALLBACK(compose_template_activate_cb),
8498 g_object_set_data(G_OBJECT(item), "template", tmpl);
8499 gtk_widget_show(item);
8500 accel_path = g_strconcat("<ComposeTemplates>" , "/", tmpl->name, NULL);
8501 gtk_menu_item_set_accel_path(GTK_MENU_ITEM(item), accel_path);
8505 gtk_widget_show(menu);
8506 gtk_menu_item_set_submenu(GTK_MENU_ITEM(compose->tmpl_menu), menu);
8509 void compose_update_actions_menu(Compose *compose)
8511 action_update_compose_menu(compose->ui_manager, "/Menu/Tools/Actions", compose);
8514 static void compose_update_privacy_systems_menu(Compose *compose)
8516 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8517 GSList *systems, *cur;
8519 GtkWidget *system_none;
8521 GtkWidget *privacy_menuitem = gtk_ui_manager_get_widget(compose->ui_manager, branch_path);
8522 GtkWidget *privacy_menu = gtk_menu_new();
8524 system_none = gtk_radio_menu_item_new_with_mnemonic(NULL, _("_None"));
8525 g_object_set_data_full(G_OBJECT(system_none), "privacy_system", NULL, NULL);
8527 g_signal_connect(G_OBJECT(system_none), "activate",
8528 G_CALLBACK(compose_set_privacy_system_cb), compose);
8530 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), system_none);
8531 gtk_widget_show(system_none);
8533 systems = privacy_get_system_ids();
8534 for (cur = systems; cur != NULL; cur = g_slist_next(cur)) {
8535 gchar *systemid = cur->data;
8537 group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(system_none));
8538 widget = gtk_radio_menu_item_new_with_label(group,
8539 privacy_system_get_name(systemid));
8540 g_object_set_data_full(G_OBJECT(widget), "privacy_system",
8541 g_strdup(systemid), g_free);
8542 g_signal_connect(G_OBJECT(widget), "activate",
8543 G_CALLBACK(compose_set_privacy_system_cb), compose);
8545 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), widget);
8546 gtk_widget_show(widget);
8549 g_slist_free(systems);
8550 gtk_menu_item_set_submenu(GTK_MENU_ITEM(privacy_menuitem), privacy_menu);
8551 gtk_widget_show_all(privacy_menu);
8552 gtk_widget_show_all(privacy_menuitem);
8555 void compose_reflect_prefs_all(void)
8560 for (cur = compose_list; cur != NULL; cur = cur->next) {
8561 compose = (Compose *)cur->data;
8562 compose_set_template_menu(compose);
8566 void compose_reflect_prefs_pixmap_theme(void)
8571 for (cur = compose_list; cur != NULL; cur = cur->next) {
8572 compose = (Compose *)cur->data;
8573 toolbar_update(TOOLBAR_COMPOSE, compose);
8577 static const gchar *compose_quote_char_from_context(Compose *compose)
8579 const gchar *qmark = NULL;
8581 cm_return_val_if_fail(compose != NULL, NULL);
8583 switch (compose->mode) {
8584 /* use forward-specific quote char */
8585 case COMPOSE_FORWARD:
8586 case COMPOSE_FORWARD_AS_ATTACH:
8587 case COMPOSE_FORWARD_INLINE:
8588 if (compose->folder && compose->folder->prefs &&
8589 compose->folder->prefs->forward_with_format)
8590 qmark = compose->folder->prefs->forward_quotemark;
8591 else if (compose->account->forward_with_format)
8592 qmark = compose->account->forward_quotemark;
8594 qmark = prefs_common.fw_quotemark;
8597 /* use reply-specific quote char in all other modes */
8599 if (compose->folder && compose->folder->prefs &&
8600 compose->folder->prefs->reply_with_format)
8601 qmark = compose->folder->prefs->reply_quotemark;
8602 else if (compose->account->reply_with_format)
8603 qmark = compose->account->reply_quotemark;
8605 qmark = prefs_common.quotemark;
8609 if (qmark == NULL || *qmark == '\0')
8615 static void compose_template_apply(Compose *compose, Template *tmpl,
8619 GtkTextBuffer *buffer;
8623 gchar *parsed_str = NULL;
8624 gint cursor_pos = 0;
8625 const gchar *err_msg = _("The body of the template has an error at line %d.");
8628 /* process the body */
8630 text = GTK_TEXT_VIEW(compose->text);
8631 buffer = gtk_text_view_get_buffer(text);
8634 qmark = compose_quote_char_from_context(compose);
8636 if (compose->replyinfo != NULL) {
8639 gtk_text_buffer_set_text(buffer, "", -1);
8640 mark = gtk_text_buffer_get_insert(buffer);
8641 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8643 parsed_str = compose_quote_fmt(compose, compose->replyinfo,
8644 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8646 } else if (compose->fwdinfo != NULL) {
8649 gtk_text_buffer_set_text(buffer, "", -1);
8650 mark = gtk_text_buffer_get_insert(buffer);
8651 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8653 parsed_str = compose_quote_fmt(compose, compose->fwdinfo,
8654 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8657 MsgInfo* dummyinfo = compose_msginfo_new_from_compose(compose);
8659 GtkTextIter start, end;
8662 gtk_text_buffer_get_start_iter(buffer, &start);
8663 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
8664 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
8666 /* clear the buffer now */
8668 gtk_text_buffer_set_text(buffer, "", -1);
8670 parsed_str = compose_quote_fmt(compose, dummyinfo,
8671 tmpl->value, qmark, tmp, FALSE, FALSE, err_msg);
8672 procmsg_msginfo_free( &dummyinfo );
8678 gtk_text_buffer_set_text(buffer, "", -1);
8679 mark = gtk_text_buffer_get_insert(buffer);
8680 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8683 if (replace && parsed_str && compose->account->auto_sig)
8684 compose_insert_sig(compose, FALSE);
8686 if (replace && parsed_str) {
8687 gtk_text_buffer_get_start_iter(buffer, &iter);
8688 gtk_text_buffer_place_cursor(buffer, &iter);
8692 cursor_pos = quote_fmt_get_cursor_pos();
8693 compose->set_cursor_pos = cursor_pos;
8694 if (cursor_pos == -1)
8696 gtk_text_buffer_get_start_iter(buffer, &iter);
8697 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
8698 gtk_text_buffer_place_cursor(buffer, &iter);
8701 /* process the other fields */
8703 compose_template_apply_fields(compose, tmpl);
8704 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
8705 quote_fmt_reset_vartable();
8706 compose_changed_cb(NULL, compose);
8709 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
8710 gtkaspell_highlight_all(compose->gtkaspell);
8714 static void compose_template_apply_fields_error(const gchar *header)
8719 tr = g_strdup(C_("'%s' stands for a header name",
8720 "Template '%s' format error."));
8721 text = g_strdup_printf(tr, prefs_common_translated_header_name(header));
8722 alertpanel_error(text);
8728 static void compose_template_apply_fields(Compose *compose, Template *tmpl)
8730 MsgInfo* dummyinfo = NULL;
8731 MsgInfo *msginfo = NULL;
8734 if (compose->replyinfo != NULL)
8735 msginfo = compose->replyinfo;
8736 else if (compose->fwdinfo != NULL)
8737 msginfo = compose->fwdinfo;
8739 dummyinfo = compose_msginfo_new_from_compose(compose);
8740 msginfo = dummyinfo;
8743 if (tmpl->from && *tmpl->from != '\0') {
8745 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8746 compose->gtkaspell);
8748 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8750 quote_fmt_scan_string(tmpl->from);
8753 buf = quote_fmt_get_buffer();
8755 compose_template_apply_fields_error("From");
8757 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
8761 if (tmpl->to && *tmpl->to != '\0') {
8763 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8764 compose->gtkaspell);
8766 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8768 quote_fmt_scan_string(tmpl->to);
8771 buf = quote_fmt_get_buffer();
8773 compose_template_apply_fields_error("To");
8775 compose_entry_append(compose, buf, COMPOSE_TO, PREF_TEMPLATE);
8779 if (tmpl->cc && *tmpl->cc != '\0') {
8781 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8782 compose->gtkaspell);
8784 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8786 quote_fmt_scan_string(tmpl->cc);
8789 buf = quote_fmt_get_buffer();
8791 compose_template_apply_fields_error("Cc");
8793 compose_entry_append(compose, buf, COMPOSE_CC, PREF_TEMPLATE);
8797 if (tmpl->bcc && *tmpl->bcc != '\0') {
8799 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8800 compose->gtkaspell);
8802 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8804 quote_fmt_scan_string(tmpl->bcc);
8807 buf = quote_fmt_get_buffer();
8809 compose_template_apply_fields_error("Bcc");
8811 compose_entry_append(compose, buf, COMPOSE_BCC, PREF_TEMPLATE);
8815 if (tmpl->replyto && *tmpl->replyto != '\0') {
8817 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8818 compose->gtkaspell);
8820 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8822 quote_fmt_scan_string(tmpl->replyto);
8825 buf = quote_fmt_get_buffer();
8827 compose_template_apply_fields_error("Reply-To");
8829 compose_entry_append(compose, buf, COMPOSE_REPLYTO, PREF_TEMPLATE);
8833 /* process the subject */
8834 if (tmpl->subject && *tmpl->subject != '\0') {
8836 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8837 compose->gtkaspell);
8839 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8841 quote_fmt_scan_string(tmpl->subject);
8844 buf = quote_fmt_get_buffer();
8846 compose_template_apply_fields_error("Subject");
8848 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
8852 procmsg_msginfo_free( &dummyinfo );
8855 static void compose_destroy(Compose *compose)
8857 GtkAllocation allocation;
8858 GtkTextBuffer *buffer;
8859 GtkClipboard *clipboard;
8861 compose_list = g_list_remove(compose_list, compose);
8863 if (compose->updating) {
8864 debug_print("danger, not destroying anything now\n");
8865 compose->deferred_destroy = TRUE;
8869 /* NOTE: address_completion_end() does nothing with the window
8870 * however this may change. */
8871 address_completion_end(compose->window);
8873 slist_free_strings_full(compose->to_list);
8874 slist_free_strings_full(compose->newsgroup_list);
8875 slist_free_strings_full(compose->header_list);
8877 slist_free_strings_full(extra_headers);
8878 extra_headers = NULL;
8880 compose->header_list = compose->newsgroup_list = compose->to_list = NULL;
8882 g_hash_table_destroy(compose->email_hashtable);
8884 hooks_unregister_hook(FOLDER_UPDATE_HOOKLIST,
8885 compose->folder_update_callback_id);
8887 procmsg_msginfo_free(&(compose->targetinfo));
8888 procmsg_msginfo_free(&(compose->replyinfo));
8889 procmsg_msginfo_free(&(compose->fwdinfo));
8891 g_free(compose->replyto);
8892 g_free(compose->cc);
8893 g_free(compose->bcc);
8894 g_free(compose->newsgroups);
8895 g_free(compose->followup_to);
8897 g_free(compose->ml_post);
8899 g_free(compose->inreplyto);
8900 g_free(compose->references);
8901 g_free(compose->msgid);
8902 g_free(compose->boundary);
8904 g_free(compose->redirect_filename);
8905 if (compose->undostruct)
8906 undo_destroy(compose->undostruct);
8908 g_free(compose->sig_str);
8910 g_free(compose->exteditor_file);
8912 g_free(compose->orig_charset);
8914 g_free(compose->privacy_system);
8915 g_free(compose->encdata);
8917 #ifndef USE_ALT_ADDRBOOK
8918 if (addressbook_get_target_compose() == compose)
8919 addressbook_set_target_compose(NULL);
8922 if (compose->gtkaspell) {
8923 gtkaspell_delete(compose->gtkaspell);
8924 compose->gtkaspell = NULL;
8928 if (!compose->batch) {
8929 gtk_widget_get_allocation(compose->window, &allocation);
8930 prefs_common.compose_width = allocation.width;
8931 prefs_common.compose_height = allocation.height;
8934 if (!gtk_widget_get_parent(compose->paned))
8935 gtk_widget_destroy(compose->paned);
8936 gtk_widget_destroy(compose->popupmenu);
8938 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
8939 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
8940 gtk_text_buffer_remove_selection_clipboard(buffer, clipboard);
8942 gtk_widget_destroy(compose->window);
8943 toolbar_destroy(compose->toolbar);
8944 g_free(compose->toolbar);
8945 cm_mutex_free(compose->mutex);
8949 static void compose_attach_info_free(AttachInfo *ainfo)
8951 g_free(ainfo->file);
8952 g_free(ainfo->content_type);
8953 g_free(ainfo->name);
8954 g_free(ainfo->charset);
8958 static void compose_attach_update_label(Compose *compose)
8963 GtkTreeModel *model;
8968 model = gtk_tree_view_get_model(GTK_TREE_VIEW(compose->attach_clist));
8969 if(!gtk_tree_model_get_iter_first(model, &iter)) {
8970 gtk_label_set_text(GTK_LABEL(compose->attach_label), "");
8974 while(gtk_tree_model_iter_next(model, &iter))
8977 text = g_strdup_printf("(%d)", i);
8978 gtk_label_set_text(GTK_LABEL(compose->attach_label), text);
8982 static void compose_attach_remove_selected(GtkAction *action, gpointer data)
8984 Compose *compose = (Compose *)data;
8985 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
8986 GtkTreeSelection *selection;
8988 GtkTreeModel *model;
8990 selection = gtk_tree_view_get_selection(tree_view);
8991 sel = gtk_tree_selection_get_selected_rows(selection, &model);
8996 for (cur = sel; cur != NULL; cur = cur->next) {
8997 GtkTreePath *path = cur->data;
8998 GtkTreeRowReference *ref = gtk_tree_row_reference_new
9001 gtk_tree_path_free(path);
9004 for (cur = sel; cur != NULL; cur = cur->next) {
9005 GtkTreeRowReference *ref = cur->data;
9006 GtkTreePath *path = gtk_tree_row_reference_get_path(ref);
9009 if (gtk_tree_model_get_iter(model, &iter, path))
9010 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
9012 gtk_tree_path_free(path);
9013 gtk_tree_row_reference_free(ref);
9017 compose_attach_update_label(compose);
9020 static struct _AttachProperty
9023 GtkWidget *mimetype_entry;
9024 GtkWidget *encoding_optmenu;
9025 GtkWidget *path_entry;
9026 GtkWidget *filename_entry;
9028 GtkWidget *cancel_btn;
9031 static void gtk_tree_path_free_(gpointer ptr, gpointer data)
9033 gtk_tree_path_free((GtkTreePath *)ptr);
9036 static void compose_attach_property(GtkAction *action, gpointer data)
9038 Compose *compose = (Compose *)data;
9039 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
9041 GtkComboBox *optmenu;
9042 GtkTreeSelection *selection;
9044 GtkTreeModel *model;
9047 static gboolean cancelled;
9049 /* only if one selected */
9050 selection = gtk_tree_view_get_selection(tree_view);
9051 if (gtk_tree_selection_count_selected_rows(selection) != 1)
9054 sel = gtk_tree_selection_get_selected_rows(selection, &model);
9058 path = (GtkTreePath *) sel->data;
9059 gtk_tree_model_get_iter(model, &iter, path);
9060 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
9063 g_list_foreach(sel, gtk_tree_path_free_, NULL);
9069 if (!attach_prop.window)
9070 compose_attach_property_create(&cancelled);
9071 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), TRUE);
9072 gtk_widget_grab_focus(attach_prop.ok_btn);
9073 gtk_widget_show(attach_prop.window);
9074 gtk_window_set_transient_for(GTK_WINDOW(attach_prop.window),
9075 GTK_WINDOW(compose->window));
9077 optmenu = GTK_COMBO_BOX(attach_prop.encoding_optmenu);
9078 if (ainfo->encoding == ENC_UNKNOWN)
9079 combobox_select_by_data(optmenu, ENC_BASE64);
9081 combobox_select_by_data(optmenu, ainfo->encoding);
9083 gtk_entry_set_text(GTK_ENTRY(attach_prop.mimetype_entry),
9084 ainfo->content_type ? ainfo->content_type : "");
9085 gtk_entry_set_text(GTK_ENTRY(attach_prop.path_entry),
9086 ainfo->file ? ainfo->file : "");
9087 gtk_entry_set_text(GTK_ENTRY(attach_prop.filename_entry),
9088 ainfo->name ? ainfo->name : "");
9091 const gchar *entry_text;
9093 gchar *cnttype = NULL;
9100 gtk_widget_hide(attach_prop.window);
9101 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), FALSE);
9106 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.mimetype_entry));
9107 if (*entry_text != '\0') {
9110 text = g_strstrip(g_strdup(entry_text));
9111 if ((p = strchr(text, '/')) && !strchr(p + 1, '/')) {
9112 cnttype = g_strdup(text);
9115 alertpanel_error(_("Invalid MIME type."));
9121 ainfo->encoding = combobox_get_active_data(optmenu);
9123 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.path_entry));
9124 if (*entry_text != '\0') {
9125 if (is_file_exist(entry_text) &&
9126 (size = get_file_size(entry_text)) > 0)
9127 file = g_strdup(entry_text);
9130 (_("File doesn't exist or is empty."));
9136 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.filename_entry));
9137 if (*entry_text != '\0') {
9138 g_free(ainfo->name);
9139 ainfo->name = g_strdup(entry_text);
9143 g_free(ainfo->content_type);
9144 ainfo->content_type = cnttype;
9147 g_free(ainfo->file);
9151 ainfo->size = (goffset)size;
9153 /* update tree store */
9154 text = to_human_readable(ainfo->size);
9155 gtk_tree_model_get_iter(model, &iter, path);
9156 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
9157 COL_MIMETYPE, ainfo->content_type,
9159 COL_NAME, ainfo->name,
9160 COL_CHARSET, ainfo->charset,
9166 gtk_tree_path_free(path);
9169 #define SET_LABEL_AND_ENTRY(str, entry, top) \
9171 label = gtk_label_new(str); \
9172 gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), \
9173 GTK_FILL, 0, 0, 0); \
9174 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); \
9176 entry = gtk_entry_new(); \
9177 gtk_table_attach(GTK_TABLE(table), entry, 1, 2, top, (top + 1), \
9178 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); \
9181 static void compose_attach_property_create(gboolean *cancelled)
9187 GtkWidget *mimetype_entry;
9190 GtkListStore *optmenu_menu;
9191 GtkWidget *path_entry;
9192 GtkWidget *filename_entry;
9195 GtkWidget *cancel_btn;
9196 GList *mime_type_list, *strlist;
9199 debug_print("Creating attach_property window...\n");
9201 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose_attach_property");
9202 gtk_widget_set_size_request(window, 480, -1);
9203 gtk_container_set_border_width(GTK_CONTAINER(window), 8);
9204 gtk_window_set_title(GTK_WINDOW(window), _("Properties"));
9205 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
9206 g_signal_connect(G_OBJECT(window), "delete_event",
9207 G_CALLBACK(attach_property_delete_event),
9209 g_signal_connect(G_OBJECT(window), "key_press_event",
9210 G_CALLBACK(attach_property_key_pressed),
9213 vbox = gtk_vbox_new(FALSE, 8);
9214 gtk_container_add(GTK_CONTAINER(window), vbox);
9216 table = gtk_table_new(4, 2, FALSE);
9217 gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
9218 gtk_table_set_row_spacings(GTK_TABLE(table), 8);
9219 gtk_table_set_col_spacings(GTK_TABLE(table), 8);
9221 label = gtk_label_new(_("MIME type"));
9222 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, (0 + 1),
9224 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
9225 #if !GTK_CHECK_VERSION(2, 24, 0)
9226 mimetype_entry = gtk_combo_box_entry_new_text();
9228 mimetype_entry = gtk_combo_box_text_new_with_entry();
9230 gtk_table_attach(GTK_TABLE(table), mimetype_entry, 1, 2, 0, (0 + 1),
9231 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
9233 /* stuff with list */
9234 mime_type_list = procmime_get_mime_type_list();
9236 for (; mime_type_list != NULL; mime_type_list = mime_type_list->next) {
9237 MimeType *type = (MimeType *) mime_type_list->data;
9240 tmp = g_strdup_printf("%s/%s", type->type, type->sub_type);
9242 if (g_list_find_custom(strlist, tmp, (GCompareFunc)strcmp2))
9245 strlist = g_list_insert_sorted(strlist, (gpointer)tmp,
9246 (GCompareFunc)strcmp2);
9249 for (mime_type_list = strlist; mime_type_list != NULL;
9250 mime_type_list = mime_type_list->next) {
9251 #if !GTK_CHECK_VERSION(2, 24, 0)
9252 gtk_combo_box_append_text(GTK_COMBO_BOX(mimetype_entry), mime_type_list->data);
9254 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(mimetype_entry), mime_type_list->data);
9256 g_free(mime_type_list->data);
9258 g_list_free(strlist);
9259 gtk_combo_box_set_active(GTK_COMBO_BOX(mimetype_entry), 0);
9260 mimetype_entry = gtk_bin_get_child(GTK_BIN((mimetype_entry)));
9262 label = gtk_label_new(_("Encoding"));
9263 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2,
9265 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
9267 hbox = gtk_hbox_new(FALSE, 0);
9268 gtk_table_attach(GTK_TABLE(table), hbox, 1, 2, 1, 2,
9269 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
9271 optmenu = gtkut_sc_combobox_create(NULL, TRUE);
9272 optmenu_menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
9274 COMBOBOX_ADD(optmenu_menu, "7bit", ENC_7BIT);
9275 COMBOBOX_ADD(optmenu_menu, "8bit", ENC_8BIT);
9276 COMBOBOX_ADD(optmenu_menu, "quoted-printable", ENC_QUOTED_PRINTABLE);
9277 COMBOBOX_ADD(optmenu_menu, "base64", ENC_BASE64);
9278 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), 0);
9280 gtk_box_pack_start(GTK_BOX(hbox), optmenu, TRUE, TRUE, 0);
9282 SET_LABEL_AND_ENTRY(_("Path"), path_entry, 2);
9283 SET_LABEL_AND_ENTRY(_("File name"), filename_entry, 3);
9285 gtkut_stock_button_set_create(&hbbox, &cancel_btn, GTK_STOCK_CANCEL,
9286 &ok_btn, GTK_STOCK_OK,
9288 gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
9289 gtk_widget_grab_default(ok_btn);
9291 g_signal_connect(G_OBJECT(ok_btn), "clicked",
9292 G_CALLBACK(attach_property_ok),
9294 g_signal_connect(G_OBJECT(cancel_btn), "clicked",
9295 G_CALLBACK(attach_property_cancel),
9298 gtk_widget_show_all(vbox);
9300 attach_prop.window = window;
9301 attach_prop.mimetype_entry = mimetype_entry;
9302 attach_prop.encoding_optmenu = optmenu;
9303 attach_prop.path_entry = path_entry;
9304 attach_prop.filename_entry = filename_entry;
9305 attach_prop.ok_btn = ok_btn;
9306 attach_prop.cancel_btn = cancel_btn;
9309 #undef SET_LABEL_AND_ENTRY
9311 static void attach_property_ok(GtkWidget *widget, gboolean *cancelled)
9317 static void attach_property_cancel(GtkWidget *widget, gboolean *cancelled)
9323 static gint attach_property_delete_event(GtkWidget *widget, GdkEventAny *event,
9324 gboolean *cancelled)
9332 static gboolean attach_property_key_pressed(GtkWidget *widget,
9334 gboolean *cancelled)
9336 if (event && event->keyval == GDK_KEY_Escape) {
9340 if (event && event->keyval == GDK_KEY_Return) {
9348 static void compose_exec_ext_editor(Compose *compose)
9353 GdkNativeWindow socket_wid = 0;
9357 tmp = g_strdup_printf("%s%ctmpmsg.%p", get_tmp_dir(),
9358 G_DIR_SEPARATOR, compose);
9360 if (compose_get_ext_editor_uses_socket()) {
9361 /* Only allow one socket */
9362 if (compose->exteditor_socket != NULL) {
9363 if (gtk_widget_is_focus(compose->exteditor_socket)) {
9364 /* Move the focus off of the socket */
9365 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9370 /* Create the receiving GtkSocket */
9371 socket = gtk_socket_new ();
9372 g_signal_connect (GTK_OBJECT(socket), "plug-removed",
9373 G_CALLBACK(compose_ext_editor_plug_removed_cb),
9375 gtk_box_pack_start(GTK_BOX(compose->edit_vbox), socket, TRUE, TRUE, 0);
9376 gtk_widget_set_size_request(socket, prefs_common.compose_width, -1);
9377 /* Realize the socket so that we can use its ID */
9378 gtk_widget_realize(socket);
9379 socket_wid = gtk_socket_get_id(GTK_SOCKET (socket));
9380 compose->exteditor_socket = socket;
9383 if (pipe(pipe_fds) < 0) {
9389 if ((pid = fork()) < 0) {
9396 /* close the write side of the pipe */
9399 compose->exteditor_file = g_strdup(tmp);
9400 compose->exteditor_pid = pid;
9402 compose_set_ext_editor_sensitive(compose, FALSE);
9405 compose->exteditor_ch = g_io_channel_unix_new(pipe_fds[0]);
9407 compose->exteditor_ch = g_io_channel_win32_new_fd(pipe_fds[0]);
9409 compose->exteditor_tag = g_io_add_watch(compose->exteditor_ch,
9413 } else { /* process-monitoring process */
9419 /* close the read side of the pipe */
9422 if (compose_write_body_to_file(compose, tmp) < 0) {
9423 fd_write_all(pipe_fds[1], "2\n", 2);
9427 pid_ed = compose_exec_ext_editor_real(tmp, socket_wid);
9429 fd_write_all(pipe_fds[1], "1\n", 2);
9433 /* wait until editor is terminated */
9434 waitpid(pid_ed, NULL, 0);
9436 fd_write_all(pipe_fds[1], "0\n", 2);
9443 #endif /* G_OS_UNIX */
9446 static gboolean compose_can_autosave(Compose *compose)
9448 if (compose->privacy_system && compose->use_encryption)
9449 return prefs_common.autosave && prefs_common.autosave_encrypted;
9451 return prefs_common.autosave;
9455 static gboolean compose_get_ext_editor_cmd_valid()
9457 gboolean has_s = FALSE;
9458 gboolean has_w = FALSE;
9459 const gchar *p = prefs_common_get_ext_editor_cmd();
9462 while ((p = strchr(p, '%'))) {
9468 } else if (*p == 'w') {
9479 static gint compose_exec_ext_editor_real(const gchar *file, GdkNativeWindow socket_wid)
9486 cm_return_val_if_fail(file != NULL, -1);
9488 if ((pid = fork()) < 0) {
9493 if (pid != 0) return pid;
9495 /* grandchild process */
9497 if (setpgid(0, getppid()))
9500 if (compose_get_ext_editor_cmd_valid()) {
9501 if (compose_get_ext_editor_uses_socket()) {
9502 p = g_strdup(prefs_common_get_ext_editor_cmd());
9503 s = strstr(p, "%w");
9505 if (strstr(p, "%s") < s)
9506 buf = g_strdup_printf(p, file, socket_wid);
9508 buf = g_strdup_printf(p, socket_wid, file);
9511 buf = g_strdup_printf(prefs_common_get_ext_editor_cmd(), file);
9514 if (prefs_common_get_ext_editor_cmd())
9515 g_warning("External editor command-line is invalid: '%s'",
9516 prefs_common_get_ext_editor_cmd());
9517 buf = g_strdup_printf(DEFAULT_EDITOR_CMD, file);
9520 cmdline = strsplit_with_quote(buf, " ", 0);
9522 execvp(cmdline[0], cmdline);
9525 g_strfreev(cmdline);
9530 static gboolean compose_ext_editor_kill(Compose *compose)
9532 pid_t pgid = compose->exteditor_pid * -1;
9535 ret = kill(pgid, 0);
9537 if (ret == 0 || (ret == -1 && EPERM == errno)) {
9541 msg = g_strdup_printf
9542 (_("The external editor is still working.\n"
9543 "Force terminating the process?\n"
9544 "process group id: %d"), -pgid);
9545 val = alertpanel_full(_("Notice"), msg, GTK_STOCK_NO, GTK_STOCK_YES,
9546 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
9550 if (val == G_ALERTALTERNATE) {
9551 g_source_remove(compose->exteditor_tag);
9552 g_io_channel_shutdown(compose->exteditor_ch,
9554 g_io_channel_unref(compose->exteditor_ch);
9556 if (kill(pgid, SIGTERM) < 0) perror("kill");
9557 waitpid(compose->exteditor_pid, NULL, 0);
9559 g_warning("Terminated process group id: %d. "
9560 "Temporary file: %s", -pgid, compose->exteditor_file);
9562 compose_set_ext_editor_sensitive(compose, TRUE);
9564 g_free(compose->exteditor_file);
9565 compose->exteditor_file = NULL;
9566 compose->exteditor_pid = -1;
9567 compose->exteditor_ch = NULL;
9568 compose->exteditor_tag = -1;
9576 static gboolean compose_input_cb(GIOChannel *source, GIOCondition condition,
9580 Compose *compose = (Compose *)data;
9583 debug_print("Compose: input from monitoring process\n");
9585 if (g_io_channel_read_chars(source, buf, sizeof(buf), &bytes_read, NULL) != G_IO_STATUS_NORMAL) {
9590 g_io_channel_shutdown(source, FALSE, NULL);
9591 g_io_channel_unref(source);
9593 waitpid(compose->exteditor_pid, NULL, 0);
9595 if (buf[0] == '0') { /* success */
9596 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9597 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
9598 GtkTextIter start, end;
9601 gtk_text_buffer_set_text(buffer, "", -1);
9602 compose_insert_file(compose, compose->exteditor_file);
9603 compose_changed_cb(NULL, compose);
9605 /* Check if we should save the draft or not */
9606 if (compose_can_autosave(compose))
9607 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
9609 if (claws_unlink(compose->exteditor_file) < 0)
9610 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9612 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
9613 gtk_text_buffer_get_start_iter(buffer, &start);
9614 gtk_text_buffer_get_end_iter(buffer, &end);
9615 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
9616 if (chars && strlen(chars) > 0)
9617 compose->modified = TRUE;
9619 } else if (buf[0] == '1') { /* failed */
9620 g_warning("Couldn't exec external editor");
9621 if (claws_unlink(compose->exteditor_file) < 0)
9622 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9623 } else if (buf[0] == '2') {
9624 g_warning("Couldn't write to file");
9625 } else if (buf[0] == '3') {
9626 g_warning("Pipe read failed");
9629 compose_set_ext_editor_sensitive(compose, TRUE);
9631 g_free(compose->exteditor_file);
9632 compose->exteditor_file = NULL;
9633 compose->exteditor_pid = -1;
9634 compose->exteditor_ch = NULL;
9635 compose->exteditor_tag = -1;
9636 if (compose->exteditor_socket) {
9637 gtk_widget_destroy(compose->exteditor_socket);
9638 compose->exteditor_socket = NULL;
9645 static char *ext_editor_menu_entries[] = {
9646 "Menu/Message/Send",
9647 "Menu/Message/SendLater",
9648 "Menu/Message/InsertFile",
9649 "Menu/Message/InsertSig",
9650 "Menu/Message/ReplaceSig",
9651 "Menu/Message/Save",
9652 "Menu/Message/Print",
9657 "Menu/Tools/ShowRuler",
9658 "Menu/Tools/Actions",
9663 static void compose_set_ext_editor_sensitive(Compose *compose,
9668 for (i = 0; ext_editor_menu_entries[i]; ++i) {
9669 cm_menu_set_sensitive_full(compose->ui_manager,
9670 ext_editor_menu_entries[i], sensitive);
9673 if (compose_get_ext_editor_uses_socket()) {
9675 if (compose->exteditor_socket)
9676 gtk_widget_hide(compose->exteditor_socket);
9677 gtk_widget_show(compose->scrolledwin);
9678 if (prefs_common.show_ruler)
9679 gtk_widget_show(compose->ruler_hbox);
9680 /* Fix the focus, as it doesn't go anywhere when the
9681 * socket is hidden or destroyed */
9682 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9684 g_assert (compose->exteditor_socket != NULL);
9685 /* Fix the focus, as it doesn't go anywhere when the
9686 * edit box is hidden */
9687 if (gtk_widget_is_focus(compose->text))
9688 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9689 gtk_widget_hide(compose->scrolledwin);
9690 gtk_widget_hide(compose->ruler_hbox);
9691 gtk_widget_show(compose->exteditor_socket);
9694 gtk_widget_set_sensitive(compose->text, sensitive);
9696 if (compose->toolbar->send_btn)
9697 gtk_widget_set_sensitive(compose->toolbar->send_btn, sensitive);
9698 if (compose->toolbar->sendl_btn)
9699 gtk_widget_set_sensitive(compose->toolbar->sendl_btn, sensitive);
9700 if (compose->toolbar->draft_btn)
9701 gtk_widget_set_sensitive(compose->toolbar->draft_btn, sensitive);
9702 if (compose->toolbar->insert_btn)
9703 gtk_widget_set_sensitive(compose->toolbar->insert_btn, sensitive);
9704 if (compose->toolbar->sig_btn)
9705 gtk_widget_set_sensitive(compose->toolbar->sig_btn, sensitive);
9706 if (compose->toolbar->exteditor_btn)
9707 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, sensitive);
9708 if (compose->toolbar->linewrap_current_btn)
9709 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, sensitive);
9710 if (compose->toolbar->linewrap_all_btn)
9711 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, sensitive);
9714 static gboolean compose_get_ext_editor_uses_socket()
9716 return (prefs_common_get_ext_editor_cmd() &&
9717 strstr(prefs_common_get_ext_editor_cmd(), "%w"));
9720 static gboolean compose_ext_editor_plug_removed_cb(GtkSocket *socket, Compose *compose)
9722 compose->exteditor_socket = NULL;
9723 /* returning FALSE allows destruction of the socket */
9726 #endif /* G_OS_UNIX */
9729 * compose_undo_state_changed:
9731 * Change the sensivity of the menuentries undo and redo
9733 static void compose_undo_state_changed(UndoMain *undostruct, gint undo_state,
9734 gint redo_state, gpointer data)
9736 Compose *compose = (Compose *)data;
9738 switch (undo_state) {
9739 case UNDO_STATE_TRUE:
9740 if (!undostruct->undo_state) {
9741 undostruct->undo_state = TRUE;
9742 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", TRUE);
9745 case UNDO_STATE_FALSE:
9746 if (undostruct->undo_state) {
9747 undostruct->undo_state = FALSE;
9748 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
9751 case UNDO_STATE_UNCHANGED:
9753 case UNDO_STATE_REFRESH:
9754 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", undostruct->undo_state);
9757 g_warning("Undo state not recognized");
9761 switch (redo_state) {
9762 case UNDO_STATE_TRUE:
9763 if (!undostruct->redo_state) {
9764 undostruct->redo_state = TRUE;
9765 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", TRUE);
9768 case UNDO_STATE_FALSE:
9769 if (undostruct->redo_state) {
9770 undostruct->redo_state = FALSE;
9771 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
9774 case UNDO_STATE_UNCHANGED:
9776 case UNDO_STATE_REFRESH:
9777 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", undostruct->redo_state);
9780 g_warning("Redo state not recognized");
9785 /* callback functions */
9787 static void compose_notebook_size_alloc(GtkNotebook *notebook,
9788 GtkAllocation *allocation,
9791 prefs_common.compose_notebook_height = gtk_paned_get_position(paned);
9794 /* compose_edit_size_alloc() - called when resized. don't know whether Gtk
9795 * includes "non-client" (windows-izm) in calculation, so this calculation
9796 * may not be accurate.
9798 static gboolean compose_edit_size_alloc(GtkEditable *widget,
9799 GtkAllocation *allocation,
9800 GtkSHRuler *shruler)
9802 if (prefs_common.show_ruler) {
9803 gint char_width = 0, char_height = 0;
9804 gint line_width_in_chars;
9806 gtkut_get_font_size(GTK_WIDGET(widget),
9807 &char_width, &char_height);
9808 line_width_in_chars =
9809 (allocation->width - allocation->x) / char_width;
9811 /* got the maximum */
9812 gtk_shruler_set_range(GTK_SHRULER(shruler),
9813 0.0, line_width_in_chars, 0);
9822 ComposePrefType type;
9823 gboolean entry_marked;
9826 static void account_activated(GtkComboBox *optmenu, gpointer data)
9828 Compose *compose = (Compose *)data;
9831 gchar *folderidentifier;
9832 gint account_id = 0;
9835 GSList *list, *saved_list = NULL;
9836 HeaderEntryState *state;
9838 /* Get ID of active account in the combo box */
9839 menu = gtk_combo_box_get_model(optmenu);
9840 cm_return_if_fail(gtk_combo_box_get_active_iter(optmenu, &iter));
9841 gtk_tree_model_get(menu, &iter, 1, &account_id, -1);
9843 ac = account_find_from_id(account_id);
9844 cm_return_if_fail(ac != NULL);
9846 if (ac != compose->account) {
9847 compose_select_account(compose, ac, FALSE);
9849 for (list = compose->header_list; list; list = list->next) {
9850 ComposeHeaderEntry *hentry=(ComposeHeaderEntry *)list->data;
9852 if (hentry->type == PREF_ACCOUNT || !list->next) {
9853 compose_destroy_headerentry(compose, hentry);
9856 state = g_malloc0(sizeof(HeaderEntryState));
9857 state->header = gtk_editable_get_chars(GTK_EDITABLE(
9858 gtk_bin_get_child(GTK_BIN(hentry->combo))), 0, -1);
9859 state->entry = gtk_editable_get_chars(
9860 GTK_EDITABLE(hentry->entry), 0, -1);
9861 state->type = hentry->type;
9863 saved_list = g_slist_append(saved_list, state);
9864 compose_destroy_headerentry(compose, hentry);
9867 compose->header_last = NULL;
9868 g_slist_free(compose->header_list);
9869 compose->header_list = NULL;
9870 compose->header_nextrow = 1;
9871 compose_create_header_entry(compose);
9873 if (ac->set_autocc && ac->auto_cc)
9874 compose_entry_append(compose, ac->auto_cc,
9875 COMPOSE_CC, PREF_ACCOUNT);
9876 if (ac->set_autobcc && ac->auto_bcc)
9877 compose_entry_append(compose, ac->auto_bcc,
9878 COMPOSE_BCC, PREF_ACCOUNT);
9879 if (ac->set_autoreplyto && ac->auto_replyto)
9880 compose_entry_append(compose, ac->auto_replyto,
9881 COMPOSE_REPLYTO, PREF_ACCOUNT);
9883 for (list = saved_list; list; list = list->next) {
9884 state = (HeaderEntryState *) list->data;
9886 compose_add_header_entry(compose, state->header,
9887 state->entry, state->type);
9889 g_free(state->header);
9890 g_free(state->entry);
9893 g_slist_free(saved_list);
9895 combobox_select_by_data(GTK_COMBO_BOX(compose->header_last->combo),
9896 (ac->protocol == A_NNTP) ?
9897 COMPOSE_NEWSGROUPS : COMPOSE_TO);
9900 /* Set message save folder */
9901 if (account_get_special_folder(compose->account, F_OUTBOX)) {
9902 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
9904 g_signal_connect(G_OBJECT(compose->savemsg_checkbtn), "toggled",
9905 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
9907 compose_set_save_to(compose, NULL);
9908 if (account_get_special_folder(compose->account, F_OUTBOX)) {
9909 folderidentifier = folder_item_get_identifier(account_get_special_folder
9910 (compose->account, F_OUTBOX));
9911 compose_set_save_to(compose, folderidentifier);
9912 g_free(folderidentifier);
9916 static void attach_selected(GtkTreeView *tree_view, GtkTreePath *tree_path,
9917 GtkTreeViewColumn *column, Compose *compose)
9919 compose_attach_property(NULL, compose);
9922 static gboolean attach_button_pressed(GtkWidget *widget, GdkEventButton *event,
9925 Compose *compose = (Compose *)data;
9926 GtkTreeSelection *attach_selection;
9927 gint attach_nr_selected;
9930 if (!event) return FALSE;
9932 if (event->button == 3) {
9933 attach_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
9934 attach_nr_selected = gtk_tree_selection_count_selected_rows(attach_selection);
9936 /* If no rows, or just one row is selected, right-click should
9937 * open menu relevant to the row being right-clicked on. We
9938 * achieve that by selecting the clicked row first. If more
9939 * than one row is selected, we shouldn't modify the selection,
9940 * as user may want to remove selected rows (attachments). */
9941 if (attach_nr_selected < 2) {
9942 gtk_tree_selection_unselect_all(attach_selection);
9943 attach_nr_selected = 0;
9944 gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget),
9945 event->x, event->y, &path, NULL, NULL, NULL);
9947 gtk_tree_selection_select_path(attach_selection, path);
9948 gtk_tree_path_free(path);
9949 attach_nr_selected++;
9953 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", (attach_nr_selected > 0));
9954 /* Properties menu item makes no sense with more than one row
9955 * selected, the properties dialog can only edit one attachment. */
9956 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", (attach_nr_selected == 1));
9958 gtk_menu_popup(GTK_MENU(compose->popupmenu), NULL, NULL,
9959 NULL, NULL, event->button, event->time);
9966 static gboolean attach_key_pressed(GtkWidget *widget, GdkEventKey *event,
9969 Compose *compose = (Compose *)data;
9971 if (!event) return FALSE;
9973 switch (event->keyval) {
9974 case GDK_KEY_Delete:
9975 compose_attach_remove_selected(NULL, compose);
9981 static void compose_allow_user_actions (Compose *compose, gboolean allow)
9983 toolbar_comp_set_sensitive(compose, allow);
9984 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message", allow);
9985 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", allow);
9987 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", allow);
9989 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", allow);
9990 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools", allow);
9991 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Help", allow);
9993 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), allow);
9997 static void compose_send_cb(GtkAction *action, gpointer data)
9999 Compose *compose = (Compose *)data;
10002 if (compose->exteditor_tag != -1) {
10003 debug_print("ignoring send: external editor still open\n");
10007 if (prefs_common.work_offline &&
10008 !inc_offline_should_override(TRUE,
10009 _("Claws Mail needs network access in order "
10010 "to send this email.")))
10013 if (compose->draft_timeout_tag >= 0) { /* CLAWS: disable draft timeout */
10014 g_source_remove(compose->draft_timeout_tag);
10015 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
10018 compose_send(compose);
10021 static void compose_send_later_cb(GtkAction *action, gpointer data)
10023 Compose *compose = (Compose *)data;
10027 compose_allow_user_actions(compose, FALSE);
10028 val = compose_queue_sub(compose, NULL, NULL, NULL, TRUE, TRUE);
10029 compose_allow_user_actions(compose, TRUE);
10033 compose_close(compose);
10034 } else if (val == -1) {
10035 alertpanel_error(_("Could not queue message."));
10036 } else if (val == -2) {
10037 alertpanel_error(_("Could not queue message:\n\n%s."), g_strerror(errno));
10038 } else if (val == -3) {
10039 if (privacy_peek_error())
10040 alertpanel_error(_("Could not queue message for sending:\n\n"
10041 "Signature failed: %s"), privacy_get_error());
10042 } else if (val == -4) {
10043 alertpanel_error(_("Could not queue message for sending:\n\n"
10044 "Charset conversion failed."));
10045 } else if (val == -5) {
10046 alertpanel_error(_("Could not queue message for sending:\n\n"
10047 "Couldn't get recipient encryption key."));
10048 } else if (val == -6) {
10051 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
10054 #define DRAFTED_AT_EXIT "drafted_at_exit"
10055 static void compose_register_draft(MsgInfo *info)
10057 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10058 DRAFTED_AT_EXIT, NULL);
10059 FILE *fp = g_fopen(filepath, "ab");
10062 fprintf(fp, "%s\t%d\n", folder_item_get_identifier(info->folder),
10070 gboolean compose_draft (gpointer data, guint action)
10072 Compose *compose = (Compose *)data;
10077 MsgFlags flag = {0, 0};
10078 static gboolean lock = FALSE;
10079 MsgInfo *newmsginfo;
10081 gboolean target_locked = FALSE;
10082 gboolean err = FALSE;
10084 if (lock) return FALSE;
10086 if (compose->sending)
10089 draft = account_get_special_folder(compose->account, F_DRAFT);
10090 cm_return_val_if_fail(draft != NULL, FALSE);
10092 if (!g_mutex_trylock(compose->mutex)) {
10093 /* we don't want to lock the mutex once it's available,
10094 * because as the only other part of compose.c locking
10095 * it is compose_close - which means once unlocked,
10096 * the compose struct will be freed */
10097 debug_print("couldn't lock mutex, probably sending\n");
10103 tmp = g_strdup_printf("%s%cdraft.%p", get_tmp_dir(),
10104 G_DIR_SEPARATOR, compose);
10105 if ((fp = g_fopen(tmp, "wb")) == NULL) {
10106 FILE_OP_ERROR(tmp, "fopen");
10110 /* chmod for security */
10111 if (change_file_mode_rw(fp, tmp) < 0) {
10112 FILE_OP_ERROR(tmp, "chmod");
10113 g_warning("can't change file mode");
10116 /* Save draft infos */
10117 err |= (fprintf(fp, "X-Claws-Account-Id:%d\n", compose->account->account_id) < 0);
10118 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
10120 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
10121 gchar *savefolderid;
10123 savefolderid = compose_get_save_to(compose);
10124 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
10125 g_free(savefolderid);
10127 if (compose->return_receipt) {
10128 err |= (fprintf(fp, "RRCPT:1\n") < 0);
10130 if (compose->privacy_system) {
10131 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
10132 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
10133 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
10136 /* Message-ID of message replying to */
10137 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
10138 gchar *folderid = NULL;
10140 if (compose->replyinfo->folder)
10141 folderid = folder_item_get_identifier(compose->replyinfo->folder);
10142 if (folderid == NULL)
10143 folderid = g_strdup("NULL");
10145 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
10148 /* Message-ID of message forwarding to */
10149 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
10150 gchar *folderid = NULL;
10152 if (compose->fwdinfo->folder)
10153 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
10154 if (folderid == NULL)
10155 folderid = g_strdup("NULL");
10157 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
10161 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
10162 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
10164 sheaders = compose_get_manual_headers_info(compose);
10165 err |= (fprintf(fp, "X-Claws-Manual-Headers:%s\n", sheaders) < 0);
10168 /* end of headers */
10169 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
10176 if (compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_STORE, action != COMPOSE_AUTO_SAVE) < 0) {
10180 if (fclose(fp) == EOF) {
10184 flag.perm_flags = MSG_NEW|MSG_UNREAD;
10185 if (compose->targetinfo) {
10186 target_locked = MSG_IS_LOCKED(compose->targetinfo->flags);
10188 flag.perm_flags |= MSG_LOCKED;
10190 flag.tmp_flags = MSG_DRAFT;
10192 folder_item_scan(draft);
10193 if ((msgnum = folder_item_add_msg(draft, tmp, &flag, TRUE)) < 0) {
10194 MsgInfo *tmpinfo = NULL;
10195 debug_print("didn't get msgnum after adding draft [%s]\n", compose->msgid?compose->msgid:"no msgid");
10196 if (compose->msgid) {
10197 tmpinfo = folder_item_get_msginfo_by_msgid(draft, compose->msgid);
10200 msgnum = tmpinfo->msgnum;
10201 procmsg_msginfo_free(&tmpinfo);
10202 debug_print("got draft msgnum %d from scanning\n", msgnum);
10204 debug_print("didn't get draft msgnum after scanning\n");
10207 debug_print("got draft msgnum %d from adding\n", msgnum);
10213 if (action != COMPOSE_AUTO_SAVE) {
10214 if (action != COMPOSE_DRAFT_FOR_EXIT)
10215 alertpanel_error(_("Could not save draft."));
10218 gtkut_window_popup(compose->window);
10219 val = alertpanel_full(_("Could not save draft"),
10220 _("Could not save draft.\n"
10221 "Do you want to cancel exit or discard this email?"),
10222 _("_Cancel exit"), _("_Discard email"), NULL,
10223 FALSE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
10224 if (val == G_ALERTALTERNATE) {
10226 g_mutex_unlock(compose->mutex); /* must be done before closing */
10227 compose_close(compose);
10231 g_mutex_unlock(compose->mutex); /* must be done before closing */
10240 if (compose->mode == COMPOSE_REEDIT) {
10241 compose_remove_reedit_target(compose, TRUE);
10244 newmsginfo = folder_item_get_msginfo(draft, msgnum);
10247 procmsg_msginfo_unset_flags(newmsginfo, ~0, ~0);
10249 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD|MSG_LOCKED, MSG_DRAFT);
10251 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD, MSG_DRAFT);
10252 if (compose_use_attach(compose) && action != COMPOSE_AUTO_SAVE)
10253 procmsg_msginfo_set_flags(newmsginfo, 0,
10254 MSG_HAS_ATTACHMENT);
10256 if (action == COMPOSE_DRAFT_FOR_EXIT) {
10257 compose_register_draft(newmsginfo);
10259 procmsg_msginfo_free(&newmsginfo);
10262 folder_item_scan(draft);
10264 if (action == COMPOSE_QUIT_EDITING || action == COMPOSE_DRAFT_FOR_EXIT) {
10266 g_mutex_unlock(compose->mutex); /* must be done before closing */
10267 compose_close(compose);
10273 path = folder_item_fetch_msg(draft, msgnum);
10274 if (path == NULL) {
10275 debug_print("can't fetch %s:%d\n", draft->path, msgnum);
10278 if (g_stat(path, &s) < 0) {
10279 FILE_OP_ERROR(path, "stat");
10285 procmsg_msginfo_free(&(compose->targetinfo));
10286 compose->targetinfo = procmsg_msginfo_new();
10287 compose->targetinfo->msgnum = msgnum;
10288 compose->targetinfo->size = (goffset)s.st_size;
10289 compose->targetinfo->mtime = s.st_mtime;
10290 compose->targetinfo->folder = draft;
10292 procmsg_msginfo_set_flags(compose->targetinfo, MSG_LOCKED, 0);
10293 compose->mode = COMPOSE_REEDIT;
10295 if (action == COMPOSE_AUTO_SAVE) {
10296 compose->autosaved_draft = compose->targetinfo;
10298 compose->modified = FALSE;
10299 compose_set_title(compose);
10303 g_mutex_unlock(compose->mutex);
10307 void compose_clear_exit_drafts(void)
10309 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10310 DRAFTED_AT_EXIT, NULL);
10311 if (is_file_exist(filepath))
10312 claws_unlink(filepath);
10317 void compose_reopen_exit_drafts(void)
10319 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10320 DRAFTED_AT_EXIT, NULL);
10321 FILE *fp = g_fopen(filepath, "rb");
10325 while (fgets(buf, sizeof(buf), fp)) {
10326 gchar **parts = g_strsplit(buf, "\t", 2);
10327 const gchar *folder = parts[0];
10328 int msgnum = parts[1] ? atoi(parts[1]):-1;
10330 if (folder && *folder && msgnum > -1) {
10331 FolderItem *item = folder_find_item_from_identifier(folder);
10332 MsgInfo *info = folder_item_get_msginfo(item, msgnum);
10334 compose_reedit(info, FALSE);
10341 compose_clear_exit_drafts();
10344 static void compose_save_cb(GtkAction *action, gpointer data)
10346 Compose *compose = (Compose *)data;
10347 compose_draft(compose, COMPOSE_KEEP_EDITING);
10348 compose->rmode = COMPOSE_REEDIT;
10351 void compose_attach_from_list(Compose *compose, GList *file_list, gboolean free_data)
10353 if (compose && file_list) {
10356 for ( tmp = file_list; tmp; tmp = tmp->next) {
10357 gchar *file = (gchar *) tmp->data;
10358 gchar *utf8_filename = conv_filename_to_utf8(file);
10359 compose_attach_append(compose, file, utf8_filename, NULL, NULL);
10360 compose_changed_cb(NULL, compose);
10365 g_free(utf8_filename);
10370 static void compose_attach_cb(GtkAction *action, gpointer data)
10372 Compose *compose = (Compose *)data;
10375 if (compose->redirect_filename != NULL)
10378 /* Set focus_window properly, in case we were called via popup menu,
10379 * which unsets it (via focus_out_event callback on compose window). */
10380 manage_window_focus_in(compose->window, NULL, NULL);
10382 file_list = filesel_select_multiple_files_open(_("Select file"), NULL);
10385 compose_attach_from_list(compose, file_list, TRUE);
10386 g_list_free(file_list);
10390 static void compose_insert_file_cb(GtkAction *action, gpointer data)
10392 Compose *compose = (Compose *)data;
10394 gint files_inserted = 0;
10396 file_list = filesel_select_multiple_files_open(_("Select file"), NULL);
10401 for ( tmp = file_list; tmp; tmp = tmp->next) {
10402 gchar *file = (gchar *) tmp->data;
10403 gchar *filedup = g_strdup(file);
10404 gchar *shortfile = g_path_get_basename(filedup);
10405 ComposeInsertResult res;
10406 /* insert the file if the file is short or if the user confirmed that
10407 he/she wants to insert the large file */
10408 res = compose_insert_file(compose, file);
10409 if (res == COMPOSE_INSERT_READ_ERROR) {
10410 alertpanel_error(_("File '%s' could not be read."), shortfile);
10411 } else if (res == COMPOSE_INSERT_INVALID_CHARACTER) {
10412 alertpanel_error(_("File '%s' contained invalid characters\n"
10413 "for the current encoding, insertion may be incorrect."),
10415 } else if (res == COMPOSE_INSERT_SUCCESS)
10422 g_list_free(file_list);
10426 if (files_inserted > 0 && compose->gtkaspell &&
10427 compose->gtkaspell->check_while_typing)
10428 gtkaspell_highlight_all(compose->gtkaspell);
10432 static void compose_insert_sig_cb(GtkAction *action, gpointer data)
10434 Compose *compose = (Compose *)data;
10436 compose_insert_sig(compose, FALSE);
10439 static void compose_replace_sig_cb(GtkAction *action, gpointer data)
10441 Compose *compose = (Compose *)data;
10443 compose_insert_sig(compose, TRUE);
10446 static gint compose_delete_cb(GtkWidget *widget, GdkEventAny *event,
10450 Compose *compose = (Compose *)data;
10452 gtkut_widget_get_uposition(widget, &x, &y);
10453 if (!compose->batch) {
10454 prefs_common.compose_x = x;
10455 prefs_common.compose_y = y;
10457 if (compose->sending || compose->updating)
10459 compose_close_cb(NULL, compose);
10463 void compose_close_toolbar(Compose *compose)
10465 compose_close_cb(NULL, compose);
10468 static void compose_close_cb(GtkAction *action, gpointer data)
10470 Compose *compose = (Compose *)data;
10474 if (compose->exteditor_tag != -1) {
10475 if (!compose_ext_editor_kill(compose))
10480 if (compose->modified) {
10481 gboolean reedit = (compose->rmode == COMPOSE_REEDIT);
10482 if (!g_mutex_trylock(compose->mutex)) {
10483 /* we don't want to lock the mutex once it's available,
10484 * because as the only other part of compose.c locking
10485 * it is compose_close - which means once unlocked,
10486 * the compose struct will be freed */
10487 debug_print("couldn't lock mutex, probably sending\n");
10491 val = alertpanel(_("Discard message"),
10492 _("This message has been modified. Discard it?"),
10493 _("_Discard"), _("_Save to Drafts"), GTK_STOCK_CANCEL);
10495 val = alertpanel(_("Save changes"),
10496 _("This message has been modified. Save the latest changes?"),
10497 _("_Don't save"), g_strconcat("+", _("_Save to Drafts"), NULL),
10500 g_mutex_unlock(compose->mutex);
10502 case G_ALERTDEFAULT:
10503 if (compose_can_autosave(compose) && !reedit)
10504 compose_remove_draft(compose);
10506 case G_ALERTALTERNATE:
10507 compose_draft(data, COMPOSE_QUIT_EDITING);
10514 compose_close(compose);
10517 static void compose_print_cb(GtkAction *action, gpointer data)
10519 Compose *compose = (Compose *) data;
10521 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
10522 if (compose->targetinfo)
10523 messageview_print(compose->targetinfo, FALSE, -1, -1, 0);
10526 static void compose_set_encoding_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
10528 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
10529 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
10530 Compose *compose = (Compose *) data;
10533 compose->out_encoding = (CharSet)value;
10536 static void compose_address_cb(GtkAction *action, gpointer data)
10538 Compose *compose = (Compose *)data;
10540 #ifndef USE_ALT_ADDRBOOK
10541 addressbook_open(compose);
10543 GError* error = NULL;
10544 addressbook_connect_signals(compose);
10545 addressbook_dbus_open(TRUE, &error);
10547 g_warning("%s", error->message);
10548 g_error_free(error);
10553 static void about_show_cb(GtkAction *action, gpointer data)
10558 static void compose_template_activate_cb(GtkWidget *widget, gpointer data)
10560 Compose *compose = (Compose *)data;
10565 tmpl = g_object_get_data(G_OBJECT(widget), "template");
10566 cm_return_if_fail(tmpl != NULL);
10568 msg = g_strdup_printf(_("Do you want to apply the template '%s'?"),
10570 val = alertpanel(_("Apply template"), msg,
10571 _("_Replace"), _("_Insert"), GTK_STOCK_CANCEL);
10574 if (val == G_ALERTDEFAULT)
10575 compose_template_apply(compose, tmpl, TRUE);
10576 else if (val == G_ALERTALTERNATE)
10577 compose_template_apply(compose, tmpl, FALSE);
10580 static void compose_ext_editor_cb(GtkAction *action, gpointer data)
10582 Compose *compose = (Compose *)data;
10585 if (compose->exteditor_tag != -1) {
10586 debug_print("ignoring open external editor: external editor still open\n");
10590 compose_exec_ext_editor(compose);
10593 static void compose_undo_cb(GtkAction *action, gpointer data)
10595 Compose *compose = (Compose *)data;
10596 gboolean prev_autowrap = compose->autowrap;
10598 compose->autowrap = FALSE;
10599 undo_undo(compose->undostruct);
10600 compose->autowrap = prev_autowrap;
10603 static void compose_redo_cb(GtkAction *action, gpointer data)
10605 Compose *compose = (Compose *)data;
10606 gboolean prev_autowrap = compose->autowrap;
10608 compose->autowrap = FALSE;
10609 undo_redo(compose->undostruct);
10610 compose->autowrap = prev_autowrap;
10613 static void entry_cut_clipboard(GtkWidget *entry)
10615 if (GTK_IS_EDITABLE(entry))
10616 gtk_editable_cut_clipboard (GTK_EDITABLE(entry));
10617 else if (GTK_IS_TEXT_VIEW(entry))
10618 gtk_text_buffer_cut_clipboard(
10619 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10620 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
10624 static void entry_copy_clipboard(GtkWidget *entry)
10626 if (GTK_IS_EDITABLE(entry))
10627 gtk_editable_copy_clipboard (GTK_EDITABLE(entry));
10628 else if (GTK_IS_TEXT_VIEW(entry))
10629 gtk_text_buffer_copy_clipboard(
10630 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10631 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
10634 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry,
10635 gboolean wrap, GdkAtom clip, GtkTextIter *insert_place)
10637 if (GTK_IS_TEXT_VIEW(entry)) {
10638 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10639 GtkTextMark *mark_start = gtk_text_buffer_get_insert(buffer);
10640 GtkTextIter start_iter, end_iter;
10642 gchar *contents = gtk_clipboard_wait_for_text(gtk_clipboard_get(clip));
10644 if (contents == NULL)
10647 /* we shouldn't delete the selection when middle-click-pasting, or we
10648 * can't mid-click-paste our own selection */
10649 if (clip != GDK_SELECTION_PRIMARY) {
10650 undo_paste_clipboard(GTK_TEXT_VIEW(compose->text), compose->undostruct);
10651 gtk_text_buffer_delete_selection(buffer, FALSE, TRUE);
10654 if (insert_place == NULL) {
10655 /* if insert_place isn't specified, insert at the cursor.
10656 * used for Ctrl-V pasting */
10657 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10658 start = gtk_text_iter_get_offset(&start_iter);
10659 gtk_text_buffer_insert(buffer, &start_iter, contents, strlen(contents));
10661 /* if insert_place is specified, paste here.
10662 * used for mid-click-pasting */
10663 start = gtk_text_iter_get_offset(insert_place);
10664 gtk_text_buffer_insert(buffer, insert_place, contents, strlen(contents));
10665 if (prefs_common.primary_paste_unselects)
10666 gtk_text_buffer_select_range(buffer, insert_place, insert_place);
10670 /* paste unwrapped: mark the paste so it's not wrapped later */
10671 end = start + strlen(contents);
10672 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, start);
10673 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, end);
10674 gtk_text_buffer_apply_tag_by_name(buffer, "no_wrap", &start_iter, &end_iter);
10675 } else if (wrap && clip == GDK_SELECTION_PRIMARY) {
10676 /* rewrap paragraph now (after a mid-click-paste) */
10677 mark_start = gtk_text_buffer_get_insert(buffer);
10678 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10679 gtk_text_iter_backward_char(&start_iter);
10680 compose_beautify_paragraph(compose, &start_iter, TRUE);
10682 } else if (GTK_IS_EDITABLE(entry))
10683 gtk_editable_paste_clipboard (GTK_EDITABLE(entry));
10685 compose->modified = TRUE;
10688 static void entry_allsel(GtkWidget *entry)
10690 if (GTK_IS_EDITABLE(entry))
10691 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
10692 else if (GTK_IS_TEXT_VIEW(entry)) {
10693 GtkTextIter startiter, enditer;
10694 GtkTextBuffer *textbuf;
10696 textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10697 gtk_text_buffer_get_start_iter(textbuf, &startiter);
10698 gtk_text_buffer_get_end_iter(textbuf, &enditer);
10700 gtk_text_buffer_move_mark_by_name(textbuf,
10701 "selection_bound", &startiter);
10702 gtk_text_buffer_move_mark_by_name(textbuf,
10703 "insert", &enditer);
10707 static void compose_cut_cb(GtkAction *action, gpointer data)
10709 Compose *compose = (Compose *)data;
10710 if (compose->focused_editable
10711 #ifndef GENERIC_UMPC
10712 && gtk_widget_has_focus(compose->focused_editable)
10715 entry_cut_clipboard(compose->focused_editable);
10718 static void compose_copy_cb(GtkAction *action, gpointer data)
10720 Compose *compose = (Compose *)data;
10721 if (compose->focused_editable
10722 #ifndef GENERIC_UMPC
10723 && gtk_widget_has_focus(compose->focused_editable)
10726 entry_copy_clipboard(compose->focused_editable);
10729 static void compose_paste_cb(GtkAction *action, gpointer data)
10731 Compose *compose = (Compose *)data;
10732 gint prev_autowrap;
10733 GtkTextBuffer *buffer;
10735 if (compose->focused_editable &&
10736 #ifndef GENERIC_UMPC
10737 gtk_widget_has_focus(compose->focused_editable)
10740 entry_paste_clipboard(compose, compose->focused_editable,
10741 prefs_common.linewrap_pastes,
10742 GDK_SELECTION_CLIPBOARD, NULL);
10747 #ifndef GENERIC_UMPC
10748 gtk_widget_has_focus(compose->text) &&
10750 compose->gtkaspell &&
10751 compose->gtkaspell->check_while_typing)
10752 gtkaspell_highlight_all(compose->gtkaspell);
10756 static void compose_paste_as_quote_cb(GtkAction *action, gpointer data)
10758 Compose *compose = (Compose *)data;
10759 gint wrap_quote = prefs_common.linewrap_quote;
10760 if (compose->focused_editable
10761 #ifndef GENERIC_UMPC
10762 && gtk_widget_has_focus(compose->focused_editable)
10765 /* let text_insert() (called directly or at a later time
10766 * after the gtk_editable_paste_clipboard) know that
10767 * text is to be inserted as a quotation. implemented
10768 * by using a simple refcount... */
10769 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data(
10770 G_OBJECT(compose->focused_editable),
10771 "paste_as_quotation"));
10772 g_object_set_data(G_OBJECT(compose->focused_editable),
10773 "paste_as_quotation",
10774 GINT_TO_POINTER(paste_as_quotation + 1));
10775 prefs_common.linewrap_quote = prefs_common.linewrap_pastes;
10776 entry_paste_clipboard(compose, compose->focused_editable,
10777 prefs_common.linewrap_pastes,
10778 GDK_SELECTION_CLIPBOARD, NULL);
10779 prefs_common.linewrap_quote = wrap_quote;
10783 static void compose_paste_no_wrap_cb(GtkAction *action, gpointer data)
10785 Compose *compose = (Compose *)data;
10786 gint prev_autowrap;
10787 GtkTextBuffer *buffer;
10789 if (compose->focused_editable
10790 #ifndef GENERIC_UMPC
10791 && gtk_widget_has_focus(compose->focused_editable)
10794 entry_paste_clipboard(compose, compose->focused_editable, FALSE,
10795 GDK_SELECTION_CLIPBOARD, NULL);
10800 #ifndef GENERIC_UMPC
10801 gtk_widget_has_focus(compose->text) &&
10803 compose->gtkaspell &&
10804 compose->gtkaspell->check_while_typing)
10805 gtkaspell_highlight_all(compose->gtkaspell);
10809 static void compose_paste_wrap_cb(GtkAction *action, gpointer data)
10811 Compose *compose = (Compose *)data;
10812 gint prev_autowrap;
10813 GtkTextBuffer *buffer;
10815 if (compose->focused_editable
10816 #ifndef GENERIC_UMPC
10817 && gtk_widget_has_focus(compose->focused_editable)
10820 entry_paste_clipboard(compose, compose->focused_editable, TRUE,
10821 GDK_SELECTION_CLIPBOARD, NULL);
10826 #ifndef GENERIC_UMPC
10827 gtk_widget_has_focus(compose->text) &&
10829 compose->gtkaspell &&
10830 compose->gtkaspell->check_while_typing)
10831 gtkaspell_highlight_all(compose->gtkaspell);
10835 static void compose_allsel_cb(GtkAction *action, gpointer data)
10837 Compose *compose = (Compose *)data;
10838 if (compose->focused_editable
10839 #ifndef GENERIC_UMPC
10840 && gtk_widget_has_focus(compose->focused_editable)
10843 entry_allsel(compose->focused_editable);
10846 static void textview_move_beginning_of_line (GtkTextView *text)
10848 GtkTextBuffer *buffer;
10852 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10854 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10855 mark = gtk_text_buffer_get_insert(buffer);
10856 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10857 gtk_text_iter_set_line_offset(&ins, 0);
10858 gtk_text_buffer_place_cursor(buffer, &ins);
10861 static void textview_move_forward_character (GtkTextView *text)
10863 GtkTextBuffer *buffer;
10867 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10869 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10870 mark = gtk_text_buffer_get_insert(buffer);
10871 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10872 if (gtk_text_iter_forward_cursor_position(&ins))
10873 gtk_text_buffer_place_cursor(buffer, &ins);
10876 static void textview_move_backward_character (GtkTextView *text)
10878 GtkTextBuffer *buffer;
10882 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10884 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10885 mark = gtk_text_buffer_get_insert(buffer);
10886 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10887 if (gtk_text_iter_backward_cursor_position(&ins))
10888 gtk_text_buffer_place_cursor(buffer, &ins);
10891 static void textview_move_forward_word (GtkTextView *text)
10893 GtkTextBuffer *buffer;
10898 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10900 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10901 mark = gtk_text_buffer_get_insert(buffer);
10902 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10903 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
10904 if (gtk_text_iter_forward_word_ends(&ins, count)) {
10905 gtk_text_iter_backward_word_start(&ins);
10906 gtk_text_buffer_place_cursor(buffer, &ins);
10910 static void textview_move_backward_word (GtkTextView *text)
10912 GtkTextBuffer *buffer;
10916 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10918 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10919 mark = gtk_text_buffer_get_insert(buffer);
10920 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10921 if (gtk_text_iter_backward_word_starts(&ins, 1))
10922 gtk_text_buffer_place_cursor(buffer, &ins);
10925 static void textview_move_end_of_line (GtkTextView *text)
10927 GtkTextBuffer *buffer;
10931 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10933 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10934 mark = gtk_text_buffer_get_insert(buffer);
10935 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10936 if (gtk_text_iter_forward_to_line_end(&ins))
10937 gtk_text_buffer_place_cursor(buffer, &ins);
10940 static void textview_move_next_line (GtkTextView *text)
10942 GtkTextBuffer *buffer;
10947 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10949 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10950 mark = gtk_text_buffer_get_insert(buffer);
10951 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10952 offset = gtk_text_iter_get_line_offset(&ins);
10953 if (gtk_text_iter_forward_line(&ins)) {
10954 gtk_text_iter_set_line_offset(&ins, offset);
10955 gtk_text_buffer_place_cursor(buffer, &ins);
10959 static void textview_move_previous_line (GtkTextView *text)
10961 GtkTextBuffer *buffer;
10966 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10968 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10969 mark = gtk_text_buffer_get_insert(buffer);
10970 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10971 offset = gtk_text_iter_get_line_offset(&ins);
10972 if (gtk_text_iter_backward_line(&ins)) {
10973 gtk_text_iter_set_line_offset(&ins, offset);
10974 gtk_text_buffer_place_cursor(buffer, &ins);
10978 static void textview_delete_forward_character (GtkTextView *text)
10980 GtkTextBuffer *buffer;
10982 GtkTextIter ins, end_iter;
10984 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10986 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10987 mark = gtk_text_buffer_get_insert(buffer);
10988 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10990 if (gtk_text_iter_forward_char(&end_iter)) {
10991 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10995 static void textview_delete_backward_character (GtkTextView *text)
10997 GtkTextBuffer *buffer;
10999 GtkTextIter ins, end_iter;
11001 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11003 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11004 mark = gtk_text_buffer_get_insert(buffer);
11005 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11007 if (gtk_text_iter_backward_char(&end_iter)) {
11008 gtk_text_buffer_delete(buffer, &end_iter, &ins);
11012 static void textview_delete_forward_word (GtkTextView *text)
11014 GtkTextBuffer *buffer;
11016 GtkTextIter ins, end_iter;
11018 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11020 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11021 mark = gtk_text_buffer_get_insert(buffer);
11022 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11024 if (gtk_text_iter_forward_word_end(&end_iter)) {
11025 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11029 static void textview_delete_backward_word (GtkTextView *text)
11031 GtkTextBuffer *buffer;
11033 GtkTextIter ins, end_iter;
11035 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11037 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11038 mark = gtk_text_buffer_get_insert(buffer);
11039 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11041 if (gtk_text_iter_backward_word_start(&end_iter)) {
11042 gtk_text_buffer_delete(buffer, &end_iter, &ins);
11046 static void textview_delete_line (GtkTextView *text)
11048 GtkTextBuffer *buffer;
11050 GtkTextIter ins, start_iter, end_iter;
11052 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11054 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11055 mark = gtk_text_buffer_get_insert(buffer);
11056 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11059 gtk_text_iter_set_line_offset(&start_iter, 0);
11062 if (gtk_text_iter_ends_line(&end_iter)){
11063 if (!gtk_text_iter_forward_char(&end_iter))
11064 gtk_text_iter_backward_char(&start_iter);
11067 gtk_text_iter_forward_to_line_end(&end_iter);
11068 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
11071 static void textview_delete_to_line_end (GtkTextView *text)
11073 GtkTextBuffer *buffer;
11075 GtkTextIter ins, end_iter;
11077 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11079 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11080 mark = gtk_text_buffer_get_insert(buffer);
11081 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11083 if (gtk_text_iter_ends_line(&end_iter))
11084 gtk_text_iter_forward_char(&end_iter);
11086 gtk_text_iter_forward_to_line_end(&end_iter);
11087 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11090 #define DO_ACTION(name, act) { \
11091 if(!strcmp(name, a_name)) { \
11095 static ComposeCallAdvancedAction compose_call_advanced_action_from_path(GtkAction *action)
11097 const gchar *a_name = gtk_action_get_name(action);
11098 DO_ACTION("Edit/Advanced/BackChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER);
11099 DO_ACTION("Edit/Advanced/ForwChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER);
11100 DO_ACTION("Edit/Advanced/BackWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD);
11101 DO_ACTION("Edit/Advanced/ForwWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD);
11102 DO_ACTION("Edit/Advanced/BegLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE);
11103 DO_ACTION("Edit/Advanced/EndLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE);
11104 DO_ACTION("Edit/Advanced/PrevLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE);
11105 DO_ACTION("Edit/Advanced/NextLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE);
11106 DO_ACTION("Edit/Advanced/DelBackChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER);
11107 DO_ACTION("Edit/Advanced/DelForwChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER);
11108 DO_ACTION("Edit/Advanced/DelBackWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD);
11109 DO_ACTION("Edit/Advanced/DelForwWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD);
11110 DO_ACTION("Edit/Advanced/DelLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE);
11111 DO_ACTION("Edit/Advanced/DelEndLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END);
11112 return COMPOSE_CALL_ADVANCED_ACTION_UNDEFINED;
11115 static void compose_advanced_action_cb(GtkAction *gaction, gpointer data)
11117 Compose *compose = (Compose *)data;
11118 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11119 ComposeCallAdvancedAction action = COMPOSE_CALL_ADVANCED_ACTION_UNDEFINED;
11121 action = compose_call_advanced_action_from_path(gaction);
11124 void (*do_action) (GtkTextView *text);
11125 } action_table[] = {
11126 {textview_move_beginning_of_line},
11127 {textview_move_forward_character},
11128 {textview_move_backward_character},
11129 {textview_move_forward_word},
11130 {textview_move_backward_word},
11131 {textview_move_end_of_line},
11132 {textview_move_next_line},
11133 {textview_move_previous_line},
11134 {textview_delete_forward_character},
11135 {textview_delete_backward_character},
11136 {textview_delete_forward_word},
11137 {textview_delete_backward_word},
11138 {textview_delete_line},
11139 {textview_delete_to_line_end}
11142 if (!gtk_widget_has_focus(GTK_WIDGET(text))) return;
11144 if (action >= COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE &&
11145 action <= COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END) {
11146 if (action_table[action].do_action)
11147 action_table[action].do_action(text);
11149 g_warning("Not implemented yet.");
11153 static void compose_grab_focus_cb(GtkWidget *widget, Compose *compose)
11155 GtkAllocation allocation;
11159 if (GTK_IS_EDITABLE(widget)) {
11160 str = gtk_editable_get_chars(GTK_EDITABLE(widget), 0, -1);
11161 gtk_editable_set_position(GTK_EDITABLE(widget),
11164 if ((parent = gtk_widget_get_parent(widget))
11165 && (parent = gtk_widget_get_parent(parent))
11166 && (parent = gtk_widget_get_parent(parent))) {
11167 if (GTK_IS_SCROLLED_WINDOW(parent)) {
11168 gtk_widget_get_allocation(widget, &allocation);
11169 gint y = allocation.y;
11170 gint height = allocation.height;
11171 GtkAdjustment *shown = gtk_scrolled_window_get_vadjustment
11172 (GTK_SCROLLED_WINDOW(parent));
11174 gfloat value = gtk_adjustment_get_value(shown);
11175 gfloat upper = gtk_adjustment_get_upper(shown);
11176 gfloat page_size = gtk_adjustment_get_page_size(shown);
11177 if (y < (int)value) {
11178 gtk_adjustment_set_value(shown, y - 1);
11180 if ((y + height) > ((int)value + (int)page_size)) {
11181 if ((y - height - 1) < ((int)upper - (int)page_size)) {
11182 gtk_adjustment_set_value(shown,
11183 y + height - (int)page_size - 1);
11185 gtk_adjustment_set_value(shown,
11186 (int)upper - (int)page_size - 1);
11193 if (GTK_IS_EDITABLE(widget) || GTK_IS_TEXT_VIEW(widget))
11194 compose->focused_editable = widget;
11196 #ifdef GENERIC_UMPC
11197 if (GTK_IS_TEXT_VIEW(widget)
11198 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->edit_vbox) {
11199 g_object_ref(compose->notebook);
11200 g_object_ref(compose->edit_vbox);
11201 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
11202 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
11203 gtk_paned_add1(GTK_PANED(compose->paned), compose->edit_vbox);
11204 gtk_paned_add2(GTK_PANED(compose->paned), compose->notebook);
11205 g_object_unref(compose->notebook);
11206 g_object_unref(compose->edit_vbox);
11207 g_signal_handlers_block_by_func(G_OBJECT(widget),
11208 G_CALLBACK(compose_grab_focus_cb),
11210 gtk_widget_grab_focus(widget);
11211 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
11212 G_CALLBACK(compose_grab_focus_cb),
11214 } else if (!GTK_IS_TEXT_VIEW(widget)
11215 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->notebook) {
11216 g_object_ref(compose->notebook);
11217 g_object_ref(compose->edit_vbox);
11218 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
11219 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
11220 gtk_paned_add1(GTK_PANED(compose->paned), compose->notebook);
11221 gtk_paned_add2(GTK_PANED(compose->paned), compose->edit_vbox);
11222 g_object_unref(compose->notebook);
11223 g_object_unref(compose->edit_vbox);
11224 g_signal_handlers_block_by_func(G_OBJECT(widget),
11225 G_CALLBACK(compose_grab_focus_cb),
11227 gtk_widget_grab_focus(widget);
11228 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
11229 G_CALLBACK(compose_grab_focus_cb),
11235 static void compose_changed_cb(GtkTextBuffer *textbuf, Compose *compose)
11237 compose->modified = TRUE;
11238 /* compose_beautify_paragraph(compose, NULL, TRUE); */
11239 #ifndef GENERIC_UMPC
11240 compose_set_title(compose);
11244 static void compose_wrap_cb(GtkAction *action, gpointer data)
11246 Compose *compose = (Compose *)data;
11247 compose_beautify_paragraph(compose, NULL, TRUE);
11250 static void compose_wrap_all_cb(GtkAction *action, gpointer data)
11252 Compose *compose = (Compose *)data;
11253 compose_wrap_all_full(compose, TRUE);
11256 static void compose_find_cb(GtkAction *action, gpointer data)
11258 Compose *compose = (Compose *)data;
11260 message_search_compose(compose);
11263 static void compose_toggle_autowrap_cb(GtkToggleAction *action,
11266 Compose *compose = (Compose *)data;
11267 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11268 if (compose->autowrap)
11269 compose_wrap_all_full(compose, TRUE);
11270 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11273 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
11276 Compose *compose = (Compose *)data;
11277 compose->autoindent = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11280 static void compose_toggle_sign_cb(GtkToggleAction *action, gpointer data)
11282 Compose *compose = (Compose *)data;
11284 compose->use_signing = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11287 static void compose_toggle_encrypt_cb(GtkToggleAction *action, gpointer data)
11289 Compose *compose = (Compose *)data;
11291 compose->use_encryption = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11294 static void activate_privacy_system(Compose *compose, PrefsAccount *account, gboolean warn)
11296 g_free(compose->privacy_system);
11297 g_free(compose->encdata);
11299 compose->privacy_system = g_strdup(account->default_privacy_system);
11300 compose_update_privacy_system_menu_item(compose, warn);
11303 static void compose_toggle_ruler_cb(GtkToggleAction *action, gpointer data)
11305 Compose *compose = (Compose *)data;
11307 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action))) {
11308 gtk_widget_show(compose->ruler_hbox);
11309 prefs_common.show_ruler = TRUE;
11311 gtk_widget_hide(compose->ruler_hbox);
11312 gtk_widget_queue_resize(compose->edit_vbox);
11313 prefs_common.show_ruler = FALSE;
11317 static void compose_attach_drag_received_cb (GtkWidget *widget,
11318 GdkDragContext *context,
11321 GtkSelectionData *data,
11324 gpointer user_data)
11326 Compose *compose = (Compose *)user_data;
11330 type = gtk_selection_data_get_data_type(data);
11331 if ((gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list"))
11332 && gtk_drag_get_source_widget(context) !=
11333 summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
11334 list = uri_list_extract_filenames(
11335 (const gchar *)gtk_selection_data_get_data(data));
11336 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11337 gchar *utf8_filename = conv_filename_to_utf8((const gchar *)tmp->data);
11338 compose_attach_append
11339 (compose, (const gchar *)tmp->data,
11340 utf8_filename, NULL, NULL);
11341 g_free(utf8_filename);
11343 if (list) compose_changed_cb(NULL, compose);
11344 list_free_strings(list);
11346 } else if (gtk_drag_get_source_widget(context)
11347 == summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
11348 /* comes from our summaryview */
11349 SummaryView * summaryview = NULL;
11350 GSList * list = NULL, *cur = NULL;
11352 if (mainwindow_get_mainwindow())
11353 summaryview = mainwindow_get_mainwindow()->summaryview;
11356 list = summary_get_selected_msg_list(summaryview);
11358 for (cur = list; cur; cur = cur->next) {
11359 MsgInfo *msginfo = (MsgInfo *)cur->data;
11360 gchar *file = NULL;
11362 file = procmsg_get_message_file_full(msginfo,
11365 compose_attach_append(compose, (const gchar *)file,
11366 (const gchar *)file, "message/rfc822", NULL);
11370 g_slist_free(list);
11374 static gboolean compose_drag_drop(GtkWidget *widget,
11375 GdkDragContext *drag_context,
11377 guint time, gpointer user_data)
11379 /* not handling this signal makes compose_insert_drag_received_cb
11384 static gboolean completion_set_focus_to_subject
11385 (GtkWidget *widget,
11386 GdkEventKey *event,
11389 cm_return_val_if_fail(compose != NULL, FALSE);
11391 /* make backtab move to subject field */
11392 if(event->keyval == GDK_KEY_ISO_Left_Tab) {
11393 gtk_widget_grab_focus(compose->subject_entry);
11399 static void compose_insert_drag_received_cb (GtkWidget *widget,
11400 GdkDragContext *drag_context,
11403 GtkSelectionData *data,
11406 gpointer user_data)
11408 Compose *compose = (Compose *)user_data;
11414 /* strangely, testing data->type == gdk_atom_intern("text/uri-list", TRUE)
11416 type = gtk_selection_data_get_data_type(data);
11417 if (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list")) {
11418 AlertValue val = G_ALERTDEFAULT;
11419 const gchar* ddata = (const gchar *)gtk_selection_data_get_data(data);
11421 list = uri_list_extract_filenames(ddata);
11422 num_files = g_list_length(list);
11423 if (list == NULL && strstr(ddata, "://")) {
11424 /* Assume a list of no files, and data has ://, is a remote link */
11425 gchar *tmpdata = g_strstrip(g_strdup(ddata));
11426 gchar *tmpfile = get_tmp_file();
11427 str_write_to_file(tmpdata, tmpfile);
11429 compose_insert_file(compose, tmpfile);
11430 claws_unlink(tmpfile);
11432 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11433 compose_beautify_paragraph(compose, NULL, TRUE);
11436 switch (prefs_common.compose_dnd_mode) {
11437 case COMPOSE_DND_ASK:
11438 msg = g_strdup_printf(
11440 "Do you want to insert the contents of the file "
11441 "into the message body, or attach it to the email?",
11442 "Do you want to insert the contents of the %d files "
11443 "into the message body, or attach them to the email?",
11446 val = alertpanel_full(_("Insert or attach?"), msg,
11447 GTK_STOCK_CANCEL, g_strconcat("+", _("_Insert"), NULL), _("_Attach"),
11448 TRUE, NULL, ALERT_QUESTION, G_ALERTALTERNATE);
11451 case COMPOSE_DND_INSERT:
11452 val = G_ALERTALTERNATE;
11454 case COMPOSE_DND_ATTACH:
11455 val = G_ALERTOTHER;
11458 /* unexpected case */
11459 g_warning("error: unexpected compose_dnd_mode option value in compose_insert_drag_received_cb()");
11462 if (val & G_ALERTDISABLE) {
11463 val &= ~G_ALERTDISABLE;
11464 /* remember what action to perform by default, only if we don't click Cancel */
11465 if (val == G_ALERTALTERNATE)
11466 prefs_common.compose_dnd_mode = COMPOSE_DND_INSERT;
11467 else if (val == G_ALERTOTHER)
11468 prefs_common.compose_dnd_mode = COMPOSE_DND_ATTACH;
11471 if (val == G_ALERTDEFAULT || val == G_ALERTCANCEL) {
11472 gtk_drag_finish(drag_context, FALSE, FALSE, time);
11473 list_free_strings(list);
11476 } else if (val == G_ALERTOTHER) {
11477 compose_attach_drag_received_cb(widget, drag_context, x, y, data, info, time, user_data);
11478 list_free_strings(list);
11483 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11484 compose_insert_file(compose, (const gchar *)tmp->data);
11486 list_free_strings(list);
11488 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11493 static void compose_header_drag_received_cb (GtkWidget *widget,
11494 GdkDragContext *drag_context,
11497 GtkSelectionData *data,
11500 gpointer user_data)
11502 GtkEditable *entry = (GtkEditable *)user_data;
11503 const gchar *email = (const gchar *)gtk_selection_data_get_data(data);
11505 /* strangely, testing data->type == gdk_atom_intern("text/plain", TRUE)
11508 if (!strncmp(email, "mailto:", strlen("mailto:"))) {
11509 gchar *decoded=g_new(gchar, strlen(email));
11512 decode_uri(decoded, email + strlen("mailto:")); /* will fit */
11513 gtk_editable_delete_text(entry, 0, -1);
11514 gtk_editable_insert_text(entry, decoded, strlen(decoded), &start);
11515 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11519 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11522 static void compose_toggle_return_receipt_cb(GtkToggleAction *action, gpointer data)
11524 Compose *compose = (Compose *)data;
11526 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11527 compose->return_receipt = TRUE;
11529 compose->return_receipt = FALSE;
11532 static void compose_toggle_remove_refs_cb(GtkToggleAction *action, gpointer data)
11534 Compose *compose = (Compose *)data;
11536 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11537 compose->remove_references = TRUE;
11539 compose->remove_references = FALSE;
11542 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
11543 ComposeHeaderEntry *headerentry)
11545 gtk_entry_set_text(GTK_ENTRY(headerentry->entry), "");
11549 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
11550 GdkEventKey *event,
11551 ComposeHeaderEntry *headerentry)
11553 if ((g_slist_length(headerentry->compose->header_list) > 0) &&
11554 ((headerentry->headernum + 1) != headerentry->compose->header_nextrow) &&
11555 !(event->state & GDK_MODIFIER_MASK) &&
11556 (event->keyval == GDK_KEY_BackSpace) &&
11557 (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) == 0)) {
11558 gtk_container_remove
11559 (GTK_CONTAINER(headerentry->compose->header_table),
11560 headerentry->combo);
11561 gtk_container_remove
11562 (GTK_CONTAINER(headerentry->compose->header_table),
11563 headerentry->entry);
11564 headerentry->compose->header_list =
11565 g_slist_remove(headerentry->compose->header_list,
11567 g_free(headerentry);
11568 } else if (event->keyval == GDK_KEY_Tab) {
11569 if (headerentry->compose->header_last == headerentry) {
11570 /* Override default next focus, and give it to subject_entry
11571 * instead of notebook tabs
11573 g_signal_stop_emission_by_name(G_OBJECT(entry), "key-press-event");
11574 gtk_widget_grab_focus(headerentry->compose->subject_entry);
11581 static gboolean scroll_postpone(gpointer data)
11583 Compose *compose = (Compose *)data;
11585 if (compose->batch)
11588 GTK_EVENTS_FLUSH();
11589 compose_show_first_last_header(compose, FALSE);
11593 static void compose_headerentry_changed_cb(GtkWidget *entry,
11594 ComposeHeaderEntry *headerentry)
11596 if (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) != 0) {
11597 compose_create_header_entry(headerentry->compose);
11598 g_signal_handlers_disconnect_matched
11599 (G_OBJECT(entry), G_SIGNAL_MATCH_DATA,
11600 0, 0, NULL, NULL, headerentry);
11602 if (!headerentry->compose->batch)
11603 g_timeout_add(0, scroll_postpone, headerentry->compose);
11607 static gboolean compose_defer_auto_save_draft(Compose *compose)
11609 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
11610 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
11614 static void compose_show_first_last_header(Compose *compose, gboolean show_first)
11616 GtkAdjustment *vadj;
11618 cm_return_if_fail(compose);
11623 cm_return_if_fail(GTK_IS_WIDGET(compose->header_table));
11624 cm_return_if_fail(GTK_IS_VIEWPORT(gtk_widget_get_parent(compose->header_table)));
11625 vadj = gtk_viewport_get_vadjustment(GTK_VIEWPORT(
11626 gtk_widget_get_parent(compose->header_table)));
11627 gtk_adjustment_set_value(vadj, (show_first ?
11628 gtk_adjustment_get_lower(vadj) :
11629 (gtk_adjustment_get_upper(vadj) -
11630 gtk_adjustment_get_page_size(vadj))));
11631 gtk_adjustment_changed(vadj);
11634 static void text_inserted(GtkTextBuffer *buffer, GtkTextIter *iter,
11635 const gchar *text, gint len, Compose *compose)
11637 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data
11638 (G_OBJECT(compose->text), "paste_as_quotation"));
11641 cm_return_if_fail(text != NULL);
11643 g_signal_handlers_block_by_func(G_OBJECT(buffer),
11644 G_CALLBACK(text_inserted),
11646 if (paste_as_quotation) {
11648 const gchar *qmark;
11650 GtkTextIter start_iter;
11653 len = strlen(text);
11655 new_text = g_strndup(text, len);
11657 qmark = compose_quote_char_from_context(compose);
11659 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11660 gtk_text_buffer_place_cursor(buffer, iter);
11662 pos = gtk_text_iter_get_offset(iter);
11664 compose_quote_fmt(compose, NULL, "%Q", qmark, new_text, TRUE, FALSE,
11665 _("Quote format error at line %d."));
11666 quote_fmt_reset_vartable();
11668 g_object_set_data(G_OBJECT(compose->text), "paste_as_quotation",
11669 GINT_TO_POINTER(paste_as_quotation - 1));
11671 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11672 gtk_text_buffer_place_cursor(buffer, iter);
11673 gtk_text_buffer_delete_mark(buffer, mark);
11675 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, pos);
11676 mark = gtk_text_buffer_create_mark(buffer, NULL, &start_iter, FALSE);
11677 compose_beautify_paragraph(compose, &start_iter, FALSE);
11678 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark);
11679 gtk_text_buffer_delete_mark(buffer, mark);
11681 if (strcmp(text, "\n") || compose->automatic_break
11682 || gtk_text_iter_starts_line(iter)) {
11683 GtkTextIter before_ins;
11684 gtk_text_buffer_insert(buffer, iter, text, len);
11685 if (!strstr(text, "\n") && gtk_text_iter_has_tag(iter, compose->no_join_tag)) {
11686 before_ins = *iter;
11687 gtk_text_iter_backward_chars(&before_ins, len);
11688 gtk_text_buffer_remove_tag_by_name(buffer, "no_join", &before_ins, iter);
11691 /* check if the preceding is just whitespace or quote */
11692 GtkTextIter start_line;
11693 gchar *tmp = NULL, *quote = NULL;
11694 gint quote_len = 0, is_normal = 0;
11695 start_line = *iter;
11696 gtk_text_iter_set_line_offset(&start_line, 0);
11697 tmp = gtk_text_buffer_get_text(buffer, &start_line, iter, FALSE);
11700 if (*tmp == '\0') {
11703 quote = compose_get_quote_str(buffer, &start_line, "e_len);
11711 gtk_text_buffer_insert(buffer, iter, text, len);
11713 gtk_text_buffer_insert_with_tags_by_name(buffer,
11714 iter, text, len, "no_join", NULL);
11719 if (!paste_as_quotation) {
11720 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11721 compose_beautify_paragraph(compose, iter, FALSE);
11722 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11723 gtk_text_buffer_delete_mark(buffer, mark);
11726 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
11727 G_CALLBACK(text_inserted),
11729 g_signal_stop_emission_by_name(G_OBJECT(buffer), "insert-text");
11731 if (compose_can_autosave(compose) &&
11732 gtk_text_buffer_get_char_count(buffer) % prefs_common.autosave_length == 0 &&
11733 compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN /* disabled while loading */)
11734 compose->draft_timeout_tag = g_timeout_add
11735 (500, (GSourceFunc) compose_defer_auto_save_draft, compose);
11739 static void compose_check_all(GtkAction *action, gpointer data)
11741 Compose *compose = (Compose *)data;
11742 if (!compose->gtkaspell)
11745 if (gtk_widget_has_focus(compose->subject_entry))
11746 claws_spell_entry_check_all(
11747 CLAWS_SPELL_ENTRY(compose->subject_entry));
11749 gtkaspell_check_all(compose->gtkaspell);
11752 static void compose_highlight_all(GtkAction *action, gpointer data)
11754 Compose *compose = (Compose *)data;
11755 if (compose->gtkaspell) {
11756 claws_spell_entry_recheck_all(
11757 CLAWS_SPELL_ENTRY(compose->subject_entry));
11758 gtkaspell_highlight_all(compose->gtkaspell);
11762 static void compose_check_backwards(GtkAction *action, gpointer data)
11764 Compose *compose = (Compose *)data;
11765 if (!compose->gtkaspell) {
11766 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
11770 if (gtk_widget_has_focus(compose->subject_entry))
11771 claws_spell_entry_check_backwards(
11772 CLAWS_SPELL_ENTRY(compose->subject_entry));
11774 gtkaspell_check_backwards(compose->gtkaspell);
11777 static void compose_check_forwards_go(GtkAction *action, gpointer data)
11779 Compose *compose = (Compose *)data;
11780 if (!compose->gtkaspell) {
11781 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
11785 if (gtk_widget_has_focus(compose->subject_entry))
11786 claws_spell_entry_check_forwards_go(
11787 CLAWS_SPELL_ENTRY(compose->subject_entry));
11789 gtkaspell_check_forwards_go(compose->gtkaspell);
11794 *\brief Guess originating forward account from MsgInfo and several
11795 * "common preference" settings. Return NULL if no guess.
11797 static PrefsAccount *compose_find_account(MsgInfo *msginfo)
11799 PrefsAccount *account = NULL;
11801 cm_return_val_if_fail(msginfo, NULL);
11802 cm_return_val_if_fail(msginfo->folder, NULL);
11803 cm_return_val_if_fail(msginfo->folder->prefs, NULL);
11805 if (msginfo->folder->prefs->enable_default_account)
11806 account = account_find_from_id(msginfo->folder->prefs->default_account);
11808 if (!account && msginfo->to && prefs_common.forward_account_autosel) {
11810 Xstrdup_a(to, msginfo->to, return NULL);
11811 extract_address(to);
11812 account = account_find_from_address(to, FALSE);
11815 if (!account && prefs_common.forward_account_autosel) {
11816 gchar cc[BUFFSIZE];
11817 if (!procheader_get_header_from_msginfo
11818 (msginfo, cc,sizeof cc , "Cc:")) {
11819 gchar *buf = cc + strlen("Cc:");
11820 extract_address(buf);
11821 account = account_find_from_address(buf, FALSE);
11825 if (!account && prefs_common.forward_account_autosel) {
11826 gchar deliveredto[BUFFSIZE];
11827 if (!procheader_get_header_from_msginfo
11828 (msginfo, deliveredto,sizeof deliveredto , "Delivered-To:")) {
11829 gchar *buf = deliveredto + strlen("Delivered-To:");
11830 extract_address(buf);
11831 account = account_find_from_address(buf, FALSE);
11836 account = msginfo->folder->folder->account;
11841 gboolean compose_close(Compose *compose)
11845 cm_return_val_if_fail(compose, FALSE);
11847 if (!g_mutex_trylock(compose->mutex)) {
11848 /* we have to wait for the (possibly deferred by auto-save)
11849 * drafting to be done, before destroying the compose under
11851 debug_print("waiting for drafting to finish...\n");
11852 compose_allow_user_actions(compose, FALSE);
11853 if (compose->close_timeout_tag == 0) {
11854 compose->close_timeout_tag =
11855 g_timeout_add (500, (GSourceFunc) compose_close,
11861 if (compose->draft_timeout_tag >= 0) {
11862 g_source_remove(compose->draft_timeout_tag);
11863 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN;
11866 gtkut_widget_get_uposition(compose->window, &x, &y);
11867 if (!compose->batch) {
11868 prefs_common.compose_x = x;
11869 prefs_common.compose_y = y;
11871 g_mutex_unlock(compose->mutex);
11872 compose_destroy(compose);
11877 * Add entry field for each address in list.
11878 * \param compose E-Mail composition object.
11879 * \param listAddress List of (formatted) E-Mail addresses.
11881 static void compose_add_field_list( Compose *compose, GList *listAddress ) {
11884 node = listAddress;
11886 addr = ( gchar * ) node->data;
11887 compose_entry_append( compose, addr, COMPOSE_TO, PREF_NONE );
11888 node = g_list_next( node );
11892 static void compose_reply_from_messageview_real(MessageView *msgview, GSList *msginfo_list,
11893 guint action, gboolean opening_multiple)
11895 gchar *body = NULL;
11896 GSList *new_msglist = NULL;
11897 MsgInfo *tmp_msginfo = NULL;
11898 gboolean originally_enc = FALSE;
11899 gboolean originally_sig = FALSE;
11900 Compose *compose = NULL;
11901 gchar *s_system = NULL;
11903 cm_return_if_fail(msgview != NULL);
11905 cm_return_if_fail(msginfo_list != NULL);
11907 if (g_slist_length(msginfo_list) == 1 && !opening_multiple) {
11908 MimeInfo *mimeinfo = messageview_get_selected_mime_part(msgview);
11909 MsgInfo *orig_msginfo = (MsgInfo *)msginfo_list->data;
11911 if (mimeinfo != NULL && mimeinfo->type == MIMETYPE_MESSAGE &&
11912 !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
11913 tmp_msginfo = procmsg_msginfo_new_from_mimeinfo(
11914 orig_msginfo, mimeinfo);
11915 if (tmp_msginfo != NULL) {
11916 new_msglist = g_slist_append(NULL, tmp_msginfo);
11918 originally_enc = MSG_IS_ENCRYPTED(orig_msginfo->flags);
11919 privacy_msginfo_get_signed_state(orig_msginfo, &s_system);
11920 originally_sig = MSG_IS_SIGNED(orig_msginfo->flags);
11922 tmp_msginfo->folder = orig_msginfo->folder;
11923 tmp_msginfo->msgnum = orig_msginfo->msgnum;
11924 if (orig_msginfo->tags) {
11925 tmp_msginfo->tags = g_slist_copy(orig_msginfo->tags);
11926 tmp_msginfo->folder->tags_dirty = TRUE;
11932 if (!opening_multiple)
11933 body = messageview_get_selection(msgview);
11936 compose = compose_reply_mode((ComposeMode)action, new_msglist, body);
11937 procmsg_msginfo_free(&tmp_msginfo);
11938 g_slist_free(new_msglist);
11940 compose = compose_reply_mode((ComposeMode)action, msginfo_list, body);
11942 if (compose && originally_enc) {
11943 compose_force_encryption(compose, compose->account, FALSE, s_system);
11946 if (compose && originally_sig && compose->account->default_sign_reply) {
11947 compose_force_signing(compose, compose->account, s_system);
11951 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
11954 void compose_reply_from_messageview(MessageView *msgview, GSList *msginfo_list,
11957 if ((!prefs_common.forward_as_attachment || action != COMPOSE_FORWARD)
11958 && action != COMPOSE_FORWARD_AS_ATTACH && g_slist_length(msginfo_list) > 1) {
11959 GSList *cur = msginfo_list;
11960 gchar *msg = g_strdup_printf(_("You are about to reply to %d "
11961 "messages. Opening the windows "
11962 "could take some time. Do you "
11963 "want to continue?"),
11964 g_slist_length(msginfo_list));
11965 if (g_slist_length(msginfo_list) > 9
11966 && alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, "+" GTK_STOCK_YES, NULL)
11967 != G_ALERTALTERNATE) {
11972 /* We'll open multiple compose windows */
11973 /* let the WM place the next windows */
11974 compose_force_window_origin = FALSE;
11975 for (; cur; cur = cur->next) {
11977 tmplist.data = cur->data;
11978 tmplist.next = NULL;
11979 compose_reply_from_messageview_real(msgview, &tmplist, action, TRUE);
11981 compose_force_window_origin = TRUE;
11983 /* forwarding multiple mails as attachments is done via a
11984 * single compose window */
11985 compose_reply_from_messageview_real(msgview, msginfo_list, action, FALSE);
11989 void compose_check_for_email_account(Compose *compose)
11991 PrefsAccount *ac = NULL, *curr = NULL;
11997 if (compose->account && compose->account->protocol == A_NNTP) {
11998 ac = account_get_cur_account();
11999 if (ac->protocol == A_NNTP) {
12000 list = account_get_list();
12002 for( ; list != NULL ; list = g_list_next(list)) {
12003 curr = (PrefsAccount *) list->data;
12004 if (curr->protocol != A_NNTP) {
12010 combobox_select_by_data(GTK_COMBO_BOX(compose->account_combo),
12015 void compose_reply_to_address(MessageView *msgview, MsgInfo *msginfo,
12016 const gchar *address)
12018 GSList *msginfo_list = NULL;
12019 gchar *body = messageview_get_selection(msgview);
12022 msginfo_list = g_slist_prepend(msginfo_list, msginfo);
12024 compose = compose_reply_mode(COMPOSE_REPLY_TO_ADDRESS, msginfo_list, body);
12025 compose_check_for_email_account(compose);
12026 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
12027 compose_entry_append(compose, address, COMPOSE_TO, PREF_NONE);
12028 compose_reply_set_subject(compose, msginfo);
12031 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
12034 void compose_set_position(Compose *compose, gint pos)
12036 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
12038 gtkut_text_view_set_position(text, pos);
12041 gboolean compose_search_string(Compose *compose,
12042 const gchar *str, gboolean case_sens)
12044 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
12046 return gtkut_text_view_search_string(text, str, case_sens);
12049 gboolean compose_search_string_backward(Compose *compose,
12050 const gchar *str, gboolean case_sens)
12052 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
12054 return gtkut_text_view_search_string_backward(text, str, case_sens);
12057 /* allocate a msginfo structure and populate its data from a compose data structure */
12058 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose)
12060 MsgInfo *newmsginfo;
12062 gchar buf[BUFFSIZE];
12064 cm_return_val_if_fail( compose != NULL, NULL );
12066 newmsginfo = procmsg_msginfo_new();
12069 get_rfc822_date(buf, sizeof(buf));
12070 newmsginfo->date = g_strdup(buf);
12073 if (compose->from_name) {
12074 newmsginfo->from = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
12075 newmsginfo->fromname = procheader_get_fromname(newmsginfo->from);
12079 if (compose->subject_entry)
12080 newmsginfo->subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
12082 /* to, cc, reply-to, newsgroups */
12083 for (list = compose->header_list; list; list = list->next) {
12084 gchar *header = gtk_editable_get_chars(
12086 gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
12087 gchar *entry = gtk_editable_get_chars(
12088 GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
12090 if ( strcasecmp(header, prefs_common_translated_header_name("To:")) == 0 ) {
12091 if ( newmsginfo->to == NULL ) {
12092 newmsginfo->to = g_strdup(entry);
12093 } else if (entry && *entry) {
12094 gchar *tmp = g_strconcat(newmsginfo->to, ", ", entry, NULL);
12095 g_free(newmsginfo->to);
12096 newmsginfo->to = tmp;
12099 if ( strcasecmp(header, prefs_common_translated_header_name("Cc:")) == 0 ) {
12100 if ( newmsginfo->cc == NULL ) {
12101 newmsginfo->cc = g_strdup(entry);
12102 } else if (entry && *entry) {
12103 gchar *tmp = g_strconcat(newmsginfo->cc, ", ", entry, NULL);
12104 g_free(newmsginfo->cc);
12105 newmsginfo->cc = tmp;
12108 if ( strcasecmp(header,
12109 prefs_common_translated_header_name("Newsgroups:")) == 0 ) {
12110 if ( newmsginfo->newsgroups == NULL ) {
12111 newmsginfo->newsgroups = g_strdup(entry);
12112 } else if (entry && *entry) {
12113 gchar *tmp = g_strconcat(newmsginfo->newsgroups, ", ", entry, NULL);
12114 g_free(newmsginfo->newsgroups);
12115 newmsginfo->newsgroups = tmp;
12123 /* other data is unset */
12129 /* update compose's dictionaries from folder dict settings */
12130 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
12131 FolderItem *folder_item)
12133 cm_return_if_fail(compose != NULL);
12135 if (compose->gtkaspell && folder_item && folder_item->prefs) {
12136 FolderItemPrefs *prefs = folder_item->prefs;
12138 if (prefs->enable_default_dictionary)
12139 gtkaspell_change_dict(compose->gtkaspell,
12140 prefs->default_dictionary, FALSE);
12141 if (folder_item->prefs->enable_default_alt_dictionary)
12142 gtkaspell_change_alt_dict(compose->gtkaspell,
12143 prefs->default_alt_dictionary);
12144 if (prefs->enable_default_dictionary
12145 || prefs->enable_default_alt_dictionary)
12146 compose_spell_menu_changed(compose);
12151 static void compose_subject_entry_activated(GtkWidget *widget, gpointer data)
12153 Compose *compose = (Compose *)data;
12155 cm_return_if_fail(compose != NULL);
12157 gtk_widget_grab_focus(compose->text);
12160 static void from_name_activate_cb(GtkWidget *widget, gpointer data)
12162 gtk_combo_box_popup(GTK_COMBO_BOX(data));