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 gboolean use_signing = FALSE;
2228 gboolean use_encryption = FALSE;
2229 gchar *privacy_system = NULL;
2230 int priority = PRIORITY_NORMAL;
2231 MsgInfo *replyinfo = NULL, *fwdinfo = NULL;
2232 gboolean autowrap = prefs_common.autowrap;
2233 gboolean autoindent = prefs_common.auto_indent;
2234 HeaderEntry *manual_headers = NULL;
2236 cm_return_val_if_fail(msginfo != NULL, NULL);
2237 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
2239 if (compose_put_existing_to_front(msginfo)) {
2243 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2244 folder_has_parent_of_type(msginfo->folder, F_DRAFT) ||
2245 folder_has_parent_of_type(msginfo->folder, F_OUTBOX)) {
2246 gchar *queueheader_buf = NULL;
2249 /* Select Account from queue headers */
2250 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2251 "X-Claws-Account-Id:")) {
2252 id = atoi(&queueheader_buf[strlen("X-Claws-Account-Id:")]);
2253 account = account_find_from_id(id);
2254 g_free(queueheader_buf);
2256 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2257 "X-Sylpheed-Account-Id:")) {
2258 id = atoi(&queueheader_buf[strlen("X-Sylpheed-Account-Id:")]);
2259 account = account_find_from_id(id);
2260 g_free(queueheader_buf);
2262 if (!account && !procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2264 id = atoi(&queueheader_buf[strlen("NAID:")]);
2265 account = account_find_from_id(id);
2266 g_free(queueheader_buf);
2268 if (!account && !procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2270 id = atoi(&queueheader_buf[strlen("MAID:")]);
2271 account = account_find_from_id(id);
2272 g_free(queueheader_buf);
2274 if (!account && !procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2276 account = account_find_from_address(queueheader_buf, FALSE);
2277 g_free(queueheader_buf);
2279 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2281 param = atoi(&queueheader_buf[strlen("X-Claws-Sign:")]);
2282 use_signing = param;
2283 g_free(queueheader_buf);
2285 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2286 "X-Sylpheed-Sign:")) {
2287 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Sign:")]);
2288 use_signing = param;
2289 g_free(queueheader_buf);
2291 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2292 "X-Claws-Encrypt:")) {
2293 param = atoi(&queueheader_buf[strlen("X-Claws-Encrypt:")]);
2294 use_encryption = param;
2295 g_free(queueheader_buf);
2297 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2298 "X-Sylpheed-Encrypt:")) {
2299 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Encrypt:")]);
2300 use_encryption = param;
2301 g_free(queueheader_buf);
2303 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2304 "X-Claws-Auto-Wrapping:")) {
2305 param = atoi(&queueheader_buf[strlen("X-Claws-Auto-Wrapping:")]);
2307 g_free(queueheader_buf);
2309 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2310 "X-Claws-Auto-Indent:")) {
2311 param = atoi(&queueheader_buf[strlen("X-Claws-Auto-Indent:")]);
2313 g_free(queueheader_buf);
2315 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2316 "X-Claws-Privacy-System:")) {
2317 privacy_system = g_strdup(&queueheader_buf[strlen("X-Claws-Privacy-System:")]);
2318 g_free(queueheader_buf);
2320 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2321 "X-Sylpheed-Privacy-System:")) {
2322 privacy_system = g_strdup(&queueheader_buf[strlen("X-Sylpheed-Privacy-System:")]);
2323 g_free(queueheader_buf);
2325 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2327 param = atoi(&queueheader_buf[strlen("X-Priority: ")]); /* mind the space */
2329 g_free(queueheader_buf);
2331 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2333 gchar **tokens = g_strsplit(&queueheader_buf[strlen("RMID:")], "\t", 0);
2334 if (tokens && tokens[0] && tokens[1] && tokens[2]) {
2335 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2336 if (orig_item != NULL) {
2337 replyinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2341 g_free(queueheader_buf);
2343 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2345 gchar **tokens = g_strsplit(&queueheader_buf[strlen("FMID:")], "\t", 0);
2346 if (tokens && tokens[0] && tokens[1] && tokens[2]) {
2347 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2348 if (orig_item != NULL) {
2349 fwdinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2353 g_free(queueheader_buf);
2355 /* Get manual headers */
2356 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2357 "X-Claws-Manual-Headers:")) {
2358 gchar *listmh = g_strdup(&queueheader_buf[strlen("X-Claws-Manual-Headers:")]);
2359 if (listmh && *listmh != '\0') {
2360 debug_print("Got manual headers: %s\n", listmh);
2361 manual_headers = procheader_entries_from_str(listmh);
2364 g_free(queueheader_buf);
2367 account = msginfo->folder->folder->account;
2370 if (!account && prefs_common.reedit_account_autosel) {
2372 if (!procheader_get_header_from_msginfo(msginfo, &from, "FROM:")) {
2373 extract_address(from);
2374 account = account_find_from_address(from, FALSE);
2379 account = cur_account;
2381 cm_return_val_if_fail(account != NULL, NULL);
2383 compose = compose_create(account, msginfo->folder, COMPOSE_REEDIT, batch);
2385 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", autowrap);
2386 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", autoindent);
2387 compose->autowrap = autowrap;
2388 compose->replyinfo = replyinfo;
2389 compose->fwdinfo = fwdinfo;
2391 compose->updating = TRUE;
2392 compose->priority = priority;
2394 if (privacy_system != NULL) {
2395 compose->privacy_system = privacy_system;
2396 compose_use_signing(compose, use_signing);
2397 compose_use_encryption(compose, use_encryption);
2398 compose_update_privacy_system_menu_item(compose, FALSE);
2400 activate_privacy_system(compose, account, FALSE);
2403 compose->targetinfo = procmsg_msginfo_copy(msginfo);
2405 compose_extract_original_charset(compose);
2407 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2408 folder_has_parent_of_type(msginfo->folder, F_DRAFT) ||
2409 folder_has_parent_of_type(msginfo->folder, F_OUTBOX)) {
2410 gchar *queueheader_buf = NULL;
2412 /* Set message save folder */
2413 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf, "SCF:")) {
2414 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
2415 compose_set_save_to(compose, &queueheader_buf[4]);
2416 g_free(queueheader_buf);
2418 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf, "RRCPT:")) {
2419 gint active = atoi(&queueheader_buf[strlen("RRCPT:")]);
2421 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
2423 g_free(queueheader_buf);
2427 if (compose_parse_header(compose, msginfo) < 0) {
2428 compose->updating = FALSE;
2429 compose_destroy(compose);
2432 compose_reedit_set_entry(compose, msginfo);
2434 textview = GTK_TEXT_VIEW(compose->text);
2435 textbuf = gtk_text_view_get_buffer(textview);
2436 compose_create_tags(textview, compose);
2438 mark = gtk_text_buffer_get_insert(textbuf);
2439 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
2441 g_signal_handlers_block_by_func(G_OBJECT(textbuf),
2442 G_CALLBACK(compose_changed_cb),
2445 if (MSG_IS_ENCRYPTED(msginfo->flags)) {
2446 fp = procmime_get_first_encrypted_text_content(msginfo);
2448 compose_force_encryption(compose, account, TRUE, NULL);
2451 fp = procmime_get_first_text_content(msginfo);
2454 g_warning("Can't get text part");
2458 gchar buf[BUFFSIZE];
2459 gboolean prev_autowrap;
2460 GtkTextBuffer *buffer;
2462 while (fgets(buf, sizeof(buf), fp) != NULL) {
2464 gtk_text_buffer_insert(textbuf, &iter, buf, -1);
2470 compose_attach_parts(compose, msginfo);
2472 compose_colorize_signature(compose);
2474 g_signal_handlers_unblock_by_func(G_OBJECT(textbuf),
2475 G_CALLBACK(compose_changed_cb),
2478 if (manual_headers != NULL) {
2479 if (compose_parse_manual_headers(compose, msginfo, manual_headers) < 0) {
2480 procheader_entries_free(manual_headers);
2481 compose->updating = FALSE;
2482 compose_destroy(compose);
2485 procheader_entries_free(manual_headers);
2488 gtk_widget_grab_focus(compose->text);
2490 if (prefs_common.auto_exteditor) {
2491 compose_exec_ext_editor(compose);
2493 compose->modified = FALSE;
2494 compose_set_title(compose);
2496 compose->updating = FALSE;
2497 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2498 SCROLL_TO_CURSOR(compose);
2500 if (compose->deferred_destroy) {
2501 compose_destroy(compose);
2505 compose->sig_str = account_get_signature_str(compose->account);
2507 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2512 Compose *compose_redirect(PrefsAccount *account, MsgInfo *msginfo,
2519 cm_return_val_if_fail(msginfo != NULL, NULL);
2522 account = account_get_reply_account(msginfo,
2523 prefs_common.reply_account_autosel);
2524 cm_return_val_if_fail(account != NULL, NULL);
2526 compose = compose_create(account, msginfo->folder, COMPOSE_REDIRECT, batch);
2528 compose->updating = TRUE;
2530 compose_create_tags(GTK_TEXT_VIEW(compose->text), compose);
2531 compose->replyinfo = NULL;
2532 compose->fwdinfo = NULL;
2534 compose_show_first_last_header(compose, TRUE);
2536 gtk_widget_grab_focus(compose->header_last->entry);
2538 filename = procmsg_get_message_file(msginfo);
2540 if (filename == NULL) {
2541 compose->updating = FALSE;
2542 compose_destroy(compose);
2547 compose->redirect_filename = filename;
2549 /* Set save folder */
2550 item = msginfo->folder;
2551 if (item && item->prefs && item->prefs->save_copy_to_folder) {
2552 gchar *folderidentifier;
2554 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
2555 folderidentifier = folder_item_get_identifier(item);
2556 compose_set_save_to(compose, folderidentifier);
2557 g_free(folderidentifier);
2560 compose_attach_parts(compose, msginfo);
2562 if (msginfo->subject)
2563 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2565 gtk_editable_set_editable(GTK_EDITABLE(compose->subject_entry), FALSE);
2567 compose_quote_fmt(compose, msginfo, "%M", NULL, NULL, FALSE, FALSE,
2568 _("The body of the \"Redirect\" template has an error at line %d."));
2569 quote_fmt_reset_vartable();
2570 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), FALSE);
2572 compose_colorize_signature(compose);
2575 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Add", FALSE);
2576 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", FALSE);
2577 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", FALSE);
2579 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/Save", FALSE);
2580 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertFile", FALSE);
2581 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/AttachFile", FALSE);
2582 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertSig", FALSE);
2583 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/ReplaceSig", FALSE);
2584 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", FALSE);
2585 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", FALSE);
2586 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/ShowRuler", FALSE);
2587 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Actions", FALSE);
2589 if (compose->toolbar->draft_btn)
2590 gtk_widget_set_sensitive(compose->toolbar->draft_btn, FALSE);
2591 if (compose->toolbar->insert_btn)
2592 gtk_widget_set_sensitive(compose->toolbar->insert_btn, FALSE);
2593 if (compose->toolbar->attach_btn)
2594 gtk_widget_set_sensitive(compose->toolbar->attach_btn, FALSE);
2595 if (compose->toolbar->sig_btn)
2596 gtk_widget_set_sensitive(compose->toolbar->sig_btn, FALSE);
2597 if (compose->toolbar->exteditor_btn)
2598 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, FALSE);
2599 if (compose->toolbar->linewrap_current_btn)
2600 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, FALSE);
2601 if (compose->toolbar->linewrap_all_btn)
2602 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, FALSE);
2604 compose->modified = FALSE;
2605 compose_set_title(compose);
2606 compose->updating = FALSE;
2607 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2608 SCROLL_TO_CURSOR(compose);
2610 if (compose->deferred_destroy) {
2611 compose_destroy(compose);
2615 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2620 const GList *compose_get_compose_list(void)
2622 return compose_list;
2625 void compose_entry_append(Compose *compose, const gchar *address,
2626 ComposeEntryType type, ComposePrefType pref_type)
2628 const gchar *header;
2630 gboolean in_quote = FALSE;
2631 if (!address || *address == '\0') return;
2638 header = N_("Bcc:");
2640 case COMPOSE_REPLYTO:
2641 header = N_("Reply-To:");
2643 case COMPOSE_NEWSGROUPS:
2644 header = N_("Newsgroups:");
2646 case COMPOSE_FOLLOWUPTO:
2647 header = N_( "Followup-To:");
2649 case COMPOSE_INREPLYTO:
2650 header = N_( "In-Reply-To:");
2657 header = prefs_common_translated_header_name(header);
2659 cur = begin = (gchar *)address;
2661 /* we separate the line by commas, but not if we're inside a quoted
2663 while (*cur != '\0') {
2665 in_quote = !in_quote;
2666 if (*cur == ',' && !in_quote) {
2667 gchar *tmp = g_strdup(begin);
2669 tmp[cur-begin]='\0';
2672 while (*tmp == ' ' || *tmp == '\t')
2674 compose_add_header_entry(compose, header, tmp, pref_type);
2675 compose_entry_indicate(compose, tmp);
2682 gchar *tmp = g_strdup(begin);
2684 tmp[cur-begin]='\0';
2685 while (*tmp == ' ' || *tmp == '\t')
2687 compose_add_header_entry(compose, header, tmp, pref_type);
2688 compose_entry_indicate(compose, tmp);
2693 static void compose_entry_indicate(Compose *compose, const gchar *mailto)
2698 for (h_list = compose->header_list; h_list != NULL; h_list = h_list->next) {
2699 entry = GTK_ENTRY(((ComposeHeaderEntry *)h_list->data)->entry);
2700 if (gtk_entry_get_text(entry) &&
2701 !g_utf8_collate(gtk_entry_get_text(entry), mailto)) {
2702 gtk_widget_modify_base(
2703 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2704 GTK_STATE_NORMAL, &default_header_bgcolor);
2705 gtk_widget_modify_text(
2706 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2707 GTK_STATE_NORMAL, &default_header_color);
2712 void compose_toolbar_cb(gint action, gpointer data)
2714 ToolbarItem *toolbar_item = (ToolbarItem*)data;
2715 Compose *compose = (Compose*)toolbar_item->parent;
2717 cm_return_if_fail(compose != NULL);
2721 compose_send_cb(NULL, compose);
2724 compose_send_later_cb(NULL, compose);
2727 compose_draft(compose, COMPOSE_QUIT_EDITING);
2730 compose_insert_file_cb(NULL, compose);
2733 compose_attach_cb(NULL, compose);
2736 compose_insert_sig(compose, FALSE);
2739 compose_insert_sig(compose, TRUE);
2742 compose_ext_editor_cb(NULL, compose);
2744 case A_LINEWRAP_CURRENT:
2745 compose_beautify_paragraph(compose, NULL, TRUE);
2747 case A_LINEWRAP_ALL:
2748 compose_wrap_all_full(compose, TRUE);
2751 compose_address_cb(NULL, compose);
2754 case A_CHECK_SPELLING:
2755 compose_check_all(NULL, compose);
2763 static MailField compose_entries_set(Compose *compose, const gchar *mailto, ComposeEntryType to_type)
2768 gchar *subject = NULL;
2772 gchar **attach = NULL;
2773 gchar *inreplyto = NULL;
2774 MailField mfield = NO_FIELD_PRESENT;
2776 /* get mailto parts but skip from */
2777 scan_mailto_url(mailto, NULL, &to, &cc, &bcc, &subject, &body, &attach, &inreplyto);
2780 compose_entry_append(compose, to, to_type, PREF_MAILTO);
2781 mfield = TO_FIELD_PRESENT;
2784 compose_entry_append(compose, cc, COMPOSE_CC, PREF_MAILTO);
2786 compose_entry_append(compose, bcc, COMPOSE_BCC, PREF_MAILTO);
2788 if (!g_utf8_validate (subject, -1, NULL)) {
2789 temp = g_locale_to_utf8 (subject, -1, NULL, &len, NULL);
2790 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), temp);
2793 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), subject);
2795 mfield = SUBJECT_FIELD_PRESENT;
2798 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2799 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2802 gboolean prev_autowrap = compose->autowrap;
2804 compose->autowrap = FALSE;
2806 mark = gtk_text_buffer_get_insert(buffer);
2807 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2809 if (!g_utf8_validate (body, -1, NULL)) {
2810 temp = g_locale_to_utf8 (body, -1, NULL, &len, NULL);
2811 gtk_text_buffer_insert(buffer, &iter, temp, -1);
2814 gtk_text_buffer_insert(buffer, &iter, body, -1);
2816 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
2818 compose->autowrap = prev_autowrap;
2819 if (compose->autowrap)
2820 compose_wrap_all(compose);
2821 mfield = BODY_FIELD_PRESENT;
2825 gint i = 0, att = 0;
2826 gchar *warn_files = NULL;
2827 while (attach[i] != NULL) {
2828 gchar *utf8_filename = conv_filename_to_utf8(attach[i]);
2829 if (utf8_filename) {
2830 if (compose_attach_append(compose, attach[i], utf8_filename, NULL, NULL)) {
2831 gchar *tmp = g_strdup_printf("%s%s\n",
2832 warn_files?warn_files:"",
2838 g_free(utf8_filename);
2840 alertpanel_error(_("Couldn't attach a file (charset conversion failed)."));
2845 alertpanel_notice(ngettext(
2846 "The following file has been attached: \n%s",
2847 "The following files have been attached: \n%s", att), warn_files);
2852 compose_entry_append(compose, inreplyto, COMPOSE_INREPLYTO, PREF_MAILTO);
2865 static gint compose_parse_header(Compose *compose, MsgInfo *msginfo)
2867 static HeaderEntry hentry[] = {
2868 {"Reply-To:", NULL, TRUE },
2869 {"Cc:", NULL, TRUE },
2870 {"References:", NULL, FALSE },
2871 {"Bcc:", NULL, TRUE },
2872 {"Newsgroups:", NULL, TRUE },
2873 {"Followup-To:", NULL, TRUE },
2874 {"List-Post:", NULL, FALSE },
2875 {"X-Priority:", NULL, FALSE },
2876 {NULL, NULL, FALSE }
2893 cm_return_val_if_fail(msginfo != NULL, -1);
2895 if ((fp = procmsg_open_message(msginfo)) == NULL) return -1;
2896 procheader_get_header_fields(fp, hentry);
2899 if (hentry[H_REPLY_TO].body != NULL) {
2900 if (hentry[H_REPLY_TO].body[0] != '\0') {
2902 conv_unmime_header(hentry[H_REPLY_TO].body,
2905 g_free(hentry[H_REPLY_TO].body);
2906 hentry[H_REPLY_TO].body = NULL;
2908 if (hentry[H_CC].body != NULL) {
2909 compose->cc = conv_unmime_header(hentry[H_CC].body, NULL, TRUE);
2910 g_free(hentry[H_CC].body);
2911 hentry[H_CC].body = NULL;
2913 if (hentry[H_REFERENCES].body != NULL) {
2914 if (compose->mode == COMPOSE_REEDIT)
2915 compose->references = hentry[H_REFERENCES].body;
2917 compose->references = compose_parse_references
2918 (hentry[H_REFERENCES].body, msginfo->msgid);
2919 g_free(hentry[H_REFERENCES].body);
2921 hentry[H_REFERENCES].body = NULL;
2923 if (hentry[H_BCC].body != NULL) {
2924 if (compose->mode == COMPOSE_REEDIT)
2926 conv_unmime_header(hentry[H_BCC].body, NULL, TRUE);
2927 g_free(hentry[H_BCC].body);
2928 hentry[H_BCC].body = NULL;
2930 if (hentry[H_NEWSGROUPS].body != NULL) {
2931 compose->newsgroups = hentry[H_NEWSGROUPS].body;
2932 hentry[H_NEWSGROUPS].body = NULL;
2934 if (hentry[H_FOLLOWUP_TO].body != NULL) {
2935 if (hentry[H_FOLLOWUP_TO].body[0] != '\0') {
2936 compose->followup_to =
2937 conv_unmime_header(hentry[H_FOLLOWUP_TO].body,
2940 g_free(hentry[H_FOLLOWUP_TO].body);
2941 hentry[H_FOLLOWUP_TO].body = NULL;
2943 if (hentry[H_LIST_POST].body != NULL) {
2944 gchar *to = NULL, *start = NULL;
2946 extract_address(hentry[H_LIST_POST].body);
2947 if (hentry[H_LIST_POST].body[0] != '\0') {
2948 start = strstr(hentry[H_LIST_POST].body, "mailto:");
2950 scan_mailto_url(start ? start : hentry[H_LIST_POST].body,
2951 NULL, &to, NULL, NULL, NULL, NULL, NULL, NULL);
2954 g_free(compose->ml_post);
2955 compose->ml_post = to;
2958 g_free(hentry[H_LIST_POST].body);
2959 hentry[H_LIST_POST].body = NULL;
2962 /* CLAWS - X-Priority */
2963 if (compose->mode == COMPOSE_REEDIT)
2964 if (hentry[H_X_PRIORITY].body != NULL) {
2967 priority = atoi(hentry[H_X_PRIORITY].body);
2968 g_free(hentry[H_X_PRIORITY].body);
2970 hentry[H_X_PRIORITY].body = NULL;
2972 if (priority < PRIORITY_HIGHEST ||
2973 priority > PRIORITY_LOWEST)
2974 priority = PRIORITY_NORMAL;
2976 compose->priority = priority;
2979 if (compose->mode == COMPOSE_REEDIT) {
2980 if (msginfo->inreplyto && *msginfo->inreplyto)
2981 compose->inreplyto = g_strdup(msginfo->inreplyto);
2983 if (msginfo->msgid && *msginfo->msgid)
2984 compose->msgid = g_strdup(msginfo->msgid);
2986 if (msginfo->msgid && *msginfo->msgid)
2987 compose->inreplyto = g_strdup(msginfo->msgid);
2989 if (!compose->references) {
2990 if (msginfo->msgid && *msginfo->msgid) {
2991 if (msginfo->inreplyto && *msginfo->inreplyto)
2992 compose->references =
2993 g_strdup_printf("<%s>\n\t<%s>",
2997 compose->references =
2998 g_strconcat("<", msginfo->msgid, ">",
3000 } else if (msginfo->inreplyto && *msginfo->inreplyto) {
3001 compose->references =
3002 g_strconcat("<", msginfo->inreplyto, ">",
3011 static gint compose_parse_manual_headers(Compose *compose, MsgInfo *msginfo, HeaderEntry *entries)
3016 cm_return_val_if_fail(msginfo != NULL, -1);
3018 if ((fp = procmsg_open_message(msginfo)) == NULL) return -1;
3019 procheader_get_header_fields(fp, entries);
3023 while (he != NULL && he->name != NULL) {
3025 GtkListStore *model = NULL;
3027 debug_print("Adding manual header: %s with value %s\n", he->name, he->body);
3028 model = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(compose->header_last->combo)));
3029 COMBOBOX_ADD(model, he->name, COMPOSE_TO);
3030 gtk_combo_box_set_active_iter(GTK_COMBO_BOX(compose->header_last->combo), &iter);
3031 gtk_entry_set_text(GTK_ENTRY(compose->header_last->entry), he->body);
3038 static gchar *compose_parse_references(const gchar *ref, const gchar *msgid)
3040 GSList *ref_id_list, *cur;
3044 ref_id_list = references_list_append(NULL, ref);
3045 if (!ref_id_list) return NULL;
3046 if (msgid && *msgid)
3047 ref_id_list = g_slist_append(ref_id_list, g_strdup(msgid));
3052 for (cur = ref_id_list; cur != NULL; cur = cur->next)
3053 /* "<" + Message-ID + ">" + CR+LF+TAB */
3054 len += strlen((gchar *)cur->data) + 5;
3056 if (len > MAX_REFERENCES_LEN) {
3057 /* remove second message-ID */
3058 if (ref_id_list && ref_id_list->next &&
3059 ref_id_list->next->next) {
3060 g_free(ref_id_list->next->data);
3061 ref_id_list = g_slist_remove
3062 (ref_id_list, ref_id_list->next->data);
3064 slist_free_strings_full(ref_id_list);
3071 new_ref = g_string_new("");
3072 for (cur = ref_id_list; cur != NULL; cur = cur->next) {
3073 if (new_ref->len > 0)
3074 g_string_append(new_ref, "\n\t");
3075 g_string_append_printf(new_ref, "<%s>", (gchar *)cur->data);
3078 slist_free_strings_full(ref_id_list);
3080 new_ref_str = new_ref->str;
3081 g_string_free(new_ref, FALSE);
3086 static gchar *compose_quote_fmt(Compose *compose, MsgInfo *msginfo,
3087 const gchar *fmt, const gchar *qmark,
3088 const gchar *body, gboolean rewrap,
3089 gboolean need_unescape,
3090 const gchar *err_msg)
3092 MsgInfo* dummyinfo = NULL;
3093 gchar *quote_str = NULL;
3095 gboolean prev_autowrap;
3096 const gchar *trimmed_body = body;
3097 gint cursor_pos = -1;
3098 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3099 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3104 SIGNAL_BLOCK(buffer);
3107 dummyinfo = compose_msginfo_new_from_compose(compose);
3108 msginfo = dummyinfo;
3111 if (qmark != NULL) {
3113 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
3114 compose->gtkaspell);
3116 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
3118 quote_fmt_scan_string(qmark);
3121 buf = quote_fmt_get_buffer();
3123 alertpanel_error(_("The \"Quotation mark\" of the template is invalid."));
3125 Xstrdup_a(quote_str, buf, goto error)
3128 if (fmt && *fmt != '\0') {
3131 while (*trimmed_body == '\n')
3135 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE,
3136 compose->gtkaspell);
3138 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE);
3140 if (need_unescape) {
3143 /* decode \-escape sequences in the internal representation of the quote format */
3144 tmp = g_malloc(strlen(fmt)+1);
3145 pref_get_unescaped_pref(tmp, fmt);
3146 quote_fmt_scan_string(tmp);
3150 quote_fmt_scan_string(fmt);
3154 buf = quote_fmt_get_buffer();
3156 gint line = quote_fmt_get_line();
3157 alertpanel_error(err_msg, line);
3163 prev_autowrap = compose->autowrap;
3164 compose->autowrap = FALSE;
3166 mark = gtk_text_buffer_get_insert(buffer);
3167 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3168 if (g_utf8_validate(buf, -1, NULL)) {
3169 gtk_text_buffer_insert(buffer, &iter, buf, -1);
3171 gchar *tmpout = NULL;
3172 tmpout = conv_codeset_strdup
3173 (buf, conv_get_locale_charset_str_no_utf8(),
3175 if (!tmpout || !g_utf8_validate(tmpout, -1, NULL)) {
3177 tmpout = g_malloc(strlen(buf)*2+1);
3178 conv_localetodisp(tmpout, strlen(buf)*2+1, buf);
3180 gtk_text_buffer_insert(buffer, &iter, tmpout, -1);
3184 cursor_pos = quote_fmt_get_cursor_pos();
3185 if (cursor_pos == -1)
3186 cursor_pos = gtk_text_iter_get_offset(&iter);
3187 compose->set_cursor_pos = cursor_pos;
3189 gtk_text_buffer_get_start_iter(buffer, &iter);
3190 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
3191 gtk_text_buffer_place_cursor(buffer, &iter);
3193 compose->autowrap = prev_autowrap;
3194 if (compose->autowrap && rewrap)
3195 compose_wrap_all(compose);
3202 SIGNAL_UNBLOCK(buffer);
3204 procmsg_msginfo_free( &dummyinfo );
3209 /* if ml_post is of type addr@host and from is of type
3210 * addr-anything@host, return TRUE
3212 static gboolean is_subscription(const gchar *ml_post, const gchar *from)
3214 gchar *left_ml = NULL;
3215 gchar *right_ml = NULL;
3216 gchar *left_from = NULL;
3217 gchar *right_from = NULL;
3218 gboolean result = FALSE;
3220 if (!ml_post || !from)
3223 left_ml = g_strdup(ml_post);
3224 if (strstr(left_ml, "@")) {
3225 right_ml = strstr(left_ml, "@")+1;
3226 *(strstr(left_ml, "@")) = '\0';
3229 left_from = g_strdup(from);
3230 if (strstr(left_from, "@")) {
3231 right_from = strstr(left_from, "@")+1;
3232 *(strstr(left_from, "@")) = '\0';
3235 if (right_ml && right_from
3236 && !strncmp(left_from, left_ml, strlen(left_ml))
3237 && !strcmp(right_from, right_ml)) {
3246 static void compose_set_folder_prefs(Compose *compose, FolderItem *folder,
3247 gboolean respect_default_to)
3251 if (!folder || !folder->prefs)
3254 if (respect_default_to && folder->prefs->enable_default_to) {
3255 compose_entry_append(compose, folder->prefs->default_to,
3256 COMPOSE_TO, PREF_FOLDER);
3257 compose_entry_indicate(compose, folder->prefs->default_to);
3259 if (folder->prefs->enable_default_cc) {
3260 compose_entry_append(compose, folder->prefs->default_cc,
3261 COMPOSE_CC, PREF_FOLDER);
3262 compose_entry_indicate(compose, folder->prefs->default_cc);
3264 if (folder->prefs->enable_default_bcc) {
3265 compose_entry_append(compose, folder->prefs->default_bcc,
3266 COMPOSE_BCC, PREF_FOLDER);
3267 compose_entry_indicate(compose, folder->prefs->default_bcc);
3269 if (folder->prefs->enable_default_replyto) {
3270 compose_entry_append(compose, folder->prefs->default_replyto,
3271 COMPOSE_REPLYTO, PREF_FOLDER);
3272 compose_entry_indicate(compose, folder->prefs->default_replyto);
3276 static void compose_reply_set_subject(Compose *compose, MsgInfo *msginfo)
3281 if (!compose || !msginfo)
3284 if (msginfo->subject && *msginfo->subject) {
3285 buf = p = g_strdup(msginfo->subject);
3286 p += subject_get_prefix_length(p);
3287 memmove(buf, p, strlen(p) + 1);
3289 buf2 = g_strdup_printf("Re: %s", buf);
3290 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
3295 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), "Re: ");
3298 static void compose_reply_set_entry(Compose *compose, MsgInfo *msginfo,
3299 gboolean to_all, gboolean to_ml,
3301 gboolean followup_and_reply_to)
3303 GSList *cc_list = NULL;
3306 gchar *replyto = NULL;
3307 gchar *ac_email = NULL;
3309 gboolean reply_to_ml = FALSE;
3310 gboolean default_reply_to = FALSE;
3312 cm_return_if_fail(compose->account != NULL);
3313 cm_return_if_fail(msginfo != NULL);
3315 reply_to_ml = to_ml && compose->ml_post;
3317 default_reply_to = msginfo->folder &&
3318 msginfo->folder->prefs->enable_default_reply_to;
3320 if (compose->account->protocol != A_NNTP) {
3321 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
3323 if (reply_to_ml && !default_reply_to) {
3325 gboolean is_subscr = is_subscription(compose->ml_post,
3328 /* normal answer to ml post with a reply-to */
3329 compose_entry_append(compose,
3331 COMPOSE_TO, PREF_ML);
3332 if (compose->replyto)
3333 compose_entry_append(compose,
3335 COMPOSE_CC, PREF_ML);
3337 /* answer to subscription confirmation */
3338 if (compose->replyto)
3339 compose_entry_append(compose,
3341 COMPOSE_TO, PREF_ML);
3342 else if (msginfo->from)
3343 compose_entry_append(compose,
3345 COMPOSE_TO, PREF_ML);
3348 else if (!(to_all || to_sender) && default_reply_to) {
3349 compose_entry_append(compose,
3350 msginfo->folder->prefs->default_reply_to,
3351 COMPOSE_TO, PREF_FOLDER);
3352 compose_entry_indicate(compose,
3353 msginfo->folder->prefs->default_reply_to);
3359 compose_entry_append(compose, msginfo->from,
3360 COMPOSE_TO, PREF_NONE);
3362 Xstrdup_a(tmp1, msginfo->from, return);
3363 extract_address(tmp1);
3364 compose_entry_append(compose,
3365 (!account_find_from_address(tmp1, FALSE))
3368 COMPOSE_TO, PREF_NONE);
3369 if (compose->replyto)
3370 compose_entry_append(compose,
3372 COMPOSE_CC, PREF_NONE);
3374 if (!folder_has_parent_of_type(msginfo->folder, F_QUEUE) &&
3375 !folder_has_parent_of_type(msginfo->folder, F_OUTBOX) &&
3376 !folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
3377 if (compose->replyto) {
3378 compose_entry_append(compose,
3380 COMPOSE_TO, PREF_NONE);
3382 compose_entry_append(compose,
3383 msginfo->from ? msginfo->from : "",
3384 COMPOSE_TO, PREF_NONE);
3387 /* replying to own mail, use original recp */
3388 compose_entry_append(compose,
3389 msginfo->to ? msginfo->to : "",
3390 COMPOSE_TO, PREF_NONE);
3391 compose_entry_append(compose,
3392 msginfo->cc ? msginfo->cc : "",
3393 COMPOSE_CC, PREF_NONE);
3398 if (to_sender || (compose->followup_to &&
3399 !strncmp(compose->followup_to, "poster", 6)))
3400 compose_entry_append
3402 (compose->replyto ? compose->replyto :
3403 msginfo->from ? msginfo->from : ""),
3404 COMPOSE_TO, PREF_NONE);
3406 else if (followup_and_reply_to || to_all) {
3407 compose_entry_append
3409 (compose->replyto ? compose->replyto :
3410 msginfo->from ? msginfo->from : ""),
3411 COMPOSE_TO, PREF_NONE);
3413 compose_entry_append
3415 compose->followup_to ? compose->followup_to :
3416 compose->newsgroups ? compose->newsgroups : "",
3417 COMPOSE_NEWSGROUPS, PREF_NONE);
3419 compose_entry_append
3421 msginfo->cc ? msginfo->cc : "",
3422 COMPOSE_CC, PREF_NONE);
3425 compose_entry_append
3427 compose->followup_to ? compose->followup_to :
3428 compose->newsgroups ? compose->newsgroups : "",
3429 COMPOSE_NEWSGROUPS, PREF_NONE);
3431 compose_reply_set_subject(compose, msginfo);
3433 if (to_ml && compose->ml_post) return;
3434 if (!to_all || compose->account->protocol == A_NNTP) return;
3436 if (compose->replyto) {
3437 Xstrdup_a(replyto, compose->replyto, return);
3438 extract_address(replyto);
3440 if (msginfo->from) {
3441 Xstrdup_a(from, msginfo->from, return);
3442 extract_address(from);
3445 if (replyto && from)
3446 cc_list = address_list_append_with_comments(cc_list, from);
3447 if (to_all && msginfo->folder &&
3448 msginfo->folder->prefs->enable_default_reply_to)
3449 cc_list = address_list_append_with_comments(cc_list,
3450 msginfo->folder->prefs->default_reply_to);
3451 cc_list = address_list_append_with_comments(cc_list, msginfo->to);
3452 cc_list = address_list_append_with_comments(cc_list, compose->cc);
3454 ac_email = g_utf8_strdown(compose->account->address, -1);
3457 for (cur = cc_list; cur != NULL; cur = cur->next) {
3458 gchar *addr = g_utf8_strdown(cur->data, -1);
3459 extract_address(addr);
3461 if (strcmp(ac_email, addr))
3462 compose_entry_append(compose, (gchar *)cur->data,
3463 COMPOSE_CC, PREF_NONE);
3465 debug_print("Cc address same as compose account's, ignoring\n");
3470 slist_free_strings_full(cc_list);
3476 #define SET_ENTRY(entry, str) \
3479 gtk_entry_set_text(GTK_ENTRY(compose->entry), str); \
3482 #define SET_ADDRESS(type, str) \
3485 compose_entry_append(compose, str, type, PREF_NONE); \
3488 static void compose_reedit_set_entry(Compose *compose, MsgInfo *msginfo)
3490 cm_return_if_fail(msginfo != NULL);
3492 SET_ENTRY(subject_entry, msginfo->subject);
3493 SET_ENTRY(from_name, msginfo->from);
3494 SET_ADDRESS(COMPOSE_TO, msginfo->to);
3495 SET_ADDRESS(COMPOSE_CC, compose->cc);
3496 SET_ADDRESS(COMPOSE_BCC, compose->bcc);
3497 SET_ADDRESS(COMPOSE_REPLYTO, compose->replyto);
3498 SET_ADDRESS(COMPOSE_NEWSGROUPS, compose->newsgroups);
3499 SET_ADDRESS(COMPOSE_FOLLOWUPTO, compose->followup_to);
3501 compose_update_priority_menu_item(compose);
3502 compose_update_privacy_system_menu_item(compose, FALSE);
3503 compose_show_first_last_header(compose, TRUE);
3509 static void compose_insert_sig(Compose *compose, gboolean replace)
3511 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3512 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3514 GtkTextIter iter, iter_end;
3515 gint cur_pos, ins_pos;
3516 gboolean prev_autowrap;
3517 gboolean found = FALSE;
3518 gboolean exists = FALSE;
3520 cm_return_if_fail(compose->account != NULL);
3524 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3525 G_CALLBACK(compose_changed_cb),
3528 mark = gtk_text_buffer_get_insert(buffer);
3529 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3530 cur_pos = gtk_text_iter_get_offset (&iter);
3533 gtk_text_buffer_get_end_iter(buffer, &iter);
3535 exists = (compose->sig_str != NULL);
3538 GtkTextIter first_iter, start_iter, end_iter;
3540 gtk_text_buffer_get_start_iter(buffer, &first_iter);
3542 if (!exists || compose->sig_str[0] == '\0')
3545 found = gtk_text_iter_forward_to_tag_toggle(&first_iter,
3546 compose->signature_tag);
3549 /* include previous \n\n */
3550 gtk_text_iter_backward_chars(&first_iter, 1);
3551 start_iter = first_iter;
3552 end_iter = first_iter;
3554 found = gtk_text_iter_forward_to_tag_toggle(&end_iter,
3555 compose->signature_tag);
3556 found &= gtk_text_iter_forward_to_tag_toggle(&end_iter,
3557 compose->signature_tag);
3559 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
3565 g_free(compose->sig_str);
3566 compose->sig_str = account_get_signature_str(compose->account);
3568 cur_pos = gtk_text_iter_get_offset(&iter);
3570 if (!compose->sig_str || (replace && !compose->account->auto_sig)) {
3571 g_free(compose->sig_str);
3572 compose->sig_str = NULL;
3574 if (compose->sig_inserted == FALSE)
3575 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
3576 compose->sig_inserted = TRUE;
3578 cur_pos = gtk_text_iter_get_offset(&iter);
3579 gtk_text_buffer_insert(buffer, &iter, compose->sig_str, -1);
3581 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cur_pos);
3582 gtk_text_iter_forward_chars(&iter, 1);
3583 gtk_text_buffer_get_end_iter(buffer, &iter_end);
3584 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &iter_end);
3586 if (cur_pos > gtk_text_buffer_get_char_count (buffer))
3587 cur_pos = gtk_text_buffer_get_char_count (buffer);
3590 /* put the cursor where it should be
3591 * either where the quote_fmt says, either where it was */
3592 if (compose->set_cursor_pos < 0)
3593 gtk_text_buffer_get_iter_at_offset(buffer, &iter, ins_pos);
3595 gtk_text_buffer_get_iter_at_offset(buffer, &iter,
3596 compose->set_cursor_pos);
3598 compose->set_cursor_pos = -1;
3599 gtk_text_buffer_place_cursor(buffer, &iter);
3600 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3601 G_CALLBACK(compose_changed_cb),
3607 static ComposeInsertResult compose_insert_file(Compose *compose, const gchar *file)
3610 GtkTextBuffer *buffer;
3613 const gchar *cur_encoding;
3614 gchar buf[BUFFSIZE];
3617 gboolean prev_autowrap;
3620 GString *file_contents = NULL;
3621 ComposeInsertResult result = COMPOSE_INSERT_SUCCESS;
3623 cm_return_val_if_fail(file != NULL, COMPOSE_INSERT_NO_FILE);
3625 /* get the size of the file we are about to insert */
3626 ret = g_stat(file, &file_stat);
3628 gchar *shortfile = g_path_get_basename(file);
3629 alertpanel_error(_("Could not get size of file '%s'."), shortfile);
3631 return COMPOSE_INSERT_NO_FILE;
3632 } else if (prefs_common.warn_large_insert == TRUE) {
3634 /* ask user for confirmation if the file is large */
3635 if (prefs_common.warn_large_insert_size < 0 ||
3636 file_stat.st_size > (prefs_common.warn_large_insert_size * 1024)) {
3640 msg = g_strdup_printf(_("You are about to insert a file of %s "
3641 "in the message body. Are you sure you want to do that?"),
3642 to_human_readable(file_stat.st_size));
3643 aval = alertpanel_full(_("Are you sure?"), msg, GTK_STOCK_CANCEL,
3644 g_strconcat("+", _("_Insert"), NULL), NULL, TRUE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
3647 /* do we ask for confirmation next time? */
3648 if (aval & G_ALERTDISABLE) {
3649 /* no confirmation next time, disable feature in preferences */
3650 aval &= ~G_ALERTDISABLE;
3651 prefs_common.warn_large_insert = FALSE;
3654 /* abort file insertion if user canceled action */
3655 if (aval != G_ALERTALTERNATE) {
3656 return COMPOSE_INSERT_NO_FILE;
3662 if ((fp = g_fopen(file, "rb")) == NULL) {
3663 FILE_OP_ERROR(file, "fopen");
3664 return COMPOSE_INSERT_READ_ERROR;
3667 prev_autowrap = compose->autowrap;
3668 compose->autowrap = FALSE;
3670 text = GTK_TEXT_VIEW(compose->text);
3671 buffer = gtk_text_view_get_buffer(text);
3672 mark = gtk_text_buffer_get_insert(buffer);
3673 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3675 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3676 G_CALLBACK(text_inserted),
3679 cur_encoding = conv_get_locale_charset_str_no_utf8();
3681 file_contents = g_string_new("");
3682 while (fgets(buf, sizeof(buf), fp) != NULL) {
3685 if (g_utf8_validate(buf, -1, NULL) == TRUE)
3686 str = g_strdup(buf);
3688 codeconv_set_strict(TRUE);
3689 str = conv_codeset_strdup
3690 (buf, cur_encoding, CS_INTERNAL);
3691 codeconv_set_strict(FALSE);
3694 result = COMPOSE_INSERT_INVALID_CHARACTER;
3700 /* strip <CR> if DOS/Windows file,
3701 replace <CR> with <LF> if Macintosh file. */
3704 if (len > 0 && str[len - 1] != '\n') {
3706 if (str[len] == '\r') str[len] = '\n';
3709 file_contents = g_string_append(file_contents, str);
3713 if (result == COMPOSE_INSERT_SUCCESS) {
3714 gtk_text_buffer_insert(buffer, &iter, file_contents->str, -1);
3716 compose_changed_cb(NULL, compose);
3717 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3718 G_CALLBACK(text_inserted),
3720 compose->autowrap = prev_autowrap;
3721 if (compose->autowrap)
3722 compose_wrap_all(compose);
3725 g_string_free(file_contents, TRUE);
3731 static gboolean compose_attach_append(Compose *compose, const gchar *file,
3732 const gchar *filename,
3733 const gchar *content_type,
3734 const gchar *charset)
3742 GtkListStore *store;
3744 gboolean has_binary = FALSE;
3746 if (!is_file_exist(file)) {
3747 gchar *file_from_uri = g_filename_from_uri(file, NULL, NULL);
3748 gboolean result = FALSE;
3749 if (file_from_uri && is_file_exist(file_from_uri)) {
3750 result = compose_attach_append(
3751 compose, file_from_uri,
3752 filename, content_type,
3755 g_free(file_from_uri);
3758 alertpanel_error("File %s doesn't exist\n", filename);
3761 if ((size = get_file_size(file)) < 0) {
3762 alertpanel_error("Can't get file size of %s\n", filename);
3766 /* In batch mode, we allow 0-length files to be attached no questions asked */
3767 if (size == 0 && !compose->batch) {
3768 gchar * msg = g_strdup_printf(_("File %s is empty."), filename);
3769 AlertValue aval = alertpanel_full(_("Empty file"), msg,
3770 GTK_STOCK_CANCEL, g_strconcat("+", _("_Attach anyway"), NULL), NULL, FALSE,
3771 NULL, ALERT_WARNING, G_ALERTDEFAULT);
3774 if (aval != G_ALERTALTERNATE) {
3778 if ((fp = g_fopen(file, "rb")) == NULL) {
3779 alertpanel_error(_("Can't read %s."), filename);
3784 ainfo = g_new0(AttachInfo, 1);
3785 auto_ainfo = g_auto_pointer_new_with_free
3786 (ainfo, (GFreeFunc) compose_attach_info_free);
3787 ainfo->file = g_strdup(file);
3790 ainfo->content_type = g_strdup(content_type);
3791 if (!g_ascii_strcasecmp(content_type, "message/rfc822")) {
3793 MsgFlags flags = {0, 0};
3795 if (procmime_get_encoding_for_text_file(file, &has_binary) == ENC_7BIT)
3796 ainfo->encoding = ENC_7BIT;
3798 ainfo->encoding = ENC_8BIT;
3800 msginfo = procheader_parse_file(file, flags, FALSE, FALSE);
3801 if (msginfo && msginfo->subject)
3802 name = g_strdup(msginfo->subject);
3804 name = g_path_get_basename(filename ? filename : file);
3806 ainfo->name = g_strdup_printf(_("Message: %s"), name);
3808 procmsg_msginfo_free(&msginfo);
3810 if (!g_ascii_strncasecmp(content_type, "text/", 5)) {
3811 ainfo->charset = g_strdup(charset);
3812 ainfo->encoding = procmime_get_encoding_for_text_file(file, &has_binary);
3814 ainfo->encoding = ENC_BASE64;
3816 name = g_path_get_basename(filename ? filename : file);
3817 ainfo->name = g_strdup(name);
3821 ainfo->content_type = procmime_get_mime_type(file);
3822 if (!ainfo->content_type) {
3823 ainfo->content_type =
3824 g_strdup("application/octet-stream");
3825 ainfo->encoding = ENC_BASE64;
3826 } else if (!g_ascii_strncasecmp(ainfo->content_type, "text/", 5))
3828 procmime_get_encoding_for_text_file(file, &has_binary);
3830 ainfo->encoding = ENC_BASE64;
3831 name = g_path_get_basename(filename ? filename : file);
3832 ainfo->name = g_strdup(name);
3836 if (ainfo->name != NULL
3837 && !strcmp(ainfo->name, ".")) {
3838 g_free(ainfo->name);
3842 if (!strcmp(ainfo->content_type, "unknown") || has_binary) {
3843 g_free(ainfo->content_type);
3844 ainfo->content_type = g_strdup("application/octet-stream");
3845 g_free(ainfo->charset);
3846 ainfo->charset = NULL;
3849 ainfo->size = (goffset)size;
3850 size_text = to_human_readable((goffset)size);
3852 store = GTK_LIST_STORE(gtk_tree_view_get_model
3853 (GTK_TREE_VIEW(compose->attach_clist)));
3855 gtk_list_store_append(store, &iter);
3856 gtk_list_store_set(store, &iter,
3857 COL_MIMETYPE, ainfo->content_type,
3858 COL_SIZE, size_text,
3859 COL_NAME, ainfo->name,
3860 COL_CHARSET, ainfo->charset,
3862 COL_AUTODATA, auto_ainfo,
3865 g_auto_pointer_free(auto_ainfo);
3866 compose_attach_update_label(compose);
3870 static void compose_use_signing(Compose *compose, gboolean use_signing)
3872 compose->use_signing = use_signing;
3873 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", use_signing);
3876 static void compose_use_encryption(Compose *compose, gboolean use_encryption)
3878 compose->use_encryption = use_encryption;
3879 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", use_encryption);
3882 #define NEXT_PART_NOT_CHILD(info) \
3884 node = info->node; \
3885 while (node->children) \
3886 node = g_node_last_child(node); \
3887 info = procmime_mimeinfo_next((MimeInfo *)node->data); \
3890 static void compose_attach_parts(Compose *compose, MsgInfo *msginfo)
3894 MimeInfo *firsttext = NULL;
3895 MimeInfo *encrypted = NULL;
3898 const gchar *partname = NULL;
3900 mimeinfo = procmime_scan_message(msginfo);
3901 if (!mimeinfo) return;
3903 if (mimeinfo->node->children == NULL) {
3904 procmime_mimeinfo_free_all(&mimeinfo);
3908 /* find first content part */
3909 child = (MimeInfo *) mimeinfo->node->children->data;
3910 while (child && child->node->children && (child->type == MIMETYPE_MULTIPART))
3911 child = (MimeInfo *)child->node->children->data;
3914 if (child->type == MIMETYPE_TEXT) {
3916 debug_print("First text part found\n");
3917 } else if (compose->mode == COMPOSE_REEDIT &&
3918 child->type == MIMETYPE_APPLICATION &&
3919 !g_ascii_strcasecmp(child->subtype, "pgp-encrypted")) {
3920 encrypted = (MimeInfo *)child->node->parent->data;
3923 child = (MimeInfo *) mimeinfo->node->children->data;
3924 while (child != NULL) {
3927 if (child == encrypted) {
3928 /* skip this part of tree */
3929 NEXT_PART_NOT_CHILD(child);
3933 if (child->type == MIMETYPE_MULTIPART) {
3934 /* get the actual content */
3935 child = procmime_mimeinfo_next(child);
3939 if (child == firsttext) {
3940 child = procmime_mimeinfo_next(child);
3944 outfile = procmime_get_tmp_file_name(child);
3945 if ((err = procmime_get_part(outfile, child)) < 0)
3946 g_warning("Can't get the part of multipart message. (%s)", g_strerror(-err));
3948 gchar *content_type;
3950 content_type = procmime_get_content_type_str(child->type, child->subtype);
3952 /* if we meet a pgp signature, we don't attach it, but
3953 * we force signing. */
3954 if ((strcmp(content_type, "application/pgp-signature") &&
3955 strcmp(content_type, "application/pkcs7-signature") &&
3956 strcmp(content_type, "application/x-pkcs7-signature"))
3957 || compose->mode == COMPOSE_REDIRECT) {
3958 partname = procmime_mimeinfo_get_parameter(child, "filename");
3959 if (partname == NULL)
3960 partname = procmime_mimeinfo_get_parameter(child, "name");
3961 if (partname == NULL)
3963 compose_attach_append(compose, outfile,
3964 partname, content_type,
3965 procmime_mimeinfo_get_parameter(child, "charset"));
3967 compose_force_signing(compose, compose->account, NULL);
3969 g_free(content_type);
3972 NEXT_PART_NOT_CHILD(child);
3974 procmime_mimeinfo_free_all(&mimeinfo);
3977 #undef NEXT_PART_NOT_CHILD
3982 WAIT_FOR_INDENT_CHAR,
3983 WAIT_FOR_INDENT_CHAR_OR_SPACE,
3986 /* return indent length, we allow:
3987 indent characters followed by indent characters or spaces/tabs,
3988 alphabets and numbers immediately followed by indent characters,
3989 and the repeating sequences of the above
3990 If quote ends with multiple spaces, only the first one is included. */
3991 static gchar *compose_get_quote_str(GtkTextBuffer *buffer,
3992 const GtkTextIter *start, gint *len)
3994 GtkTextIter iter = *start;
3998 IndentState state = WAIT_FOR_INDENT_CHAR;
4001 gint alnum_count = 0;
4002 gint space_count = 0;
4005 if (prefs_common.quote_chars == NULL) {
4009 while (!gtk_text_iter_ends_line(&iter)) {
4010 wc = gtk_text_iter_get_char(&iter);
4011 if (g_unichar_iswide(wc))
4013 clen = g_unichar_to_utf8(wc, ch);
4017 is_indent = strchr(prefs_common.quote_chars, ch[0]) ? TRUE : FALSE;
4018 is_space = g_unichar_isspace(wc);
4020 if (state == WAIT_FOR_INDENT_CHAR) {
4021 if (!is_indent && !g_unichar_isalnum(wc))
4024 quote_len += alnum_count + space_count + 1;
4025 alnum_count = space_count = 0;
4026 state = WAIT_FOR_INDENT_CHAR_OR_SPACE;
4029 } else if (state == WAIT_FOR_INDENT_CHAR_OR_SPACE) {
4030 if (!is_indent && !is_space && !g_unichar_isalnum(wc))
4034 else if (is_indent) {
4035 quote_len += alnum_count + space_count + 1;
4036 alnum_count = space_count = 0;
4039 state = WAIT_FOR_INDENT_CHAR;
4043 gtk_text_iter_forward_char(&iter);
4046 if (quote_len > 0 && space_count > 0)
4052 if (quote_len > 0) {
4054 gtk_text_iter_forward_chars(&iter, quote_len);
4055 return gtk_text_buffer_get_text(buffer, start, &iter, FALSE);
4061 /* return >0 if the line is itemized */
4062 static int compose_itemized_length(GtkTextBuffer *buffer,
4063 const GtkTextIter *start)
4065 GtkTextIter iter = *start;
4070 if (gtk_text_iter_ends_line(&iter))
4075 wc = gtk_text_iter_get_char(&iter);
4076 if (!g_unichar_isspace(wc))
4078 gtk_text_iter_forward_char(&iter);
4079 if (gtk_text_iter_ends_line(&iter))
4083 clen = g_unichar_to_utf8(wc, ch);
4087 if (!strchr("*-+", ch[0]))
4090 gtk_text_iter_forward_char(&iter);
4091 if (gtk_text_iter_ends_line(&iter))
4093 wc = gtk_text_iter_get_char(&iter);
4094 if (g_unichar_isspace(wc)) {
4100 /* return the string at the start of the itemization */
4101 static gchar * compose_get_itemized_chars(GtkTextBuffer *buffer,
4102 const GtkTextIter *start)
4104 GtkTextIter iter = *start;
4107 GString *item_chars = g_string_new("");
4110 if (gtk_text_iter_ends_line(&iter))
4115 wc = gtk_text_iter_get_char(&iter);
4116 if (!g_unichar_isspace(wc))
4118 gtk_text_iter_forward_char(&iter);
4119 if (gtk_text_iter_ends_line(&iter))
4121 g_string_append_unichar(item_chars, wc);
4124 str = item_chars->str;
4125 g_string_free(item_chars, FALSE);
4129 /* return the number of spaces at a line's start */
4130 static int compose_left_offset_length(GtkTextBuffer *buffer,
4131 const GtkTextIter *start)
4133 GtkTextIter iter = *start;
4136 if (gtk_text_iter_ends_line(&iter))
4140 wc = gtk_text_iter_get_char(&iter);
4141 if (!g_unichar_isspace(wc))
4144 gtk_text_iter_forward_char(&iter);
4145 if (gtk_text_iter_ends_line(&iter))
4149 gtk_text_iter_forward_char(&iter);
4150 if (gtk_text_iter_ends_line(&iter))
4155 static gboolean compose_get_line_break_pos(GtkTextBuffer *buffer,
4156 const GtkTextIter *start,
4157 GtkTextIter *break_pos,
4161 GtkTextIter iter = *start, line_end = *start;
4162 PangoLogAttr *attrs;
4169 gboolean can_break = FALSE;
4170 gboolean do_break = FALSE;
4171 gboolean was_white = FALSE;
4172 gboolean prev_dont_break = FALSE;
4174 gtk_text_iter_forward_to_line_end(&line_end);
4175 str = gtk_text_buffer_get_text(buffer, &iter, &line_end, FALSE);
4176 len = g_utf8_strlen(str, -1);
4180 g_warning("compose_get_line_break_pos: len = 0!");
4184 /* g_print("breaking line: %d: %s (len = %d)\n",
4185 gtk_text_iter_get_line(&iter), str, len); */
4187 attrs = g_new(PangoLogAttr, len + 1);
4189 pango_default_break(str, -1, NULL, attrs, len + 1);
4193 /* skip quote and leading spaces */
4194 for (i = 0; *p != '\0' && i < len; i++) {
4197 wc = g_utf8_get_char(p);
4198 if (i >= quote_len && !g_unichar_isspace(wc))
4200 if (g_unichar_iswide(wc))
4202 else if (*p == '\t')
4206 p = g_utf8_next_char(p);
4209 for (; *p != '\0' && i < len; i++) {
4210 PangoLogAttr *attr = attrs + i;
4214 if (attr->is_line_break && can_break && was_white && !prev_dont_break)
4217 was_white = attr->is_white;
4219 /* don't wrap URI */
4220 if ((uri_len = get_uri_len(p)) > 0) {
4222 if (pos > 0 && col > max_col) {
4232 wc = g_utf8_get_char(p);
4233 if (g_unichar_iswide(wc)) {
4235 if (prev_dont_break && can_break && attr->is_line_break)
4237 } else if (*p == '\t')
4241 if (pos > 0 && col > max_col) {
4246 if (*p == '-' || *p == '/')
4247 prev_dont_break = TRUE;
4249 prev_dont_break = FALSE;
4251 p = g_utf8_next_char(p);
4255 /* debug_print("compose_get_line_break_pos(): do_break = %d, pos = %d, col = %d\n", do_break, pos, col); */
4260 *break_pos = *start;
4261 gtk_text_iter_set_line_offset(break_pos, pos);
4266 static gboolean compose_join_next_line(Compose *compose,
4267 GtkTextBuffer *buffer,
4269 const gchar *quote_str)
4271 GtkTextIter iter_ = *iter, cur, prev, next, end;
4272 PangoLogAttr attrs[3];
4274 gchar *next_quote_str;
4277 gboolean keep_cursor = FALSE;
4279 if (!gtk_text_iter_forward_line(&iter_) ||
4280 gtk_text_iter_ends_line(&iter_)) {
4283 next_quote_str = compose_get_quote_str(buffer, &iter_, "e_len);
4285 if ((quote_str || next_quote_str) &&
4286 strcmp2(quote_str, next_quote_str) != 0) {
4287 g_free(next_quote_str);
4290 g_free(next_quote_str);
4293 if (quote_len > 0) {
4294 gtk_text_iter_forward_chars(&end, quote_len);
4295 if (gtk_text_iter_ends_line(&end)) {
4300 /* don't join itemized lines */
4301 if (compose_itemized_length(buffer, &end) > 0) {
4305 /* don't join signature separator */
4306 if (compose_is_sig_separator(compose, buffer, &iter_)) {
4309 /* delete quote str */
4311 gtk_text_buffer_delete(buffer, &iter_, &end);
4313 /* don't join line breaks put by the user */
4315 gtk_text_iter_backward_char(&cur);
4316 if (gtk_text_iter_has_tag(&cur, compose->no_join_tag)) {
4317 gtk_text_iter_forward_char(&cur);
4321 gtk_text_iter_forward_char(&cur);
4322 /* delete linebreak and extra spaces */
4323 while (gtk_text_iter_backward_char(&cur)) {
4324 wc1 = gtk_text_iter_get_char(&cur);
4325 if (!g_unichar_isspace(wc1))
4330 while (!gtk_text_iter_ends_line(&cur)) {
4331 wc1 = gtk_text_iter_get_char(&cur);
4332 if (!g_unichar_isspace(wc1))
4334 gtk_text_iter_forward_char(&cur);
4337 if (!gtk_text_iter_equal(&prev, &next)) {
4340 mark = gtk_text_buffer_get_insert(buffer);
4341 gtk_text_buffer_get_iter_at_mark(buffer, &cur, mark);
4342 if (gtk_text_iter_equal(&prev, &cur))
4344 gtk_text_buffer_delete(buffer, &prev, &next);
4348 /* insert space if required */
4349 gtk_text_iter_backward_char(&prev);
4350 wc1 = gtk_text_iter_get_char(&prev);
4351 wc2 = gtk_text_iter_get_char(&next);
4352 gtk_text_iter_forward_char(&next);
4353 str = gtk_text_buffer_get_text(buffer, &prev, &next, FALSE);
4354 pango_default_break(str, -1, NULL, attrs, 3);
4355 if (!attrs[1].is_line_break ||
4356 (!g_unichar_iswide(wc1) || !g_unichar_iswide(wc2))) {
4357 gtk_text_buffer_insert(buffer, &iter_, " ", 1);
4359 gtk_text_iter_backward_char(&iter_);
4360 gtk_text_buffer_place_cursor(buffer, &iter_);
4369 #define ADD_TXT_POS(bp_, ep_, pti_) \
4370 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
4371 last = last->next; \
4372 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
4373 last->next = NULL; \
4375 g_warning("alloc error scanning URIs"); \
4378 static gboolean compose_beautify_paragraph(Compose *compose, GtkTextIter *par_iter, gboolean force)
4380 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4381 GtkTextBuffer *buffer;
4382 GtkTextIter iter, break_pos, end_of_line;
4383 gchar *quote_str = NULL;
4385 gboolean wrap_quote = force || prefs_common.linewrap_quote;
4386 gboolean prev_autowrap = compose->autowrap;
4387 gint startq_offset = -1, noq_offset = -1;
4388 gint uri_start = -1, uri_stop = -1;
4389 gint nouri_start = -1, nouri_stop = -1;
4390 gint num_blocks = 0;
4391 gint quotelevel = -1;
4392 gboolean modified = force;
4393 gboolean removed = FALSE;
4394 gboolean modified_before_remove = FALSE;
4396 gboolean start = TRUE;
4397 gint itemized_len = 0, rem_item_len = 0;
4398 gchar *itemized_chars = NULL;
4399 gboolean item_continuation = FALSE;
4404 if (compose->draft_timeout_tag == COMPOSE_DRAFT_TIMEOUT_FORBIDDEN) {
4408 compose->autowrap = FALSE;
4410 buffer = gtk_text_view_get_buffer(text);
4411 undo_wrapping(compose->undostruct, TRUE);
4416 mark = gtk_text_buffer_get_insert(buffer);
4417 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
4421 if (compose->draft_timeout_tag == COMPOSE_DRAFT_TIMEOUT_FORBIDDEN) {
4422 if (gtk_text_iter_ends_line(&iter)) {
4423 while (gtk_text_iter_ends_line(&iter) &&
4424 gtk_text_iter_forward_line(&iter))
4427 while (gtk_text_iter_backward_line(&iter)) {
4428 if (gtk_text_iter_ends_line(&iter)) {
4429 gtk_text_iter_forward_line(&iter);
4435 /* move to line start */
4436 gtk_text_iter_set_line_offset(&iter, 0);
4439 itemized_len = compose_itemized_length(buffer, &iter);
4441 if (!itemized_len) {
4442 itemized_len = compose_left_offset_length(buffer, &iter);
4443 item_continuation = TRUE;
4447 itemized_chars = compose_get_itemized_chars(buffer, &iter);
4449 /* go until paragraph end (empty line) */
4450 while (start || !gtk_text_iter_ends_line(&iter)) {
4451 gchar *scanpos = NULL;
4452 /* parse table - in order of priority */
4454 const gchar *needle; /* token */
4456 /* token search function */
4457 gchar *(*search) (const gchar *haystack,
4458 const gchar *needle);
4459 /* part parsing function */
4460 gboolean (*parse) (const gchar *start,
4461 const gchar *scanpos,
4465 /* part to URI function */
4466 gchar *(*build_uri) (const gchar *bp,
4470 static struct table parser[] = {
4471 {"http://", strcasestr, get_uri_part, make_uri_string},
4472 {"https://", strcasestr, get_uri_part, make_uri_string},
4473 {"ftp://", strcasestr, get_uri_part, make_uri_string},
4474 {"sftp://", strcasestr, get_uri_part, make_uri_string},
4475 {"gopher://",strcasestr, get_uri_part, make_uri_string},
4476 {"www.", strcasestr, get_uri_part, make_http_string},
4477 {"mailto:", strcasestr, get_uri_part, make_uri_string},
4478 {"@", strcasestr, get_email_part, make_email_string}
4480 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
4481 gint last_index = PARSE_ELEMS;
4483 gchar *o_walk = NULL, *walk = NULL, *bp = NULL, *ep = NULL;
4487 if (!prev_autowrap && num_blocks == 0) {
4489 g_signal_handlers_block_by_func(G_OBJECT(buffer),
4490 G_CALLBACK(text_inserted),
4493 if (gtk_text_iter_has_tag(&iter, compose->no_wrap_tag) && !force)
4496 uri_start = uri_stop = -1;
4498 quote_str = compose_get_quote_str(buffer, &iter, "e_len);
4501 /* debug_print("compose_beautify_paragraph(): quote_str = '%s'\n", quote_str); */
4502 if (startq_offset == -1)
4503 startq_offset = gtk_text_iter_get_offset(&iter);
4504 quotelevel = get_quote_level(quote_str, prefs_common.quote_chars);
4505 if (quotelevel > 2) {
4506 /* recycle colors */
4507 if (prefs_common.recycle_quote_colors)
4516 if (startq_offset == -1)
4517 noq_offset = gtk_text_iter_get_offset(&iter);
4521 if (prev_autowrap == FALSE && !force && !wrap_quote) {
4524 if (gtk_text_iter_ends_line(&iter)) {
4526 } else if (compose_get_line_break_pos(buffer, &iter, &break_pos,
4527 prefs_common.linewrap_len,
4529 GtkTextIter prev, next, cur;
4530 if (prev_autowrap != FALSE || force) {
4531 compose->automatic_break = TRUE;
4533 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4534 compose->automatic_break = FALSE;
4535 if (itemized_len && compose->autoindent) {
4536 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4537 if (!item_continuation)
4538 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4540 } else if (quote_str && wrap_quote) {
4541 compose->automatic_break = TRUE;
4543 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4544 compose->automatic_break = FALSE;
4545 if (itemized_len && compose->autoindent) {
4546 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4547 if (!item_continuation)
4548 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4552 /* remove trailing spaces */
4554 rem_item_len = itemized_len;
4555 while (compose->autoindent && rem_item_len-- > 0)
4556 gtk_text_iter_backward_char(&cur);
4557 gtk_text_iter_backward_char(&cur);
4560 while (!gtk_text_iter_starts_line(&cur)) {
4563 gtk_text_iter_backward_char(&cur);
4564 wc = gtk_text_iter_get_char(&cur);
4565 if (!g_unichar_isspace(wc))
4569 if (!gtk_text_iter_equal(&prev, &next)) {
4570 gtk_text_buffer_delete(buffer, &prev, &next);
4572 gtk_text_iter_forward_char(&break_pos);
4576 gtk_text_buffer_insert(buffer, &break_pos,
4580 modified |= compose_join_next_line(compose, buffer, &iter, quote_str);
4582 /* move iter to current line start */
4583 gtk_text_iter_set_line_offset(&iter, 0);
4590 /* move iter to next line start */
4596 if (!prev_autowrap && num_blocks > 0) {
4598 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
4599 G_CALLBACK(text_inserted),
4603 while (!gtk_text_iter_ends_line(&end_of_line)) {
4604 gtk_text_iter_forward_char(&end_of_line);
4606 o_walk = walk = gtk_text_buffer_get_text(buffer, &iter, &end_of_line, FALSE);
4608 nouri_start = gtk_text_iter_get_offset(&iter);
4609 nouri_stop = gtk_text_iter_get_offset(&end_of_line);
4611 walk_pos = gtk_text_iter_get_offset(&iter);
4612 /* FIXME: this looks phony. scanning for anything in the parse table */
4613 for (n = 0; n < PARSE_ELEMS; n++) {
4616 tmp = parser[n].search(walk, parser[n].needle);
4618 if (scanpos == NULL || tmp < scanpos) {
4627 /* check if URI can be parsed */
4628 if (parser[last_index].parse(walk, scanpos, (const gchar **)&bp,
4629 (const gchar **)&ep, FALSE)
4630 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
4634 strlen(parser[last_index].needle);
4637 uri_start = walk_pos + (bp - o_walk);
4638 uri_stop = walk_pos + (ep - o_walk);
4642 gtk_text_iter_forward_line(&iter);
4645 if (startq_offset != -1) {
4646 GtkTextIter startquote, endquote;
4647 gtk_text_buffer_get_iter_at_offset(
4648 buffer, &startquote, startq_offset);
4651 switch (quotelevel) {
4653 if (!gtk_text_iter_has_tag(&startquote, compose->quote0_tag) ||
4654 !gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) {
4655 gtk_text_buffer_apply_tag_by_name(
4656 buffer, "quote0", &startquote, &endquote);
4657 gtk_text_buffer_remove_tag_by_name(
4658 buffer, "quote1", &startquote, &endquote);
4659 gtk_text_buffer_remove_tag_by_name(
4660 buffer, "quote2", &startquote, &endquote);
4665 if (!gtk_text_iter_has_tag(&startquote, compose->quote1_tag) ||
4666 !gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) {
4667 gtk_text_buffer_apply_tag_by_name(
4668 buffer, "quote1", &startquote, &endquote);
4669 gtk_text_buffer_remove_tag_by_name(
4670 buffer, "quote0", &startquote, &endquote);
4671 gtk_text_buffer_remove_tag_by_name(
4672 buffer, "quote2", &startquote, &endquote);
4677 if (!gtk_text_iter_has_tag(&startquote, compose->quote2_tag) ||
4678 !gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag)) {
4679 gtk_text_buffer_apply_tag_by_name(
4680 buffer, "quote2", &startquote, &endquote);
4681 gtk_text_buffer_remove_tag_by_name(
4682 buffer, "quote0", &startquote, &endquote);
4683 gtk_text_buffer_remove_tag_by_name(
4684 buffer, "quote1", &startquote, &endquote);
4690 } else if (noq_offset != -1) {
4691 GtkTextIter startnoquote, endnoquote;
4692 gtk_text_buffer_get_iter_at_offset(
4693 buffer, &startnoquote, noq_offset);
4696 if ((gtk_text_iter_has_tag(&startnoquote, compose->quote0_tag)
4697 && gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) ||
4698 (gtk_text_iter_has_tag(&startnoquote, compose->quote1_tag)
4699 && gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) ||
4700 (gtk_text_iter_has_tag(&startnoquote, compose->quote2_tag)
4701 && gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag))) {
4702 gtk_text_buffer_remove_tag_by_name(
4703 buffer, "quote0", &startnoquote, &endnoquote);
4704 gtk_text_buffer_remove_tag_by_name(
4705 buffer, "quote1", &startnoquote, &endnoquote);
4706 gtk_text_buffer_remove_tag_by_name(
4707 buffer, "quote2", &startnoquote, &endnoquote);
4713 if (uri_start != nouri_start && uri_stop != nouri_stop) {
4714 GtkTextIter nouri_start_iter, nouri_end_iter;
4715 gtk_text_buffer_get_iter_at_offset(
4716 buffer, &nouri_start_iter, nouri_start);
4717 gtk_text_buffer_get_iter_at_offset(
4718 buffer, &nouri_end_iter, nouri_stop);
4719 if (gtk_text_iter_has_tag(&nouri_start_iter, compose->uri_tag) &&
4720 gtk_text_iter_has_tag(&nouri_end_iter, compose->uri_tag)) {
4721 gtk_text_buffer_remove_tag_by_name(
4722 buffer, "link", &nouri_start_iter, &nouri_end_iter);
4723 modified_before_remove = modified;
4728 if (uri_start >= 0 && uri_stop > 0) {
4729 GtkTextIter uri_start_iter, uri_end_iter, back;
4730 gtk_text_buffer_get_iter_at_offset(
4731 buffer, &uri_start_iter, uri_start);
4732 gtk_text_buffer_get_iter_at_offset(
4733 buffer, &uri_end_iter, uri_stop);
4734 back = uri_end_iter;
4735 gtk_text_iter_backward_char(&back);
4736 if (!gtk_text_iter_has_tag(&uri_start_iter, compose->uri_tag) ||
4737 !gtk_text_iter_has_tag(&back, compose->uri_tag)) {
4738 gtk_text_buffer_apply_tag_by_name(
4739 buffer, "link", &uri_start_iter, &uri_end_iter);
4741 if (removed && !modified_before_remove) {
4747 /* debug_print("not modified, out after %d lines\n", lines); */
4751 /* debug_print("modified, out after %d lines\n", lines); */
4753 g_free(itemized_chars);
4756 undo_wrapping(compose->undostruct, FALSE);
4757 compose->autowrap = prev_autowrap;
4762 void compose_action_cb(void *data)
4764 Compose *compose = (Compose *)data;
4765 compose_wrap_all(compose);
4768 static void compose_wrap_all(Compose *compose)
4770 compose_wrap_all_full(compose, FALSE);
4773 static void compose_wrap_all_full(Compose *compose, gboolean force)
4775 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4776 GtkTextBuffer *buffer;
4778 gboolean modified = TRUE;
4780 buffer = gtk_text_view_get_buffer(text);
4782 gtk_text_buffer_get_start_iter(buffer, &iter);
4784 undo_wrapping(compose->undostruct, TRUE);
4786 while (!gtk_text_iter_is_end(&iter) && modified)
4787 modified = compose_beautify_paragraph(compose, &iter, force);
4789 undo_wrapping(compose->undostruct, FALSE);
4793 static void compose_set_title(Compose *compose)
4799 edited = compose->modified ? _(" [Edited]") : "";
4801 subject = gtk_editable_get_chars(
4802 GTK_EDITABLE(compose->subject_entry), 0, -1);
4804 #ifndef GENERIC_UMPC
4805 if (subject && strlen(subject))
4806 str = g_strdup_printf(_("%s - Compose message%s"),
4809 str = g_strdup_printf(_("[no subject] - Compose message%s"), edited);
4811 str = g_strdup(_("Compose message"));
4814 gtk_window_set_title(GTK_WINDOW(compose->window), str);
4820 * compose_current_mail_account:
4822 * Find a current mail account (the currently selected account, or the
4823 * default account, if a news account is currently selected). If a
4824 * mail account cannot be found, display an error message.
4826 * Return value: Mail account, or NULL if not found.
4828 static PrefsAccount *
4829 compose_current_mail_account(void)
4833 if (cur_account && cur_account->protocol != A_NNTP)
4836 ac = account_get_default();
4837 if (!ac || ac->protocol == A_NNTP) {
4838 alertpanel_error(_("Account for sending mail is not specified.\n"
4839 "Please select a mail account before sending."));
4846 #define QUOTE_IF_REQUIRED(out, str) \
4848 if (*str != '"' && strpbrk(str, ",.:;[]<>()@\\\"")) { \
4852 len = strlen(str) + 3; \
4853 if ((__tmp = alloca(len)) == NULL) { \
4854 g_warning("can't allocate memory"); \
4855 g_string_free(header, TRUE); \
4858 g_snprintf(__tmp, len, "\"%s\"", str); \
4863 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4864 g_warning("can't allocate memory"); \
4865 g_string_free(header, TRUE); \
4868 strcpy(__tmp, str); \
4874 #define QUOTE_IF_REQUIRED_NORMAL(out, str, errret) \
4876 if (*str != '"' && strpbrk(str, ",.:;[]<>()@\\\"")) { \
4880 len = strlen(str) + 3; \
4881 if ((__tmp = alloca(len)) == NULL) { \
4882 g_warning("can't allocate memory"); \
4885 g_snprintf(__tmp, len, "\"%s\"", str); \
4890 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4891 g_warning("can't allocate memory"); \
4894 strcpy(__tmp, str); \
4900 static void compose_select_account(Compose *compose, PrefsAccount *account,
4903 gchar *from = NULL, *header = NULL;
4904 ComposeHeaderEntry *header_entry;
4905 #if GTK_CHECK_VERSION(2, 24, 0)
4909 cm_return_if_fail(account != NULL);
4911 compose->account = account;
4912 if (account->name && *account->name) {
4914 QUOTE_IF_REQUIRED_NORMAL(buf, account->name, return);
4915 qbuf = escape_internal_quotes(buf, '"');
4916 from = g_strdup_printf("%s <%s>",
4917 qbuf, account->address);
4920 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4922 from = g_strdup_printf("<%s>",
4924 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4929 compose_set_title(compose);
4931 if (account->default_sign && compose->mode != COMPOSE_REDIRECT)
4932 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", TRUE);
4934 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", FALSE);
4935 if (account->default_encrypt && compose->mode != COMPOSE_REDIRECT)
4936 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", TRUE);
4938 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", FALSE);
4940 activate_privacy_system(compose, account, FALSE);
4942 if (!init && compose->mode != COMPOSE_REDIRECT) {
4943 undo_block(compose->undostruct);
4944 compose_insert_sig(compose, TRUE);
4945 undo_unblock(compose->undostruct);
4948 header_entry = (ComposeHeaderEntry *) compose->header_list->data;
4949 #if !GTK_CHECK_VERSION(2, 24, 0)
4950 header = gtk_combo_box_get_active_text(GTK_COMBO_BOX(header_entry->combo));
4952 if (gtk_combo_box_get_active_iter(GTK_COMBO_BOX(header_entry->combo), &iter))
4953 gtk_tree_model_get(gtk_combo_box_get_model(GTK_COMBO_BOX(
4954 header_entry->combo)), &iter, COMBOBOX_TEXT, &header, -1);
4957 if (header && !strlen(gtk_entry_get_text(GTK_ENTRY(header_entry->entry)))) {
4958 if (account->protocol == A_NNTP) {
4959 if (!strcmp(header, _("To:")))
4960 combobox_select_by_text(
4961 GTK_COMBO_BOX(header_entry->combo),
4964 if (!strcmp(header, _("Newsgroups:")))
4965 combobox_select_by_text(
4966 GTK_COMBO_BOX(header_entry->combo),
4974 /* use account's dict info if set */
4975 if (compose->gtkaspell) {
4976 if (account->enable_default_dictionary)
4977 gtkaspell_change_dict(compose->gtkaspell,
4978 account->default_dictionary, FALSE);
4979 if (account->enable_default_alt_dictionary)
4980 gtkaspell_change_alt_dict(compose->gtkaspell,
4981 account->default_alt_dictionary);
4982 if (account->enable_default_dictionary
4983 || account->enable_default_alt_dictionary)
4984 compose_spell_menu_changed(compose);
4989 gboolean compose_check_for_valid_recipient(Compose *compose) {
4990 gchar *recipient_headers_mail[] = {"To:", "Cc:", "Bcc:", NULL};
4991 gchar *recipient_headers_news[] = {"Newsgroups:", NULL};
4992 gboolean recipient_found = FALSE;
4996 /* free to and newsgroup list */
4997 slist_free_strings_full(compose->to_list);
4998 compose->to_list = NULL;
5000 slist_free_strings_full(compose->newsgroup_list);
5001 compose->newsgroup_list = NULL;
5003 /* search header entries for to and newsgroup entries */
5004 for (list = compose->header_list; list; list = list->next) {
5007 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5008 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5011 if (entry[0] != '\0') {
5012 for (strptr = recipient_headers_mail; *strptr != NULL; strptr++) {
5013 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
5014 compose->to_list = address_list_append(compose->to_list, entry);
5015 recipient_found = TRUE;
5018 for (strptr = recipient_headers_news; *strptr != NULL; strptr++) {
5019 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
5020 compose->newsgroup_list = newsgroup_list_append(compose->newsgroup_list, entry);
5021 recipient_found = TRUE;
5028 return recipient_found;
5031 static gboolean compose_check_for_set_recipients(Compose *compose)
5033 if (compose->account->set_autocc && compose->account->auto_cc) {
5034 gboolean found_other = FALSE;
5036 /* search header entries for to and newsgroup entries */
5037 for (list = compose->header_list; list; list = list->next) {
5040 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5041 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5044 if (strcmp(entry, compose->account->auto_cc)
5045 || strcmp(header, prefs_common_translated_header_name("Cc:"))) {
5056 if (compose->batch) {
5057 gtk_widget_show_all(compose->window);
5059 text = g_strdup_printf(_("The only recipient is the default '%s' address. Send anyway?"),
5060 prefs_common_translated_header_name("Cc"));
5061 aval = alertpanel(_("Send"),
5063 GTK_STOCK_CANCEL, g_strconcat("+", _("_Send"), NULL), NULL);
5065 if (aval != G_ALERTALTERNATE)
5069 if (compose->account->set_autobcc && compose->account->auto_bcc) {
5070 gboolean found_other = FALSE;
5072 /* search header entries for to and newsgroup entries */
5073 for (list = compose->header_list; list; list = list->next) {
5076 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5077 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5080 if (strcmp(entry, compose->account->auto_bcc)
5081 || strcmp(header, prefs_common_translated_header_name("Bcc:"))) {
5093 if (compose->batch) {
5094 gtk_widget_show_all(compose->window);
5096 text = g_strdup_printf(_("The only recipient is the default '%s' address. Send anyway?"),
5097 prefs_common_translated_header_name("Bcc"));
5098 aval = alertpanel(_("Send"),
5100 GTK_STOCK_CANCEL, g_strconcat("+", _("_Send"), NULL), NULL);
5102 if (aval != G_ALERTALTERNATE)
5109 static gboolean compose_check_entries(Compose *compose, gboolean check_everything)
5113 if (compose_check_for_valid_recipient(compose) == FALSE) {
5114 if (compose->batch) {
5115 gtk_widget_show_all(compose->window);
5117 alertpanel_error(_("Recipient is not specified."));
5121 if (compose_check_for_set_recipients(compose) == FALSE) {
5125 if (!compose->batch && prefs_common.warn_empty_subj == TRUE) {
5126 str = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5127 if (*str == '\0' && check_everything == TRUE &&
5128 compose->mode != COMPOSE_REDIRECT) {
5130 gchar *button_label;
5133 if (compose->sending)
5134 button_label = g_strconcat("+", _("_Send"), NULL);
5136 button_label = g_strconcat("+", _("_Queue"), NULL);
5137 message = g_strdup_printf(_("Subject is empty. %s"),
5138 compose->sending?_("Send it anyway?"):
5139 _("Queue it anyway?"));
5141 aval = alertpanel_full(compose->sending?_("Send"):_("Send later"), message,
5142 GTK_STOCK_CANCEL, button_label, NULL, TRUE, NULL,
5143 ALERT_QUESTION, G_ALERTDEFAULT);
5145 if (aval & G_ALERTDISABLE) {
5146 aval &= ~G_ALERTDISABLE;
5147 prefs_common.warn_empty_subj = FALSE;
5149 if (aval != G_ALERTALTERNATE)
5154 if (!compose->batch && prefs_common.warn_sending_many_recipients == TRUE
5155 && check_everything == TRUE) {
5159 /* count To and Cc recipients */
5160 for (list = compose->header_list; list; list = list->next) {
5164 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5165 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5168 if ((entry[0] != '\0')
5169 && (strcmp(header, prefs_common_translated_header_name("To:"))
5170 || strcmp(header, prefs_common_translated_header_name("Cc:")))) {
5176 if (cnt > prefs_common.warn_sending_many_recipients_num) {
5178 gchar *button_label;
5181 if (compose->sending)
5182 button_label = g_strconcat("+", _("_Send"), NULL);
5184 button_label = g_strconcat("+", _("_Queue"), NULL);
5185 message = g_strdup_printf(_("Sending to %d recipients. %s"), cnt,
5186 compose->sending?_("Send it anyway?"):
5187 _("Queue it anyway?"));
5189 aval = alertpanel_full(compose->sending?_("Send"):_("Send later"), message,
5190 GTK_STOCK_CANCEL, button_label, NULL, TRUE, NULL,
5191 ALERT_QUESTION, G_ALERTDEFAULT);
5193 if (aval & G_ALERTDISABLE) {
5194 aval &= ~G_ALERTDISABLE;
5195 prefs_common.warn_empty_subj = FALSE;
5197 if (aval != G_ALERTALTERNATE)
5202 if (check_everything && hooks_invoke(COMPOSE_CHECK_BEFORE_SEND_HOOKLIST, compose))
5208 gint compose_send(Compose *compose)
5211 FolderItem *folder = NULL;
5213 gchar *msgpath = NULL;
5214 gboolean discard_window = FALSE;
5215 gchar *errstr = NULL;
5216 gchar *tmsgid = NULL;
5217 MainWindow *mainwin = mainwindow_get_mainwindow();
5218 gboolean queued_removed = FALSE;
5220 if (prefs_common.send_dialog_invisible
5221 || compose->batch == TRUE)
5222 discard_window = TRUE;
5224 compose_allow_user_actions (compose, FALSE);
5225 compose->sending = TRUE;
5227 if (compose_check_entries(compose, TRUE) == FALSE) {
5228 if (compose->batch) {
5229 gtk_widget_show_all(compose->window);
5235 val = compose_queue(compose, &msgnum, &folder, &msgpath, TRUE);
5238 if (compose->batch) {
5239 gtk_widget_show_all(compose->window);
5242 alertpanel_error(_("Could not queue message for sending:\n\n"
5243 "Charset conversion failed."));
5244 } else if (val == -5) {
5245 alertpanel_error(_("Could not queue message for sending:\n\n"
5246 "Couldn't get recipient encryption key."));
5247 } else if (val == -6) {
5249 } else if (val == -3) {
5250 if (privacy_peek_error())
5251 alertpanel_error(_("Could not queue message for sending:\n\n"
5252 "Signature failed: %s"), privacy_get_error());
5253 } else if (val == -2 && errno != 0) {
5254 alertpanel_error(_("Could not queue message for sending:\n\n%s."), g_strerror(errno));
5256 alertpanel_error(_("Could not queue message for sending."));
5261 tmsgid = compose->msgid ? g_strdup(compose->msgid) : NULL;
5262 if (discard_window) {
5263 compose->sending = FALSE;
5264 compose_close(compose);
5265 /* No more compose access in the normal codepath
5266 * after this point! */
5271 alertpanel_error(_("The message was queued but could not be "
5272 "sent.\nUse \"Send queued messages\" from "
5273 "the main window to retry."));
5274 if (!discard_window) {
5281 if (msgpath == NULL) {
5282 msgpath = folder_item_fetch_msg(folder, msgnum);
5283 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5286 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5287 claws_unlink(msgpath);
5290 if (!discard_window) {
5292 if (!queued_removed)
5293 folder_item_remove_msg(folder, msgnum);
5294 folder_item_scan(folder);
5296 /* make sure we delete that */
5297 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5299 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5300 folder_item_remove_msg(folder, tmp->msgnum);
5301 procmsg_msginfo_free(&tmp);
5308 if (!queued_removed)
5309 folder_item_remove_msg(folder, msgnum);
5310 folder_item_scan(folder);
5312 /* make sure we delete that */
5313 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5315 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5316 folder_item_remove_msg(folder, tmp->msgnum);
5317 procmsg_msginfo_free(&tmp);
5320 if (!discard_window) {
5321 compose->sending = FALSE;
5322 compose_allow_user_actions (compose, TRUE);
5323 compose_close(compose);
5327 alertpanel_error_log(_("%s\nUse \"Send queued messages\" from "
5328 "the main window to retry."), errstr);
5331 alertpanel_error_log(_("The message was queued but could not be "
5332 "sent.\nUse \"Send queued messages\" from "
5333 "the main window to retry."));
5335 if (!discard_window) {
5344 toolbar_main_set_sensitive(mainwin);
5345 main_window_set_menu_sensitive(mainwin);
5351 compose_allow_user_actions (compose, TRUE);
5352 compose->sending = FALSE;
5353 compose->modified = TRUE;
5354 toolbar_main_set_sensitive(mainwin);
5355 main_window_set_menu_sensitive(mainwin);
5360 static gboolean compose_use_attach(Compose *compose)
5362 GtkTreeModel *model = gtk_tree_view_get_model
5363 (GTK_TREE_VIEW(compose->attach_clist));
5364 return gtk_tree_model_iter_n_children(model, NULL) > 0;
5367 static gint compose_redirect_write_headers_from_headerlist(Compose *compose,
5370 gchar buf[BUFFSIZE];
5372 gboolean first_to_address;
5373 gboolean first_cc_address;
5375 ComposeHeaderEntry *headerentry;
5376 const gchar *headerentryname;
5377 const gchar *cc_hdr;
5378 const gchar *to_hdr;
5379 gboolean err = FALSE;
5381 debug_print("Writing redirect header\n");
5383 cc_hdr = prefs_common_translated_header_name("Cc:");
5384 to_hdr = prefs_common_translated_header_name("To:");
5386 first_to_address = TRUE;
5387 for (list = compose->header_list; list; list = list->next) {
5388 headerentry = ((ComposeHeaderEntry *)list->data);
5389 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5391 if (g_utf8_collate(headerentryname, to_hdr) == 0) {
5392 const gchar *entstr = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5393 Xstrdup_a(str, entstr, return -1);
5395 if (str[0] != '\0') {
5396 compose_convert_header
5397 (compose, buf, sizeof(buf), str,
5398 strlen("Resent-To") + 2, TRUE);
5400 if (first_to_address) {
5401 err |= (fprintf(fp, "Resent-To: ") < 0);
5402 first_to_address = FALSE;
5404 err |= (fprintf(fp, ",") < 0);
5406 err |= (fprintf(fp, "%s", buf) < 0);
5410 if (!first_to_address) {
5411 err |= (fprintf(fp, "\n") < 0);
5414 first_cc_address = TRUE;
5415 for (list = compose->header_list; list; list = list->next) {
5416 headerentry = ((ComposeHeaderEntry *)list->data);
5417 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5419 if (g_utf8_collate(headerentryname, cc_hdr) == 0) {
5420 const gchar *strg = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5421 Xstrdup_a(str, strg, return -1);
5423 if (str[0] != '\0') {
5424 compose_convert_header
5425 (compose, buf, sizeof(buf), str,
5426 strlen("Resent-Cc") + 2, TRUE);
5428 if (first_cc_address) {
5429 err |= (fprintf(fp, "Resent-Cc: ") < 0);
5430 first_cc_address = FALSE;
5432 err |= (fprintf(fp, ",") < 0);
5434 err |= (fprintf(fp, "%s", buf) < 0);
5438 if (!first_cc_address) {
5439 err |= (fprintf(fp, "\n") < 0);
5442 return (err ? -1:0);
5445 static gint compose_redirect_write_headers(Compose *compose, FILE *fp)
5447 gchar buf[BUFFSIZE];
5449 const gchar *entstr;
5450 /* struct utsname utsbuf; */
5451 gboolean err = FALSE;
5453 cm_return_val_if_fail(fp != NULL, -1);
5454 cm_return_val_if_fail(compose->account != NULL, -1);
5455 cm_return_val_if_fail(compose->account->address != NULL, -1);
5458 if (prefs_common.hide_timezone)
5459 get_rfc822_date_hide_tz(buf, sizeof(buf));
5461 get_rfc822_date(buf, sizeof(buf));
5462 err |= (fprintf(fp, "Resent-Date: %s\n", buf) < 0);
5465 if (compose->account->name && *compose->account->name) {
5466 compose_convert_header
5467 (compose, buf, sizeof(buf), compose->account->name,
5468 strlen("From: "), TRUE);
5469 err |= (fprintf(fp, "Resent-From: %s <%s>\n",
5470 buf, compose->account->address) < 0);
5472 err |= (fprintf(fp, "Resent-From: %s\n", compose->account->address) < 0);
5475 entstr = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5476 if (*entstr != '\0') {
5477 Xstrdup_a(str, entstr, return -1);
5480 compose_convert_header(compose, buf, sizeof(buf), str,
5481 strlen("Subject: "), FALSE);
5482 err |= (fprintf(fp, "Subject: %s\n", buf) < 0);
5486 /* Resent-Message-ID */
5487 if (compose->account->gen_msgid) {
5488 gchar *addr = prefs_account_generate_msgid(compose->account);
5489 err |= (fprintf(fp, "Resent-Message-ID: <%s>\n", addr) < 0);
5491 g_free(compose->msgid);
5492 compose->msgid = addr;
5494 compose->msgid = NULL;
5497 if (compose_redirect_write_headers_from_headerlist(compose, fp))
5500 /* separator between header and body */
5501 err |= (fputs("\n", fp) == EOF);
5503 return (err ? -1:0);
5506 static gint compose_redirect_write_to_file(Compose *compose, FILE *fdest)
5511 gchar rewrite_buf[BUFFSIZE];
5513 gboolean skip = FALSE;
5514 gboolean err = FALSE;
5515 gchar *not_included[]={
5516 "Return-Path:", "Delivered-To:", "Received:",
5517 "Subject:", "X-UIDL:", "AF:",
5518 "NF:", "PS:", "SRH:",
5519 "SFN:", "DSR:", "MID:",
5520 "CFG:", "PT:", "S:",
5521 "RQ:", "SSV:", "NSV:",
5522 "SSH:", "R:", "MAID:",
5523 "NAID:", "RMID:", "FMID:",
5524 "SCF:", "RRCPT:", "NG:",
5525 "X-Claws-Privacy", "X-Claws-Sign:", "X-Claws-Encrypt",
5526 "X-Claws-End-Special-Headers:", "X-Claws-Account-Id:",
5527 "X-Sylpheed-Privacy", "X-Sylpheed-Sign:", "X-Sylpheed-Encrypt",
5528 "X-Sylpheed-End-Special-Headers:", "X-Sylpheed-Account-Id:",
5529 "X-Claws-Auto-Wrapping:", "X-Claws-Auto-Indent:",
5534 if ((fp = g_fopen(compose->redirect_filename, "rb")) == NULL) {
5535 FILE_OP_ERROR(compose->redirect_filename, "fopen");
5539 while ((ret = procheader_get_one_field_asis(&buf, fp)) != -1) {
5541 for (i = 0; not_included[i] != NULL; i++) {
5542 if (g_ascii_strncasecmp(buf, not_included[i],
5543 strlen(not_included[i])) == 0) {
5555 if (fputs(buf, fdest) == -1) {
5561 if (!prefs_common.redirect_keep_from) {
5562 if (g_ascii_strncasecmp(buf, "From:",
5563 strlen("From:")) == 0) {
5564 err |= (fputs(" (by way of ", fdest) == EOF);
5565 if (compose->account->name
5566 && *compose->account->name) {
5567 gchar buffer[BUFFSIZE];
5569 compose_convert_header
5570 (compose, buffer, sizeof(buffer),
5571 compose->account->name,
5574 err |= (fprintf(fdest, "%s <%s>",
5576 compose->account->address) < 0);
5578 err |= (fprintf(fdest, "%s",
5579 compose->account->address) < 0);
5580 err |= (fputs(")", fdest) == EOF);
5586 if (fputs("\n", fdest) == -1)
5590 if (err || ret == -1)
5593 if (compose_redirect_write_headers(compose, fdest))
5596 while ((len = fread(rewrite_buf, sizeof(gchar), sizeof(rewrite_buf), fp)) > 0) {
5597 if (fwrite(rewrite_buf, sizeof(gchar), len, fdest) != len)
5611 static gint compose_write_to_file(Compose *compose, FILE *fp, gint action, gboolean attach_parts)
5613 GtkTextBuffer *buffer;
5614 GtkTextIter start, end;
5615 gchar *chars, *tmp_enc_file, *content;
5617 const gchar *out_codeset;
5618 EncodingType encoding = ENC_UNKNOWN;
5619 MimeInfo *mimemsg, *mimetext;
5621 const gchar *src_codeset = CS_INTERNAL;
5622 gchar *from_addr = NULL;
5623 gchar *from_name = NULL;
5626 if (action == COMPOSE_WRITE_FOR_SEND) {
5627 attach_parts = TRUE;
5629 /* We're sending the message, generate a Message-ID
5631 if (compose->msgid == NULL &&
5632 compose->account->gen_msgid) {
5633 compose->msgid = prefs_account_generate_msgid(compose->account);
5637 /* create message MimeInfo */
5638 mimemsg = procmime_mimeinfo_new();
5639 mimemsg->type = MIMETYPE_MESSAGE;
5640 mimemsg->subtype = g_strdup("rfc822");
5641 mimemsg->content = MIMECONTENT_MEM;
5642 mimemsg->tmp = TRUE; /* must free content later */
5643 mimemsg->data.mem = compose_get_header(compose);
5645 /* Create text part MimeInfo */
5646 /* get all composed text */
5647 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5648 gtk_text_buffer_get_start_iter(buffer, &start);
5649 gtk_text_buffer_get_end_iter(buffer, &end);
5650 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5652 out_codeset = conv_get_charset_str(compose->out_encoding);
5654 if (!out_codeset && is_ascii_str(chars)) {
5655 out_codeset = CS_US_ASCII;
5656 } else if (prefs_common.outgoing_fallback_to_ascii &&
5657 is_ascii_str(chars)) {
5658 out_codeset = CS_US_ASCII;
5659 encoding = ENC_7BIT;
5663 gchar *test_conv_global_out = NULL;
5664 gchar *test_conv_reply = NULL;
5666 /* automatic mode. be automatic. */
5667 codeconv_set_strict(TRUE);
5669 out_codeset = conv_get_outgoing_charset_str();
5671 debug_print("trying to convert to %s\n", out_codeset);
5672 test_conv_global_out = conv_codeset_strdup(chars, src_codeset, out_codeset);
5675 if (!test_conv_global_out && compose->orig_charset
5676 && strcmp(compose->orig_charset, CS_US_ASCII)) {
5677 out_codeset = compose->orig_charset;
5678 debug_print("failure; trying to convert to %s\n", out_codeset);
5679 test_conv_reply = conv_codeset_strdup(chars, src_codeset, out_codeset);
5682 if (!test_conv_global_out && !test_conv_reply) {
5684 out_codeset = CS_INTERNAL;
5685 debug_print("failure; finally using %s\n", out_codeset);
5687 g_free(test_conv_global_out);
5688 g_free(test_conv_reply);
5689 codeconv_set_strict(FALSE);
5692 if (encoding == ENC_UNKNOWN) {
5693 if (prefs_common.encoding_method == CTE_BASE64)
5694 encoding = ENC_BASE64;
5695 else if (prefs_common.encoding_method == CTE_QUOTED_PRINTABLE)
5696 encoding = ENC_QUOTED_PRINTABLE;
5697 else if (prefs_common.encoding_method == CTE_8BIT)
5698 encoding = ENC_8BIT;
5700 encoding = procmime_get_encoding_for_charset(out_codeset);
5703 debug_print("src encoding = %s, out encoding = %s, transfer encoding = %s\n",
5704 src_codeset, out_codeset, procmime_get_encoding_str(encoding));
5706 if (action == COMPOSE_WRITE_FOR_SEND) {
5707 codeconv_set_strict(TRUE);
5708 buf = conv_codeset_strdup(chars, src_codeset, out_codeset);
5709 codeconv_set_strict(FALSE);
5714 msg = g_strdup_printf(_("Can't convert the character encoding of the message \n"
5715 "to the specified %s charset.\n"
5716 "Send it as %s?"), out_codeset, src_codeset);
5717 aval = alertpanel_full(_("Error"), msg, GTK_STOCK_CANCEL,
5718 g_strconcat("+", _("_Send"), NULL), NULL, FALSE,
5719 NULL, ALERT_ERROR, G_ALERTDEFAULT);
5722 if (aval != G_ALERTALTERNATE) {
5727 out_codeset = src_codeset;
5733 out_codeset = src_codeset;
5738 if (prefs_common.rewrite_first_from && (encoding == ENC_8BIT || encoding == ENC_7BIT)) {
5739 if (!strncmp(buf, "From ", sizeof("From ")-1) ||
5740 strstr(buf, "\nFrom ") != NULL) {
5741 encoding = ENC_QUOTED_PRINTABLE;
5745 mimetext = procmime_mimeinfo_new();
5746 mimetext->content = MIMECONTENT_MEM;
5747 mimetext->tmp = TRUE; /* must free content later */
5748 /* dup'ed because procmime_encode_content can turn it into a tmpfile
5749 * and free the data, which we need later. */
5750 mimetext->data.mem = g_strdup(buf);
5751 mimetext->type = MIMETYPE_TEXT;
5752 mimetext->subtype = g_strdup("plain");
5753 g_hash_table_insert(mimetext->typeparameters, g_strdup("charset"),
5754 g_strdup(out_codeset));
5756 /* protect trailing spaces when signing message */
5757 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5758 privacy_system_can_sign(compose->privacy_system)) {
5759 encoding = ENC_QUOTED_PRINTABLE;
5763 debug_print("main text: %Id bytes encoded as %s in %d\n",
5765 debug_print("main text: %zd bytes encoded as %s in %d\n",
5767 strlen(buf), out_codeset, encoding);
5769 /* check for line length limit */
5770 if (action == COMPOSE_WRITE_FOR_SEND &&
5771 encoding != ENC_QUOTED_PRINTABLE && encoding != ENC_BASE64 &&
5772 check_line_length(buf, 1000, &line) < 0) {
5775 msg = g_strdup_printf
5776 (_("Line %d exceeds the line length limit (998 bytes).\n"
5777 "The contents of the message might be broken on the way to the delivery.\n"
5779 "Send it anyway?"), line + 1);
5780 aval = alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, GTK_STOCK_OK, NULL);
5782 if (aval != G_ALERTALTERNATE) {
5788 if (encoding != ENC_UNKNOWN)
5789 procmime_encode_content(mimetext, encoding);
5791 /* append attachment parts */
5792 if (compose_use_attach(compose) && attach_parts) {
5793 MimeInfo *mimempart;
5794 gchar *boundary = NULL;
5795 mimempart = procmime_mimeinfo_new();
5796 mimempart->content = MIMECONTENT_EMPTY;
5797 mimempart->type = MIMETYPE_MULTIPART;
5798 mimempart->subtype = g_strdup("mixed");
5802 boundary = generate_mime_boundary(NULL);
5803 } while (strstr(buf, boundary) != NULL);
5805 g_hash_table_insert(mimempart->typeparameters, g_strdup("boundary"),
5808 mimetext->disposition = DISPOSITIONTYPE_INLINE;
5810 g_node_append(mimempart->node, mimetext->node);
5811 g_node_append(mimemsg->node, mimempart->node);
5813 if (compose_add_attachments(compose, mimempart) < 0)
5816 g_node_append(mimemsg->node, mimetext->node);
5820 if (strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) != 0) {
5821 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
5822 /* extract name and address */
5823 if (strstr(spec, " <") && strstr(spec, ">")) {
5824 from_addr = g_strdup(strrchr(spec, '<')+1);
5825 *(strrchr(from_addr, '>')) = '\0';
5826 from_name = g_strdup(spec);
5827 *(strrchr(from_name, '<')) = '\0';
5834 /* sign message if sending */
5835 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5836 privacy_system_can_sign(compose->privacy_system))
5837 if (!privacy_sign(compose->privacy_system, mimemsg,
5838 compose->account, from_addr)) {
5846 if (compose->use_encryption) {
5847 if (compose->encdata != NULL &&
5848 strcmp(compose->encdata, "_DONT_ENCRYPT_")) {
5850 /* First, write an unencrypted copy and save it to outbox, if
5851 * user wants that. */
5852 if (compose->account->save_encrypted_as_clear_text) {
5853 debug_print("saving sent message unencrypted...\n");
5854 FILE *tmpfp = get_tmpfile_in_dir(get_mime_tmp_dir(), &tmp_enc_file);
5858 /* fp now points to a file with headers written,
5859 * let's make a copy. */
5861 content = file_read_stream_to_str(fp);
5863 str_write_to_file(content, tmp_enc_file);
5866 /* Now write the unencrypted body. */
5867 if ((tmpfp = g_fopen(tmp_enc_file, "a")) != NULL) {
5868 procmime_write_mimeinfo(mimemsg, tmpfp);
5871 outbox = folder_find_item_from_identifier(compose_get_save_to(compose));
5873 outbox = folder_get_default_outbox();
5875 procmsg_save_to_outbox(outbox, tmp_enc_file, TRUE);
5876 claws_unlink(tmp_enc_file);
5878 g_warning("Can't open file '%s'", tmp_enc_file);
5881 g_warning("couldn't get tempfile");
5884 if (!privacy_encrypt(compose->privacy_system, mimemsg, compose->encdata)) {
5885 debug_print("Couldn't encrypt mime structure: %s.\n",
5886 privacy_get_error());
5887 alertpanel_error(_("Couldn't encrypt the email: %s"),
5888 privacy_get_error());
5893 procmime_write_mimeinfo(mimemsg, fp);
5895 procmime_mimeinfo_free_all(&mimemsg);
5900 static gint compose_write_body_to_file(Compose *compose, const gchar *file)
5902 GtkTextBuffer *buffer;
5903 GtkTextIter start, end;
5908 if ((fp = g_fopen(file, "wb")) == NULL) {
5909 FILE_OP_ERROR(file, "fopen");
5913 /* chmod for security */
5914 if (change_file_mode_rw(fp, file) < 0) {
5915 FILE_OP_ERROR(file, "chmod");
5916 g_warning("can't change file mode");
5919 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5920 gtk_text_buffer_get_start_iter(buffer, &start);
5921 gtk_text_buffer_get_end_iter(buffer, &end);
5922 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5924 chars = conv_codeset_strdup
5925 (tmp, CS_INTERNAL, conv_get_locale_charset_str());
5934 len = strlen(chars);
5935 if (fwrite(chars, sizeof(gchar), len, fp) != len) {
5936 FILE_OP_ERROR(file, "fwrite");
5945 if (fclose(fp) == EOF) {
5946 FILE_OP_ERROR(file, "fclose");
5953 static gint compose_remove_reedit_target(Compose *compose, gboolean force)
5956 MsgInfo *msginfo = compose->targetinfo;
5958 cm_return_val_if_fail(compose->mode == COMPOSE_REEDIT, -1);
5959 if (!msginfo) return -1;
5961 if (!force && MSG_IS_LOCKED(msginfo->flags))
5964 item = msginfo->folder;
5965 cm_return_val_if_fail(item != NULL, -1);
5967 if (procmsg_msg_exist(msginfo) &&
5968 (folder_has_parent_of_type(item, F_QUEUE) ||
5969 folder_has_parent_of_type(item, F_DRAFT)
5970 || msginfo == compose->autosaved_draft)) {
5971 if (folder_item_remove_msg(item, msginfo->msgnum) < 0) {
5972 g_warning("can't remove the old message");
5975 debug_print("removed reedit target %d\n", msginfo->msgnum);
5982 static void compose_remove_draft(Compose *compose)
5985 MsgInfo *msginfo = compose->targetinfo;
5986 drafts = account_get_special_folder(compose->account, F_DRAFT);
5988 if (procmsg_msg_exist(msginfo)) {
5989 folder_item_remove_msg(drafts, msginfo->msgnum);
5994 gint compose_queue(Compose *compose, gint *msgnum, FolderItem **item, gchar **msgpath,
5995 gboolean remove_reedit_target)
5997 return compose_queue_sub (compose, msgnum, item, msgpath, FALSE, remove_reedit_target);
6000 static gboolean compose_warn_encryption(Compose *compose)
6002 const gchar *warning = privacy_get_encrypt_warning(compose->privacy_system);
6003 AlertValue val = G_ALERTALTERNATE;
6005 if (warning == NULL)
6008 val = alertpanel_full(_("Encryption warning"), warning,
6009 GTK_STOCK_CANCEL, g_strconcat("+", _("C_ontinue"), NULL), NULL,
6010 TRUE, NULL, ALERT_WARNING, G_ALERTALTERNATE);
6011 if (val & G_ALERTDISABLE) {
6012 val &= ~G_ALERTDISABLE;
6013 if (val == G_ALERTALTERNATE)
6014 privacy_inhibit_encrypt_warning(compose->privacy_system,
6018 if (val == G_ALERTALTERNATE) {
6025 static gint compose_queue_sub(Compose *compose, gint *msgnum, FolderItem **item,
6026 gchar **msgpath, gboolean perform_checks,
6027 gboolean remove_reedit_target)
6034 PrefsAccount *mailac = NULL, *newsac = NULL;
6035 gboolean err = FALSE;
6037 debug_print("queueing message...\n");
6038 cm_return_val_if_fail(compose->account != NULL, -1);
6040 if (compose_check_entries(compose, perform_checks) == FALSE) {
6041 if (compose->batch) {
6042 gtk_widget_show_all(compose->window);
6047 if (!compose->to_list && !compose->newsgroup_list) {
6048 g_warning("can't get recipient list.");
6052 if (compose->to_list) {
6053 if (compose->account->protocol != A_NNTP)
6054 mailac = compose->account;
6055 else if (cur_account && cur_account->protocol != A_NNTP)
6056 mailac = cur_account;
6057 else if (!(mailac = compose_current_mail_account())) {
6058 alertpanel_error(_("No account for sending mails available!"));
6063 if (compose->newsgroup_list) {
6064 if (compose->account->protocol == A_NNTP)
6065 newsac = compose->account;
6067 alertpanel_error(_("Selected account isn't NNTP: Posting is impossible."));
6072 /* write queue header */
6073 tmp = g_strdup_printf("%s%cqueue.%p%08x", get_tmp_dir(),
6074 G_DIR_SEPARATOR, compose, (guint) rand());
6075 debug_print("queuing to %s\n", tmp);
6076 if ((fp = g_fopen(tmp, "w+b")) == NULL) {
6077 FILE_OP_ERROR(tmp, "fopen");
6082 if (change_file_mode_rw(fp, tmp) < 0) {
6083 FILE_OP_ERROR(tmp, "chmod");
6084 g_warning("can't change file mode");
6087 /* queueing variables */
6088 err |= (fprintf(fp, "AF:\n") < 0);
6089 err |= (fprintf(fp, "NF:0\n") < 0);
6090 err |= (fprintf(fp, "PS:10\n") < 0);
6091 err |= (fprintf(fp, "SRH:1\n") < 0);
6092 err |= (fprintf(fp, "SFN:\n") < 0);
6093 err |= (fprintf(fp, "DSR:\n") < 0);
6095 err |= (fprintf(fp, "MID:<%s>\n", compose->msgid) < 0);
6097 err |= (fprintf(fp, "MID:\n") < 0);
6098 err |= (fprintf(fp, "CFG:\n") < 0);
6099 err |= (fprintf(fp, "PT:0\n") < 0);
6100 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
6101 err |= (fprintf(fp, "RQ:\n") < 0);
6103 err |= (fprintf(fp, "SSV:%s\n", mailac->smtp_server) < 0);
6105 err |= (fprintf(fp, "SSV:\n") < 0);
6107 err |= (fprintf(fp, "NSV:%s\n", newsac->nntp_server) < 0);
6109 err |= (fprintf(fp, "NSV:\n") < 0);
6110 err |= (fprintf(fp, "SSH:\n") < 0);
6111 /* write recepient list */
6112 if (compose->to_list) {
6113 err |= (fprintf(fp, "R:<%s>", (gchar *)compose->to_list->data) < 0);
6114 for (cur = compose->to_list->next; cur != NULL;
6116 err |= (fprintf(fp, ",<%s>", (gchar *)cur->data) < 0);
6117 err |= (fprintf(fp, "\n") < 0);
6119 /* write newsgroup list */
6120 if (compose->newsgroup_list) {
6121 err |= (fprintf(fp, "NG:") < 0);
6122 err |= (fprintf(fp, "%s", (gchar *)compose->newsgroup_list->data) < 0);
6123 for (cur = compose->newsgroup_list->next; cur != NULL; cur = cur->next)
6124 err |= (fprintf(fp, ",%s", (gchar *)cur->data) < 0);
6125 err |= (fprintf(fp, "\n") < 0);
6127 /* Sylpheed account IDs */
6129 err |= (fprintf(fp, "MAID:%d\n", mailac->account_id) < 0);
6131 err |= (fprintf(fp, "NAID:%d\n", newsac->account_id) < 0);
6134 if (compose->privacy_system != NULL) {
6135 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
6136 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
6137 if (compose->use_encryption) {
6138 if (!compose_warn_encryption(compose)) {
6144 if (mailac && mailac->encrypt_to_self) {
6145 GSList *tmp_list = g_slist_copy(compose->to_list);
6146 tmp_list = g_slist_append(tmp_list, compose->account->address);
6147 compose->encdata = privacy_get_encrypt_data(compose->privacy_system, tmp_list);
6148 g_slist_free(tmp_list);
6150 compose->encdata = privacy_get_encrypt_data(compose->privacy_system, compose->to_list);
6152 if (compose->encdata != NULL) {
6153 if (strcmp(compose->encdata, "_DONT_ENCRYPT_")) {
6154 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
6155 err |= (fprintf(fp, "X-Claws-Encrypt-Data:%s\n",
6156 compose->encdata) < 0);
6157 } /* else we finally dont want to encrypt */
6159 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
6160 /* and if encdata was null, it means there's been a problem in
6163 g_warning("failed to write queue message");
6172 /* Save copy folder */
6173 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
6174 gchar *savefolderid;
6176 savefolderid = compose_get_save_to(compose);
6177 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
6178 g_free(savefolderid);
6180 /* Save copy folder */
6181 if (compose->return_receipt) {
6182 err |= (fprintf(fp, "RRCPT:1\n") < 0);
6184 /* Message-ID of message replying to */
6185 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
6186 gchar *folderid = NULL;
6188 if (compose->replyinfo->folder)
6189 folderid = folder_item_get_identifier(compose->replyinfo->folder);
6190 if (folderid == NULL)
6191 folderid = g_strdup("NULL");
6193 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
6196 /* Message-ID of message forwarding to */
6197 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
6198 gchar *folderid = NULL;
6200 if (compose->fwdinfo->folder)
6201 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
6202 if (folderid == NULL)
6203 folderid = g_strdup("NULL");
6205 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
6209 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
6210 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
6212 /* end of headers */
6213 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
6215 if (compose->redirect_filename != NULL) {
6216 if (compose_redirect_write_to_file(compose, fp) < 0) {
6224 if ((result = compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_SEND, TRUE)) < 0) {
6228 return result - 1; /* -2 for a generic error, -3 for signing error, -4 for encoding */
6232 g_warning("failed to write queue message");
6238 if (fclose(fp) == EOF) {
6239 FILE_OP_ERROR(tmp, "fclose");
6245 if (item && *item) {
6248 queue = account_get_special_folder(compose->account, F_QUEUE);
6251 g_warning("can't find queue folder");
6256 folder_item_scan(queue);
6257 if ((num = folder_item_add_msg(queue, tmp, NULL, FALSE)) < 0) {
6258 g_warning("can't queue the message");
6264 if (msgpath == NULL) {
6270 if (compose->mode == COMPOSE_REEDIT && remove_reedit_target) {
6271 compose_remove_reedit_target(compose, FALSE);
6274 if ((msgnum != NULL) && (item != NULL)) {
6282 static int compose_add_attachments(Compose *compose, MimeInfo *parent)
6285 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
6288 gchar *type, *subtype;
6289 GtkTreeModel *model;
6292 model = gtk_tree_view_get_model(tree_view);
6294 if (!gtk_tree_model_get_iter_first(model, &iter))
6297 gtk_tree_model_get(model, &iter,
6301 if (!is_file_exist(ainfo->file)) {
6302 gchar *msg = g_strdup_printf(_("Attachment %s doesn't exist anymore. Ignore?"), ainfo->file);
6303 AlertValue val = alertpanel_full(_("Warning"), msg, _("Cancel sending"), _("Ignore attachment"),
6304 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
6306 if (val == G_ALERTDEFAULT) {
6311 if (g_stat(ainfo->file, &statbuf) < 0)
6314 mimepart = procmime_mimeinfo_new();
6315 mimepart->content = MIMECONTENT_FILE;
6316 mimepart->data.filename = g_strdup(ainfo->file);
6317 mimepart->tmp = FALSE; /* or we destroy our attachment */
6318 mimepart->offset = 0;
6319 mimepart->length = statbuf.st_size;
6321 type = g_strdup(ainfo->content_type);
6323 if (!strchr(type, '/')) {
6325 type = g_strdup("application/octet-stream");
6328 subtype = strchr(type, '/') + 1;
6329 *(subtype - 1) = '\0';
6330 mimepart->type = procmime_get_media_type(type);
6331 mimepart->subtype = g_strdup(subtype);
6334 if (mimepart->type == MIMETYPE_MESSAGE &&
6335 !g_ascii_strcasecmp(mimepart->subtype, "rfc822")) {
6336 mimepart->disposition = DISPOSITIONTYPE_INLINE;
6337 } else if (mimepart->type == MIMETYPE_TEXT) {
6338 if (!ainfo->name && g_ascii_strcasecmp(mimepart->subtype, "plain")) {
6339 /* Text parts with no name come from multipart/alternative
6340 * forwards. Make sure the recipient won't look at the
6341 * original HTML part by mistake. */
6342 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6343 ainfo->name = g_strdup_printf(_("Original %s part"),
6347 g_hash_table_insert(mimepart->typeparameters,
6348 g_strdup("charset"), g_strdup(ainfo->charset));
6350 if (ainfo->name && mimepart->type != MIMETYPE_MESSAGE) {
6351 if (mimepart->type == MIMETYPE_APPLICATION &&
6352 !strcmp2(mimepart->subtype, "octet-stream"))
6353 g_hash_table_insert(mimepart->typeparameters,
6354 g_strdup("name"), g_strdup(ainfo->name));
6355 g_hash_table_insert(mimepart->dispositionparameters,
6356 g_strdup("filename"), g_strdup(ainfo->name));
6357 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6360 if (mimepart->type == MIMETYPE_MESSAGE
6361 || mimepart->type == MIMETYPE_MULTIPART)
6362 ainfo->encoding = ENC_BINARY;
6363 else if (compose->use_signing) {
6364 if (ainfo->encoding == ENC_7BIT)
6365 ainfo->encoding = ENC_QUOTED_PRINTABLE;
6366 else if (ainfo->encoding == ENC_8BIT)
6367 ainfo->encoding = ENC_BASE64;
6372 procmime_encode_content(mimepart, ainfo->encoding);
6374 g_node_append(parent->node, mimepart->node);
6375 } while (gtk_tree_model_iter_next(model, &iter));
6380 static gchar *compose_quote_list_of_addresses(gchar *str)
6382 GSList *list = NULL, *item = NULL;
6383 gchar *qname = NULL, *faddr = NULL, *result = NULL;
6385 list = address_list_append_with_comments(list, str);
6386 for (item = list; item != NULL; item = item->next) {
6387 gchar *spec = item->data;
6388 gchar *endofname = strstr(spec, " <");
6389 if (endofname != NULL) {
6392 QUOTE_IF_REQUIRED_NORMAL(qname, spec, return NULL);
6393 qqname = escape_internal_quotes(qname, '"');
6395 if (*qname != *spec || qqname != qname) { /* has been quoted, compute new */
6396 gchar *addr = g_strdup(endofname);
6397 gchar *name = (qqname != qname)? qqname: g_strdup(qname);
6398 faddr = g_strconcat(name, addr, NULL);
6401 debug_print("new auto-quoted address: '%s'\n", faddr);
6405 result = g_strdup((faddr != NULL)? faddr: spec);
6407 result = g_strconcat(result,
6409 (faddr != NULL)? faddr: spec,
6412 if (faddr != NULL) {
6417 slist_free_strings_full(list);
6422 #define IS_IN_CUSTOM_HEADER(header) \
6423 (compose->account->add_customhdr && \
6424 custom_header_find(compose->account->customhdr_list, header) != NULL)
6426 static void compose_add_headerfield_from_headerlist(Compose *compose,
6428 const gchar *fieldname,
6429 const gchar *seperator)
6431 gchar *str, *fieldname_w_colon;
6432 gboolean add_field = FALSE;
6434 ComposeHeaderEntry *headerentry;
6435 const gchar *headerentryname;
6436 const gchar *trans_fieldname;
6439 if (IS_IN_CUSTOM_HEADER(fieldname))
6442 debug_print("Adding %s-fields\n", fieldname);
6444 fieldstr = g_string_sized_new(64);
6446 fieldname_w_colon = g_strconcat(fieldname, ":", NULL);
6447 trans_fieldname = prefs_common_translated_header_name(fieldname_w_colon);
6449 for (list = compose->header_list; list; list = list->next) {
6450 headerentry = ((ComposeHeaderEntry *)list->data);
6451 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
6453 if (!g_utf8_collate(trans_fieldname, headerentryname)) {
6454 gchar * ustr = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
6456 str = compose_quote_list_of_addresses(ustr);
6458 if (str != NULL && str[0] != '\0') {
6460 g_string_append(fieldstr, seperator);
6461 g_string_append(fieldstr, str);
6470 buf = g_new0(gchar, fieldstr->len * 4 + 256);
6471 compose_convert_header
6472 (compose, buf, fieldstr->len * 4 + 256, fieldstr->str,
6473 strlen(fieldname) + 2, TRUE);
6474 g_string_append_printf(header, "%s: %s\n", fieldname, buf);
6478 g_free(fieldname_w_colon);
6479 g_string_free(fieldstr, TRUE);
6484 static gchar *compose_get_manual_headers_info(Compose *compose)
6486 GString *sh_header = g_string_new(" ");
6488 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6490 for (list = compose->header_list; list; list = list->next) {
6491 ComposeHeaderEntry *headerentry;
6494 gchar *headername_wcolon;
6495 const gchar *headername_trans;
6497 gboolean standard_header = FALSE;
6499 headerentry = ((ComposeHeaderEntry *)list->data);
6501 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6503 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6508 if (!strstr(tmp, ":")) {
6509 headername_wcolon = g_strconcat(tmp, ":", NULL);
6510 headername = g_strdup(tmp);
6512 headername_wcolon = g_strdup(tmp);
6513 headername = g_strdup(strtok(tmp, ":"));
6517 string = std_headers;
6518 while (*string != NULL) {
6519 headername_trans = prefs_common_translated_header_name(*string);
6520 if (!strcmp(headername_trans, headername_wcolon))
6521 standard_header = TRUE;
6524 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
6525 g_string_append_printf(sh_header, "%s ", headername);
6527 g_free(headername_wcolon);
6529 g_string_truncate(sh_header, strlen(sh_header->str) - 1); /* remove last space */
6530 return g_string_free(sh_header, FALSE);
6533 static gchar *compose_get_header(Compose *compose)
6535 gchar buf[BUFFSIZE];
6536 const gchar *entry_str;
6540 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6542 gchar *from_name = NULL, *from_address = NULL;
6545 cm_return_val_if_fail(compose->account != NULL, NULL);
6546 cm_return_val_if_fail(compose->account->address != NULL, NULL);
6548 header = g_string_sized_new(64);
6551 if (prefs_common.hide_timezone)
6552 get_rfc822_date_hide_tz(buf, sizeof(buf));
6554 get_rfc822_date(buf, sizeof(buf));
6555 g_string_append_printf(header, "Date: %s\n", buf);
6559 if (compose->account->name && *compose->account->name) {
6561 QUOTE_IF_REQUIRED(buf, compose->account->name);
6562 tmp = g_strdup_printf("%s <%s>",
6563 buf, compose->account->address);
6565 tmp = g_strdup_printf("%s",
6566 compose->account->address);
6568 if (!strcmp(gtk_entry_get_text(GTK_ENTRY(compose->from_name)), tmp)
6569 || strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) == 0) {
6571 from_name = compose->account->name ? g_strdup(compose->account->name):NULL;
6572 from_address = g_strdup(compose->account->address);
6574 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
6575 /* extract name and address */
6576 if (strstr(spec, " <") && strstr(spec, ">")) {
6577 from_address = g_strdup(strrchr(spec, '<')+1);
6578 *(strrchr(from_address, '>')) = '\0';
6579 from_name = g_strdup(spec);
6580 *(strrchr(from_name, '<')) = '\0';
6583 from_address = g_strdup(spec);
6590 if (from_name && *from_name) {
6592 compose_convert_header
6593 (compose, buf, sizeof(buf), from_name,
6594 strlen("From: "), TRUE);
6595 QUOTE_IF_REQUIRED(name, buf);
6596 qname = escape_internal_quotes(name, '"');
6598 g_string_append_printf(header, "From: %s <%s>\n",
6599 qname, from_address);
6600 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To") &&
6601 compose->return_receipt) {
6602 compose_convert_header(compose, buf, sizeof(buf), from_name,
6603 strlen("Disposition-Notification-To: "),
6605 g_string_append_printf(header, "Disposition-Notification-To: %s <%s>\n", buf, from_address);
6610 g_string_append_printf(header, "From: %s\n", from_address);
6611 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To") &&
6612 compose->return_receipt)
6613 g_string_append_printf(header, "Disposition-Notification-To: %s\n", from_address);
6617 g_free(from_address);
6620 compose_add_headerfield_from_headerlist(compose, header, "To", ", ");
6623 compose_add_headerfield_from_headerlist(compose, header, "Newsgroups", ",");
6626 compose_add_headerfield_from_headerlist(compose, header, "Cc", ", ");
6630 * If this account is a NNTP account remove Bcc header from
6631 * message body since it otherwise will be publicly shown
6633 if (compose->account->protocol != A_NNTP)
6634 compose_add_headerfield_from_headerlist(compose, header, "Bcc", ", ");
6637 str = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
6639 if (*str != '\0' && !IS_IN_CUSTOM_HEADER("Subject")) {
6642 compose_convert_header(compose, buf, sizeof(buf), str,
6643 strlen("Subject: "), FALSE);
6644 g_string_append_printf(header, "Subject: %s\n", buf);
6650 if (compose->msgid != NULL && strlen(compose->msgid) > 0) {
6651 g_string_append_printf(header, "Message-ID: <%s>\n",
6655 if (compose->remove_references == FALSE) {
6657 if (compose->inreplyto && compose->to_list)
6658 g_string_append_printf(header, "In-Reply-To: <%s>\n", compose->inreplyto);
6661 if (compose->references)
6662 g_string_append_printf(header, "References: %s\n", compose->references);
6666 compose_add_headerfield_from_headerlist(compose, header, "Followup-To", ",");
6669 compose_add_headerfield_from_headerlist(compose, header, "Reply-To", ", ");
6672 if (compose->account->organization &&
6673 strlen(compose->account->organization) &&
6674 !IS_IN_CUSTOM_HEADER("Organization")) {
6675 compose_convert_header(compose, buf, sizeof(buf),
6676 compose->account->organization,
6677 strlen("Organization: "), FALSE);
6678 g_string_append_printf(header, "Organization: %s\n", buf);
6681 /* Program version and system info */
6682 if (compose->account->gen_xmailer &&
6683 g_slist_length(compose->to_list) && !IS_IN_CUSTOM_HEADER("X-Mailer") &&
6684 !compose->newsgroup_list) {
6685 g_string_append_printf(header, "X-Mailer: %s (GTK+ %d.%d.%d; %s)\n",
6687 gtk_major_version, gtk_minor_version, gtk_micro_version,
6690 if (compose->account->gen_xmailer &&
6691 g_slist_length(compose->newsgroup_list) && !IS_IN_CUSTOM_HEADER("X-Newsreader")) {
6692 g_string_append_printf(header, "X-Newsreader: %s (GTK+ %d.%d.%d; %s)\n",
6694 gtk_major_version, gtk_minor_version, gtk_micro_version,
6698 /* custom headers */
6699 if (compose->account->add_customhdr) {
6702 for (cur = compose->account->customhdr_list; cur != NULL;
6704 CustomHeader *chdr = (CustomHeader *)cur->data;
6706 if (custom_header_is_allowed(chdr->name)
6707 && chdr->value != NULL
6708 && *(chdr->value) != '\0') {
6709 compose_convert_header
6710 (compose, buf, sizeof(buf),
6712 strlen(chdr->name) + 2, FALSE);
6713 g_string_append_printf(header, "%s: %s\n", chdr->name, buf);
6718 /* Automatic Faces and X-Faces */
6719 if (get_account_xface (buf, sizeof(buf), compose->account->account_name) == 0) {
6720 g_string_append_printf(header, "X-Face: %s\n", buf);
6722 else if (get_default_xface (buf, sizeof(buf)) == 0) {
6723 g_string_append_printf(header, "X-Face: %s\n", buf);
6725 if (get_account_face (buf, sizeof(buf), compose->account->account_name) == 0) {
6726 g_string_append_printf(header, "Face: %s\n", buf);
6728 else if (get_default_face (buf, sizeof(buf)) == 0) {
6729 g_string_append_printf(header, "Face: %s\n", buf);
6733 switch (compose->priority) {
6734 case PRIORITY_HIGHEST: g_string_append_printf(header, "Importance: high\n"
6735 "X-Priority: 1 (Highest)\n");
6737 case PRIORITY_HIGH: g_string_append_printf(header, "Importance: high\n"
6738 "X-Priority: 2 (High)\n");
6740 case PRIORITY_NORMAL: break;
6741 case PRIORITY_LOW: g_string_append_printf(header, "Importance: low\n"
6742 "X-Priority: 4 (Low)\n");
6744 case PRIORITY_LOWEST: g_string_append_printf(header, "Importance: low\n"
6745 "X-Priority: 5 (Lowest)\n");
6747 default: debug_print("compose: priority unknown : %d\n",
6751 /* get special headers */
6752 for (list = compose->header_list; list; list = list->next) {
6753 ComposeHeaderEntry *headerentry;
6756 gchar *headername_wcolon;
6757 const gchar *headername_trans;
6760 gboolean standard_header = FALSE;
6762 headerentry = ((ComposeHeaderEntry *)list->data);
6764 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6766 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6771 if (!strstr(tmp, ":")) {
6772 headername_wcolon = g_strconcat(tmp, ":", NULL);
6773 headername = g_strdup(tmp);
6775 headername_wcolon = g_strdup(tmp);
6776 headername = g_strdup(strtok(tmp, ":"));
6780 entry_str = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
6781 Xstrdup_a(headervalue, entry_str, return NULL);
6782 subst_char(headervalue, '\r', ' ');
6783 subst_char(headervalue, '\n', ' ');
6784 string = std_headers;
6785 while (*string != NULL) {
6786 headername_trans = prefs_common_translated_header_name(*string);
6787 if (!strcmp(headername_trans, headername_wcolon))
6788 standard_header = TRUE;
6791 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
6792 g_string_append_printf(header, "%s %s\n", headername_wcolon, headervalue);
6795 g_free(headername_wcolon);
6799 g_string_free(header, FALSE);
6804 #undef IS_IN_CUSTOM_HEADER
6806 static void compose_convert_header(Compose *compose, gchar *dest, gint len, gchar *src,
6807 gint header_len, gboolean addr_field)
6809 gchar *tmpstr = NULL;
6810 const gchar *out_codeset = NULL;
6812 cm_return_if_fail(src != NULL);
6813 cm_return_if_fail(dest != NULL);
6815 if (len < 1) return;
6817 tmpstr = g_strdup(src);
6819 subst_char(tmpstr, '\n', ' ');
6820 subst_char(tmpstr, '\r', ' ');
6823 if (!g_utf8_validate(tmpstr, -1, NULL)) {
6824 gchar *mybuf = g_malloc(strlen(tmpstr)*2 +1);
6825 conv_localetodisp(mybuf, strlen(tmpstr)*2 +1, tmpstr);
6830 codeconv_set_strict(TRUE);
6831 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6832 conv_get_charset_str(compose->out_encoding));
6833 codeconv_set_strict(FALSE);
6835 if (!dest || *dest == '\0') {
6836 gchar *test_conv_global_out = NULL;
6837 gchar *test_conv_reply = NULL;
6839 /* automatic mode. be automatic. */
6840 codeconv_set_strict(TRUE);
6842 out_codeset = conv_get_outgoing_charset_str();
6844 debug_print("trying to convert to %s\n", out_codeset);
6845 test_conv_global_out = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6848 if (!test_conv_global_out && compose->orig_charset
6849 && strcmp(compose->orig_charset, CS_US_ASCII)) {
6850 out_codeset = compose->orig_charset;
6851 debug_print("failure; trying to convert to %s\n", out_codeset);
6852 test_conv_reply = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6855 if (!test_conv_global_out && !test_conv_reply) {
6857 out_codeset = CS_INTERNAL;
6858 debug_print("finally using %s\n", out_codeset);
6860 g_free(test_conv_global_out);
6861 g_free(test_conv_reply);
6862 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6864 codeconv_set_strict(FALSE);
6869 static void compose_add_to_addressbook_cb(GtkMenuItem *menuitem, gpointer user_data)
6873 cm_return_if_fail(user_data != NULL);
6875 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(user_data)));
6876 g_strstrip(address);
6877 if (*address != '\0') {
6878 gchar *name = procheader_get_fromname(address);
6879 extract_address(address);
6880 #ifndef USE_ALT_ADDRBOOK
6881 addressbook_add_contact(name, address, NULL, NULL);
6883 debug_print("%s: %s\n", name, address);
6884 if (addressadd_selection(name, address, NULL, NULL)) {
6885 debug_print( "addressbook_add_contact - added\n" );
6892 static void compose_entry_popup_extend(GtkEntry *entry, GtkMenu *menu, gpointer user_data)
6894 GtkWidget *menuitem;
6897 cm_return_if_fail(menu != NULL);
6898 cm_return_if_fail(GTK_IS_MENU_SHELL(menu));
6900 menuitem = gtk_separator_menu_item_new();
6901 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6902 gtk_widget_show(menuitem);
6904 menuitem = gtk_menu_item_new_with_mnemonic(_("Add to address _book"));
6905 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6907 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
6908 g_strstrip(address);
6909 if (*address == '\0') {
6910 gtk_widget_set_sensitive(GTK_WIDGET(menuitem), FALSE);
6913 g_signal_connect(G_OBJECT(menuitem), "activate",
6914 G_CALLBACK(compose_add_to_addressbook_cb), entry);
6915 gtk_widget_show(menuitem);
6918 void compose_add_extra_header(gchar *header, GtkListStore *model)
6921 if (strcmp(header, "")) {
6922 COMBOBOX_ADD(model, header, COMPOSE_TO);
6926 void compose_add_extra_header_entries(GtkListStore *model)
6930 gchar buf[BUFFSIZE];
6933 if (extra_headers == NULL) {
6934 exhrc = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, "extraheaderrc", NULL);
6935 if ((exh = g_fopen(exhrc, "rb")) == NULL) {
6936 debug_print("extra headers file not found\n");
6937 goto extra_headers_done;
6939 while (fgets(buf, BUFFSIZE, exh) != NULL) {
6940 lastc = strlen(buf) - 1; /* remove trailing control chars */
6941 while (lastc >= 0 && buf[lastc] != ':')
6942 buf[lastc--] = '\0';
6943 if (lastc > 0 && buf[0] != '#' && buf[lastc] == ':') {
6944 buf[lastc] = '\0'; /* remove trailing : for comparison */
6945 if (custom_header_is_allowed(buf)) {
6947 extra_headers = g_slist_prepend(extra_headers, g_strdup(buf));
6950 g_message("disallowed extra header line: %s\n", buf);
6954 g_message("invalid extra header line: %s\n", buf);
6960 extra_headers = g_slist_prepend(extra_headers, g_strdup("")); /* end of list */
6961 extra_headers = g_slist_reverse(extra_headers);
6963 g_slist_foreach(extra_headers, (GFunc)compose_add_extra_header, (gpointer)model);
6966 static void compose_create_header_entry(Compose *compose)
6968 gchar *headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6975 const gchar *header = NULL;
6976 ComposeHeaderEntry *headerentry;
6977 gboolean standard_header = FALSE;
6978 GtkListStore *model;
6981 headerentry = g_new0(ComposeHeaderEntry, 1);
6983 /* Combo box model */
6984 model = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_BOOLEAN);
6985 #if !GTK_CHECK_VERSION(2, 24, 0)
6986 combo = gtk_combo_box_entry_new_with_model(GTK_TREE_MODEL(model), 0);
6988 COMBOBOX_ADD(model, prefs_common_translated_header_name("To:"),
6990 COMBOBOX_ADD(model, prefs_common_translated_header_name("Cc:"),
6992 COMBOBOX_ADD(model, prefs_common_translated_header_name("Bcc:"),
6994 COMBOBOX_ADD(model, prefs_common_translated_header_name("Newsgroups:"),
6995 COMPOSE_NEWSGROUPS);
6996 COMBOBOX_ADD(model, prefs_common_translated_header_name("Reply-To:"),
6998 COMBOBOX_ADD(model, prefs_common_translated_header_name("Followup-To:"),
6999 COMPOSE_FOLLOWUPTO);
7000 compose_add_extra_header_entries(model);
7003 #if GTK_CHECK_VERSION(2, 24, 0)
7004 combo = gtk_combo_box_new_with_model_and_entry(GTK_TREE_MODEL(model));
7005 GtkCellRenderer *cell = gtk_cell_renderer_text_new();
7006 gtk_cell_renderer_set_alignment(cell, 0.0, 0.5);
7007 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), cell, TRUE);
7008 gtk_combo_box_set_entry_text_column(GTK_COMBO_BOX(combo), 0);
7010 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
7011 g_signal_connect(G_OBJECT(gtk_bin_get_child(GTK_BIN(combo))), "grab_focus",
7012 G_CALLBACK(compose_grab_focus_cb), compose);
7013 gtk_widget_show(combo);
7015 /* Putting only the combobox child into focus chain of its parent causes
7016 * the parent to be skipped when changing focus via Tab or Shift+Tab.
7017 * This eliminates need to pres Tab twice in order to really get from the
7018 * combobox to next widget. */
7020 l = g_list_prepend(l, gtk_bin_get_child(GTK_BIN(combo)));
7021 gtk_container_set_focus_chain(GTK_CONTAINER(combo), l);
7024 gtk_table_attach(GTK_TABLE(compose->header_table), combo, 0, 1,
7025 compose->header_nextrow, compose->header_nextrow+1,
7026 GTK_SHRINK, GTK_FILL, 0, 0);
7027 if (compose->header_last && (compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN)) {
7028 const gchar *last_header_entry = gtk_entry_get_text(
7029 GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
7031 while (*string != NULL) {
7032 if (!strcmp(prefs_common_translated_header_name(*string), last_header_entry))
7033 standard_header = TRUE;
7036 if (standard_header)
7037 header = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
7039 if (!compose->header_last || !standard_header) {
7040 switch(compose->account->protocol) {
7042 header = prefs_common_translated_header_name("Newsgroups:");
7045 header = prefs_common_translated_header_name("To:");
7050 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((combo)))), header);
7052 gtk_editable_set_editable(
7053 GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((combo)))),
7054 prefs_common.type_any_header);
7056 g_signal_connect_after(G_OBJECT(gtk_bin_get_child(GTK_BIN((combo)))), "grab_focus",
7057 G_CALLBACK(compose_grab_focus_cb), compose);
7059 /* Entry field with cleanup button */
7060 button = gtk_button_new();
7061 gtk_button_set_image(GTK_BUTTON(button),
7062 gtk_image_new_from_stock(GTK_STOCK_CLEAR, GTK_ICON_SIZE_MENU));
7063 gtk_widget_show(button);
7064 CLAWS_SET_TIP(button,
7065 _("Delete entry contents"));
7066 entry = gtk_entry_new();
7067 gtk_widget_show(entry);
7068 CLAWS_SET_TIP(entry,
7069 _("Use <tab> to autocomplete from addressbook"));
7070 hbox = gtk_hbox_new (FALSE, 0);
7071 gtk_widget_show(hbox);
7072 gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, TRUE, 0);
7073 gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
7074 gtk_table_attach(GTK_TABLE(compose->header_table), hbox, 1, 2,
7075 compose->header_nextrow, compose->header_nextrow+1,
7076 GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
7078 g_signal_connect(G_OBJECT(entry), "key-press-event",
7079 G_CALLBACK(compose_headerentry_key_press_event_cb),
7081 g_signal_connect(G_OBJECT(entry), "changed",
7082 G_CALLBACK(compose_headerentry_changed_cb),
7084 g_signal_connect_after(G_OBJECT(entry), "grab_focus",
7085 G_CALLBACK(compose_grab_focus_cb), compose);
7087 g_signal_connect(G_OBJECT(button), "clicked",
7088 G_CALLBACK(compose_headerentry_button_clicked_cb),
7092 gtk_drag_dest_set(entry, GTK_DEST_DEFAULT_ALL, compose_mime_types,
7093 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7094 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7095 g_signal_connect(G_OBJECT(entry), "drag_data_received",
7096 G_CALLBACK(compose_header_drag_received_cb),
7098 g_signal_connect(G_OBJECT(entry), "drag-drop",
7099 G_CALLBACK(compose_drag_drop),
7101 g_signal_connect(G_OBJECT(entry), "populate-popup",
7102 G_CALLBACK(compose_entry_popup_extend),
7105 address_completion_register_entry(GTK_ENTRY(entry), TRUE);
7107 headerentry->compose = compose;
7108 headerentry->combo = combo;
7109 headerentry->entry = entry;
7110 headerentry->button = button;
7111 headerentry->hbox = hbox;
7112 headerentry->headernum = compose->header_nextrow;
7113 headerentry->type = PREF_NONE;
7115 compose->header_nextrow++;
7116 compose->header_last = headerentry;
7117 compose->header_list =
7118 g_slist_append(compose->header_list,
7122 static void compose_add_header_entry(Compose *compose, const gchar *header,
7123 gchar *text, ComposePrefType pref_type)
7125 ComposeHeaderEntry *last_header = compose->header_last;
7126 gchar *tmp = g_strdup(text), *email;
7127 gboolean replyto_hdr;
7129 replyto_hdr = (!strcasecmp(header,
7130 prefs_common_translated_header_name("Reply-To:")) ||
7132 prefs_common_translated_header_name("Followup-To:")) ||
7134 prefs_common_translated_header_name("In-Reply-To:")));
7136 extract_address(tmp);
7137 email = g_utf8_strdown(tmp, -1);
7139 if (replyto_hdr == FALSE &&
7140 g_hash_table_lookup(compose->email_hashtable, email) != NULL)
7142 debug_print("Ignoring duplicate address - %s %s, pref_type: %d\n",
7143 header, text, (gint) pref_type);
7149 if (!strcasecmp(header, prefs_common_translated_header_name("In-Reply-To:")))
7150 gtk_entry_set_text(GTK_ENTRY(
7151 gtk_bin_get_child(GTK_BIN(last_header->combo))), header);
7153 combobox_select_by_text(GTK_COMBO_BOX(last_header->combo), header);
7154 gtk_entry_set_text(GTK_ENTRY(last_header->entry), text);
7155 last_header->type = pref_type;
7157 if (replyto_hdr == FALSE)
7158 g_hash_table_insert(compose->email_hashtable, email,
7159 GUINT_TO_POINTER(1));
7166 static void compose_destroy_headerentry(Compose *compose,
7167 ComposeHeaderEntry *headerentry)
7169 gchar *text = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
7172 extract_address(text);
7173 email = g_utf8_strdown(text, -1);
7174 g_hash_table_remove(compose->email_hashtable, email);
7178 gtk_widget_destroy(headerentry->combo);
7179 gtk_widget_destroy(headerentry->entry);
7180 gtk_widget_destroy(headerentry->button);
7181 gtk_widget_destroy(headerentry->hbox);
7182 g_free(headerentry);
7185 static void compose_remove_header_entries(Compose *compose)
7188 for (list = compose->header_list; list; list = list->next)
7189 compose_destroy_headerentry(compose, (ComposeHeaderEntry *)list->data);
7191 compose->header_last = NULL;
7192 g_slist_free(compose->header_list);
7193 compose->header_list = NULL;
7194 compose->header_nextrow = 1;
7195 compose_create_header_entry(compose);
7198 static GtkWidget *compose_create_header(Compose *compose)
7200 GtkWidget *from_optmenu_hbox;
7201 GtkWidget *header_table_main;
7202 GtkWidget *header_scrolledwin;
7203 GtkWidget *header_table;
7205 /* parent with account selection and from header */
7206 header_table_main = gtk_table_new(2, 2, FALSE);
7207 gtk_widget_show(header_table_main);
7208 gtk_container_set_border_width(GTK_CONTAINER(header_table_main), BORDER_WIDTH);
7210 from_optmenu_hbox = compose_account_option_menu_create(compose);
7211 gtk_table_attach(GTK_TABLE(header_table_main), from_optmenu_hbox,
7212 0, 2, 0, 1, GTK_EXPAND | GTK_FILL, GTK_SHRINK, 0, 0);
7214 /* child with header labels and entries */
7215 header_scrolledwin = gtk_scrolled_window_new(NULL, NULL);
7216 gtk_widget_show(header_scrolledwin);
7217 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
7219 header_table = gtk_table_new(2, 2, FALSE);
7220 gtk_widget_show(header_table);
7221 gtk_container_set_border_width(GTK_CONTAINER(header_table), 0);
7222 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin), header_table);
7223 gtk_container_set_focus_vadjustment(GTK_CONTAINER(header_table),
7224 gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(header_scrolledwin)));
7225 gtk_viewport_set_shadow_type(GTK_VIEWPORT(gtk_bin_get_child(GTK_BIN(header_scrolledwin))), GTK_SHADOW_NONE);
7227 gtk_table_attach(GTK_TABLE(header_table_main), header_scrolledwin,
7228 0, 2, 1, 2, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 2);
7230 compose->header_table = header_table;
7231 compose->header_list = NULL;
7232 compose->header_nextrow = 0;
7234 compose_create_header_entry(compose);
7236 compose->table = NULL;
7238 return header_table_main;
7241 static gboolean popup_attach_button_pressed(GtkWidget *widget, gpointer data)
7243 Compose *compose = (Compose *)data;
7244 GdkEventButton event;
7247 event.time = gtk_get_current_event_time();
7249 return attach_button_pressed(compose->attach_clist, &event, compose);
7252 static GtkWidget *compose_create_attach(Compose *compose)
7254 GtkWidget *attach_scrwin;
7255 GtkWidget *attach_clist;
7257 GtkListStore *store;
7258 GtkCellRenderer *renderer;
7259 GtkTreeViewColumn *column;
7260 GtkTreeSelection *selection;
7262 /* attachment list */
7263 attach_scrwin = gtk_scrolled_window_new(NULL, NULL);
7264 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(attach_scrwin),
7265 GTK_POLICY_AUTOMATIC,
7266 GTK_POLICY_AUTOMATIC);
7267 gtk_widget_set_size_request(attach_scrwin, -1, 80);
7269 store = gtk_list_store_new(N_ATTACH_COLS,
7275 G_TYPE_AUTO_POINTER,
7277 attach_clist = GTK_WIDGET(gtk_tree_view_new_with_model
7278 (GTK_TREE_MODEL(store)));
7279 gtk_container_add(GTK_CONTAINER(attach_scrwin), attach_clist);
7280 g_object_unref(store);
7282 renderer = gtk_cell_renderer_text_new();
7283 column = gtk_tree_view_column_new_with_attributes
7284 (_("Mime type"), renderer, "text",
7285 COL_MIMETYPE, NULL);
7286 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7288 renderer = gtk_cell_renderer_text_new();
7289 column = gtk_tree_view_column_new_with_attributes
7290 (_("Size"), renderer, "text",
7292 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7294 renderer = gtk_cell_renderer_text_new();
7295 column = gtk_tree_view_column_new_with_attributes
7296 (_("Name"), renderer, "text",
7298 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7300 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(attach_clist),
7301 prefs_common.use_stripes_everywhere);
7302 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(attach_clist));
7303 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
7305 g_signal_connect(G_OBJECT(attach_clist), "row_activated",
7306 G_CALLBACK(attach_selected), compose);
7307 g_signal_connect(G_OBJECT(attach_clist), "button_press_event",
7308 G_CALLBACK(attach_button_pressed), compose);
7309 g_signal_connect(G_OBJECT(attach_clist), "popup-menu",
7310 G_CALLBACK(popup_attach_button_pressed), compose);
7311 g_signal_connect(G_OBJECT(attach_clist), "key_press_event",
7312 G_CALLBACK(attach_key_pressed), compose);
7315 gtk_drag_dest_set(attach_clist,
7316 GTK_DEST_DEFAULT_ALL, compose_mime_types,
7317 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7318 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7319 g_signal_connect(G_OBJECT(attach_clist), "drag_data_received",
7320 G_CALLBACK(compose_attach_drag_received_cb),
7322 g_signal_connect(G_OBJECT(attach_clist), "drag-drop",
7323 G_CALLBACK(compose_drag_drop),
7326 compose->attach_scrwin = attach_scrwin;
7327 compose->attach_clist = attach_clist;
7329 return attach_scrwin;
7332 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose);
7333 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose);
7335 static GtkWidget *compose_create_others(Compose *compose)
7338 GtkWidget *savemsg_checkbtn;
7339 GtkWidget *savemsg_combo;
7340 GtkWidget *savemsg_select;
7343 gchar *folderidentifier;
7345 /* Table for settings */
7346 table = gtk_table_new(3, 1, FALSE);
7347 gtk_container_set_border_width(GTK_CONTAINER(table), BORDER_WIDTH);
7348 gtk_widget_show(table);
7349 gtk_table_set_row_spacings(GTK_TABLE(table), VSPACING_NARROW);
7352 /* Save Message to folder */
7353 savemsg_checkbtn = gtk_check_button_new_with_label(_("Save Message to "));
7354 gtk_widget_show(savemsg_checkbtn);
7355 gtk_table_attach(GTK_TABLE(table), savemsg_checkbtn, 0, 1, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7356 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7357 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), prefs_common.savemsg);
7359 g_signal_connect(G_OBJECT(savemsg_checkbtn), "toggled",
7360 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
7362 #if !GTK_CHECK_VERSION(2, 24, 0)
7363 savemsg_combo = gtk_combo_box_entry_new_text();
7365 savemsg_combo = gtk_combo_box_text_new_with_entry();
7367 compose->savemsg_checkbtn = savemsg_checkbtn;
7368 compose->savemsg_combo = savemsg_combo;
7369 gtk_widget_show(savemsg_combo);
7371 if (prefs_common.compose_save_to_history)
7372 #if !GTK_CHECK_VERSION(2, 24, 0)
7373 combobox_set_popdown_strings(GTK_COMBO_BOX(savemsg_combo),
7374 prefs_common.compose_save_to_history);
7376 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(savemsg_combo),
7377 prefs_common.compose_save_to_history);
7379 gtk_table_attach(GTK_TABLE(table), savemsg_combo, 1, 2, rowcount, rowcount + 1, GTK_FILL|GTK_EXPAND, GTK_SHRINK, 0, 0);
7380 gtk_widget_set_sensitive(GTK_WIDGET(savemsg_combo), prefs_common.savemsg);
7381 g_signal_connect_after(G_OBJECT(savemsg_combo), "grab_focus",
7382 G_CALLBACK(compose_grab_focus_cb), compose);
7383 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7384 folderidentifier = folder_item_get_identifier(account_get_special_folder
7385 (compose->account, F_OUTBOX));
7386 compose_set_save_to(compose, folderidentifier);
7387 g_free(folderidentifier);
7390 savemsg_select = gtkut_get_browse_file_btn(_("_Browse"));
7391 gtk_widget_show(savemsg_select);
7392 gtk_table_attach(GTK_TABLE(table), savemsg_select, 2, 3, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7393 g_signal_connect(G_OBJECT(savemsg_select), "clicked",
7394 G_CALLBACK(compose_savemsg_select_cb),
7400 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose)
7402 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo),
7403 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn)));
7406 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose)
7411 dest = foldersel_folder_sel(NULL, FOLDER_SEL_COPY, NULL, FALSE);
7414 path = folder_item_get_identifier(dest);
7416 compose_set_save_to(compose, path);
7420 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry, gboolean wrap,
7421 GdkAtom clip, GtkTextIter *insert_place);
7424 static gboolean text_clicked(GtkWidget *text, GdkEventButton *event,
7428 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
7430 if (event->button == 3) {
7432 GtkTextIter sel_start, sel_end;
7433 gboolean stuff_selected;
7435 /* move the cursor to allow GtkAspell to check the word
7436 * under the mouse */
7437 if (event->x && event->y) {
7438 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7439 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7441 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7444 GtkTextMark *mark = gtk_text_buffer_get_insert(buffer);
7445 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7448 stuff_selected = gtk_text_buffer_get_selection_bounds(
7450 &sel_start, &sel_end);
7452 gtk_text_buffer_place_cursor (buffer, &iter);
7453 /* reselect stuff */
7455 && gtk_text_iter_in_range(&iter, &sel_start, &sel_end)) {
7456 gtk_text_buffer_select_range(buffer,
7457 &sel_start, &sel_end);
7459 return FALSE; /* pass the event so that the right-click goes through */
7462 if (event->button == 2) {
7467 /* get the middle-click position to paste at the correct place */
7468 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7469 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7471 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7474 entry_paste_clipboard(compose, text,
7475 prefs_common.linewrap_pastes,
7476 GDK_SELECTION_PRIMARY, &iter);
7484 static void compose_spell_menu_changed(void *data)
7486 Compose *compose = (Compose *)data;
7488 GtkWidget *menuitem;
7489 GtkWidget *parent_item;
7490 GtkMenu *menu = GTK_MENU(gtk_menu_new());
7493 if (compose->gtkaspell == NULL)
7496 parent_item = gtk_ui_manager_get_widget(compose->ui_manager,
7497 "/Menu/Spelling/Options");
7499 /* setting the submenu removes /Spelling/Options from the factory
7500 * so we need to save it */
7502 if (parent_item == NULL) {
7503 parent_item = compose->aspell_options_menu;
7504 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), NULL);
7506 compose->aspell_options_menu = parent_item;
7508 spell_menu = gtkaspell_make_config_menu(compose->gtkaspell);
7510 spell_menu = g_slist_reverse(spell_menu);
7511 for (items = spell_menu;
7512 items; items = items->next) {
7513 menuitem = GTK_WIDGET(GTK_MENU_ITEM(items->data));
7514 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), GTK_WIDGET(menuitem));
7515 gtk_widget_show(GTK_WIDGET(menuitem));
7517 g_slist_free(spell_menu);
7519 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), GTK_WIDGET(menu));
7520 gtk_widget_show(parent_item);
7523 static void compose_dict_changed(void *data)
7525 Compose *compose = (Compose *) data;
7527 if(!compose->gtkaspell)
7529 if(compose->gtkaspell->recheck_when_changing_dict == FALSE)
7532 gtkaspell_highlight_all(compose->gtkaspell);
7533 claws_spell_entry_recheck_all(CLAWS_SPELL_ENTRY(compose->subject_entry));
7537 static gboolean compose_popup_menu(GtkWidget *widget, gpointer data)
7539 Compose *compose = (Compose *)data;
7540 GdkEventButton event;
7543 event.time = gtk_get_current_event_time();
7547 return text_clicked(compose->text, &event, compose);
7550 static gboolean compose_force_window_origin = TRUE;
7551 static Compose *compose_create(PrefsAccount *account,
7560 GtkWidget *handlebox;
7562 GtkWidget *notebook;
7564 GtkWidget *attach_hbox;
7565 GtkWidget *attach_lab1;
7566 GtkWidget *attach_lab2;
7571 GtkWidget *subject_hbox;
7572 GtkWidget *subject_frame;
7573 GtkWidget *subject_entry;
7577 GtkWidget *edit_vbox;
7578 GtkWidget *ruler_hbox;
7580 GtkWidget *scrolledwin;
7582 GtkTextBuffer *buffer;
7583 GtkClipboard *clipboard;
7585 UndoMain *undostruct;
7587 GtkWidget *popupmenu;
7588 GtkWidget *tmpl_menu;
7589 GtkActionGroup *action_group = NULL;
7592 GtkAspell * gtkaspell = NULL;
7595 static GdkGeometry geometry;
7597 cm_return_val_if_fail(account != NULL, NULL);
7599 gtkut_convert_int_to_gdk_color(prefs_common.default_header_bgcolor,
7600 &default_header_bgcolor);
7601 gtkut_convert_int_to_gdk_color(prefs_common.default_header_color,
7602 &default_header_color);
7604 debug_print("Creating compose window...\n");
7605 compose = g_new0(Compose, 1);
7607 compose->batch = batch;
7608 compose->account = account;
7609 compose->folder = folder;
7611 compose->mutex = cm_mutex_new();
7612 compose->set_cursor_pos = -1;
7614 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose");
7616 gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
7617 gtk_widget_set_size_request(window, prefs_common.compose_width,
7618 prefs_common.compose_height);
7620 if (!geometry.max_width) {
7621 geometry.max_width = gdk_screen_width();
7622 geometry.max_height = gdk_screen_height();
7625 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7626 &geometry, GDK_HINT_MAX_SIZE);
7627 if (!geometry.min_width) {
7628 geometry.min_width = 600;
7629 geometry.min_height = 440;
7631 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7632 &geometry, GDK_HINT_MIN_SIZE);
7634 #ifndef GENERIC_UMPC
7635 if (compose_force_window_origin)
7636 gtk_window_move(GTK_WINDOW(window), prefs_common.compose_x,
7637 prefs_common.compose_y);
7639 g_signal_connect(G_OBJECT(window), "delete_event",
7640 G_CALLBACK(compose_delete_cb), compose);
7641 MANAGE_WINDOW_SIGNALS_CONNECT(window);
7642 gtk_widget_realize(window);
7644 gtkut_widget_set_composer_icon(window);
7646 vbox = gtk_vbox_new(FALSE, 0);
7647 gtk_container_add(GTK_CONTAINER(window), vbox);
7649 compose->ui_manager = gtk_ui_manager_new();
7650 action_group = cm_menu_create_action_group_full(compose->ui_manager,"Menu", compose_entries,
7651 G_N_ELEMENTS(compose_entries), (gpointer)compose);
7652 gtk_action_group_add_toggle_actions(action_group, compose_toggle_entries,
7653 G_N_ELEMENTS(compose_toggle_entries), (gpointer)compose);
7654 gtk_action_group_add_radio_actions(action_group, compose_radio_rm_entries,
7655 G_N_ELEMENTS(compose_radio_rm_entries), COMPOSE_REPLY, G_CALLBACK(compose_reply_change_mode_cb), (gpointer)compose);
7656 gtk_action_group_add_radio_actions(action_group, compose_radio_prio_entries,
7657 G_N_ELEMENTS(compose_radio_prio_entries), PRIORITY_NORMAL, G_CALLBACK(compose_set_priority_cb), (gpointer)compose);
7658 gtk_action_group_add_radio_actions(action_group, compose_radio_enc_entries,
7659 G_N_ELEMENTS(compose_radio_enc_entries), C_AUTO, G_CALLBACK(compose_set_encoding_cb), (gpointer)compose);
7661 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Menu", NULL, GTK_UI_MANAGER_MENUBAR)
7663 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Message", "Message", GTK_UI_MANAGER_MENU)
7664 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Edit", "Edit", GTK_UI_MANAGER_MENU)
7666 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Spelling", "Spelling", GTK_UI_MANAGER_MENU)
7668 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Options", "Options", GTK_UI_MANAGER_MENU)
7669 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Tools", "Tools", GTK_UI_MANAGER_MENU)
7670 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Help", "Help", GTK_UI_MANAGER_MENU)
7673 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Send", "Message/Send", GTK_UI_MANAGER_MENUITEM)
7674 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "SendLater", "Message/SendLater", GTK_UI_MANAGER_MENUITEM)
7675 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator1", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7676 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "AttachFile", "Message/AttachFile", GTK_UI_MANAGER_MENUITEM)
7677 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertFile", "Message/InsertFile", GTK_UI_MANAGER_MENUITEM)
7678 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertSig", "Message/InsertSig", GTK_UI_MANAGER_MENUITEM)
7679 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "ReplaceSig", "Message/ReplaceSig", GTK_UI_MANAGER_MENUITEM)
7680 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator2", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7681 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Save", "Message/Save", GTK_UI_MANAGER_MENUITEM)
7682 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator3", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7683 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Print", "Message/Print", GTK_UI_MANAGER_MENUITEM)
7684 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator4", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7685 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Close", "Message/Close", GTK_UI_MANAGER_MENUITEM)
7688 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Undo", "Edit/Undo", GTK_UI_MANAGER_MENUITEM)
7689 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Redo", "Edit/Redo", GTK_UI_MANAGER_MENUITEM)
7690 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator1", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7692 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Cut", "Edit/Cut", GTK_UI_MANAGER_MENUITEM)
7693 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Copy", "Edit/Copy", GTK_UI_MANAGER_MENUITEM)
7694 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Paste", "Edit/Paste", GTK_UI_MANAGER_MENUITEM)
7696 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SpecialPaste", "Edit/SpecialPaste", GTK_UI_MANAGER_MENU)
7697 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "AsQuotation", "Edit/SpecialPaste/AsQuotation", GTK_UI_MANAGER_MENUITEM)
7698 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Wrapped", "Edit/SpecialPaste/Wrapped", GTK_UI_MANAGER_MENUITEM)
7699 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Unwrapped", "Edit/SpecialPaste/Unwrapped", GTK_UI_MANAGER_MENUITEM)
7701 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SelectAll", "Edit/SelectAll", GTK_UI_MANAGER_MENUITEM)
7703 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Advanced", "Edit/Advanced", GTK_UI_MANAGER_MENU)
7704 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackChar", "Edit/Advanced/BackChar", GTK_UI_MANAGER_MENUITEM)
7705 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwChar", "Edit/Advanced/ForwChar", GTK_UI_MANAGER_MENUITEM)
7706 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackWord", "Edit/Advanced/BackWord", GTK_UI_MANAGER_MENUITEM)
7707 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwWord", "Edit/Advanced/ForwWord", GTK_UI_MANAGER_MENUITEM)
7708 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BegLine", "Edit/Advanced/BegLine", GTK_UI_MANAGER_MENUITEM)
7709 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "EndLine", "Edit/Advanced/EndLine", GTK_UI_MANAGER_MENUITEM)
7710 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "PrevLine", "Edit/Advanced/PrevLine", GTK_UI_MANAGER_MENUITEM)
7711 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "NextLine", "Edit/Advanced/NextLine", GTK_UI_MANAGER_MENUITEM)
7712 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackChar", "Edit/Advanced/DelBackChar", GTK_UI_MANAGER_MENUITEM)
7713 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwChar", "Edit/Advanced/DelForwChar", GTK_UI_MANAGER_MENUITEM)
7714 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackWord", "Edit/Advanced/DelBackWord", GTK_UI_MANAGER_MENUITEM)
7715 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwWord", "Edit/Advanced/DelForwWord", GTK_UI_MANAGER_MENUITEM)
7716 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelLine", "Edit/Advanced/DelLine", GTK_UI_MANAGER_MENUITEM)
7717 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelEndLine", "Edit/Advanced/DelEndLine", GTK_UI_MANAGER_MENUITEM)
7719 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator2", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7721 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Find", "Edit/Find", GTK_UI_MANAGER_MENUITEM)
7722 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapPara", "Edit/WrapPara", GTK_UI_MANAGER_MENUITEM)
7723 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapAllLines", "Edit/WrapAllLines", GTK_UI_MANAGER_MENUITEM)
7724 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoWrap", "Edit/AutoWrap", GTK_UI_MANAGER_MENUITEM)
7725 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoIndent", "Edit/AutoIndent", GTK_UI_MANAGER_MENUITEM)
7727 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator3", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7729 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "ExtEditor", "Edit/ExtEditor", GTK_UI_MANAGER_MENUITEM)
7733 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckAllSel", "Spelling/CheckAllSel", GTK_UI_MANAGER_MENUITEM)
7734 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "HighlightAll", "Spelling/HighlightAll", GTK_UI_MANAGER_MENUITEM)
7735 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckBackwards", "Spelling/CheckBackwards", GTK_UI_MANAGER_MENUITEM)
7736 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "ForwardNext", "Spelling/ForwardNext", GTK_UI_MANAGER_MENUITEM)
7737 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Separator1", "Spelling/---", GTK_UI_MANAGER_SEPARATOR)
7738 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Options", "Spelling/Options", GTK_UI_MANAGER_MENU)
7742 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "ReplyMode", "Options/ReplyMode", GTK_UI_MANAGER_MENU)
7743 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Normal", "Options/ReplyMode/Normal", GTK_UI_MANAGER_MENUITEM)
7744 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "All", "Options/ReplyMode/All", GTK_UI_MANAGER_MENUITEM)
7745 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Sender", "Options/ReplyMode/Sender", GTK_UI_MANAGER_MENUITEM)
7746 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "List", "Options/ReplyMode/List", GTK_UI_MANAGER_MENUITEM)
7748 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator1", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7749 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "PrivacySystem", "Options/PrivacySystem", GTK_UI_MANAGER_MENU)
7750 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/PrivacySystem", "PlaceHolder", "Options/PrivacySystem/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7751 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Sign", "Options/Sign", GTK_UI_MANAGER_MENUITEM)
7752 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encrypt", "Options/Encrypt", GTK_UI_MANAGER_MENUITEM)
7755 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator2", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7756 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Priority", "Options/Priority", GTK_UI_MANAGER_MENU)
7757 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Highest", "Options/Priority/Highest", GTK_UI_MANAGER_MENUITEM)
7758 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "High", "Options/Priority/High", GTK_UI_MANAGER_MENUITEM)
7759 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Normal", "Options/Priority/Normal", GTK_UI_MANAGER_MENUITEM)
7760 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Low", "Options/Priority/Low", GTK_UI_MANAGER_MENUITEM)
7761 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Lowest", "Options/Priority/Lowest", GTK_UI_MANAGER_MENUITEM)
7763 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator3", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7764 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RequestRetRcpt", "Options/RequestRetRcpt", GTK_UI_MANAGER_MENUITEM)
7765 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator4", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7766 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RemoveReferences", "Options/RemoveReferences", GTK_UI_MANAGER_MENUITEM)
7767 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator5", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7769 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encoding", "Options/Encoding", GTK_UI_MANAGER_MENU)
7771 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_AUTO, "Options/Encoding/"CS_AUTO, GTK_UI_MANAGER_MENUITEM)
7772 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator1", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7773 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_US_ASCII, "Options/Encoding/"CS_US_ASCII, GTK_UI_MANAGER_MENUITEM)
7774 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_UTF_8, "Options/Encoding/"CS_UTF_8, GTK_UI_MANAGER_MENUITEM)
7775 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator2", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7777 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Western", "Options/Encoding/Western", GTK_UI_MANAGER_MENU)
7778 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)
7779 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)
7780 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Western", CS_WINDOWS_1252, "Options/Encoding/Western/"CS_WINDOWS_1252, GTK_UI_MANAGER_MENUITEM)
7782 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_2, "Options/Encoding/"CS_ISO_8859_2, GTK_UI_MANAGER_MENUITEM)
7784 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Baltic", "Options/Encoding/Baltic", GTK_UI_MANAGER_MENU)
7785 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)
7786 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)
7788 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_7, "Options/Encoding/"CS_ISO_8859_7, GTK_UI_MANAGER_MENUITEM)
7790 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Hebrew", "Options/Encoding/Hebrew", GTK_UI_MANAGER_MENU)
7791 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)
7792 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Hebrew", CS_WINDOWS_1255, "Options/Encoding/Hebrew/"CS_WINDOWS_1255, GTK_UI_MANAGER_MENUITEM)
7794 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Arabic", "Options/Encoding/Arabic", GTK_UI_MANAGER_MENU)
7795 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)
7796 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Arabic", CS_WINDOWS_1256, "Options/Encoding/Arabic/"CS_WINDOWS_1256, GTK_UI_MANAGER_MENUITEM)
7798 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_9, "Options/Encoding/"CS_ISO_8859_9, GTK_UI_MANAGER_MENUITEM)
7800 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Cyrillic", "Options/Encoding/Cyrillic", GTK_UI_MANAGER_MENU)
7801 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)
7802 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_R, "Options/Encoding/Cyrillic/"CS_KOI8_R, GTK_UI_MANAGER_MENUITEM)
7803 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_MACCYR, "Options/Encoding/Cyrillic/"CS_MACCYR, GTK_UI_MANAGER_MENUITEM)
7804 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_U, "Options/Encoding/Cyrillic/"CS_KOI8_U, GTK_UI_MANAGER_MENUITEM)
7805 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_WINDOWS_1251, "Options/Encoding/Cyrillic/"CS_WINDOWS_1251, GTK_UI_MANAGER_MENUITEM)
7807 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Japanese", "Options/Encoding/Japanese", GTK_UI_MANAGER_MENU)
7808 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)
7809 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)
7810 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_EUC_JP, "Options/Encoding/Japanese/"CS_EUC_JP, GTK_UI_MANAGER_MENUITEM)
7811 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_SHIFT_JIS, "Options/Encoding/Japanese/"CS_SHIFT_JIS, GTK_UI_MANAGER_MENUITEM)
7813 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Chinese", "Options/Encoding/Chinese", GTK_UI_MANAGER_MENU)
7814 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB18030, "Options/Encoding/Chinese/"CS_GB18030, GTK_UI_MANAGER_MENUITEM)
7815 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB2312, "Options/Encoding/Chinese/"CS_GB2312, GTK_UI_MANAGER_MENUITEM)
7816 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GBK, "Options/Encoding/Chinese/"CS_GBK, GTK_UI_MANAGER_MENUITEM)
7817 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_BIG5, "Options/Encoding/Chinese/"CS_BIG5, GTK_UI_MANAGER_MENUITEM)
7818 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_EUC_TW, "Options/Encoding/Chinese/"CS_EUC_TW, GTK_UI_MANAGER_MENUITEM)
7820 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Korean", "Options/Encoding/Korean", GTK_UI_MANAGER_MENU)
7821 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Korean", CS_EUC_KR, "Options/Encoding/Korean/"CS_EUC_KR, GTK_UI_MANAGER_MENUITEM)
7822 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)
7824 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Thai", "Options/Encoding/Thai", GTK_UI_MANAGER_MENU)
7825 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_TIS_620, "Options/Encoding/Thai/"CS_TIS_620, GTK_UI_MANAGER_MENUITEM)
7826 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_WINDOWS_874, "Options/Encoding/Thai/"CS_WINDOWS_874, GTK_UI_MANAGER_MENUITEM)
7830 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "ShowRuler", "Tools/ShowRuler", GTK_UI_MANAGER_MENUITEM)
7831 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "AddressBook", "Tools/AddressBook", GTK_UI_MANAGER_MENUITEM)
7832 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Template", "Tools/Template", GTK_UI_MANAGER_MENU)
7833 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Template", "PlaceHolder", "Tools/Template/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7834 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Actions", "Tools/Actions", GTK_UI_MANAGER_MENU)
7835 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Actions", "PlaceHolder", "Tools/Actions/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7838 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Help", "About", "Help/About", GTK_UI_MANAGER_MENUITEM)
7840 menubar = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu");
7841 gtk_widget_show_all(menubar);
7843 gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(compose->ui_manager));
7844 gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, TRUE, 0);
7846 if (prefs_common.toolbar_detachable) {
7847 handlebox = gtk_handle_box_new();
7849 handlebox = gtk_hbox_new(FALSE, 0);
7851 gtk_box_pack_start(GTK_BOX(vbox), handlebox, FALSE, FALSE, 0);
7853 gtk_widget_realize(handlebox);
7854 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, handlebox,
7857 vbox2 = gtk_vbox_new(FALSE, 2);
7858 gtk_box_pack_start(GTK_BOX(vbox), vbox2, TRUE, TRUE, 0);
7859 gtk_container_set_border_width(GTK_CONTAINER(vbox2), 0);
7862 notebook = gtk_notebook_new();
7863 gtk_widget_show(notebook);
7865 /* header labels and entries */
7866 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7867 compose_create_header(compose),
7868 gtk_label_new_with_mnemonic(_("Hea_der")));
7869 /* attachment list */
7870 attach_hbox = gtk_hbox_new(FALSE, 0);
7871 gtk_widget_show(attach_hbox);
7873 attach_lab1 = gtk_label_new_with_mnemonic(_("_Attachments"));
7874 gtk_widget_show(attach_lab1);
7875 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab1, TRUE, TRUE, 0);
7877 attach_lab2 = gtk_label_new("");
7878 gtk_widget_show(attach_lab2);
7879 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab2, FALSE, FALSE, 0);
7881 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7882 compose_create_attach(compose),
7885 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7886 compose_create_others(compose),
7887 gtk_label_new_with_mnemonic(_("Othe_rs")));
7890 subject_hbox = gtk_hbox_new(FALSE, 0);
7891 gtk_widget_show(subject_hbox);
7893 subject_frame = gtk_frame_new(NULL);
7894 gtk_frame_set_shadow_type(GTK_FRAME(subject_frame), GTK_SHADOW_NONE);
7895 gtk_box_pack_start(GTK_BOX(subject_hbox), subject_frame, TRUE, TRUE, 0);
7896 gtk_widget_show(subject_frame);
7898 subject = gtk_hbox_new(FALSE, HSPACING_NARROW);
7899 gtk_container_set_border_width(GTK_CONTAINER(subject), 0);
7900 gtk_widget_show(subject);
7902 label = gtk_label_new_with_mnemonic(_("S_ubject:"));
7903 gtk_box_pack_start(GTK_BOX(subject), label, FALSE, FALSE, 0);
7904 gtk_widget_show(label);
7907 subject_entry = claws_spell_entry_new();
7909 subject_entry = gtk_entry_new();
7911 gtk_box_pack_start(GTK_BOX(subject), subject_entry, TRUE, TRUE, 0);
7912 g_signal_connect_after(G_OBJECT(subject_entry), "grab_focus",
7913 G_CALLBACK(compose_grab_focus_cb), compose);
7914 gtk_label_set_mnemonic_widget(GTK_LABEL(label), subject_entry);
7915 gtk_widget_show(subject_entry);
7916 compose->subject_entry = subject_entry;
7917 gtk_container_add(GTK_CONTAINER(subject_frame), subject);
7919 edit_vbox = gtk_vbox_new(FALSE, 0);
7921 gtk_box_pack_start(GTK_BOX(edit_vbox), subject_hbox, FALSE, FALSE, 0);
7924 ruler_hbox = gtk_hbox_new(FALSE, 0);
7925 gtk_box_pack_start(GTK_BOX(edit_vbox), ruler_hbox, FALSE, FALSE, 0);
7927 ruler = gtk_shruler_new(GTK_ORIENTATION_HORIZONTAL);
7928 gtk_shruler_set_range(GTK_SHRULER(ruler), 0.0, 100.0, 1.0);
7929 gtk_box_pack_start(GTK_BOX(ruler_hbox), ruler, TRUE, TRUE,
7933 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
7934 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
7935 GTK_POLICY_AUTOMATIC,
7936 GTK_POLICY_AUTOMATIC);
7937 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
7939 gtk_box_pack_start(GTK_BOX(edit_vbox), scrolledwin, TRUE, TRUE, 0);
7941 text = gtk_text_view_new();
7942 if (prefs_common.show_compose_margin) {
7943 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text), 6);
7944 gtk_text_view_set_right_margin(GTK_TEXT_VIEW(text), 6);
7946 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
7947 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
7948 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), TRUE);
7949 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
7950 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
7952 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
7953 g_signal_connect_after(G_OBJECT(text), "size_allocate",
7954 G_CALLBACK(compose_edit_size_alloc),
7956 g_signal_connect(G_OBJECT(buffer), "changed",
7957 G_CALLBACK(compose_changed_cb), compose);
7958 g_signal_connect(G_OBJECT(text), "grab_focus",
7959 G_CALLBACK(compose_grab_focus_cb), compose);
7960 g_signal_connect(G_OBJECT(buffer), "insert_text",
7961 G_CALLBACK(text_inserted), compose);
7962 g_signal_connect(G_OBJECT(text), "button_press_event",
7963 G_CALLBACK(text_clicked), compose);
7964 g_signal_connect(G_OBJECT(text), "popup-menu",
7965 G_CALLBACK(compose_popup_menu), compose);
7966 g_signal_connect(G_OBJECT(subject_entry), "changed",
7967 G_CALLBACK(compose_changed_cb), compose);
7968 g_signal_connect(G_OBJECT(subject_entry), "activate",
7969 G_CALLBACK(compose_subject_entry_activated), compose);
7972 gtk_drag_dest_set(text, GTK_DEST_DEFAULT_ALL, compose_mime_types,
7973 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7974 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7975 g_signal_connect(G_OBJECT(text), "drag_data_received",
7976 G_CALLBACK(compose_insert_drag_received_cb),
7978 g_signal_connect(G_OBJECT(text), "drag-drop",
7979 G_CALLBACK(compose_drag_drop),
7981 g_signal_connect(G_OBJECT(text), "key-press-event",
7982 G_CALLBACK(completion_set_focus_to_subject),
7984 gtk_widget_show_all(vbox);
7986 /* pane between attach clist and text */
7987 paned = gtk_vpaned_new();
7988 gtk_container_add(GTK_CONTAINER(vbox2), paned);
7989 gtk_paned_pack1(GTK_PANED(paned), notebook, FALSE, FALSE);
7990 gtk_paned_pack2(GTK_PANED(paned), edit_vbox, TRUE, FALSE);
7991 gtk_paned_set_position(GTK_PANED(paned), prefs_common.compose_notebook_height);
7992 g_signal_connect(G_OBJECT(notebook), "size_allocate",
7993 G_CALLBACK(compose_notebook_size_alloc), paned);
7995 gtk_widget_show_all(paned);
7998 if (prefs_common.textfont) {
7999 PangoFontDescription *font_desc;
8001 font_desc = pango_font_description_from_string
8002 (prefs_common.textfont);
8004 gtk_widget_modify_font(text, font_desc);
8005 pango_font_description_free(font_desc);
8009 gtk_action_group_add_actions(action_group, compose_popup_entries,
8010 G_N_ELEMENTS(compose_popup_entries), (gpointer)compose);
8011 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Popup", NULL, GTK_UI_MANAGER_MENUBAR)
8012 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup", "Compose", "Compose", GTK_UI_MANAGER_MENU)
8013 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Add", "Compose/Add", GTK_UI_MANAGER_MENUITEM)
8014 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Remove", "Compose/Remove", GTK_UI_MANAGER_MENUITEM)
8015 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Separator1", "Compose/---", GTK_UI_MANAGER_SEPARATOR)
8016 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Properties", "Compose/Properties", GTK_UI_MANAGER_MENUITEM)
8018 popupmenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(gtk_ui_manager_get_widget(compose->ui_manager, "/Popup/Compose")));
8020 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
8021 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
8022 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
8024 tmpl_menu = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu/Tools/Template");
8026 undostruct = undo_init(text);
8027 undo_set_change_state_func(undostruct, &compose_undo_state_changed,
8030 address_completion_start(window);
8032 compose->window = window;
8033 compose->vbox = vbox;
8034 compose->menubar = menubar;
8035 compose->handlebox = handlebox;
8037 compose->vbox2 = vbox2;
8039 compose->paned = paned;
8041 compose->attach_label = attach_lab2;
8043 compose->notebook = notebook;
8044 compose->edit_vbox = edit_vbox;
8045 compose->ruler_hbox = ruler_hbox;
8046 compose->ruler = ruler;
8047 compose->scrolledwin = scrolledwin;
8048 compose->text = text;
8050 compose->focused_editable = NULL;
8052 compose->popupmenu = popupmenu;
8054 compose->tmpl_menu = tmpl_menu;
8056 compose->mode = mode;
8057 compose->rmode = mode;
8059 compose->targetinfo = NULL;
8060 compose->replyinfo = NULL;
8061 compose->fwdinfo = NULL;
8063 compose->email_hashtable = g_hash_table_new_full(g_str_hash,
8064 g_str_equal, (GDestroyNotify) g_free, NULL);
8066 compose->replyto = NULL;
8068 compose->bcc = NULL;
8069 compose->followup_to = NULL;
8071 compose->ml_post = NULL;
8073 compose->inreplyto = NULL;
8074 compose->references = NULL;
8075 compose->msgid = NULL;
8076 compose->boundary = NULL;
8078 compose->autowrap = prefs_common.autowrap;
8079 compose->autoindent = prefs_common.auto_indent;
8080 compose->use_signing = FALSE;
8081 compose->use_encryption = FALSE;
8082 compose->privacy_system = NULL;
8083 compose->encdata = NULL;
8085 compose->modified = FALSE;
8087 compose->return_receipt = FALSE;
8089 compose->to_list = NULL;
8090 compose->newsgroup_list = NULL;
8092 compose->undostruct = undostruct;
8094 compose->sig_str = NULL;
8096 compose->exteditor_file = NULL;
8097 compose->exteditor_pid = -1;
8098 compose->exteditor_tag = -1;
8099 compose->exteditor_socket = NULL;
8100 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN; /* inhibit auto-drafting while loading */
8102 compose->folder_update_callback_id =
8103 hooks_register_hook(FOLDER_UPDATE_HOOKLIST,
8104 compose_update_folder_hook,
8105 (gpointer) compose);
8108 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
8109 if (mode != COMPOSE_REDIRECT) {
8110 if (prefs_common.enable_aspell && prefs_common.dictionary &&
8111 strcmp(prefs_common.dictionary, "")) {
8112 gtkaspell = gtkaspell_new(prefs_common.dictionary,
8113 prefs_common.alt_dictionary,
8114 conv_get_locale_charset_str(),
8115 prefs_common.misspelled_col,
8116 prefs_common.check_while_typing,
8117 prefs_common.recheck_when_changing_dict,
8118 prefs_common.use_alternate,
8119 prefs_common.use_both_dicts,
8120 GTK_TEXT_VIEW(text),
8121 GTK_WINDOW(compose->window),
8122 compose_dict_changed,
8123 compose_spell_menu_changed,
8126 alertpanel_error(_("Spell checker could not "
8128 gtkaspell_checkers_strerror());
8129 gtkaspell_checkers_reset_error();
8131 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", TRUE);
8135 compose->gtkaspell = gtkaspell;
8136 compose_spell_menu_changed(compose);
8137 claws_spell_entry_set_gtkaspell(CLAWS_SPELL_ENTRY(subject_entry), gtkaspell);
8140 compose_select_account(compose, account, TRUE);
8142 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", prefs_common.autowrap);
8143 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", prefs_common.auto_indent);
8145 if (account->set_autocc && account->auto_cc && mode != COMPOSE_REEDIT)
8146 compose_entry_append(compose, account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8148 if (account->set_autobcc && account->auto_bcc && mode != COMPOSE_REEDIT)
8149 compose_entry_append(compose, account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8151 if (account->set_autoreplyto && account->auto_replyto && mode != COMPOSE_REEDIT)
8152 compose_entry_append(compose, account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8154 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", compose->mode == COMPOSE_REPLY);
8155 if (account->protocol != A_NNTP)
8156 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
8157 prefs_common_translated_header_name("To:"));
8159 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
8160 prefs_common_translated_header_name("Newsgroups:"));
8162 #ifndef USE_ALT_ADDRBOOK
8163 addressbook_set_target_compose(compose);
8165 if (mode != COMPOSE_REDIRECT)
8166 compose_set_template_menu(compose);
8168 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Template", FALSE);
8171 compose_list = g_list_append(compose_list, compose);
8173 if (!prefs_common.show_ruler)
8174 gtk_widget_hide(ruler_hbox);
8176 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Tools/ShowRuler", prefs_common.show_ruler);
8179 compose->priority = PRIORITY_NORMAL;
8180 compose_update_priority_menu_item(compose);
8182 compose_set_out_encoding(compose);
8185 compose_update_actions_menu(compose);
8187 /* Privacy Systems menu */
8188 compose_update_privacy_systems_menu(compose);
8190 activate_privacy_system(compose, account, TRUE);
8191 toolbar_set_style(compose->toolbar->toolbar, compose->handlebox, prefs_common.toolbar_style);
8193 gtk_widget_realize(window);
8195 gtk_widget_show(window);
8201 static GtkWidget *compose_account_option_menu_create(Compose *compose)
8206 GtkWidget *optmenubox;
8207 GtkWidget *fromlabel;
8210 GtkWidget *from_name = NULL;
8212 gint num = 0, def_menu = 0;
8214 accounts = account_get_list();
8215 cm_return_val_if_fail(accounts != NULL, NULL);
8217 optmenubox = gtk_event_box_new();
8218 optmenu = gtkut_sc_combobox_create(optmenubox, FALSE);
8219 menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
8221 hbox = gtk_hbox_new(FALSE, 4);
8222 from_name = gtk_entry_new();
8224 g_signal_connect_after(G_OBJECT(from_name), "grab_focus",
8225 G_CALLBACK(compose_grab_focus_cb), compose);
8226 g_signal_connect_after(G_OBJECT(from_name), "activate",
8227 G_CALLBACK(from_name_activate_cb), optmenu);
8229 for (; accounts != NULL; accounts = accounts->next, num++) {
8230 PrefsAccount *ac = (PrefsAccount *)accounts->data;
8231 gchar *name, *from = NULL;
8233 if (ac == compose->account) def_menu = num;
8235 name = g_markup_printf_escaped("<i>%s</i>",
8238 if (ac == compose->account) {
8239 if (ac->name && *ac->name) {
8241 QUOTE_IF_REQUIRED_NORMAL(buf, ac->name, return NULL);
8242 from = g_strdup_printf("%s <%s>",
8244 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8246 from = g_strdup_printf("%s",
8248 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8250 if (cur_account != compose->account) {
8251 gtk_widget_modify_base(
8252 GTK_WIDGET(from_name),
8253 GTK_STATE_NORMAL, &default_header_bgcolor);
8254 gtk_widget_modify_text(
8255 GTK_WIDGET(from_name),
8256 GTK_STATE_NORMAL, &default_header_color);
8259 COMBOBOX_ADD(menu, name, ac->account_id);
8264 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), def_menu);
8266 g_signal_connect(G_OBJECT(optmenu), "changed",
8267 G_CALLBACK(account_activated),
8269 g_signal_connect(G_OBJECT(from_name), "populate-popup",
8270 G_CALLBACK(compose_entry_popup_extend),
8273 fromlabel = gtk_label_new_with_mnemonic(_("_From:"));
8274 gtk_label_set_mnemonic_widget(GTK_LABEL(fromlabel), from_name);
8276 gtk_box_pack_start(GTK_BOX(hbox), fromlabel, FALSE, FALSE, 4);
8277 gtk_box_pack_start(GTK_BOX(hbox), optmenubox, FALSE, FALSE, 0);
8278 gtk_box_pack_start(GTK_BOX(hbox), from_name, TRUE, TRUE, 0);
8280 /* Putting only the GtkEntry into focus chain of parent hbox causes
8281 * the account selector combobox next to it to be unreachable when
8282 * navigating widgets in GtkTable with up/down arrow keys.
8283 * Note: gtk_widget_set_can_focus() was not enough. */
8285 l = g_list_prepend(l, from_name);
8286 gtk_container_set_focus_chain(GTK_CONTAINER(hbox), l);
8289 CLAWS_SET_TIP(optmenubox,
8290 _("Account to use for this email"));
8291 CLAWS_SET_TIP(from_name,
8292 _("Sender address to be used"));
8294 compose->account_combo = optmenu;
8295 compose->from_name = from_name;
8300 static void compose_set_priority_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8302 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8303 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8304 Compose *compose = (Compose *) data;
8306 compose->priority = value;
8310 static void compose_reply_change_mode(Compose *compose,
8313 gboolean was_modified = compose->modified;
8315 gboolean all = FALSE, ml = FALSE, sender = FALSE, followup = FALSE;
8317 cm_return_if_fail(compose->replyinfo != NULL);
8319 if (action == COMPOSE_REPLY && prefs_common.default_reply_list)
8321 if (action == COMPOSE_REPLY && compose->rmode == COMPOSE_FOLLOWUP_AND_REPLY_TO)
8323 if (action == COMPOSE_REPLY_TO_ALL)
8325 if (action == COMPOSE_REPLY_TO_SENDER)
8327 if (action == COMPOSE_REPLY_TO_LIST)
8330 compose_remove_header_entries(compose);
8331 compose_reply_set_entry(compose, compose->replyinfo, all, ml, sender, followup);
8332 if (compose->account->set_autocc && compose->account->auto_cc)
8333 compose_entry_append(compose, compose->account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8335 if (compose->account->set_autobcc && compose->account->auto_bcc)
8336 compose_entry_append(compose, compose->account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8338 if (compose->account->set_autoreplyto && compose->account->auto_replyto)
8339 compose_entry_append(compose, compose->account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8340 compose_show_first_last_header(compose, TRUE);
8341 compose->modified = was_modified;
8342 compose_set_title(compose);
8345 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8347 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8348 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8349 Compose *compose = (Compose *) data;
8352 compose_reply_change_mode(compose, value);
8355 static void compose_update_priority_menu_item(Compose * compose)
8357 GtkWidget *menuitem = NULL;
8358 switch (compose->priority) {
8359 case PRIORITY_HIGHEST:
8360 menuitem = gtk_ui_manager_get_widget
8361 (compose->ui_manager, "/Menu/Options/Priority/Highest");
8364 menuitem = gtk_ui_manager_get_widget
8365 (compose->ui_manager, "/Menu/Options/Priority/High");
8367 case PRIORITY_NORMAL:
8368 menuitem = gtk_ui_manager_get_widget
8369 (compose->ui_manager, "/Menu/Options/Priority/Normal");
8372 menuitem = gtk_ui_manager_get_widget
8373 (compose->ui_manager, "/Menu/Options/Priority/Low");
8375 case PRIORITY_LOWEST:
8376 menuitem = gtk_ui_manager_get_widget
8377 (compose->ui_manager, "/Menu/Options/Priority/Lowest");
8380 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8383 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data)
8385 Compose *compose = (Compose *) data;
8387 gboolean can_sign = FALSE, can_encrypt = FALSE;
8389 cm_return_if_fail(GTK_IS_CHECK_MENU_ITEM(widget));
8391 if (!gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget)))
8394 systemid = g_object_get_data(G_OBJECT(widget), "privacy_system");
8395 g_free(compose->privacy_system);
8396 compose->privacy_system = NULL;
8397 g_free(compose->encdata);
8398 compose->encdata = NULL;
8399 if (systemid != NULL) {
8400 compose->privacy_system = g_strdup(systemid);
8402 can_sign = privacy_system_can_sign(systemid);
8403 can_encrypt = privacy_system_can_encrypt(systemid);
8406 debug_print("activated privacy system: %s\n", systemid != NULL ? systemid : "None");
8408 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8409 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8412 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn)
8414 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8415 GtkWidget *menuitem = NULL;
8416 GList *children, *amenu;
8417 gboolean can_sign = FALSE, can_encrypt = FALSE;
8418 gboolean found = FALSE;
8420 if (compose->privacy_system != NULL) {
8422 menuitem = gtk_menu_item_get_submenu(GTK_MENU_ITEM(
8423 gtk_ui_manager_get_widget(compose->ui_manager, branch_path)));
8424 cm_return_if_fail(menuitem != NULL);
8426 children = gtk_container_get_children(GTK_CONTAINER(GTK_MENU_SHELL(menuitem)));
8429 while (amenu != NULL) {
8430 systemid = g_object_get_data(G_OBJECT(amenu->data), "privacy_system");
8431 if (systemid != NULL) {
8432 if (strcmp(systemid, compose->privacy_system) == 0 &&
8433 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8434 menuitem = GTK_WIDGET(amenu->data);
8436 can_sign = privacy_system_can_sign(systemid);
8437 can_encrypt = privacy_system_can_encrypt(systemid);
8441 } else if (strlen(compose->privacy_system) == 0 &&
8442 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8443 menuitem = GTK_WIDGET(amenu->data);
8446 can_encrypt = FALSE;
8451 amenu = amenu->next;
8453 g_list_free(children);
8454 if (menuitem != NULL)
8455 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8457 if (warn && !found && strlen(compose->privacy_system)) {
8458 alertpanel_warning(_("The privacy system '%s' cannot be loaded. You "
8459 "will not be able to sign or encrypt this message."),
8460 compose->privacy_system);
8464 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8465 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8468 static void compose_set_out_encoding(Compose *compose)
8470 CharSet out_encoding;
8471 const gchar *branch = NULL;
8472 out_encoding = conv_get_charset_from_str(prefs_common.outgoing_charset);
8474 switch(out_encoding) {
8475 case C_AUTO: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8476 case C_US_ASCII: branch = "Menu/Options/Encoding/" CS_US_ASCII; break;
8477 case C_UTF_8: branch = "Menu/Options/Encoding/" CS_UTF_8; break;
8478 case C_ISO_8859_2: branch = "Menu/Options/Encoding/" CS_ISO_8859_2; break;
8479 case C_ISO_8859_7: branch = "Menu/Options/Encoding/" CS_ISO_8859_7; break;
8480 case C_ISO_8859_9: branch = "Menu/Options/Encoding/" CS_ISO_8859_9; break;
8481 case C_ISO_8859_1: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_1; break;
8482 case C_ISO_8859_15: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_15; break;
8483 case C_WINDOWS_1252: branch = "Menu/Options/Encoding/Western/" CS_WINDOWS_1252; break;
8484 case C_ISO_8859_13: branch = "Menu/Options/Encoding/Baltic/" CS_ISO_8859_13; break;
8485 case C_ISO_8859_4: branch = "Menu/Options/Encoding/Baltic" CS_ISO_8859_4; break;
8486 case C_ISO_8859_8: branch = "Menu/Options/Encoding/Hebrew/" CS_ISO_8859_8; break;
8487 case C_WINDOWS_1255: branch = "Menu/Options/Encoding/Hebrew/" CS_WINDOWS_1255; break;
8488 case C_ISO_8859_6: branch = "Menu/Options/Encoding/Arabic/" CS_ISO_8859_6; break;
8489 case C_WINDOWS_1256: branch = "Menu/Options/Encoding/Arabic/" CS_WINDOWS_1256; break;
8490 case C_ISO_8859_5: branch = "Menu/Options/Encoding/Cyrillic/" CS_ISO_8859_5; break;
8491 case C_KOI8_R: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_R; break;
8492 case C_MACCYR: branch = "Menu/Options/Encoding/Cyrillic/" CS_MACCYR; break;
8493 case C_KOI8_U: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_U; break;
8494 case C_WINDOWS_1251: branch = "Menu/Options/Encoding/Cyrillic/" CS_WINDOWS_1251; break;
8495 case C_ISO_2022_JP: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP; break;
8496 case C_ISO_2022_JP_2: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP_2; break;
8497 case C_EUC_JP: branch = "Menu/Options/Encoding/Japanese/" CS_EUC_JP; break;
8498 case C_SHIFT_JIS: branch = "Menu/Options/Encoding/Japanese/" CS_SHIFT_JIS; break;
8499 case C_GB18030: branch = "Menu/Options/Encoding/Chinese/" CS_GB18030; break;
8500 case C_GB2312: branch = "Menu/Options/Encoding/Chinese/" CS_GB2312; break;
8501 case C_GBK: branch = "Menu/Options/Encoding/Chinese/" CS_GBK; break;
8502 case C_BIG5: branch = "Menu/Options/Encoding/Chinese/" CS_BIG5; break;
8503 case C_EUC_TW: branch = "Menu/Options/Encoding/Chinese/" CS_EUC_TW; break;
8504 case C_EUC_KR: branch = "Menu/Options/Encoding/Korean/" CS_EUC_KR; break;
8505 case C_ISO_2022_KR: branch = "Menu/Options/Encoding/Korean/" CS_ISO_2022_KR; break;
8506 case C_TIS_620: branch = "Menu/Options/Encoding/Thai/" CS_TIS_620; break;
8507 case C_WINDOWS_874: branch = "Menu/Options/Encoding/Thai/" CS_WINDOWS_874; break;
8508 default: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8510 cm_toggle_menu_set_active_full(compose->ui_manager, (gchar *)branch, TRUE);
8513 static void compose_set_template_menu(Compose *compose)
8515 GSList *tmpl_list, *cur;
8519 tmpl_list = template_get_config();
8521 menu = gtk_menu_new();
8523 gtk_menu_set_accel_group (GTK_MENU (menu),
8524 gtk_ui_manager_get_accel_group(compose->ui_manager));
8525 for (cur = tmpl_list; cur != NULL; cur = cur->next) {
8526 Template *tmpl = (Template *)cur->data;
8527 gchar *accel_path = NULL;
8528 item = gtk_menu_item_new_with_label(tmpl->name);
8529 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
8530 g_signal_connect(G_OBJECT(item), "activate",
8531 G_CALLBACK(compose_template_activate_cb),
8533 g_object_set_data(G_OBJECT(item), "template", tmpl);
8534 gtk_widget_show(item);
8535 accel_path = g_strconcat("<ComposeTemplates>" , "/", tmpl->name, NULL);
8536 gtk_menu_item_set_accel_path(GTK_MENU_ITEM(item), accel_path);
8540 gtk_widget_show(menu);
8541 gtk_menu_item_set_submenu(GTK_MENU_ITEM(compose->tmpl_menu), menu);
8544 void compose_update_actions_menu(Compose *compose)
8546 action_update_compose_menu(compose->ui_manager, "/Menu/Tools/Actions", compose);
8549 static void compose_update_privacy_systems_menu(Compose *compose)
8551 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8552 GSList *systems, *cur;
8554 GtkWidget *system_none;
8556 GtkWidget *privacy_menuitem = gtk_ui_manager_get_widget(compose->ui_manager, branch_path);
8557 GtkWidget *privacy_menu = gtk_menu_new();
8559 system_none = gtk_radio_menu_item_new_with_mnemonic(NULL, _("_None"));
8560 g_object_set_data_full(G_OBJECT(system_none), "privacy_system", NULL, NULL);
8562 g_signal_connect(G_OBJECT(system_none), "activate",
8563 G_CALLBACK(compose_set_privacy_system_cb), compose);
8565 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), system_none);
8566 gtk_widget_show(system_none);
8568 systems = privacy_get_system_ids();
8569 for (cur = systems; cur != NULL; cur = g_slist_next(cur)) {
8570 gchar *systemid = cur->data;
8572 group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(system_none));
8573 widget = gtk_radio_menu_item_new_with_label(group,
8574 privacy_system_get_name(systemid));
8575 g_object_set_data_full(G_OBJECT(widget), "privacy_system",
8576 g_strdup(systemid), g_free);
8577 g_signal_connect(G_OBJECT(widget), "activate",
8578 G_CALLBACK(compose_set_privacy_system_cb), compose);
8580 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), widget);
8581 gtk_widget_show(widget);
8584 g_slist_free(systems);
8585 gtk_menu_item_set_submenu(GTK_MENU_ITEM(privacy_menuitem), privacy_menu);
8586 gtk_widget_show_all(privacy_menu);
8587 gtk_widget_show_all(privacy_menuitem);
8590 void compose_reflect_prefs_all(void)
8595 for (cur = compose_list; cur != NULL; cur = cur->next) {
8596 compose = (Compose *)cur->data;
8597 compose_set_template_menu(compose);
8601 void compose_reflect_prefs_pixmap_theme(void)
8606 for (cur = compose_list; cur != NULL; cur = cur->next) {
8607 compose = (Compose *)cur->data;
8608 toolbar_update(TOOLBAR_COMPOSE, compose);
8612 static const gchar *compose_quote_char_from_context(Compose *compose)
8614 const gchar *qmark = NULL;
8616 cm_return_val_if_fail(compose != NULL, NULL);
8618 switch (compose->mode) {
8619 /* use forward-specific quote char */
8620 case COMPOSE_FORWARD:
8621 case COMPOSE_FORWARD_AS_ATTACH:
8622 case COMPOSE_FORWARD_INLINE:
8623 if (compose->folder && compose->folder->prefs &&
8624 compose->folder->prefs->forward_with_format)
8625 qmark = compose->folder->prefs->forward_quotemark;
8626 else if (compose->account->forward_with_format)
8627 qmark = compose->account->forward_quotemark;
8629 qmark = prefs_common.fw_quotemark;
8632 /* use reply-specific quote char in all other modes */
8634 if (compose->folder && compose->folder->prefs &&
8635 compose->folder->prefs->reply_with_format)
8636 qmark = compose->folder->prefs->reply_quotemark;
8637 else if (compose->account->reply_with_format)
8638 qmark = compose->account->reply_quotemark;
8640 qmark = prefs_common.quotemark;
8644 if (qmark == NULL || *qmark == '\0')
8650 static void compose_template_apply(Compose *compose, Template *tmpl,
8654 GtkTextBuffer *buffer;
8658 gchar *parsed_str = NULL;
8659 gint cursor_pos = 0;
8660 const gchar *err_msg = _("The body of the template has an error at line %d.");
8663 /* process the body */
8665 text = GTK_TEXT_VIEW(compose->text);
8666 buffer = gtk_text_view_get_buffer(text);
8669 qmark = compose_quote_char_from_context(compose);
8671 if (compose->replyinfo != NULL) {
8674 gtk_text_buffer_set_text(buffer, "", -1);
8675 mark = gtk_text_buffer_get_insert(buffer);
8676 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8678 parsed_str = compose_quote_fmt(compose, compose->replyinfo,
8679 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8681 } else if (compose->fwdinfo != NULL) {
8684 gtk_text_buffer_set_text(buffer, "", -1);
8685 mark = gtk_text_buffer_get_insert(buffer);
8686 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8688 parsed_str = compose_quote_fmt(compose, compose->fwdinfo,
8689 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8692 MsgInfo* dummyinfo = compose_msginfo_new_from_compose(compose);
8694 GtkTextIter start, end;
8697 gtk_text_buffer_get_start_iter(buffer, &start);
8698 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
8699 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
8701 /* clear the buffer now */
8703 gtk_text_buffer_set_text(buffer, "", -1);
8705 parsed_str = compose_quote_fmt(compose, dummyinfo,
8706 tmpl->value, qmark, tmp, FALSE, FALSE, err_msg);
8707 procmsg_msginfo_free( &dummyinfo );
8713 gtk_text_buffer_set_text(buffer, "", -1);
8714 mark = gtk_text_buffer_get_insert(buffer);
8715 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8718 if (replace && parsed_str && compose->account->auto_sig)
8719 compose_insert_sig(compose, FALSE);
8721 if (replace && parsed_str) {
8722 gtk_text_buffer_get_start_iter(buffer, &iter);
8723 gtk_text_buffer_place_cursor(buffer, &iter);
8727 cursor_pos = quote_fmt_get_cursor_pos();
8728 compose->set_cursor_pos = cursor_pos;
8729 if (cursor_pos == -1)
8731 gtk_text_buffer_get_start_iter(buffer, &iter);
8732 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
8733 gtk_text_buffer_place_cursor(buffer, &iter);
8736 /* process the other fields */
8738 compose_template_apply_fields(compose, tmpl);
8739 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
8740 quote_fmt_reset_vartable();
8741 compose_changed_cb(NULL, compose);
8744 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
8745 gtkaspell_highlight_all(compose->gtkaspell);
8749 static void compose_template_apply_fields_error(const gchar *header)
8754 tr = g_strdup(C_("'%s' stands for a header name",
8755 "Template '%s' format error."));
8756 text = g_strdup_printf(tr, prefs_common_translated_header_name(header));
8757 alertpanel_error(text);
8763 static void compose_template_apply_fields(Compose *compose, Template *tmpl)
8765 MsgInfo* dummyinfo = NULL;
8766 MsgInfo *msginfo = NULL;
8769 if (compose->replyinfo != NULL)
8770 msginfo = compose->replyinfo;
8771 else if (compose->fwdinfo != NULL)
8772 msginfo = compose->fwdinfo;
8774 dummyinfo = compose_msginfo_new_from_compose(compose);
8775 msginfo = dummyinfo;
8778 if (tmpl->from && *tmpl->from != '\0') {
8780 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8781 compose->gtkaspell);
8783 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8785 quote_fmt_scan_string(tmpl->from);
8788 buf = quote_fmt_get_buffer();
8790 compose_template_apply_fields_error("From");
8792 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
8796 if (tmpl->to && *tmpl->to != '\0') {
8798 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8799 compose->gtkaspell);
8801 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8803 quote_fmt_scan_string(tmpl->to);
8806 buf = quote_fmt_get_buffer();
8808 compose_template_apply_fields_error("To");
8810 compose_entry_append(compose, buf, COMPOSE_TO, PREF_TEMPLATE);
8814 if (tmpl->cc && *tmpl->cc != '\0') {
8816 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8817 compose->gtkaspell);
8819 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8821 quote_fmt_scan_string(tmpl->cc);
8824 buf = quote_fmt_get_buffer();
8826 compose_template_apply_fields_error("Cc");
8828 compose_entry_append(compose, buf, COMPOSE_CC, PREF_TEMPLATE);
8832 if (tmpl->bcc && *tmpl->bcc != '\0') {
8834 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8835 compose->gtkaspell);
8837 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8839 quote_fmt_scan_string(tmpl->bcc);
8842 buf = quote_fmt_get_buffer();
8844 compose_template_apply_fields_error("Bcc");
8846 compose_entry_append(compose, buf, COMPOSE_BCC, PREF_TEMPLATE);
8850 if (tmpl->replyto && *tmpl->replyto != '\0') {
8852 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8853 compose->gtkaspell);
8855 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8857 quote_fmt_scan_string(tmpl->replyto);
8860 buf = quote_fmt_get_buffer();
8862 compose_template_apply_fields_error("Reply-To");
8864 compose_entry_append(compose, buf, COMPOSE_REPLYTO, PREF_TEMPLATE);
8868 /* process the subject */
8869 if (tmpl->subject && *tmpl->subject != '\0') {
8871 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8872 compose->gtkaspell);
8874 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8876 quote_fmt_scan_string(tmpl->subject);
8879 buf = quote_fmt_get_buffer();
8881 compose_template_apply_fields_error("Subject");
8883 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
8887 procmsg_msginfo_free( &dummyinfo );
8890 static void compose_destroy(Compose *compose)
8892 GtkAllocation allocation;
8893 GtkTextBuffer *buffer;
8894 GtkClipboard *clipboard;
8896 compose_list = g_list_remove(compose_list, compose);
8898 if (compose->updating) {
8899 debug_print("danger, not destroying anything now\n");
8900 compose->deferred_destroy = TRUE;
8904 /* NOTE: address_completion_end() does nothing with the window
8905 * however this may change. */
8906 address_completion_end(compose->window);
8908 slist_free_strings_full(compose->to_list);
8909 slist_free_strings_full(compose->newsgroup_list);
8910 slist_free_strings_full(compose->header_list);
8912 slist_free_strings_full(extra_headers);
8913 extra_headers = NULL;
8915 compose->header_list = compose->newsgroup_list = compose->to_list = NULL;
8917 g_hash_table_destroy(compose->email_hashtable);
8919 hooks_unregister_hook(FOLDER_UPDATE_HOOKLIST,
8920 compose->folder_update_callback_id);
8922 procmsg_msginfo_free(&(compose->targetinfo));
8923 procmsg_msginfo_free(&(compose->replyinfo));
8924 procmsg_msginfo_free(&(compose->fwdinfo));
8926 g_free(compose->replyto);
8927 g_free(compose->cc);
8928 g_free(compose->bcc);
8929 g_free(compose->newsgroups);
8930 g_free(compose->followup_to);
8932 g_free(compose->ml_post);
8934 g_free(compose->inreplyto);
8935 g_free(compose->references);
8936 g_free(compose->msgid);
8937 g_free(compose->boundary);
8939 g_free(compose->redirect_filename);
8940 if (compose->undostruct)
8941 undo_destroy(compose->undostruct);
8943 g_free(compose->sig_str);
8945 g_free(compose->exteditor_file);
8947 g_free(compose->orig_charset);
8949 g_free(compose->privacy_system);
8950 g_free(compose->encdata);
8952 #ifndef USE_ALT_ADDRBOOK
8953 if (addressbook_get_target_compose() == compose)
8954 addressbook_set_target_compose(NULL);
8957 if (compose->gtkaspell) {
8958 gtkaspell_delete(compose->gtkaspell);
8959 compose->gtkaspell = NULL;
8963 if (!compose->batch) {
8964 gtk_widget_get_allocation(compose->window, &allocation);
8965 prefs_common.compose_width = allocation.width;
8966 prefs_common.compose_height = allocation.height;
8969 if (!gtk_widget_get_parent(compose->paned))
8970 gtk_widget_destroy(compose->paned);
8971 gtk_widget_destroy(compose->popupmenu);
8973 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
8974 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
8975 gtk_text_buffer_remove_selection_clipboard(buffer, clipboard);
8977 gtk_widget_destroy(compose->window);
8978 toolbar_destroy(compose->toolbar);
8979 g_free(compose->toolbar);
8980 cm_mutex_free(compose->mutex);
8984 static void compose_attach_info_free(AttachInfo *ainfo)
8986 g_free(ainfo->file);
8987 g_free(ainfo->content_type);
8988 g_free(ainfo->name);
8989 g_free(ainfo->charset);
8993 static void compose_attach_update_label(Compose *compose)
8998 GtkTreeModel *model;
9003 model = gtk_tree_view_get_model(GTK_TREE_VIEW(compose->attach_clist));
9004 if(!gtk_tree_model_get_iter_first(model, &iter)) {
9005 gtk_label_set_text(GTK_LABEL(compose->attach_label), "");
9009 while(gtk_tree_model_iter_next(model, &iter))
9012 text = g_strdup_printf("(%d)", i);
9013 gtk_label_set_text(GTK_LABEL(compose->attach_label), text);
9017 static void compose_attach_remove_selected(GtkAction *action, gpointer data)
9019 Compose *compose = (Compose *)data;
9020 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
9021 GtkTreeSelection *selection;
9023 GtkTreeModel *model;
9025 selection = gtk_tree_view_get_selection(tree_view);
9026 sel = gtk_tree_selection_get_selected_rows(selection, &model);
9031 for (cur = sel; cur != NULL; cur = cur->next) {
9032 GtkTreePath *path = cur->data;
9033 GtkTreeRowReference *ref = gtk_tree_row_reference_new
9036 gtk_tree_path_free(path);
9039 for (cur = sel; cur != NULL; cur = cur->next) {
9040 GtkTreeRowReference *ref = cur->data;
9041 GtkTreePath *path = gtk_tree_row_reference_get_path(ref);
9044 if (gtk_tree_model_get_iter(model, &iter, path))
9045 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
9047 gtk_tree_path_free(path);
9048 gtk_tree_row_reference_free(ref);
9052 compose_attach_update_label(compose);
9055 static struct _AttachProperty
9058 GtkWidget *mimetype_entry;
9059 GtkWidget *encoding_optmenu;
9060 GtkWidget *path_entry;
9061 GtkWidget *filename_entry;
9063 GtkWidget *cancel_btn;
9066 static void gtk_tree_path_free_(gpointer ptr, gpointer data)
9068 gtk_tree_path_free((GtkTreePath *)ptr);
9071 static void compose_attach_property(GtkAction *action, gpointer data)
9073 Compose *compose = (Compose *)data;
9074 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
9076 GtkComboBox *optmenu;
9077 GtkTreeSelection *selection;
9079 GtkTreeModel *model;
9082 static gboolean cancelled;
9084 /* only if one selected */
9085 selection = gtk_tree_view_get_selection(tree_view);
9086 if (gtk_tree_selection_count_selected_rows(selection) != 1)
9089 sel = gtk_tree_selection_get_selected_rows(selection, &model);
9093 path = (GtkTreePath *) sel->data;
9094 gtk_tree_model_get_iter(model, &iter, path);
9095 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
9098 g_list_foreach(sel, gtk_tree_path_free_, NULL);
9104 if (!attach_prop.window)
9105 compose_attach_property_create(&cancelled);
9106 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), TRUE);
9107 gtk_widget_grab_focus(attach_prop.ok_btn);
9108 gtk_widget_show(attach_prop.window);
9109 gtk_window_set_transient_for(GTK_WINDOW(attach_prop.window),
9110 GTK_WINDOW(compose->window));
9112 optmenu = GTK_COMBO_BOX(attach_prop.encoding_optmenu);
9113 if (ainfo->encoding == ENC_UNKNOWN)
9114 combobox_select_by_data(optmenu, ENC_BASE64);
9116 combobox_select_by_data(optmenu, ainfo->encoding);
9118 gtk_entry_set_text(GTK_ENTRY(attach_prop.mimetype_entry),
9119 ainfo->content_type ? ainfo->content_type : "");
9120 gtk_entry_set_text(GTK_ENTRY(attach_prop.path_entry),
9121 ainfo->file ? ainfo->file : "");
9122 gtk_entry_set_text(GTK_ENTRY(attach_prop.filename_entry),
9123 ainfo->name ? ainfo->name : "");
9126 const gchar *entry_text;
9128 gchar *cnttype = NULL;
9135 gtk_widget_hide(attach_prop.window);
9136 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), FALSE);
9141 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.mimetype_entry));
9142 if (*entry_text != '\0') {
9145 text = g_strstrip(g_strdup(entry_text));
9146 if ((p = strchr(text, '/')) && !strchr(p + 1, '/')) {
9147 cnttype = g_strdup(text);
9150 alertpanel_error(_("Invalid MIME type."));
9156 ainfo->encoding = combobox_get_active_data(optmenu);
9158 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.path_entry));
9159 if (*entry_text != '\0') {
9160 if (is_file_exist(entry_text) &&
9161 (size = get_file_size(entry_text)) > 0)
9162 file = g_strdup(entry_text);
9165 (_("File doesn't exist or is empty."));
9171 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.filename_entry));
9172 if (*entry_text != '\0') {
9173 g_free(ainfo->name);
9174 ainfo->name = g_strdup(entry_text);
9178 g_free(ainfo->content_type);
9179 ainfo->content_type = cnttype;
9182 g_free(ainfo->file);
9186 ainfo->size = (goffset)size;
9188 /* update tree store */
9189 text = to_human_readable(ainfo->size);
9190 gtk_tree_model_get_iter(model, &iter, path);
9191 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
9192 COL_MIMETYPE, ainfo->content_type,
9194 COL_NAME, ainfo->name,
9195 COL_CHARSET, ainfo->charset,
9201 gtk_tree_path_free(path);
9204 #define SET_LABEL_AND_ENTRY(str, entry, top) \
9206 label = gtk_label_new(str); \
9207 gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), \
9208 GTK_FILL, 0, 0, 0); \
9209 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); \
9211 entry = gtk_entry_new(); \
9212 gtk_table_attach(GTK_TABLE(table), entry, 1, 2, top, (top + 1), \
9213 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); \
9216 static void compose_attach_property_create(gboolean *cancelled)
9222 GtkWidget *mimetype_entry;
9225 GtkListStore *optmenu_menu;
9226 GtkWidget *path_entry;
9227 GtkWidget *filename_entry;
9230 GtkWidget *cancel_btn;
9231 GList *mime_type_list, *strlist;
9234 debug_print("Creating attach_property window...\n");
9236 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose_attach_property");
9237 gtk_widget_set_size_request(window, 480, -1);
9238 gtk_container_set_border_width(GTK_CONTAINER(window), 8);
9239 gtk_window_set_title(GTK_WINDOW(window), _("Properties"));
9240 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
9241 g_signal_connect(G_OBJECT(window), "delete_event",
9242 G_CALLBACK(attach_property_delete_event),
9244 g_signal_connect(G_OBJECT(window), "key_press_event",
9245 G_CALLBACK(attach_property_key_pressed),
9248 vbox = gtk_vbox_new(FALSE, 8);
9249 gtk_container_add(GTK_CONTAINER(window), vbox);
9251 table = gtk_table_new(4, 2, FALSE);
9252 gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
9253 gtk_table_set_row_spacings(GTK_TABLE(table), 8);
9254 gtk_table_set_col_spacings(GTK_TABLE(table), 8);
9256 label = gtk_label_new(_("MIME type"));
9257 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, (0 + 1),
9259 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
9260 #if !GTK_CHECK_VERSION(2, 24, 0)
9261 mimetype_entry = gtk_combo_box_entry_new_text();
9263 mimetype_entry = gtk_combo_box_text_new_with_entry();
9265 gtk_table_attach(GTK_TABLE(table), mimetype_entry, 1, 2, 0, (0 + 1),
9266 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
9268 /* stuff with list */
9269 mime_type_list = procmime_get_mime_type_list();
9271 for (; mime_type_list != NULL; mime_type_list = mime_type_list->next) {
9272 MimeType *type = (MimeType *) mime_type_list->data;
9275 tmp = g_strdup_printf("%s/%s", type->type, type->sub_type);
9277 if (g_list_find_custom(strlist, tmp, (GCompareFunc)strcmp2))
9280 strlist = g_list_insert_sorted(strlist, (gpointer)tmp,
9281 (GCompareFunc)strcmp2);
9284 for (mime_type_list = strlist; mime_type_list != NULL;
9285 mime_type_list = mime_type_list->next) {
9286 #if !GTK_CHECK_VERSION(2, 24, 0)
9287 gtk_combo_box_append_text(GTK_COMBO_BOX(mimetype_entry), mime_type_list->data);
9289 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(mimetype_entry), mime_type_list->data);
9291 g_free(mime_type_list->data);
9293 g_list_free(strlist);
9294 gtk_combo_box_set_active(GTK_COMBO_BOX(mimetype_entry), 0);
9295 mimetype_entry = gtk_bin_get_child(GTK_BIN((mimetype_entry)));
9297 label = gtk_label_new(_("Encoding"));
9298 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2,
9300 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
9302 hbox = gtk_hbox_new(FALSE, 0);
9303 gtk_table_attach(GTK_TABLE(table), hbox, 1, 2, 1, 2,
9304 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
9306 optmenu = gtkut_sc_combobox_create(NULL, TRUE);
9307 optmenu_menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
9309 COMBOBOX_ADD(optmenu_menu, "7bit", ENC_7BIT);
9310 COMBOBOX_ADD(optmenu_menu, "8bit", ENC_8BIT);
9311 COMBOBOX_ADD(optmenu_menu, "quoted-printable", ENC_QUOTED_PRINTABLE);
9312 COMBOBOX_ADD(optmenu_menu, "base64", ENC_BASE64);
9313 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), 0);
9315 gtk_box_pack_start(GTK_BOX(hbox), optmenu, TRUE, TRUE, 0);
9317 SET_LABEL_AND_ENTRY(_("Path"), path_entry, 2);
9318 SET_LABEL_AND_ENTRY(_("File name"), filename_entry, 3);
9320 gtkut_stock_button_set_create(&hbbox, &cancel_btn, GTK_STOCK_CANCEL,
9321 &ok_btn, GTK_STOCK_OK,
9323 gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
9324 gtk_widget_grab_default(ok_btn);
9326 g_signal_connect(G_OBJECT(ok_btn), "clicked",
9327 G_CALLBACK(attach_property_ok),
9329 g_signal_connect(G_OBJECT(cancel_btn), "clicked",
9330 G_CALLBACK(attach_property_cancel),
9333 gtk_widget_show_all(vbox);
9335 attach_prop.window = window;
9336 attach_prop.mimetype_entry = mimetype_entry;
9337 attach_prop.encoding_optmenu = optmenu;
9338 attach_prop.path_entry = path_entry;
9339 attach_prop.filename_entry = filename_entry;
9340 attach_prop.ok_btn = ok_btn;
9341 attach_prop.cancel_btn = cancel_btn;
9344 #undef SET_LABEL_AND_ENTRY
9346 static void attach_property_ok(GtkWidget *widget, gboolean *cancelled)
9352 static void attach_property_cancel(GtkWidget *widget, gboolean *cancelled)
9358 static gint attach_property_delete_event(GtkWidget *widget, GdkEventAny *event,
9359 gboolean *cancelled)
9367 static gboolean attach_property_key_pressed(GtkWidget *widget,
9369 gboolean *cancelled)
9371 if (event && event->keyval == GDK_KEY_Escape) {
9375 if (event && event->keyval == GDK_KEY_Return) {
9383 static void compose_exec_ext_editor(Compose *compose)
9388 GdkNativeWindow socket_wid = 0;
9392 tmp = g_strdup_printf("%s%ctmpmsg.%p", get_tmp_dir(),
9393 G_DIR_SEPARATOR, compose);
9395 if (compose_get_ext_editor_uses_socket()) {
9396 /* Only allow one socket */
9397 if (compose->exteditor_socket != NULL) {
9398 if (gtk_widget_is_focus(compose->exteditor_socket)) {
9399 /* Move the focus off of the socket */
9400 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9405 /* Create the receiving GtkSocket */
9406 socket = gtk_socket_new ();
9407 g_signal_connect (GTK_OBJECT(socket), "plug-removed",
9408 G_CALLBACK(compose_ext_editor_plug_removed_cb),
9410 gtk_box_pack_start(GTK_BOX(compose->edit_vbox), socket, TRUE, TRUE, 0);
9411 gtk_widget_set_size_request(socket, prefs_common.compose_width, -1);
9412 /* Realize the socket so that we can use its ID */
9413 gtk_widget_realize(socket);
9414 socket_wid = gtk_socket_get_id(GTK_SOCKET (socket));
9415 compose->exteditor_socket = socket;
9418 if (pipe(pipe_fds) < 0) {
9424 if ((pid = fork()) < 0) {
9431 /* close the write side of the pipe */
9434 compose->exteditor_file = g_strdup(tmp);
9435 compose->exteditor_pid = pid;
9437 compose_set_ext_editor_sensitive(compose, FALSE);
9440 compose->exteditor_ch = g_io_channel_unix_new(pipe_fds[0]);
9442 compose->exteditor_ch = g_io_channel_win32_new_fd(pipe_fds[0]);
9444 compose->exteditor_tag = g_io_add_watch(compose->exteditor_ch,
9448 } else { /* process-monitoring process */
9454 /* close the read side of the pipe */
9457 if (compose_write_body_to_file(compose, tmp) < 0) {
9458 fd_write_all(pipe_fds[1], "2\n", 2);
9462 pid_ed = compose_exec_ext_editor_real(tmp, socket_wid);
9464 fd_write_all(pipe_fds[1], "1\n", 2);
9468 /* wait until editor is terminated */
9469 waitpid(pid_ed, NULL, 0);
9471 fd_write_all(pipe_fds[1], "0\n", 2);
9478 #endif /* G_OS_UNIX */
9481 static gboolean compose_can_autosave(Compose *compose)
9483 if (compose->privacy_system && compose->use_encryption)
9484 return prefs_common.autosave && prefs_common.autosave_encrypted;
9486 return prefs_common.autosave;
9490 static gboolean compose_get_ext_editor_cmd_valid()
9492 gboolean has_s = FALSE;
9493 gboolean has_w = FALSE;
9494 const gchar *p = prefs_common_get_ext_editor_cmd();
9497 while ((p = strchr(p, '%'))) {
9503 } else if (*p == 'w') {
9514 static gint compose_exec_ext_editor_real(const gchar *file, GdkNativeWindow socket_wid)
9521 cm_return_val_if_fail(file != NULL, -1);
9523 if ((pid = fork()) < 0) {
9528 if (pid != 0) return pid;
9530 /* grandchild process */
9532 if (setpgid(0, getppid()))
9535 if (compose_get_ext_editor_cmd_valid()) {
9536 if (compose_get_ext_editor_uses_socket()) {
9537 p = g_strdup(prefs_common_get_ext_editor_cmd());
9538 s = strstr(p, "%w");
9540 if (strstr(p, "%s") < s)
9541 buf = g_strdup_printf(p, file, socket_wid);
9543 buf = g_strdup_printf(p, socket_wid, file);
9546 buf = g_strdup_printf(prefs_common_get_ext_editor_cmd(), file);
9549 if (prefs_common_get_ext_editor_cmd())
9550 g_warning("External editor command-line is invalid: '%s'",
9551 prefs_common_get_ext_editor_cmd());
9552 buf = g_strdup_printf(DEFAULT_EDITOR_CMD, file);
9555 cmdline = strsplit_with_quote(buf, " ", 0);
9557 execvp(cmdline[0], cmdline);
9560 g_strfreev(cmdline);
9565 static gboolean compose_ext_editor_kill(Compose *compose)
9567 pid_t pgid = compose->exteditor_pid * -1;
9570 ret = kill(pgid, 0);
9572 if (ret == 0 || (ret == -1 && EPERM == errno)) {
9576 msg = g_strdup_printf
9577 (_("The external editor is still working.\n"
9578 "Force terminating the process?\n"
9579 "process group id: %d"), -pgid);
9580 val = alertpanel_full(_("Notice"), msg, GTK_STOCK_NO, GTK_STOCK_YES,
9581 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
9585 if (val == G_ALERTALTERNATE) {
9586 g_source_remove(compose->exteditor_tag);
9587 g_io_channel_shutdown(compose->exteditor_ch,
9589 g_io_channel_unref(compose->exteditor_ch);
9591 if (kill(pgid, SIGTERM) < 0) perror("kill");
9592 waitpid(compose->exteditor_pid, NULL, 0);
9594 g_warning("Terminated process group id: %d. "
9595 "Temporary file: %s", -pgid, compose->exteditor_file);
9597 compose_set_ext_editor_sensitive(compose, TRUE);
9599 g_free(compose->exteditor_file);
9600 compose->exteditor_file = NULL;
9601 compose->exteditor_pid = -1;
9602 compose->exteditor_ch = NULL;
9603 compose->exteditor_tag = -1;
9611 static gboolean compose_input_cb(GIOChannel *source, GIOCondition condition,
9615 Compose *compose = (Compose *)data;
9618 debug_print("Compose: input from monitoring process\n");
9620 if (g_io_channel_read_chars(source, buf, sizeof(buf), &bytes_read, NULL) != G_IO_STATUS_NORMAL) {
9625 g_io_channel_shutdown(source, FALSE, NULL);
9626 g_io_channel_unref(source);
9628 waitpid(compose->exteditor_pid, NULL, 0);
9630 if (buf[0] == '0') { /* success */
9631 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9632 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
9633 GtkTextIter start, end;
9636 gtk_text_buffer_set_text(buffer, "", -1);
9637 compose_insert_file(compose, compose->exteditor_file);
9638 compose_changed_cb(NULL, compose);
9640 /* Check if we should save the draft or not */
9641 if (compose_can_autosave(compose))
9642 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
9644 if (claws_unlink(compose->exteditor_file) < 0)
9645 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9647 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
9648 gtk_text_buffer_get_start_iter(buffer, &start);
9649 gtk_text_buffer_get_end_iter(buffer, &end);
9650 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
9651 if (chars && strlen(chars) > 0)
9652 compose->modified = TRUE;
9654 } else if (buf[0] == '1') { /* failed */
9655 g_warning("Couldn't exec external editor");
9656 if (claws_unlink(compose->exteditor_file) < 0)
9657 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9658 } else if (buf[0] == '2') {
9659 g_warning("Couldn't write to file");
9660 } else if (buf[0] == '3') {
9661 g_warning("Pipe read failed");
9664 compose_set_ext_editor_sensitive(compose, TRUE);
9666 g_free(compose->exteditor_file);
9667 compose->exteditor_file = NULL;
9668 compose->exteditor_pid = -1;
9669 compose->exteditor_ch = NULL;
9670 compose->exteditor_tag = -1;
9671 if (compose->exteditor_socket) {
9672 gtk_widget_destroy(compose->exteditor_socket);
9673 compose->exteditor_socket = NULL;
9680 static char *ext_editor_menu_entries[] = {
9681 "Menu/Message/Send",
9682 "Menu/Message/SendLater",
9683 "Menu/Message/InsertFile",
9684 "Menu/Message/InsertSig",
9685 "Menu/Message/ReplaceSig",
9686 "Menu/Message/Save",
9687 "Menu/Message/Print",
9692 "Menu/Tools/ShowRuler",
9693 "Menu/Tools/Actions",
9698 static void compose_set_ext_editor_sensitive(Compose *compose,
9703 for (i = 0; ext_editor_menu_entries[i]; ++i) {
9704 cm_menu_set_sensitive_full(compose->ui_manager,
9705 ext_editor_menu_entries[i], sensitive);
9708 if (compose_get_ext_editor_uses_socket()) {
9710 if (compose->exteditor_socket)
9711 gtk_widget_hide(compose->exteditor_socket);
9712 gtk_widget_show(compose->scrolledwin);
9713 if (prefs_common.show_ruler)
9714 gtk_widget_show(compose->ruler_hbox);
9715 /* Fix the focus, as it doesn't go anywhere when the
9716 * socket is hidden or destroyed */
9717 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9719 g_assert (compose->exteditor_socket != NULL);
9720 /* Fix the focus, as it doesn't go anywhere when the
9721 * edit box is hidden */
9722 if (gtk_widget_is_focus(compose->text))
9723 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9724 gtk_widget_hide(compose->scrolledwin);
9725 gtk_widget_hide(compose->ruler_hbox);
9726 gtk_widget_show(compose->exteditor_socket);
9729 gtk_widget_set_sensitive(compose->text, sensitive);
9731 if (compose->toolbar->send_btn)
9732 gtk_widget_set_sensitive(compose->toolbar->send_btn, sensitive);
9733 if (compose->toolbar->sendl_btn)
9734 gtk_widget_set_sensitive(compose->toolbar->sendl_btn, sensitive);
9735 if (compose->toolbar->draft_btn)
9736 gtk_widget_set_sensitive(compose->toolbar->draft_btn, sensitive);
9737 if (compose->toolbar->insert_btn)
9738 gtk_widget_set_sensitive(compose->toolbar->insert_btn, sensitive);
9739 if (compose->toolbar->sig_btn)
9740 gtk_widget_set_sensitive(compose->toolbar->sig_btn, sensitive);
9741 if (compose->toolbar->exteditor_btn)
9742 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, sensitive);
9743 if (compose->toolbar->linewrap_current_btn)
9744 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, sensitive);
9745 if (compose->toolbar->linewrap_all_btn)
9746 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, sensitive);
9749 static gboolean compose_get_ext_editor_uses_socket()
9751 return (prefs_common_get_ext_editor_cmd() &&
9752 strstr(prefs_common_get_ext_editor_cmd(), "%w"));
9755 static gboolean compose_ext_editor_plug_removed_cb(GtkSocket *socket, Compose *compose)
9757 compose->exteditor_socket = NULL;
9758 /* returning FALSE allows destruction of the socket */
9761 #endif /* G_OS_UNIX */
9764 * compose_undo_state_changed:
9766 * Change the sensivity of the menuentries undo and redo
9768 static void compose_undo_state_changed(UndoMain *undostruct, gint undo_state,
9769 gint redo_state, gpointer data)
9771 Compose *compose = (Compose *)data;
9773 switch (undo_state) {
9774 case UNDO_STATE_TRUE:
9775 if (!undostruct->undo_state) {
9776 undostruct->undo_state = TRUE;
9777 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", TRUE);
9780 case UNDO_STATE_FALSE:
9781 if (undostruct->undo_state) {
9782 undostruct->undo_state = FALSE;
9783 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
9786 case UNDO_STATE_UNCHANGED:
9788 case UNDO_STATE_REFRESH:
9789 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", undostruct->undo_state);
9792 g_warning("Undo state not recognized");
9796 switch (redo_state) {
9797 case UNDO_STATE_TRUE:
9798 if (!undostruct->redo_state) {
9799 undostruct->redo_state = TRUE;
9800 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", TRUE);
9803 case UNDO_STATE_FALSE:
9804 if (undostruct->redo_state) {
9805 undostruct->redo_state = FALSE;
9806 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
9809 case UNDO_STATE_UNCHANGED:
9811 case UNDO_STATE_REFRESH:
9812 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", undostruct->redo_state);
9815 g_warning("Redo state not recognized");
9820 /* callback functions */
9822 static void compose_notebook_size_alloc(GtkNotebook *notebook,
9823 GtkAllocation *allocation,
9826 prefs_common.compose_notebook_height = gtk_paned_get_position(paned);
9829 /* compose_edit_size_alloc() - called when resized. don't know whether Gtk
9830 * includes "non-client" (windows-izm) in calculation, so this calculation
9831 * may not be accurate.
9833 static gboolean compose_edit_size_alloc(GtkEditable *widget,
9834 GtkAllocation *allocation,
9835 GtkSHRuler *shruler)
9837 if (prefs_common.show_ruler) {
9838 gint char_width = 0, char_height = 0;
9839 gint line_width_in_chars;
9841 gtkut_get_font_size(GTK_WIDGET(widget),
9842 &char_width, &char_height);
9843 line_width_in_chars =
9844 (allocation->width - allocation->x) / char_width;
9846 /* got the maximum */
9847 gtk_shruler_set_range(GTK_SHRULER(shruler),
9848 0.0, line_width_in_chars, 0);
9857 ComposePrefType type;
9858 gboolean entry_marked;
9861 static void account_activated(GtkComboBox *optmenu, gpointer data)
9863 Compose *compose = (Compose *)data;
9866 gchar *folderidentifier;
9867 gint account_id = 0;
9870 GSList *list, *saved_list = NULL;
9871 HeaderEntryState *state;
9873 /* Get ID of active account in the combo box */
9874 menu = gtk_combo_box_get_model(optmenu);
9875 cm_return_if_fail(gtk_combo_box_get_active_iter(optmenu, &iter));
9876 gtk_tree_model_get(menu, &iter, 1, &account_id, -1);
9878 ac = account_find_from_id(account_id);
9879 cm_return_if_fail(ac != NULL);
9881 if (ac != compose->account) {
9882 compose_select_account(compose, ac, FALSE);
9884 for (list = compose->header_list; list; list = list->next) {
9885 ComposeHeaderEntry *hentry=(ComposeHeaderEntry *)list->data;
9887 if (hentry->type == PREF_ACCOUNT || !list->next) {
9888 compose_destroy_headerentry(compose, hentry);
9891 state = g_malloc0(sizeof(HeaderEntryState));
9892 state->header = gtk_editable_get_chars(GTK_EDITABLE(
9893 gtk_bin_get_child(GTK_BIN(hentry->combo))), 0, -1);
9894 state->entry = gtk_editable_get_chars(
9895 GTK_EDITABLE(hentry->entry), 0, -1);
9896 state->type = hentry->type;
9898 saved_list = g_slist_append(saved_list, state);
9899 compose_destroy_headerentry(compose, hentry);
9902 compose->header_last = NULL;
9903 g_slist_free(compose->header_list);
9904 compose->header_list = NULL;
9905 compose->header_nextrow = 1;
9906 compose_create_header_entry(compose);
9908 if (ac->set_autocc && ac->auto_cc)
9909 compose_entry_append(compose, ac->auto_cc,
9910 COMPOSE_CC, PREF_ACCOUNT);
9911 if (ac->set_autobcc && ac->auto_bcc)
9912 compose_entry_append(compose, ac->auto_bcc,
9913 COMPOSE_BCC, PREF_ACCOUNT);
9914 if (ac->set_autoreplyto && ac->auto_replyto)
9915 compose_entry_append(compose, ac->auto_replyto,
9916 COMPOSE_REPLYTO, PREF_ACCOUNT);
9918 for (list = saved_list; list; list = list->next) {
9919 state = (HeaderEntryState *) list->data;
9921 compose_add_header_entry(compose, state->header,
9922 state->entry, state->type);
9924 g_free(state->header);
9925 g_free(state->entry);
9928 g_slist_free(saved_list);
9930 combobox_select_by_data(GTK_COMBO_BOX(compose->header_last->combo),
9931 (ac->protocol == A_NNTP) ?
9932 COMPOSE_NEWSGROUPS : COMPOSE_TO);
9935 /* Set message save folder */
9936 if (account_get_special_folder(compose->account, F_OUTBOX)) {
9937 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
9939 g_signal_connect(G_OBJECT(compose->savemsg_checkbtn), "toggled",
9940 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
9942 compose_set_save_to(compose, NULL);
9943 if (account_get_special_folder(compose->account, F_OUTBOX)) {
9944 folderidentifier = folder_item_get_identifier(account_get_special_folder
9945 (compose->account, F_OUTBOX));
9946 compose_set_save_to(compose, folderidentifier);
9947 g_free(folderidentifier);
9951 static void attach_selected(GtkTreeView *tree_view, GtkTreePath *tree_path,
9952 GtkTreeViewColumn *column, Compose *compose)
9954 compose_attach_property(NULL, compose);
9957 static gboolean attach_button_pressed(GtkWidget *widget, GdkEventButton *event,
9960 Compose *compose = (Compose *)data;
9961 GtkTreeSelection *attach_selection;
9962 gint attach_nr_selected;
9965 if (!event) return FALSE;
9967 if (event->button == 3) {
9968 attach_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
9969 attach_nr_selected = gtk_tree_selection_count_selected_rows(attach_selection);
9971 /* If no rows, or just one row is selected, right-click should
9972 * open menu relevant to the row being right-clicked on. We
9973 * achieve that by selecting the clicked row first. If more
9974 * than one row is selected, we shouldn't modify the selection,
9975 * as user may want to remove selected rows (attachments). */
9976 if (attach_nr_selected < 2) {
9977 gtk_tree_selection_unselect_all(attach_selection);
9978 attach_nr_selected = 0;
9979 gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget),
9980 event->x, event->y, &path, NULL, NULL, NULL);
9982 gtk_tree_selection_select_path(attach_selection, path);
9983 gtk_tree_path_free(path);
9984 attach_nr_selected++;
9988 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", (attach_nr_selected > 0));
9989 /* Properties menu item makes no sense with more than one row
9990 * selected, the properties dialog can only edit one attachment. */
9991 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", (attach_nr_selected == 1));
9993 gtk_menu_popup(GTK_MENU(compose->popupmenu), NULL, NULL,
9994 NULL, NULL, event->button, event->time);
10001 static gboolean attach_key_pressed(GtkWidget *widget, GdkEventKey *event,
10004 Compose *compose = (Compose *)data;
10006 if (!event) return FALSE;
10008 switch (event->keyval) {
10009 case GDK_KEY_Delete:
10010 compose_attach_remove_selected(NULL, compose);
10016 static void compose_allow_user_actions (Compose *compose, gboolean allow)
10018 toolbar_comp_set_sensitive(compose, allow);
10019 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message", allow);
10020 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", allow);
10022 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", allow);
10024 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", allow);
10025 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools", allow);
10026 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Help", allow);
10028 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), allow);
10032 static void compose_send_cb(GtkAction *action, gpointer data)
10034 Compose *compose = (Compose *)data;
10037 if (compose->exteditor_tag != -1) {
10038 debug_print("ignoring send: external editor still open\n");
10042 if (prefs_common.work_offline &&
10043 !inc_offline_should_override(TRUE,
10044 _("Claws Mail needs network access in order "
10045 "to send this email.")))
10048 if (compose->draft_timeout_tag >= 0) { /* CLAWS: disable draft timeout */
10049 g_source_remove(compose->draft_timeout_tag);
10050 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
10053 compose_send(compose);
10056 static void compose_send_later_cb(GtkAction *action, gpointer data)
10058 Compose *compose = (Compose *)data;
10062 compose_allow_user_actions(compose, FALSE);
10063 val = compose_queue_sub(compose, NULL, NULL, NULL, TRUE, TRUE);
10064 compose_allow_user_actions(compose, TRUE);
10068 compose_close(compose);
10069 } else if (val == -1) {
10070 alertpanel_error(_("Could not queue message."));
10071 } else if (val == -2) {
10072 alertpanel_error(_("Could not queue message:\n\n%s."), g_strerror(errno));
10073 } else if (val == -3) {
10074 if (privacy_peek_error())
10075 alertpanel_error(_("Could not queue message for sending:\n\n"
10076 "Signature failed: %s"), privacy_get_error());
10077 } else if (val == -4) {
10078 alertpanel_error(_("Could not queue message for sending:\n\n"
10079 "Charset conversion failed."));
10080 } else if (val == -5) {
10081 alertpanel_error(_("Could not queue message for sending:\n\n"
10082 "Couldn't get recipient encryption key."));
10083 } else if (val == -6) {
10086 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
10089 #define DRAFTED_AT_EXIT "drafted_at_exit"
10090 static void compose_register_draft(MsgInfo *info)
10092 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10093 DRAFTED_AT_EXIT, NULL);
10094 FILE *fp = g_fopen(filepath, "ab");
10097 fprintf(fp, "%s\t%d\n", folder_item_get_identifier(info->folder),
10105 gboolean compose_draft (gpointer data, guint action)
10107 Compose *compose = (Compose *)data;
10112 MsgFlags flag = {0, 0};
10113 static gboolean lock = FALSE;
10114 MsgInfo *newmsginfo;
10116 gboolean target_locked = FALSE;
10117 gboolean err = FALSE;
10119 if (lock) return FALSE;
10121 if (compose->sending)
10124 draft = account_get_special_folder(compose->account, F_DRAFT);
10125 cm_return_val_if_fail(draft != NULL, FALSE);
10127 if (!g_mutex_trylock(compose->mutex)) {
10128 /* we don't want to lock the mutex once it's available,
10129 * because as the only other part of compose.c locking
10130 * it is compose_close - which means once unlocked,
10131 * the compose struct will be freed */
10132 debug_print("couldn't lock mutex, probably sending\n");
10138 tmp = g_strdup_printf("%s%cdraft.%p", get_tmp_dir(),
10139 G_DIR_SEPARATOR, compose);
10140 if ((fp = g_fopen(tmp, "wb")) == NULL) {
10141 FILE_OP_ERROR(tmp, "fopen");
10145 /* chmod for security */
10146 if (change_file_mode_rw(fp, tmp) < 0) {
10147 FILE_OP_ERROR(tmp, "chmod");
10148 g_warning("can't change file mode");
10151 /* Save draft infos */
10152 err |= (fprintf(fp, "X-Claws-Account-Id:%d\n", compose->account->account_id) < 0);
10153 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
10155 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
10156 gchar *savefolderid;
10158 savefolderid = compose_get_save_to(compose);
10159 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
10160 g_free(savefolderid);
10162 if (compose->return_receipt) {
10163 err |= (fprintf(fp, "RRCPT:1\n") < 0);
10165 if (compose->privacy_system) {
10166 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
10167 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
10168 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
10171 /* Message-ID of message replying to */
10172 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
10173 gchar *folderid = NULL;
10175 if (compose->replyinfo->folder)
10176 folderid = folder_item_get_identifier(compose->replyinfo->folder);
10177 if (folderid == NULL)
10178 folderid = g_strdup("NULL");
10180 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
10183 /* Message-ID of message forwarding to */
10184 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
10185 gchar *folderid = NULL;
10187 if (compose->fwdinfo->folder)
10188 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
10189 if (folderid == NULL)
10190 folderid = g_strdup("NULL");
10192 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
10196 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
10197 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
10199 sheaders = compose_get_manual_headers_info(compose);
10200 err |= (fprintf(fp, "X-Claws-Manual-Headers:%s\n", sheaders) < 0);
10203 /* end of headers */
10204 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
10211 if (compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_STORE, action != COMPOSE_AUTO_SAVE) < 0) {
10215 if (fclose(fp) == EOF) {
10219 flag.perm_flags = MSG_NEW|MSG_UNREAD;
10220 if (compose->targetinfo) {
10221 target_locked = MSG_IS_LOCKED(compose->targetinfo->flags);
10223 flag.perm_flags |= MSG_LOCKED;
10225 flag.tmp_flags = MSG_DRAFT;
10227 folder_item_scan(draft);
10228 if ((msgnum = folder_item_add_msg(draft, tmp, &flag, TRUE)) < 0) {
10229 MsgInfo *tmpinfo = NULL;
10230 debug_print("didn't get msgnum after adding draft [%s]\n", compose->msgid?compose->msgid:"no msgid");
10231 if (compose->msgid) {
10232 tmpinfo = folder_item_get_msginfo_by_msgid(draft, compose->msgid);
10235 msgnum = tmpinfo->msgnum;
10236 procmsg_msginfo_free(&tmpinfo);
10237 debug_print("got draft msgnum %d from scanning\n", msgnum);
10239 debug_print("didn't get draft msgnum after scanning\n");
10242 debug_print("got draft msgnum %d from adding\n", msgnum);
10248 if (action != COMPOSE_AUTO_SAVE) {
10249 if (action != COMPOSE_DRAFT_FOR_EXIT)
10250 alertpanel_error(_("Could not save draft."));
10253 gtkut_window_popup(compose->window);
10254 val = alertpanel_full(_("Could not save draft"),
10255 _("Could not save draft.\n"
10256 "Do you want to cancel exit or discard this email?"),
10257 _("_Cancel exit"), _("_Discard email"), NULL,
10258 FALSE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
10259 if (val == G_ALERTALTERNATE) {
10261 g_mutex_unlock(compose->mutex); /* must be done before closing */
10262 compose_close(compose);
10266 g_mutex_unlock(compose->mutex); /* must be done before closing */
10275 if (compose->mode == COMPOSE_REEDIT) {
10276 compose_remove_reedit_target(compose, TRUE);
10279 newmsginfo = folder_item_get_msginfo(draft, msgnum);
10282 procmsg_msginfo_unset_flags(newmsginfo, ~0, ~0);
10284 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD|MSG_LOCKED, MSG_DRAFT);
10286 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD, MSG_DRAFT);
10287 if (compose_use_attach(compose) && action != COMPOSE_AUTO_SAVE)
10288 procmsg_msginfo_set_flags(newmsginfo, 0,
10289 MSG_HAS_ATTACHMENT);
10291 if (action == COMPOSE_DRAFT_FOR_EXIT) {
10292 compose_register_draft(newmsginfo);
10294 procmsg_msginfo_free(&newmsginfo);
10297 folder_item_scan(draft);
10299 if (action == COMPOSE_QUIT_EDITING || action == COMPOSE_DRAFT_FOR_EXIT) {
10301 g_mutex_unlock(compose->mutex); /* must be done before closing */
10302 compose_close(compose);
10308 path = folder_item_fetch_msg(draft, msgnum);
10309 if (path == NULL) {
10310 debug_print("can't fetch %s:%d\n", draft->path, msgnum);
10313 if (g_stat(path, &s) < 0) {
10314 FILE_OP_ERROR(path, "stat");
10320 procmsg_msginfo_free(&(compose->targetinfo));
10321 compose->targetinfo = procmsg_msginfo_new();
10322 compose->targetinfo->msgnum = msgnum;
10323 compose->targetinfo->size = (goffset)s.st_size;
10324 compose->targetinfo->mtime = s.st_mtime;
10325 compose->targetinfo->folder = draft;
10327 procmsg_msginfo_set_flags(compose->targetinfo, MSG_LOCKED, 0);
10328 compose->mode = COMPOSE_REEDIT;
10330 if (action == COMPOSE_AUTO_SAVE) {
10331 compose->autosaved_draft = compose->targetinfo;
10333 compose->modified = FALSE;
10334 compose_set_title(compose);
10338 g_mutex_unlock(compose->mutex);
10342 void compose_clear_exit_drafts(void)
10344 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10345 DRAFTED_AT_EXIT, NULL);
10346 if (is_file_exist(filepath))
10347 claws_unlink(filepath);
10352 void compose_reopen_exit_drafts(void)
10354 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10355 DRAFTED_AT_EXIT, NULL);
10356 FILE *fp = g_fopen(filepath, "rb");
10360 while (fgets(buf, sizeof(buf), fp)) {
10361 gchar **parts = g_strsplit(buf, "\t", 2);
10362 const gchar *folder = parts[0];
10363 int msgnum = parts[1] ? atoi(parts[1]):-1;
10365 if (folder && *folder && msgnum > -1) {
10366 FolderItem *item = folder_find_item_from_identifier(folder);
10367 MsgInfo *info = folder_item_get_msginfo(item, msgnum);
10369 compose_reedit(info, FALSE);
10376 compose_clear_exit_drafts();
10379 static void compose_save_cb(GtkAction *action, gpointer data)
10381 Compose *compose = (Compose *)data;
10382 compose_draft(compose, COMPOSE_KEEP_EDITING);
10383 compose->rmode = COMPOSE_REEDIT;
10386 void compose_attach_from_list(Compose *compose, GList *file_list, gboolean free_data)
10388 if (compose && file_list) {
10391 for ( tmp = file_list; tmp; tmp = tmp->next) {
10392 gchar *file = (gchar *) tmp->data;
10393 gchar *utf8_filename = conv_filename_to_utf8(file);
10394 compose_attach_append(compose, file, utf8_filename, NULL, NULL);
10395 compose_changed_cb(NULL, compose);
10400 g_free(utf8_filename);
10405 static void compose_attach_cb(GtkAction *action, gpointer data)
10407 Compose *compose = (Compose *)data;
10410 if (compose->redirect_filename != NULL)
10413 /* Set focus_window properly, in case we were called via popup menu,
10414 * which unsets it (via focus_out_event callback on compose window). */
10415 manage_window_focus_in(compose->window, NULL, NULL);
10417 file_list = filesel_select_multiple_files_open(_("Select file"), NULL);
10420 compose_attach_from_list(compose, file_list, TRUE);
10421 g_list_free(file_list);
10425 static void compose_insert_file_cb(GtkAction *action, gpointer data)
10427 Compose *compose = (Compose *)data;
10429 gint files_inserted = 0;
10431 file_list = filesel_select_multiple_files_open(_("Select file"), NULL);
10436 for ( tmp = file_list; tmp; tmp = tmp->next) {
10437 gchar *file = (gchar *) tmp->data;
10438 gchar *filedup = g_strdup(file);
10439 gchar *shortfile = g_path_get_basename(filedup);
10440 ComposeInsertResult res;
10441 /* insert the file if the file is short or if the user confirmed that
10442 he/she wants to insert the large file */
10443 res = compose_insert_file(compose, file);
10444 if (res == COMPOSE_INSERT_READ_ERROR) {
10445 alertpanel_error(_("File '%s' could not be read."), shortfile);
10446 } else if (res == COMPOSE_INSERT_INVALID_CHARACTER) {
10447 alertpanel_error(_("File '%s' contained invalid characters\n"
10448 "for the current encoding, insertion may be incorrect."),
10450 } else if (res == COMPOSE_INSERT_SUCCESS)
10457 g_list_free(file_list);
10461 if (files_inserted > 0 && compose->gtkaspell &&
10462 compose->gtkaspell->check_while_typing)
10463 gtkaspell_highlight_all(compose->gtkaspell);
10467 static void compose_insert_sig_cb(GtkAction *action, gpointer data)
10469 Compose *compose = (Compose *)data;
10471 compose_insert_sig(compose, FALSE);
10474 static void compose_replace_sig_cb(GtkAction *action, gpointer data)
10476 Compose *compose = (Compose *)data;
10478 compose_insert_sig(compose, TRUE);
10481 static gint compose_delete_cb(GtkWidget *widget, GdkEventAny *event,
10485 Compose *compose = (Compose *)data;
10487 gtkut_widget_get_uposition(widget, &x, &y);
10488 if (!compose->batch) {
10489 prefs_common.compose_x = x;
10490 prefs_common.compose_y = y;
10492 if (compose->sending || compose->updating)
10494 compose_close_cb(NULL, compose);
10498 void compose_close_toolbar(Compose *compose)
10500 compose_close_cb(NULL, compose);
10503 static void compose_close_cb(GtkAction *action, gpointer data)
10505 Compose *compose = (Compose *)data;
10509 if (compose->exteditor_tag != -1) {
10510 if (!compose_ext_editor_kill(compose))
10515 if (compose->modified) {
10516 gboolean reedit = (compose->rmode == COMPOSE_REEDIT);
10517 if (!g_mutex_trylock(compose->mutex)) {
10518 /* we don't want to lock the mutex once it's available,
10519 * because as the only other part of compose.c locking
10520 * it is compose_close - which means once unlocked,
10521 * the compose struct will be freed */
10522 debug_print("couldn't lock mutex, probably sending\n");
10526 val = alertpanel(_("Discard message"),
10527 _("This message has been modified. Discard it?"),
10528 _("_Discard"), _("_Save to Drafts"), GTK_STOCK_CANCEL);
10530 val = alertpanel(_("Save changes"),
10531 _("This message has been modified. Save the latest changes?"),
10532 _("_Don't save"), g_strconcat("+", _("_Save to Drafts"), NULL),
10535 g_mutex_unlock(compose->mutex);
10537 case G_ALERTDEFAULT:
10538 if (compose_can_autosave(compose) && !reedit)
10539 compose_remove_draft(compose);
10541 case G_ALERTALTERNATE:
10542 compose_draft(data, COMPOSE_QUIT_EDITING);
10549 compose_close(compose);
10552 static void compose_print_cb(GtkAction *action, gpointer data)
10554 Compose *compose = (Compose *) data;
10556 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
10557 if (compose->targetinfo)
10558 messageview_print(compose->targetinfo, FALSE, -1, -1, 0);
10561 static void compose_set_encoding_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
10563 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
10564 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
10565 Compose *compose = (Compose *) data;
10568 compose->out_encoding = (CharSet)value;
10571 static void compose_address_cb(GtkAction *action, gpointer data)
10573 Compose *compose = (Compose *)data;
10575 #ifndef USE_ALT_ADDRBOOK
10576 addressbook_open(compose);
10578 GError* error = NULL;
10579 addressbook_connect_signals(compose);
10580 addressbook_dbus_open(TRUE, &error);
10582 g_warning("%s", error->message);
10583 g_error_free(error);
10588 static void about_show_cb(GtkAction *action, gpointer data)
10593 static void compose_template_activate_cb(GtkWidget *widget, gpointer data)
10595 Compose *compose = (Compose *)data;
10600 tmpl = g_object_get_data(G_OBJECT(widget), "template");
10601 cm_return_if_fail(tmpl != NULL);
10603 msg = g_strdup_printf(_("Do you want to apply the template '%s'?"),
10605 val = alertpanel(_("Apply template"), msg,
10606 _("_Replace"), _("_Insert"), GTK_STOCK_CANCEL);
10609 if (val == G_ALERTDEFAULT)
10610 compose_template_apply(compose, tmpl, TRUE);
10611 else if (val == G_ALERTALTERNATE)
10612 compose_template_apply(compose, tmpl, FALSE);
10615 static void compose_ext_editor_cb(GtkAction *action, gpointer data)
10617 Compose *compose = (Compose *)data;
10620 if (compose->exteditor_tag != -1) {
10621 debug_print("ignoring open external editor: external editor still open\n");
10625 compose_exec_ext_editor(compose);
10628 static void compose_undo_cb(GtkAction *action, gpointer data)
10630 Compose *compose = (Compose *)data;
10631 gboolean prev_autowrap = compose->autowrap;
10633 compose->autowrap = FALSE;
10634 undo_undo(compose->undostruct);
10635 compose->autowrap = prev_autowrap;
10638 static void compose_redo_cb(GtkAction *action, gpointer data)
10640 Compose *compose = (Compose *)data;
10641 gboolean prev_autowrap = compose->autowrap;
10643 compose->autowrap = FALSE;
10644 undo_redo(compose->undostruct);
10645 compose->autowrap = prev_autowrap;
10648 static void entry_cut_clipboard(GtkWidget *entry)
10650 if (GTK_IS_EDITABLE(entry))
10651 gtk_editable_cut_clipboard (GTK_EDITABLE(entry));
10652 else if (GTK_IS_TEXT_VIEW(entry))
10653 gtk_text_buffer_cut_clipboard(
10654 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10655 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
10659 static void entry_copy_clipboard(GtkWidget *entry)
10661 if (GTK_IS_EDITABLE(entry))
10662 gtk_editable_copy_clipboard (GTK_EDITABLE(entry));
10663 else if (GTK_IS_TEXT_VIEW(entry))
10664 gtk_text_buffer_copy_clipboard(
10665 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10666 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
10669 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry,
10670 gboolean wrap, GdkAtom clip, GtkTextIter *insert_place)
10672 if (GTK_IS_TEXT_VIEW(entry)) {
10673 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10674 GtkTextMark *mark_start = gtk_text_buffer_get_insert(buffer);
10675 GtkTextIter start_iter, end_iter;
10677 gchar *contents = gtk_clipboard_wait_for_text(gtk_clipboard_get(clip));
10679 if (contents == NULL)
10682 /* we shouldn't delete the selection when middle-click-pasting, or we
10683 * can't mid-click-paste our own selection */
10684 if (clip != GDK_SELECTION_PRIMARY) {
10685 undo_paste_clipboard(GTK_TEXT_VIEW(compose->text), compose->undostruct);
10686 gtk_text_buffer_delete_selection(buffer, FALSE, TRUE);
10689 if (insert_place == NULL) {
10690 /* if insert_place isn't specified, insert at the cursor.
10691 * used for Ctrl-V pasting */
10692 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10693 start = gtk_text_iter_get_offset(&start_iter);
10694 gtk_text_buffer_insert(buffer, &start_iter, contents, strlen(contents));
10696 /* if insert_place is specified, paste here.
10697 * used for mid-click-pasting */
10698 start = gtk_text_iter_get_offset(insert_place);
10699 gtk_text_buffer_insert(buffer, insert_place, contents, strlen(contents));
10700 if (prefs_common.primary_paste_unselects)
10701 gtk_text_buffer_select_range(buffer, insert_place, insert_place);
10705 /* paste unwrapped: mark the paste so it's not wrapped later */
10706 end = start + strlen(contents);
10707 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, start);
10708 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, end);
10709 gtk_text_buffer_apply_tag_by_name(buffer, "no_wrap", &start_iter, &end_iter);
10710 } else if (wrap && clip == GDK_SELECTION_PRIMARY) {
10711 /* rewrap paragraph now (after a mid-click-paste) */
10712 mark_start = gtk_text_buffer_get_insert(buffer);
10713 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10714 gtk_text_iter_backward_char(&start_iter);
10715 compose_beautify_paragraph(compose, &start_iter, TRUE);
10717 } else if (GTK_IS_EDITABLE(entry))
10718 gtk_editable_paste_clipboard (GTK_EDITABLE(entry));
10720 compose->modified = TRUE;
10723 static void entry_allsel(GtkWidget *entry)
10725 if (GTK_IS_EDITABLE(entry))
10726 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
10727 else if (GTK_IS_TEXT_VIEW(entry)) {
10728 GtkTextIter startiter, enditer;
10729 GtkTextBuffer *textbuf;
10731 textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10732 gtk_text_buffer_get_start_iter(textbuf, &startiter);
10733 gtk_text_buffer_get_end_iter(textbuf, &enditer);
10735 gtk_text_buffer_move_mark_by_name(textbuf,
10736 "selection_bound", &startiter);
10737 gtk_text_buffer_move_mark_by_name(textbuf,
10738 "insert", &enditer);
10742 static void compose_cut_cb(GtkAction *action, gpointer data)
10744 Compose *compose = (Compose *)data;
10745 if (compose->focused_editable
10746 #ifndef GENERIC_UMPC
10747 && gtk_widget_has_focus(compose->focused_editable)
10750 entry_cut_clipboard(compose->focused_editable);
10753 static void compose_copy_cb(GtkAction *action, gpointer data)
10755 Compose *compose = (Compose *)data;
10756 if (compose->focused_editable
10757 #ifndef GENERIC_UMPC
10758 && gtk_widget_has_focus(compose->focused_editable)
10761 entry_copy_clipboard(compose->focused_editable);
10764 static void compose_paste_cb(GtkAction *action, gpointer data)
10766 Compose *compose = (Compose *)data;
10767 gint prev_autowrap;
10768 GtkTextBuffer *buffer;
10770 if (compose->focused_editable &&
10771 #ifndef GENERIC_UMPC
10772 gtk_widget_has_focus(compose->focused_editable)
10775 entry_paste_clipboard(compose, compose->focused_editable,
10776 prefs_common.linewrap_pastes,
10777 GDK_SELECTION_CLIPBOARD, NULL);
10782 #ifndef GENERIC_UMPC
10783 gtk_widget_has_focus(compose->text) &&
10785 compose->gtkaspell &&
10786 compose->gtkaspell->check_while_typing)
10787 gtkaspell_highlight_all(compose->gtkaspell);
10791 static void compose_paste_as_quote_cb(GtkAction *action, gpointer data)
10793 Compose *compose = (Compose *)data;
10794 gint wrap_quote = prefs_common.linewrap_quote;
10795 if (compose->focused_editable
10796 #ifndef GENERIC_UMPC
10797 && gtk_widget_has_focus(compose->focused_editable)
10800 /* let text_insert() (called directly or at a later time
10801 * after the gtk_editable_paste_clipboard) know that
10802 * text is to be inserted as a quotation. implemented
10803 * by using a simple refcount... */
10804 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data(
10805 G_OBJECT(compose->focused_editable),
10806 "paste_as_quotation"));
10807 g_object_set_data(G_OBJECT(compose->focused_editable),
10808 "paste_as_quotation",
10809 GINT_TO_POINTER(paste_as_quotation + 1));
10810 prefs_common.linewrap_quote = prefs_common.linewrap_pastes;
10811 entry_paste_clipboard(compose, compose->focused_editable,
10812 prefs_common.linewrap_pastes,
10813 GDK_SELECTION_CLIPBOARD, NULL);
10814 prefs_common.linewrap_quote = wrap_quote;
10818 static void compose_paste_no_wrap_cb(GtkAction *action, gpointer data)
10820 Compose *compose = (Compose *)data;
10821 gint prev_autowrap;
10822 GtkTextBuffer *buffer;
10824 if (compose->focused_editable
10825 #ifndef GENERIC_UMPC
10826 && gtk_widget_has_focus(compose->focused_editable)
10829 entry_paste_clipboard(compose, compose->focused_editable, FALSE,
10830 GDK_SELECTION_CLIPBOARD, NULL);
10835 #ifndef GENERIC_UMPC
10836 gtk_widget_has_focus(compose->text) &&
10838 compose->gtkaspell &&
10839 compose->gtkaspell->check_while_typing)
10840 gtkaspell_highlight_all(compose->gtkaspell);
10844 static void compose_paste_wrap_cb(GtkAction *action, gpointer data)
10846 Compose *compose = (Compose *)data;
10847 gint prev_autowrap;
10848 GtkTextBuffer *buffer;
10850 if (compose->focused_editable
10851 #ifndef GENERIC_UMPC
10852 && gtk_widget_has_focus(compose->focused_editable)
10855 entry_paste_clipboard(compose, compose->focused_editable, TRUE,
10856 GDK_SELECTION_CLIPBOARD, NULL);
10861 #ifndef GENERIC_UMPC
10862 gtk_widget_has_focus(compose->text) &&
10864 compose->gtkaspell &&
10865 compose->gtkaspell->check_while_typing)
10866 gtkaspell_highlight_all(compose->gtkaspell);
10870 static void compose_allsel_cb(GtkAction *action, gpointer data)
10872 Compose *compose = (Compose *)data;
10873 if (compose->focused_editable
10874 #ifndef GENERIC_UMPC
10875 && gtk_widget_has_focus(compose->focused_editable)
10878 entry_allsel(compose->focused_editable);
10881 static void textview_move_beginning_of_line (GtkTextView *text)
10883 GtkTextBuffer *buffer;
10887 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10889 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10890 mark = gtk_text_buffer_get_insert(buffer);
10891 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10892 gtk_text_iter_set_line_offset(&ins, 0);
10893 gtk_text_buffer_place_cursor(buffer, &ins);
10896 static void textview_move_forward_character (GtkTextView *text)
10898 GtkTextBuffer *buffer;
10902 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10904 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10905 mark = gtk_text_buffer_get_insert(buffer);
10906 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10907 if (gtk_text_iter_forward_cursor_position(&ins))
10908 gtk_text_buffer_place_cursor(buffer, &ins);
10911 static void textview_move_backward_character (GtkTextView *text)
10913 GtkTextBuffer *buffer;
10917 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10919 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10920 mark = gtk_text_buffer_get_insert(buffer);
10921 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10922 if (gtk_text_iter_backward_cursor_position(&ins))
10923 gtk_text_buffer_place_cursor(buffer, &ins);
10926 static void textview_move_forward_word (GtkTextView *text)
10928 GtkTextBuffer *buffer;
10933 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10935 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10936 mark = gtk_text_buffer_get_insert(buffer);
10937 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10938 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
10939 if (gtk_text_iter_forward_word_ends(&ins, count)) {
10940 gtk_text_iter_backward_word_start(&ins);
10941 gtk_text_buffer_place_cursor(buffer, &ins);
10945 static void textview_move_backward_word (GtkTextView *text)
10947 GtkTextBuffer *buffer;
10951 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10953 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10954 mark = gtk_text_buffer_get_insert(buffer);
10955 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10956 if (gtk_text_iter_backward_word_starts(&ins, 1))
10957 gtk_text_buffer_place_cursor(buffer, &ins);
10960 static void textview_move_end_of_line (GtkTextView *text)
10962 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 if (gtk_text_iter_forward_to_line_end(&ins))
10972 gtk_text_buffer_place_cursor(buffer, &ins);
10975 static void textview_move_next_line (GtkTextView *text)
10977 GtkTextBuffer *buffer;
10982 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10984 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10985 mark = gtk_text_buffer_get_insert(buffer);
10986 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10987 offset = gtk_text_iter_get_line_offset(&ins);
10988 if (gtk_text_iter_forward_line(&ins)) {
10989 gtk_text_iter_set_line_offset(&ins, offset);
10990 gtk_text_buffer_place_cursor(buffer, &ins);
10994 static void textview_move_previous_line (GtkTextView *text)
10996 GtkTextBuffer *buffer;
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);
11006 offset = gtk_text_iter_get_line_offset(&ins);
11007 if (gtk_text_iter_backward_line(&ins)) {
11008 gtk_text_iter_set_line_offset(&ins, offset);
11009 gtk_text_buffer_place_cursor(buffer, &ins);
11013 static void textview_delete_forward_character (GtkTextView *text)
11015 GtkTextBuffer *buffer;
11017 GtkTextIter ins, end_iter;
11019 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11021 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11022 mark = gtk_text_buffer_get_insert(buffer);
11023 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11025 if (gtk_text_iter_forward_char(&end_iter)) {
11026 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11030 static void textview_delete_backward_character (GtkTextView *text)
11032 GtkTextBuffer *buffer;
11034 GtkTextIter ins, end_iter;
11036 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11038 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11039 mark = gtk_text_buffer_get_insert(buffer);
11040 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11042 if (gtk_text_iter_backward_char(&end_iter)) {
11043 gtk_text_buffer_delete(buffer, &end_iter, &ins);
11047 static void textview_delete_forward_word (GtkTextView *text)
11049 GtkTextBuffer *buffer;
11051 GtkTextIter ins, end_iter;
11053 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11055 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11056 mark = gtk_text_buffer_get_insert(buffer);
11057 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11059 if (gtk_text_iter_forward_word_end(&end_iter)) {
11060 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11064 static void textview_delete_backward_word (GtkTextView *text)
11066 GtkTextBuffer *buffer;
11068 GtkTextIter ins, end_iter;
11070 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11072 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11073 mark = gtk_text_buffer_get_insert(buffer);
11074 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11076 if (gtk_text_iter_backward_word_start(&end_iter)) {
11077 gtk_text_buffer_delete(buffer, &end_iter, &ins);
11081 static void textview_delete_line (GtkTextView *text)
11083 GtkTextBuffer *buffer;
11085 GtkTextIter ins, start_iter, end_iter;
11087 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11089 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11090 mark = gtk_text_buffer_get_insert(buffer);
11091 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11094 gtk_text_iter_set_line_offset(&start_iter, 0);
11097 if (gtk_text_iter_ends_line(&end_iter)){
11098 if (!gtk_text_iter_forward_char(&end_iter))
11099 gtk_text_iter_backward_char(&start_iter);
11102 gtk_text_iter_forward_to_line_end(&end_iter);
11103 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
11106 static void textview_delete_to_line_end (GtkTextView *text)
11108 GtkTextBuffer *buffer;
11110 GtkTextIter ins, end_iter;
11112 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11114 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11115 mark = gtk_text_buffer_get_insert(buffer);
11116 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11118 if (gtk_text_iter_ends_line(&end_iter))
11119 gtk_text_iter_forward_char(&end_iter);
11121 gtk_text_iter_forward_to_line_end(&end_iter);
11122 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11125 #define DO_ACTION(name, act) { \
11126 if(!strcmp(name, a_name)) { \
11130 static ComposeCallAdvancedAction compose_call_advanced_action_from_path(GtkAction *action)
11132 const gchar *a_name = gtk_action_get_name(action);
11133 DO_ACTION("Edit/Advanced/BackChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER);
11134 DO_ACTION("Edit/Advanced/ForwChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER);
11135 DO_ACTION("Edit/Advanced/BackWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD);
11136 DO_ACTION("Edit/Advanced/ForwWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD);
11137 DO_ACTION("Edit/Advanced/BegLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE);
11138 DO_ACTION("Edit/Advanced/EndLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE);
11139 DO_ACTION("Edit/Advanced/PrevLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE);
11140 DO_ACTION("Edit/Advanced/NextLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE);
11141 DO_ACTION("Edit/Advanced/DelBackChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER);
11142 DO_ACTION("Edit/Advanced/DelForwChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER);
11143 DO_ACTION("Edit/Advanced/DelBackWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD);
11144 DO_ACTION("Edit/Advanced/DelForwWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD);
11145 DO_ACTION("Edit/Advanced/DelLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE);
11146 DO_ACTION("Edit/Advanced/DelEndLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END);
11147 return COMPOSE_CALL_ADVANCED_ACTION_UNDEFINED;
11150 static void compose_advanced_action_cb(GtkAction *gaction, gpointer data)
11152 Compose *compose = (Compose *)data;
11153 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11154 ComposeCallAdvancedAction action = COMPOSE_CALL_ADVANCED_ACTION_UNDEFINED;
11156 action = compose_call_advanced_action_from_path(gaction);
11159 void (*do_action) (GtkTextView *text);
11160 } action_table[] = {
11161 {textview_move_beginning_of_line},
11162 {textview_move_forward_character},
11163 {textview_move_backward_character},
11164 {textview_move_forward_word},
11165 {textview_move_backward_word},
11166 {textview_move_end_of_line},
11167 {textview_move_next_line},
11168 {textview_move_previous_line},
11169 {textview_delete_forward_character},
11170 {textview_delete_backward_character},
11171 {textview_delete_forward_word},
11172 {textview_delete_backward_word},
11173 {textview_delete_line},
11174 {textview_delete_to_line_end}
11177 if (!gtk_widget_has_focus(GTK_WIDGET(text))) return;
11179 if (action >= COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE &&
11180 action <= COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END) {
11181 if (action_table[action].do_action)
11182 action_table[action].do_action(text);
11184 g_warning("Not implemented yet.");
11188 static void compose_grab_focus_cb(GtkWidget *widget, Compose *compose)
11190 GtkAllocation allocation;
11194 if (GTK_IS_EDITABLE(widget)) {
11195 str = gtk_editable_get_chars(GTK_EDITABLE(widget), 0, -1);
11196 gtk_editable_set_position(GTK_EDITABLE(widget),
11199 if ((parent = gtk_widget_get_parent(widget))
11200 && (parent = gtk_widget_get_parent(parent))
11201 && (parent = gtk_widget_get_parent(parent))) {
11202 if (GTK_IS_SCROLLED_WINDOW(parent)) {
11203 gtk_widget_get_allocation(widget, &allocation);
11204 gint y = allocation.y;
11205 gint height = allocation.height;
11206 GtkAdjustment *shown = gtk_scrolled_window_get_vadjustment
11207 (GTK_SCROLLED_WINDOW(parent));
11209 gfloat value = gtk_adjustment_get_value(shown);
11210 gfloat upper = gtk_adjustment_get_upper(shown);
11211 gfloat page_size = gtk_adjustment_get_page_size(shown);
11212 if (y < (int)value) {
11213 gtk_adjustment_set_value(shown, y - 1);
11215 if ((y + height) > ((int)value + (int)page_size)) {
11216 if ((y - height - 1) < ((int)upper - (int)page_size)) {
11217 gtk_adjustment_set_value(shown,
11218 y + height - (int)page_size - 1);
11220 gtk_adjustment_set_value(shown,
11221 (int)upper - (int)page_size - 1);
11228 if (GTK_IS_EDITABLE(widget) || GTK_IS_TEXT_VIEW(widget))
11229 compose->focused_editable = widget;
11231 #ifdef GENERIC_UMPC
11232 if (GTK_IS_TEXT_VIEW(widget)
11233 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->edit_vbox) {
11234 g_object_ref(compose->notebook);
11235 g_object_ref(compose->edit_vbox);
11236 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
11237 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
11238 gtk_paned_add1(GTK_PANED(compose->paned), compose->edit_vbox);
11239 gtk_paned_add2(GTK_PANED(compose->paned), compose->notebook);
11240 g_object_unref(compose->notebook);
11241 g_object_unref(compose->edit_vbox);
11242 g_signal_handlers_block_by_func(G_OBJECT(widget),
11243 G_CALLBACK(compose_grab_focus_cb),
11245 gtk_widget_grab_focus(widget);
11246 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
11247 G_CALLBACK(compose_grab_focus_cb),
11249 } else if (!GTK_IS_TEXT_VIEW(widget)
11250 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->notebook) {
11251 g_object_ref(compose->notebook);
11252 g_object_ref(compose->edit_vbox);
11253 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
11254 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
11255 gtk_paned_add1(GTK_PANED(compose->paned), compose->notebook);
11256 gtk_paned_add2(GTK_PANED(compose->paned), compose->edit_vbox);
11257 g_object_unref(compose->notebook);
11258 g_object_unref(compose->edit_vbox);
11259 g_signal_handlers_block_by_func(G_OBJECT(widget),
11260 G_CALLBACK(compose_grab_focus_cb),
11262 gtk_widget_grab_focus(widget);
11263 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
11264 G_CALLBACK(compose_grab_focus_cb),
11270 static void compose_changed_cb(GtkTextBuffer *textbuf, Compose *compose)
11272 compose->modified = TRUE;
11273 /* compose_beautify_paragraph(compose, NULL, TRUE); */
11274 #ifndef GENERIC_UMPC
11275 compose_set_title(compose);
11279 static void compose_wrap_cb(GtkAction *action, gpointer data)
11281 Compose *compose = (Compose *)data;
11282 compose_beautify_paragraph(compose, NULL, TRUE);
11285 static void compose_wrap_all_cb(GtkAction *action, gpointer data)
11287 Compose *compose = (Compose *)data;
11288 compose_wrap_all_full(compose, TRUE);
11291 static void compose_find_cb(GtkAction *action, gpointer data)
11293 Compose *compose = (Compose *)data;
11295 message_search_compose(compose);
11298 static void compose_toggle_autowrap_cb(GtkToggleAction *action,
11301 Compose *compose = (Compose *)data;
11302 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11303 if (compose->autowrap)
11304 compose_wrap_all_full(compose, TRUE);
11305 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11308 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
11311 Compose *compose = (Compose *)data;
11312 compose->autoindent = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11315 static void compose_toggle_sign_cb(GtkToggleAction *action, gpointer data)
11317 Compose *compose = (Compose *)data;
11319 compose->use_signing = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11322 static void compose_toggle_encrypt_cb(GtkToggleAction *action, gpointer data)
11324 Compose *compose = (Compose *)data;
11326 compose->use_encryption = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11329 static void activate_privacy_system(Compose *compose, PrefsAccount *account, gboolean warn)
11331 g_free(compose->privacy_system);
11332 g_free(compose->encdata);
11334 compose->privacy_system = g_strdup(account->default_privacy_system);
11335 compose_update_privacy_system_menu_item(compose, warn);
11338 static void compose_toggle_ruler_cb(GtkToggleAction *action, gpointer data)
11340 Compose *compose = (Compose *)data;
11342 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action))) {
11343 gtk_widget_show(compose->ruler_hbox);
11344 prefs_common.show_ruler = TRUE;
11346 gtk_widget_hide(compose->ruler_hbox);
11347 gtk_widget_queue_resize(compose->edit_vbox);
11348 prefs_common.show_ruler = FALSE;
11352 static void compose_attach_drag_received_cb (GtkWidget *widget,
11353 GdkDragContext *context,
11356 GtkSelectionData *data,
11359 gpointer user_data)
11361 Compose *compose = (Compose *)user_data;
11365 type = gtk_selection_data_get_data_type(data);
11366 if ((gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list"))
11367 && gtk_drag_get_source_widget(context) !=
11368 summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
11369 list = uri_list_extract_filenames(
11370 (const gchar *)gtk_selection_data_get_data(data));
11371 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11372 gchar *utf8_filename = conv_filename_to_utf8((const gchar *)tmp->data);
11373 compose_attach_append
11374 (compose, (const gchar *)tmp->data,
11375 utf8_filename, NULL, NULL);
11376 g_free(utf8_filename);
11378 if (list) compose_changed_cb(NULL, compose);
11379 list_free_strings(list);
11381 } else if (gtk_drag_get_source_widget(context)
11382 == summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
11383 /* comes from our summaryview */
11384 SummaryView * summaryview = NULL;
11385 GSList * list = NULL, *cur = NULL;
11387 if (mainwindow_get_mainwindow())
11388 summaryview = mainwindow_get_mainwindow()->summaryview;
11391 list = summary_get_selected_msg_list(summaryview);
11393 for (cur = list; cur; cur = cur->next) {
11394 MsgInfo *msginfo = (MsgInfo *)cur->data;
11395 gchar *file = NULL;
11397 file = procmsg_get_message_file_full(msginfo,
11400 compose_attach_append(compose, (const gchar *)file,
11401 (const gchar *)file, "message/rfc822", NULL);
11405 g_slist_free(list);
11409 static gboolean compose_drag_drop(GtkWidget *widget,
11410 GdkDragContext *drag_context,
11412 guint time, gpointer user_data)
11414 /* not handling this signal makes compose_insert_drag_received_cb
11419 static gboolean completion_set_focus_to_subject
11420 (GtkWidget *widget,
11421 GdkEventKey *event,
11424 cm_return_val_if_fail(compose != NULL, FALSE);
11426 /* make backtab move to subject field */
11427 if(event->keyval == GDK_KEY_ISO_Left_Tab) {
11428 gtk_widget_grab_focus(compose->subject_entry);
11434 static void compose_insert_drag_received_cb (GtkWidget *widget,
11435 GdkDragContext *drag_context,
11438 GtkSelectionData *data,
11441 gpointer user_data)
11443 Compose *compose = (Compose *)user_data;
11449 /* strangely, testing data->type == gdk_atom_intern("text/uri-list", TRUE)
11451 type = gtk_selection_data_get_data_type(data);
11452 if (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list")) {
11453 AlertValue val = G_ALERTDEFAULT;
11454 const gchar* ddata = (const gchar *)gtk_selection_data_get_data(data);
11456 list = uri_list_extract_filenames(ddata);
11457 num_files = g_list_length(list);
11458 if (list == NULL && strstr(ddata, "://")) {
11459 /* Assume a list of no files, and data has ://, is a remote link */
11460 gchar *tmpdata = g_strstrip(g_strdup(ddata));
11461 gchar *tmpfile = get_tmp_file();
11462 str_write_to_file(tmpdata, tmpfile);
11464 compose_insert_file(compose, tmpfile);
11465 claws_unlink(tmpfile);
11467 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11468 compose_beautify_paragraph(compose, NULL, TRUE);
11471 switch (prefs_common.compose_dnd_mode) {
11472 case COMPOSE_DND_ASK:
11473 msg = g_strdup_printf(
11475 "Do you want to insert the contents of the file "
11476 "into the message body, or attach it to the email?",
11477 "Do you want to insert the contents of the %d files "
11478 "into the message body, or attach them to the email?",
11481 val = alertpanel_full(_("Insert or attach?"), msg,
11482 GTK_STOCK_CANCEL, g_strconcat("+", _("_Insert"), NULL), _("_Attach"),
11483 TRUE, NULL, ALERT_QUESTION, G_ALERTALTERNATE);
11486 case COMPOSE_DND_INSERT:
11487 val = G_ALERTALTERNATE;
11489 case COMPOSE_DND_ATTACH:
11490 val = G_ALERTOTHER;
11493 /* unexpected case */
11494 g_warning("error: unexpected compose_dnd_mode option value in compose_insert_drag_received_cb()");
11497 if (val & G_ALERTDISABLE) {
11498 val &= ~G_ALERTDISABLE;
11499 /* remember what action to perform by default, only if we don't click Cancel */
11500 if (val == G_ALERTALTERNATE)
11501 prefs_common.compose_dnd_mode = COMPOSE_DND_INSERT;
11502 else if (val == G_ALERTOTHER)
11503 prefs_common.compose_dnd_mode = COMPOSE_DND_ATTACH;
11506 if (val == G_ALERTDEFAULT || val == G_ALERTCANCEL) {
11507 gtk_drag_finish(drag_context, FALSE, FALSE, time);
11508 list_free_strings(list);
11511 } else if (val == G_ALERTOTHER) {
11512 compose_attach_drag_received_cb(widget, drag_context, x, y, data, info, time, user_data);
11513 list_free_strings(list);
11518 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11519 compose_insert_file(compose, (const gchar *)tmp->data);
11521 list_free_strings(list);
11523 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11528 static void compose_header_drag_received_cb (GtkWidget *widget,
11529 GdkDragContext *drag_context,
11532 GtkSelectionData *data,
11535 gpointer user_data)
11537 GtkEditable *entry = (GtkEditable *)user_data;
11538 const gchar *email = (const gchar *)gtk_selection_data_get_data(data);
11540 /* strangely, testing data->type == gdk_atom_intern("text/plain", TRUE)
11543 if (!strncmp(email, "mailto:", strlen("mailto:"))) {
11544 gchar *decoded=g_new(gchar, strlen(email));
11547 decode_uri(decoded, email + strlen("mailto:")); /* will fit */
11548 gtk_editable_delete_text(entry, 0, -1);
11549 gtk_editable_insert_text(entry, decoded, strlen(decoded), &start);
11550 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11554 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11557 static void compose_toggle_return_receipt_cb(GtkToggleAction *action, gpointer data)
11559 Compose *compose = (Compose *)data;
11561 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11562 compose->return_receipt = TRUE;
11564 compose->return_receipt = FALSE;
11567 static void compose_toggle_remove_refs_cb(GtkToggleAction *action, gpointer data)
11569 Compose *compose = (Compose *)data;
11571 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11572 compose->remove_references = TRUE;
11574 compose->remove_references = FALSE;
11577 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
11578 ComposeHeaderEntry *headerentry)
11580 gtk_entry_set_text(GTK_ENTRY(headerentry->entry), "");
11584 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
11585 GdkEventKey *event,
11586 ComposeHeaderEntry *headerentry)
11588 if ((g_slist_length(headerentry->compose->header_list) > 0) &&
11589 ((headerentry->headernum + 1) != headerentry->compose->header_nextrow) &&
11590 !(event->state & GDK_MODIFIER_MASK) &&
11591 (event->keyval == GDK_KEY_BackSpace) &&
11592 (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) == 0)) {
11593 gtk_container_remove
11594 (GTK_CONTAINER(headerentry->compose->header_table),
11595 headerentry->combo);
11596 gtk_container_remove
11597 (GTK_CONTAINER(headerentry->compose->header_table),
11598 headerentry->entry);
11599 headerentry->compose->header_list =
11600 g_slist_remove(headerentry->compose->header_list,
11602 g_free(headerentry);
11603 } else if (event->keyval == GDK_KEY_Tab) {
11604 if (headerentry->compose->header_last == headerentry) {
11605 /* Override default next focus, and give it to subject_entry
11606 * instead of notebook tabs
11608 g_signal_stop_emission_by_name(G_OBJECT(entry), "key-press-event");
11609 gtk_widget_grab_focus(headerentry->compose->subject_entry);
11616 static gboolean scroll_postpone(gpointer data)
11618 Compose *compose = (Compose *)data;
11620 if (compose->batch)
11623 GTK_EVENTS_FLUSH();
11624 compose_show_first_last_header(compose, FALSE);
11628 static void compose_headerentry_changed_cb(GtkWidget *entry,
11629 ComposeHeaderEntry *headerentry)
11631 if (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) != 0) {
11632 compose_create_header_entry(headerentry->compose);
11633 g_signal_handlers_disconnect_matched
11634 (G_OBJECT(entry), G_SIGNAL_MATCH_DATA,
11635 0, 0, NULL, NULL, headerentry);
11637 if (!headerentry->compose->batch)
11638 g_timeout_add(0, scroll_postpone, headerentry->compose);
11642 static gboolean compose_defer_auto_save_draft(Compose *compose)
11644 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
11645 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
11649 static void compose_show_first_last_header(Compose *compose, gboolean show_first)
11651 GtkAdjustment *vadj;
11653 cm_return_if_fail(compose);
11658 cm_return_if_fail(GTK_IS_WIDGET(compose->header_table));
11659 cm_return_if_fail(GTK_IS_VIEWPORT(gtk_widget_get_parent(compose->header_table)));
11660 vadj = gtk_viewport_get_vadjustment(GTK_VIEWPORT(
11661 gtk_widget_get_parent(compose->header_table)));
11662 gtk_adjustment_set_value(vadj, (show_first ?
11663 gtk_adjustment_get_lower(vadj) :
11664 (gtk_adjustment_get_upper(vadj) -
11665 gtk_adjustment_get_page_size(vadj))));
11666 gtk_adjustment_changed(vadj);
11669 static void text_inserted(GtkTextBuffer *buffer, GtkTextIter *iter,
11670 const gchar *text, gint len, Compose *compose)
11672 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data
11673 (G_OBJECT(compose->text), "paste_as_quotation"));
11676 cm_return_if_fail(text != NULL);
11678 g_signal_handlers_block_by_func(G_OBJECT(buffer),
11679 G_CALLBACK(text_inserted),
11681 if (paste_as_quotation) {
11683 const gchar *qmark;
11685 GtkTextIter start_iter;
11688 len = strlen(text);
11690 new_text = g_strndup(text, len);
11692 qmark = compose_quote_char_from_context(compose);
11694 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11695 gtk_text_buffer_place_cursor(buffer, iter);
11697 pos = gtk_text_iter_get_offset(iter);
11699 compose_quote_fmt(compose, NULL, "%Q", qmark, new_text, TRUE, FALSE,
11700 _("Quote format error at line %d."));
11701 quote_fmt_reset_vartable();
11703 g_object_set_data(G_OBJECT(compose->text), "paste_as_quotation",
11704 GINT_TO_POINTER(paste_as_quotation - 1));
11706 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11707 gtk_text_buffer_place_cursor(buffer, iter);
11708 gtk_text_buffer_delete_mark(buffer, mark);
11710 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, pos);
11711 mark = gtk_text_buffer_create_mark(buffer, NULL, &start_iter, FALSE);
11712 compose_beautify_paragraph(compose, &start_iter, FALSE);
11713 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark);
11714 gtk_text_buffer_delete_mark(buffer, mark);
11716 if (strcmp(text, "\n") || compose->automatic_break
11717 || gtk_text_iter_starts_line(iter)) {
11718 GtkTextIter before_ins;
11719 gtk_text_buffer_insert(buffer, iter, text, len);
11720 if (!strstr(text, "\n") && gtk_text_iter_has_tag(iter, compose->no_join_tag)) {
11721 before_ins = *iter;
11722 gtk_text_iter_backward_chars(&before_ins, len);
11723 gtk_text_buffer_remove_tag_by_name(buffer, "no_join", &before_ins, iter);
11726 /* check if the preceding is just whitespace or quote */
11727 GtkTextIter start_line;
11728 gchar *tmp = NULL, *quote = NULL;
11729 gint quote_len = 0, is_normal = 0;
11730 start_line = *iter;
11731 gtk_text_iter_set_line_offset(&start_line, 0);
11732 tmp = gtk_text_buffer_get_text(buffer, &start_line, iter, FALSE);
11735 if (*tmp == '\0') {
11738 quote = compose_get_quote_str(buffer, &start_line, "e_len);
11746 gtk_text_buffer_insert(buffer, iter, text, len);
11748 gtk_text_buffer_insert_with_tags_by_name(buffer,
11749 iter, text, len, "no_join", NULL);
11754 if (!paste_as_quotation) {
11755 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11756 compose_beautify_paragraph(compose, iter, FALSE);
11757 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11758 gtk_text_buffer_delete_mark(buffer, mark);
11761 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
11762 G_CALLBACK(text_inserted),
11764 g_signal_stop_emission_by_name(G_OBJECT(buffer), "insert-text");
11766 if (compose_can_autosave(compose) &&
11767 gtk_text_buffer_get_char_count(buffer) % prefs_common.autosave_length == 0 &&
11768 compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN /* disabled while loading */)
11769 compose->draft_timeout_tag = g_timeout_add
11770 (500, (GSourceFunc) compose_defer_auto_save_draft, compose);
11774 static void compose_check_all(GtkAction *action, gpointer data)
11776 Compose *compose = (Compose *)data;
11777 if (!compose->gtkaspell)
11780 if (gtk_widget_has_focus(compose->subject_entry))
11781 claws_spell_entry_check_all(
11782 CLAWS_SPELL_ENTRY(compose->subject_entry));
11784 gtkaspell_check_all(compose->gtkaspell);
11787 static void compose_highlight_all(GtkAction *action, gpointer data)
11789 Compose *compose = (Compose *)data;
11790 if (compose->gtkaspell) {
11791 claws_spell_entry_recheck_all(
11792 CLAWS_SPELL_ENTRY(compose->subject_entry));
11793 gtkaspell_highlight_all(compose->gtkaspell);
11797 static void compose_check_backwards(GtkAction *action, gpointer data)
11799 Compose *compose = (Compose *)data;
11800 if (!compose->gtkaspell) {
11801 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
11805 if (gtk_widget_has_focus(compose->subject_entry))
11806 claws_spell_entry_check_backwards(
11807 CLAWS_SPELL_ENTRY(compose->subject_entry));
11809 gtkaspell_check_backwards(compose->gtkaspell);
11812 static void compose_check_forwards_go(GtkAction *action, gpointer data)
11814 Compose *compose = (Compose *)data;
11815 if (!compose->gtkaspell) {
11816 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
11820 if (gtk_widget_has_focus(compose->subject_entry))
11821 claws_spell_entry_check_forwards_go(
11822 CLAWS_SPELL_ENTRY(compose->subject_entry));
11824 gtkaspell_check_forwards_go(compose->gtkaspell);
11829 *\brief Guess originating forward account from MsgInfo and several
11830 * "common preference" settings. Return NULL if no guess.
11832 static PrefsAccount *compose_find_account(MsgInfo *msginfo)
11834 PrefsAccount *account = NULL;
11836 cm_return_val_if_fail(msginfo, NULL);
11837 cm_return_val_if_fail(msginfo->folder, NULL);
11838 cm_return_val_if_fail(msginfo->folder->prefs, NULL);
11840 if (msginfo->folder->prefs->enable_default_account)
11841 account = account_find_from_id(msginfo->folder->prefs->default_account);
11843 if (!account && msginfo->to && prefs_common.forward_account_autosel) {
11845 Xstrdup_a(to, msginfo->to, return NULL);
11846 extract_address(to);
11847 account = account_find_from_address(to, FALSE);
11850 if (!account && prefs_common.forward_account_autosel) {
11852 if (!procheader_get_header_from_msginfo
11853 (msginfo, &cc, "Cc:")) {
11854 gchar *buf = cc + strlen("Cc:");
11855 extract_address(buf);
11856 account = account_find_from_address(buf, FALSE);
11861 if (!account && prefs_common.forward_account_autosel) {
11862 gchar *deliveredto = NULL;
11863 if (!procheader_get_header_from_msginfo
11864 (msginfo, &deliveredto, "Delivered-To:")) {
11865 gchar *buf = deliveredto + strlen("Delivered-To:");
11866 extract_address(buf);
11867 account = account_find_from_address(buf, FALSE);
11868 g_free(deliveredto);
11873 account = msginfo->folder->folder->account;
11878 gboolean compose_close(Compose *compose)
11882 cm_return_val_if_fail(compose, FALSE);
11884 if (!g_mutex_trylock(compose->mutex)) {
11885 /* we have to wait for the (possibly deferred by auto-save)
11886 * drafting to be done, before destroying the compose under
11888 debug_print("waiting for drafting to finish...\n");
11889 compose_allow_user_actions(compose, FALSE);
11890 if (compose->close_timeout_tag == 0) {
11891 compose->close_timeout_tag =
11892 g_timeout_add (500, (GSourceFunc) compose_close,
11898 if (compose->draft_timeout_tag >= 0) {
11899 g_source_remove(compose->draft_timeout_tag);
11900 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN;
11903 gtkut_widget_get_uposition(compose->window, &x, &y);
11904 if (!compose->batch) {
11905 prefs_common.compose_x = x;
11906 prefs_common.compose_y = y;
11908 g_mutex_unlock(compose->mutex);
11909 compose_destroy(compose);
11914 * Add entry field for each address in list.
11915 * \param compose E-Mail composition object.
11916 * \param listAddress List of (formatted) E-Mail addresses.
11918 static void compose_add_field_list( Compose *compose, GList *listAddress ) {
11921 node = listAddress;
11923 addr = ( gchar * ) node->data;
11924 compose_entry_append( compose, addr, COMPOSE_TO, PREF_NONE );
11925 node = g_list_next( node );
11929 static void compose_reply_from_messageview_real(MessageView *msgview, GSList *msginfo_list,
11930 guint action, gboolean opening_multiple)
11932 gchar *body = NULL;
11933 GSList *new_msglist = NULL;
11934 MsgInfo *tmp_msginfo = NULL;
11935 gboolean originally_enc = FALSE;
11936 gboolean originally_sig = FALSE;
11937 Compose *compose = NULL;
11938 gchar *s_system = NULL;
11940 cm_return_if_fail(msgview != NULL);
11942 cm_return_if_fail(msginfo_list != NULL);
11944 if (g_slist_length(msginfo_list) == 1 && !opening_multiple) {
11945 MimeInfo *mimeinfo = messageview_get_selected_mime_part(msgview);
11946 MsgInfo *orig_msginfo = (MsgInfo *)msginfo_list->data;
11948 if (mimeinfo != NULL && mimeinfo->type == MIMETYPE_MESSAGE &&
11949 !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
11950 tmp_msginfo = procmsg_msginfo_new_from_mimeinfo(
11951 orig_msginfo, mimeinfo);
11952 if (tmp_msginfo != NULL) {
11953 new_msglist = g_slist_append(NULL, tmp_msginfo);
11955 originally_enc = MSG_IS_ENCRYPTED(orig_msginfo->flags);
11956 privacy_msginfo_get_signed_state(orig_msginfo, &s_system);
11957 originally_sig = MSG_IS_SIGNED(orig_msginfo->flags);
11959 tmp_msginfo->folder = orig_msginfo->folder;
11960 tmp_msginfo->msgnum = orig_msginfo->msgnum;
11961 if (orig_msginfo->tags) {
11962 tmp_msginfo->tags = g_slist_copy(orig_msginfo->tags);
11963 tmp_msginfo->folder->tags_dirty = TRUE;
11969 if (!opening_multiple)
11970 body = messageview_get_selection(msgview);
11973 compose = compose_reply_mode((ComposeMode)action, new_msglist, body);
11974 procmsg_msginfo_free(&tmp_msginfo);
11975 g_slist_free(new_msglist);
11977 compose = compose_reply_mode((ComposeMode)action, msginfo_list, body);
11979 if (compose && originally_enc) {
11980 compose_force_encryption(compose, compose->account, FALSE, s_system);
11983 if (compose && originally_sig && compose->account->default_sign_reply) {
11984 compose_force_signing(compose, compose->account, s_system);
11988 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
11991 void compose_reply_from_messageview(MessageView *msgview, GSList *msginfo_list,
11994 if ((!prefs_common.forward_as_attachment || action != COMPOSE_FORWARD)
11995 && action != COMPOSE_FORWARD_AS_ATTACH && g_slist_length(msginfo_list) > 1) {
11996 GSList *cur = msginfo_list;
11997 gchar *msg = g_strdup_printf(_("You are about to reply to %d "
11998 "messages. Opening the windows "
11999 "could take some time. Do you "
12000 "want to continue?"),
12001 g_slist_length(msginfo_list));
12002 if (g_slist_length(msginfo_list) > 9
12003 && alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, "+" GTK_STOCK_YES, NULL)
12004 != G_ALERTALTERNATE) {
12009 /* We'll open multiple compose windows */
12010 /* let the WM place the next windows */
12011 compose_force_window_origin = FALSE;
12012 for (; cur; cur = cur->next) {
12014 tmplist.data = cur->data;
12015 tmplist.next = NULL;
12016 compose_reply_from_messageview_real(msgview, &tmplist, action, TRUE);
12018 compose_force_window_origin = TRUE;
12020 /* forwarding multiple mails as attachments is done via a
12021 * single compose window */
12022 compose_reply_from_messageview_real(msgview, msginfo_list, action, FALSE);
12026 void compose_check_for_email_account(Compose *compose)
12028 PrefsAccount *ac = NULL, *curr = NULL;
12034 if (compose->account && compose->account->protocol == A_NNTP) {
12035 ac = account_get_cur_account();
12036 if (ac->protocol == A_NNTP) {
12037 list = account_get_list();
12039 for( ; list != NULL ; list = g_list_next(list)) {
12040 curr = (PrefsAccount *) list->data;
12041 if (curr->protocol != A_NNTP) {
12047 combobox_select_by_data(GTK_COMBO_BOX(compose->account_combo),
12052 void compose_reply_to_address(MessageView *msgview, MsgInfo *msginfo,
12053 const gchar *address)
12055 GSList *msginfo_list = NULL;
12056 gchar *body = messageview_get_selection(msgview);
12059 msginfo_list = g_slist_prepend(msginfo_list, msginfo);
12061 compose = compose_reply_mode(COMPOSE_REPLY_TO_ADDRESS, msginfo_list, body);
12062 compose_check_for_email_account(compose);
12063 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
12064 compose_entry_append(compose, address, COMPOSE_TO, PREF_NONE);
12065 compose_reply_set_subject(compose, msginfo);
12068 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
12071 void compose_set_position(Compose *compose, gint pos)
12073 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
12075 gtkut_text_view_set_position(text, pos);
12078 gboolean compose_search_string(Compose *compose,
12079 const gchar *str, gboolean case_sens)
12081 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
12083 return gtkut_text_view_search_string(text, str, case_sens);
12086 gboolean compose_search_string_backward(Compose *compose,
12087 const gchar *str, gboolean case_sens)
12089 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
12091 return gtkut_text_view_search_string_backward(text, str, case_sens);
12094 /* allocate a msginfo structure and populate its data from a compose data structure */
12095 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose)
12097 MsgInfo *newmsginfo;
12099 gchar buf[BUFFSIZE];
12101 cm_return_val_if_fail( compose != NULL, NULL );
12103 newmsginfo = procmsg_msginfo_new();
12106 get_rfc822_date(buf, sizeof(buf));
12107 newmsginfo->date = g_strdup(buf);
12110 if (compose->from_name) {
12111 newmsginfo->from = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
12112 newmsginfo->fromname = procheader_get_fromname(newmsginfo->from);
12116 if (compose->subject_entry)
12117 newmsginfo->subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
12119 /* to, cc, reply-to, newsgroups */
12120 for (list = compose->header_list; list; list = list->next) {
12121 gchar *header = gtk_editable_get_chars(
12123 gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
12124 gchar *entry = gtk_editable_get_chars(
12125 GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
12127 if ( strcasecmp(header, prefs_common_translated_header_name("To:")) == 0 ) {
12128 if ( newmsginfo->to == NULL ) {
12129 newmsginfo->to = g_strdup(entry);
12130 } else if (entry && *entry) {
12131 gchar *tmp = g_strconcat(newmsginfo->to, ", ", entry, NULL);
12132 g_free(newmsginfo->to);
12133 newmsginfo->to = tmp;
12136 if ( strcasecmp(header, prefs_common_translated_header_name("Cc:")) == 0 ) {
12137 if ( newmsginfo->cc == NULL ) {
12138 newmsginfo->cc = g_strdup(entry);
12139 } else if (entry && *entry) {
12140 gchar *tmp = g_strconcat(newmsginfo->cc, ", ", entry, NULL);
12141 g_free(newmsginfo->cc);
12142 newmsginfo->cc = tmp;
12145 if ( strcasecmp(header,
12146 prefs_common_translated_header_name("Newsgroups:")) == 0 ) {
12147 if ( newmsginfo->newsgroups == NULL ) {
12148 newmsginfo->newsgroups = g_strdup(entry);
12149 } else if (entry && *entry) {
12150 gchar *tmp = g_strconcat(newmsginfo->newsgroups, ", ", entry, NULL);
12151 g_free(newmsginfo->newsgroups);
12152 newmsginfo->newsgroups = tmp;
12160 /* other data is unset */
12166 /* update compose's dictionaries from folder dict settings */
12167 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
12168 FolderItem *folder_item)
12170 cm_return_if_fail(compose != NULL);
12172 if (compose->gtkaspell && folder_item && folder_item->prefs) {
12173 FolderItemPrefs *prefs = folder_item->prefs;
12175 if (prefs->enable_default_dictionary)
12176 gtkaspell_change_dict(compose->gtkaspell,
12177 prefs->default_dictionary, FALSE);
12178 if (folder_item->prefs->enable_default_alt_dictionary)
12179 gtkaspell_change_alt_dict(compose->gtkaspell,
12180 prefs->default_alt_dictionary);
12181 if (prefs->enable_default_dictionary
12182 || prefs->enable_default_alt_dictionary)
12183 compose_spell_menu_changed(compose);
12188 static void compose_subject_entry_activated(GtkWidget *widget, gpointer data)
12190 Compose *compose = (Compose *)data;
12192 cm_return_if_fail(compose != NULL);
12194 gtk_widget_grab_focus(compose->text);
12197 static void from_name_activate_cb(GtkWidget *widget, gpointer data)
12199 gtk_combo_box_popup(GTK_COMBO_BOX(data));