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 check_subject,
317 gboolean remove_reedit_target);
318 static int compose_add_attachments (Compose *compose,
320 static gchar *compose_get_header (Compose *compose);
321 static gchar *compose_get_manual_headers_info (Compose *compose);
323 static void compose_convert_header (Compose *compose,
328 gboolean addr_field);
330 static void compose_attach_info_free (AttachInfo *ainfo);
331 static void compose_attach_remove_selected (GtkAction *action,
334 static void compose_template_apply (Compose *compose,
337 static void compose_attach_property (GtkAction *action,
339 static void compose_attach_property_create (gboolean *cancelled);
340 static void attach_property_ok (GtkWidget *widget,
341 gboolean *cancelled);
342 static void attach_property_cancel (GtkWidget *widget,
343 gboolean *cancelled);
344 static gint attach_property_delete_event (GtkWidget *widget,
346 gboolean *cancelled);
347 static gboolean attach_property_key_pressed (GtkWidget *widget,
349 gboolean *cancelled);
351 static void compose_exec_ext_editor (Compose *compose);
353 static gint compose_exec_ext_editor_real (const gchar *file,
354 GdkNativeWindow socket_wid);
355 static gboolean compose_ext_editor_kill (Compose *compose);
356 static gboolean compose_input_cb (GIOChannel *source,
357 GIOCondition condition,
359 static void compose_set_ext_editor_sensitive (Compose *compose,
361 static gboolean compose_get_ext_editor_cmd_valid();
362 static gboolean compose_get_ext_editor_uses_socket();
363 static gboolean compose_ext_editor_plug_removed_cb
366 #endif /* G_OS_UNIX */
368 static void compose_undo_state_changed (UndoMain *undostruct,
373 static void compose_create_header_entry (Compose *compose);
374 static void compose_add_header_entry (Compose *compose, const gchar *header,
375 gchar *text, ComposePrefType pref_type);
376 static void compose_remove_header_entries(Compose *compose);
378 static void compose_update_priority_menu_item(Compose * compose);
380 static void compose_spell_menu_changed (void *data);
381 static void compose_dict_changed (void *data);
383 static void compose_add_field_list ( Compose *compose,
384 GList *listAddress );
386 /* callback functions */
388 static void compose_notebook_size_alloc (GtkNotebook *notebook,
389 GtkAllocation *allocation,
391 static gboolean compose_edit_size_alloc (GtkEditable *widget,
392 GtkAllocation *allocation,
393 GtkSHRuler *shruler);
394 static void account_activated (GtkComboBox *optmenu,
396 static void attach_selected (GtkTreeView *tree_view,
397 GtkTreePath *tree_path,
398 GtkTreeViewColumn *column,
400 static gboolean attach_button_pressed (GtkWidget *widget,
401 GdkEventButton *event,
403 static gboolean attach_key_pressed (GtkWidget *widget,
406 static void compose_send_cb (GtkAction *action, gpointer data);
407 static void compose_send_later_cb (GtkAction *action, gpointer data);
409 static void compose_save_cb (GtkAction *action,
412 static void compose_attach_cb (GtkAction *action,
414 static void compose_insert_file_cb (GtkAction *action,
416 static void compose_insert_sig_cb (GtkAction *action,
418 static void compose_replace_sig_cb (GtkAction *action,
421 static void compose_close_cb (GtkAction *action,
423 static void compose_print_cb (GtkAction *action,
426 static void compose_set_encoding_cb (GtkAction *action, GtkRadioAction *current, gpointer data);
428 static void compose_address_cb (GtkAction *action,
430 static void about_show_cb (GtkAction *action,
432 static void compose_template_activate_cb(GtkWidget *widget,
435 static void compose_ext_editor_cb (GtkAction *action,
438 static gint compose_delete_cb (GtkWidget *widget,
442 static void compose_undo_cb (GtkAction *action,
444 static void compose_redo_cb (GtkAction *action,
446 static void compose_cut_cb (GtkAction *action,
448 static void compose_copy_cb (GtkAction *action,
450 static void compose_paste_cb (GtkAction *action,
452 static void compose_paste_as_quote_cb (GtkAction *action,
454 static void compose_paste_no_wrap_cb (GtkAction *action,
456 static void compose_paste_wrap_cb (GtkAction *action,
458 static void compose_allsel_cb (GtkAction *action,
461 static void compose_advanced_action_cb (GtkAction *action,
464 static void compose_grab_focus_cb (GtkWidget *widget,
467 static void compose_changed_cb (GtkTextBuffer *textbuf,
470 static void compose_wrap_cb (GtkAction *action,
472 static void compose_wrap_all_cb (GtkAction *action,
474 static void compose_find_cb (GtkAction *action,
476 static void compose_toggle_autowrap_cb (GtkToggleAction *action,
478 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
481 static void compose_toggle_ruler_cb (GtkToggleAction *action,
483 static void compose_toggle_sign_cb (GtkToggleAction *action,
485 static void compose_toggle_encrypt_cb (GtkToggleAction *action,
487 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data);
488 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn);
489 static void activate_privacy_system (Compose *compose,
490 PrefsAccount *account,
492 static void compose_use_signing(Compose *compose, gboolean use_signing);
493 static void compose_use_encryption(Compose *compose, gboolean use_encryption);
494 static void compose_toggle_return_receipt_cb(GtkToggleAction *action,
496 static void compose_toggle_remove_refs_cb(GtkToggleAction *action,
498 static void compose_set_priority_cb (GtkAction *action, GtkRadioAction *current, gpointer data);
499 static void compose_reply_change_mode (Compose *compose, ComposeMode action);
500 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data);
502 static void compose_attach_drag_received_cb (GtkWidget *widget,
503 GdkDragContext *drag_context,
506 GtkSelectionData *data,
510 static void compose_insert_drag_received_cb (GtkWidget *widget,
511 GdkDragContext *drag_context,
514 GtkSelectionData *data,
518 static void compose_header_drag_received_cb (GtkWidget *widget,
519 GdkDragContext *drag_context,
522 GtkSelectionData *data,
527 static gboolean compose_drag_drop (GtkWidget *widget,
528 GdkDragContext *drag_context,
530 guint time, gpointer user_data);
531 static gboolean completion_set_focus_to_subject
536 static void text_inserted (GtkTextBuffer *buffer,
541 static Compose *compose_generic_reply(MsgInfo *msginfo,
542 ComposeQuoteMode quote_mode,
546 gboolean followup_and_reply_to,
549 static void compose_headerentry_changed_cb (GtkWidget *entry,
550 ComposeHeaderEntry *headerentry);
551 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
553 ComposeHeaderEntry *headerentry);
554 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
555 ComposeHeaderEntry *headerentry);
557 static void compose_show_first_last_header (Compose *compose, gboolean show_first);
559 static void compose_allow_user_actions (Compose *compose, gboolean allow);
561 static void compose_nothing_cb (GtkAction *action, gpointer data)
567 static void compose_check_all (GtkAction *action, gpointer data);
568 static void compose_highlight_all (GtkAction *action, gpointer data);
569 static void compose_check_backwards (GtkAction *action, gpointer data);
570 static void compose_check_forwards_go (GtkAction *action, gpointer data);
573 static PrefsAccount *compose_find_account (MsgInfo *msginfo);
575 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose);
578 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
579 FolderItem *folder_item);
581 static void compose_attach_update_label(Compose *compose);
582 static void compose_set_folder_prefs(Compose *compose, FolderItem *folder,
583 gboolean respect_default_to);
584 static void compose_subject_entry_activated(GtkWidget *widget, gpointer data);
585 static void from_name_activate_cb(GtkWidget *widget, gpointer data);
587 static GtkActionEntry compose_popup_entries[] =
589 {"Compose", NULL, "Compose", NULL, NULL, NULL },
590 {"Compose/Add", NULL, N_("_Add..."), NULL, NULL, G_CALLBACK(compose_attach_cb) },
591 {"Compose/Remove", NULL, N_("_Remove"), NULL, NULL, G_CALLBACK(compose_attach_remove_selected) },
592 {"Compose/---", NULL, "---", NULL, NULL, NULL },
593 {"Compose/Properties", NULL, N_("_Properties..."), NULL, NULL, G_CALLBACK(compose_attach_property) },
596 static GtkActionEntry compose_entries[] =
598 {"Menu", NULL, "Menu", NULL, NULL, NULL },
600 {"Message", NULL, N_("_Message"), NULL, NULL, NULL },
601 {"Edit", NULL, N_("_Edit"), NULL, NULL, NULL },
603 {"Spelling", NULL, N_("_Spelling"), NULL, NULL, NULL },
605 {"Options", NULL, N_("_Options"), NULL, NULL, NULL },
606 {"Tools", NULL, N_("_Tools"), NULL, NULL, NULL },
607 {"Help", NULL, N_("_Help"), NULL, NULL, NULL },
609 {"Message/Send", NULL, N_("S_end"), "<control>Return", NULL, G_CALLBACK(compose_send_cb) },
610 {"Message/SendLater", NULL, N_("Send _later"), "<shift><control>S", NULL, G_CALLBACK(compose_send_later_cb) },
611 {"Message/---", NULL, "---", NULL, NULL, NULL },
613 {"Message/AttachFile", NULL, N_("_Attach file"), "<control>M", NULL, G_CALLBACK(compose_attach_cb) },
614 {"Message/InsertFile", NULL, N_("_Insert file"), "<control>I", NULL, G_CALLBACK(compose_insert_file_cb) },
615 {"Message/InsertSig", NULL, N_("Insert si_gnature"), "<control>G", NULL, G_CALLBACK(compose_insert_sig_cb) },
616 {"Message/ReplaceSig", NULL, N_("_Replace signature"), NULL, NULL, G_CALLBACK(compose_replace_sig_cb) },
617 /* {"Message/---", NULL, "---", NULL, NULL, NULL }, */
618 {"Message/Save", NULL, N_("_Save"), "<control>S", NULL, G_CALLBACK(compose_save_cb) }, /*COMPOSE_KEEP_EDITING*/
619 /* {"Message/---", NULL, "---", NULL, NULL, NULL }, */
620 {"Message/Print", NULL, N_("_Print"), NULL, NULL, G_CALLBACK(compose_print_cb) },
621 /* {"Message/---", NULL, "---", NULL, NULL, NULL }, */
622 {"Message/Close", NULL, N_("_Close"), "<control>W", NULL, G_CALLBACK(compose_close_cb) },
625 {"Edit/Undo", NULL, N_("_Undo"), "<control>Z", NULL, G_CALLBACK(compose_undo_cb) },
626 {"Edit/Redo", NULL, N_("_Redo"), "<control>Y", NULL, G_CALLBACK(compose_redo_cb) },
627 {"Edit/---", NULL, "---", NULL, NULL, NULL },
629 {"Edit/Cut", NULL, N_("Cu_t"), "<control>X", NULL, G_CALLBACK(compose_cut_cb) },
630 {"Edit/Copy", NULL, N_("_Copy"), "<control>C", NULL, G_CALLBACK(compose_copy_cb) },
631 {"Edit/Paste", NULL, N_("_Paste"), "<control>V", NULL, G_CALLBACK(compose_paste_cb) },
633 {"Edit/SpecialPaste", NULL, N_("_Special paste"), NULL, NULL, NULL },
634 {"Edit/SpecialPaste/AsQuotation", NULL, N_("As _quotation"), NULL, NULL, G_CALLBACK(compose_paste_as_quote_cb) },
635 {"Edit/SpecialPaste/Wrapped", NULL, N_("_Wrapped"), NULL, NULL, G_CALLBACK(compose_paste_wrap_cb) },
636 {"Edit/SpecialPaste/Unwrapped", NULL, N_("_Unwrapped"), NULL, NULL, G_CALLBACK(compose_paste_no_wrap_cb) },
638 {"Edit/SelectAll", NULL, N_("Select _all"), "<control>A", NULL, G_CALLBACK(compose_allsel_cb) },
640 {"Edit/Advanced", NULL, N_("A_dvanced"), NULL, NULL, NULL },
641 {"Edit/Advanced/BackChar", NULL, N_("Move a character backward"), "<shift><control>B", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER*/
642 {"Edit/Advanced/ForwChar", NULL, N_("Move a character forward"), "<shift><control>F", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER*/
643 {"Edit/Advanced/BackWord", NULL, N_("Move a word backward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD*/
644 {"Edit/Advanced/ForwWord", NULL, N_("Move a word forward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD*/
645 {"Edit/Advanced/BegLine", NULL, N_("Move to beginning of line"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE*/
646 {"Edit/Advanced/EndLine", NULL, N_("Move to end of line"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE*/
647 {"Edit/Advanced/PrevLine", NULL, N_("Move to previous line"), "<control>P", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE*/
648 {"Edit/Advanced/NextLine", NULL, N_("Move to next line"), "<control>N", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE*/
649 {"Edit/Advanced/DelBackChar", NULL, N_("Delete a character backward"), "<control>H", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER*/
650 {"Edit/Advanced/DelForwChar", NULL, N_("Delete a character forward"), "<control>D", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER*/
651 {"Edit/Advanced/DelBackWord", NULL, N_("Delete a word backward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD*/
652 {"Edit/Advanced/DelForwWord", NULL, N_("Delete a word forward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD*/
653 {"Edit/Advanced/DelLine", NULL, N_("Delete line"), "<control>U", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE*/
654 {"Edit/Advanced/DelEndLine", NULL, N_("Delete to end of line"), "<control>K", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END*/
656 /* {"Edit/---", NULL, "---", NULL, NULL, NULL }, */
657 {"Edit/Find", NULL, N_("_Find"), "<control>F", NULL, G_CALLBACK(compose_find_cb) },
659 /* {"Edit/---", NULL, "---", NULL, NULL, NULL }, */
660 {"Edit/WrapPara", NULL, N_("_Wrap current paragraph"), "<control>L", NULL, G_CALLBACK(compose_wrap_cb) }, /* 0 */
661 {"Edit/WrapAllLines", NULL, N_("Wrap all long _lines"), "<control><alt>L", NULL, G_CALLBACK(compose_wrap_all_cb) }, /* 1 */
662 /* {"Edit/---", NULL, "---", NULL, NULL, NULL }, */
663 {"Edit/ExtEditor", NULL, N_("Edit with e_xternal editor"), "<shift><control>X", NULL, G_CALLBACK(compose_ext_editor_cb) },
666 {"Spelling/CheckAllSel", NULL, N_("_Check all or check selection"), NULL, NULL, G_CALLBACK(compose_check_all) },
667 {"Spelling/HighlightAll", NULL, N_("_Highlight all misspelled words"), NULL, NULL, G_CALLBACK(compose_highlight_all) },
668 {"Spelling/CheckBackwards", NULL, N_("Check _backwards misspelled word"), NULL, NULL, G_CALLBACK(compose_check_backwards) },
669 {"Spelling/ForwardNext", NULL, N_("_Forward to next misspelled word"), NULL, NULL, G_CALLBACK(compose_check_forwards_go) },
671 {"Spelling/---", NULL, "---", NULL, NULL, NULL },
672 {"Spelling/Options", NULL, N_("_Options"), NULL, NULL, NULL },
676 {"Options/ReplyMode", NULL, N_("Reply _mode"), NULL, NULL, NULL },
677 {"Options/---", NULL, "---", NULL, NULL, NULL },
678 {"Options/PrivacySystem", NULL, N_("Privacy _System"), NULL, NULL, NULL },
679 {"Options/PrivacySystem/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
681 /* {"Options/---", NULL, "---", NULL, NULL, NULL }, */
682 {"Options/Priority", NULL, N_("_Priority"), NULL, NULL, NULL },
684 {"Options/Encoding", NULL, N_("Character _encoding"), NULL, NULL, NULL },
685 {"Options/Encoding/---", NULL, "---", NULL, NULL, NULL },
686 #define ENC_ACTION(cs_char,c_char,string) \
687 {"Options/Encoding/" cs_char, NULL, N_(string), NULL, NULL, c_char }
689 {"Options/Encoding/Western", NULL, N_("Western European"), NULL, NULL, NULL },
690 {"Options/Encoding/Baltic", NULL, N_("Baltic"), NULL, NULL, NULL },
691 {"Options/Encoding/Hebrew", NULL, N_("Hebrew"), NULL, NULL, NULL },
692 {"Options/Encoding/Arabic", NULL, N_("Arabic"), NULL, NULL, NULL },
693 {"Options/Encoding/Cyrillic", NULL, N_("Cyrillic"), NULL, NULL, NULL },
694 {"Options/Encoding/Japanese", NULL, N_("Japanese"), NULL, NULL, NULL },
695 {"Options/Encoding/Chinese", NULL, N_("Chinese"), NULL, NULL, NULL },
696 {"Options/Encoding/Korean", NULL, N_("Korean"), NULL, NULL, NULL },
697 {"Options/Encoding/Thai", NULL, N_("Thai"), NULL, NULL, NULL },
700 {"Tools/AddressBook", NULL, N_("_Address book"), NULL, NULL, G_CALLBACK(compose_address_cb) },
702 {"Tools/Template", NULL, N_("_Template"), NULL, NULL, NULL },
703 {"Tools/Template/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
704 {"Tools/Actions", NULL, N_("Actio_ns"), NULL, NULL, NULL },
705 {"Tools/Actions/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
708 {"Help/About", NULL, N_("_About"), NULL, NULL, G_CALLBACK(about_show_cb) },
711 static GtkToggleActionEntry compose_toggle_entries[] =
713 {"Edit/AutoWrap", NULL, N_("Aut_o wrapping"), "<shift><control>L", NULL, G_CALLBACK(compose_toggle_autowrap_cb), FALSE }, /* Toggle */
714 {"Edit/AutoIndent", NULL, N_("Auto _indent"), NULL, NULL, G_CALLBACK(compose_toggle_autoindent_cb), FALSE }, /* Toggle */
715 {"Options/Sign", NULL, N_("Si_gn"), NULL, NULL, G_CALLBACK(compose_toggle_sign_cb), FALSE }, /* Toggle */
716 {"Options/Encrypt", NULL, N_("_Encrypt"), NULL, NULL, G_CALLBACK(compose_toggle_encrypt_cb), FALSE }, /* Toggle */
717 {"Options/RequestRetRcpt", NULL, N_("_Request Return Receipt"), NULL, NULL, G_CALLBACK(compose_toggle_return_receipt_cb), FALSE }, /* Toggle */
718 {"Options/RemoveReferences", NULL, N_("Remo_ve references"), NULL, NULL, G_CALLBACK(compose_toggle_remove_refs_cb), FALSE }, /* Toggle */
719 {"Tools/ShowRuler", NULL, N_("Show _ruler"), NULL, NULL, G_CALLBACK(compose_toggle_ruler_cb), FALSE }, /* Toggle */
722 static GtkRadioActionEntry compose_radio_rm_entries[] =
724 {"Options/ReplyMode/Normal", NULL, N_("_Normal"), NULL, NULL, COMPOSE_REPLY }, /* RADIO compose_reply_change_mode_cb */
725 {"Options/ReplyMode/All", NULL, N_("_All"), NULL, NULL, COMPOSE_REPLY_TO_ALL }, /* RADIO compose_reply_change_mode_cb */
726 {"Options/ReplyMode/Sender", NULL, N_("_Sender"), NULL, NULL, COMPOSE_REPLY_TO_SENDER }, /* RADIO compose_reply_change_mode_cb */
727 {"Options/ReplyMode/List", NULL, N_("_Mailing-list"), NULL, NULL, COMPOSE_REPLY_TO_LIST }, /* RADIO compose_reply_change_mode_cb */
730 static GtkRadioActionEntry compose_radio_prio_entries[] =
732 {"Options/Priority/Highest", NULL, N_("_Highest"), NULL, NULL, PRIORITY_HIGHEST }, /* RADIO compose_set_priority_cb */
733 {"Options/Priority/High", NULL, N_("Hi_gh"), NULL, NULL, PRIORITY_HIGH }, /* RADIO compose_set_priority_cb */
734 {"Options/Priority/Normal", NULL, N_("_Normal"), NULL, NULL, PRIORITY_NORMAL }, /* RADIO compose_set_priority_cb */
735 {"Options/Priority/Low", NULL, N_("Lo_w"), NULL, NULL, PRIORITY_LOW }, /* RADIO compose_set_priority_cb */
736 {"Options/Priority/Lowest", NULL, N_("_Lowest"), NULL, NULL, PRIORITY_LOWEST }, /* RADIO compose_set_priority_cb */
739 static GtkRadioActionEntry compose_radio_enc_entries[] =
741 ENC_ACTION(CS_AUTO, C_AUTO, N_("_Automatic")), /* RADIO compose_set_encoding_cb */
742 ENC_ACTION(CS_US_ASCII, C_US_ASCII, N_("7bit ASCII (US-ASC_II)")), /* RADIO compose_set_encoding_cb */
743 ENC_ACTION(CS_UTF_8, C_UTF_8, N_("Unicode (_UTF-8)")), /* RADIO compose_set_encoding_cb */
744 ENC_ACTION("Western/"CS_ISO_8859_1, C_ISO_8859_1, "ISO-8859-_1"), /* RADIO compose_set_encoding_cb */
745 ENC_ACTION("Western/"CS_ISO_8859_15, C_ISO_8859_15, "ISO-8859-15"), /* RADIO compose_set_encoding_cb */
746 ENC_ACTION("Western/"CS_WINDOWS_1252, C_WINDOWS_1252, "Windows-1252"), /* RADIO compose_set_encoding_cb */
747 ENC_ACTION(CS_ISO_8859_2, C_ISO_8859_2, N_("Central European (ISO-8859-_2)")), /* RADIO compose_set_encoding_cb */
748 ENC_ACTION("Baltic/"CS_ISO_8859_13, C_ISO_8859_13, "ISO-8859-13"), /* RADIO compose_set_encoding_cb */
749 ENC_ACTION("Baltic/"CS_ISO_8859_4, C_ISO_8859_14, "ISO-8859-_4"), /* RADIO compose_set_encoding_cb */
750 ENC_ACTION(CS_ISO_8859_7, C_ISO_8859_7, N_("Greek (ISO-8859-_7)")), /* RADIO compose_set_encoding_cb */
751 ENC_ACTION("Hebrew/"CS_ISO_8859_8, C_ISO_8859_8, "ISO-8859-_8"), /* RADIO compose_set_encoding_cb */
752 ENC_ACTION("Hebrew/"CS_WINDOWS_1255, C_WINDOWS_1255, "Windows-1255"), /* RADIO compose_set_encoding_cb */
753 ENC_ACTION("Arabic/"CS_ISO_8859_6, C_ISO_8859_6, "ISO-8859-_6"), /* RADIO compose_set_encoding_cb */
754 ENC_ACTION("Arabic/"CS_WINDOWS_1256, C_WINDOWS_1256, "Windows-1256"), /* RADIO compose_set_encoding_cb */
755 ENC_ACTION(CS_ISO_8859_9, C_ISO_8859_9, N_("Turkish (ISO-8859-_9)")), /* RADIO compose_set_encoding_cb */
756 ENC_ACTION("Cyrillic/"CS_ISO_8859_5, C_ISO_8859_5, "ISO-8859-_5"), /* RADIO compose_set_encoding_cb */
757 ENC_ACTION("Cyrillic/"CS_KOI8_R, C_KOI8_R, "KOI8-_R"), /* RADIO compose_set_encoding_cb */
758 ENC_ACTION("Cyrillic/"CS_MACCYR, C_MACCYR, "_Mac-Cyrillic"), /* RADIO compose_set_encoding_cb */
759 ENC_ACTION("Cyrillic/"CS_KOI8_U, C_KOI8_U, "KOI8-_U"), /* RADIO compose_set_encoding_cb */
760 ENC_ACTION("Cyrillic/"CS_WINDOWS_1251, C_WINDOWS_1251, "Windows-1251"), /* RADIO compose_set_encoding_cb */
761 ENC_ACTION("Japanese/"CS_ISO_2022_JP, C_ISO_2022_JP, "ISO-2022-_JP"), /* RADIO compose_set_encoding_cb */
762 ENC_ACTION("Japanese/"CS_ISO_2022_JP_2, C_ISO_2022_JP_2, "ISO-2022-JP-_2"), /* RADIO compose_set_encoding_cb */
763 ENC_ACTION("Japanese/"CS_EUC_JP, C_EUC_JP, "_EUC-JP"), /* RADIO compose_set_encoding_cb */
764 ENC_ACTION("Japanese/"CS_SHIFT_JIS, C_SHIFT_JIS, "_Shift-JIS"), /* RADIO compose_set_encoding_cb */
765 ENC_ACTION("Chinese/"CS_GB18030, C_GB18030, "_GB18030"), /* RADIO compose_set_encoding_cb */
766 ENC_ACTION("Chinese/"CS_GB2312, C_GB2312, "_GB2312"), /* RADIO compose_set_encoding_cb */
767 ENC_ACTION("Chinese/"CS_GBK, C_GBK, "GB_K"), /* RADIO compose_set_encoding_cb */
768 ENC_ACTION("Chinese/"CS_BIG5, C_BIG5, "_Big5-JP"), /* RADIO compose_set_encoding_cb */
769 ENC_ACTION("Chinese/"CS_EUC_TW, C_EUC_TW, "EUC-_TW"), /* RADIO compose_set_encoding_cb */
770 ENC_ACTION("Korean/"CS_EUC_KR, C_EUC_KR, "_EUC-KR"), /* RADIO compose_set_encoding_cb */
771 ENC_ACTION("Korean/"CS_ISO_2022_KR, C_ISO_2022_KR, "_ISO-2022-KR"), /* RADIO compose_set_encoding_cb */
772 ENC_ACTION("Thai/"CS_TIS_620, C_TIS_620, "_TIS-620-KR"), /* RADIO compose_set_encoding_cb */
773 ENC_ACTION("Thai/"CS_WINDOWS_874, C_WINDOWS_874, "_Windows-874"), /* RADIO compose_set_encoding_cb */
776 static GtkTargetEntry compose_mime_types[] =
778 {"text/uri-list", 0, 0},
779 {"UTF8_STRING", 0, 0},
783 static gboolean compose_put_existing_to_front(MsgInfo *info)
785 const GList *compose_list = compose_get_compose_list();
786 const GList *elem = NULL;
789 for (elem = compose_list; elem != NULL && elem->data != NULL;
791 Compose *c = (Compose*)elem->data;
793 if (!c->targetinfo || !c->targetinfo->msgid ||
797 if (!strcmp(c->targetinfo->msgid, info->msgid)) {
798 gtkut_window_popup(c->window);
806 static GdkColor quote_color1 =
807 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
808 static GdkColor quote_color2 =
809 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
810 static GdkColor quote_color3 =
811 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
813 static GdkColor quote_bgcolor1 =
814 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
815 static GdkColor quote_bgcolor2 =
816 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
817 static GdkColor quote_bgcolor3 =
818 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
820 static GdkColor signature_color = {
827 static GdkColor uri_color = {
834 static void compose_create_tags(GtkTextView *text, Compose *compose)
836 GtkTextBuffer *buffer;
837 GdkColor black = {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
838 #if !GTK_CHECK_VERSION(2, 24, 0)
845 buffer = gtk_text_view_get_buffer(text);
847 if (prefs_common.enable_color) {
848 /* grab the quote colors, converting from an int to a GdkColor */
849 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_col,
851 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_col,
853 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_col,
855 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_bgcol,
857 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_bgcol,
859 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_bgcol,
861 gtkut_convert_int_to_gdk_color(prefs_common.signature_col,
863 gtkut_convert_int_to_gdk_color(prefs_common.uri_col,
866 signature_color = quote_color1 = quote_color2 = quote_color3 =
867 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 = uri_color = black;
870 if (prefs_common.enable_color && prefs_common.enable_bgcolor) {
871 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
872 "foreground-gdk", "e_color1,
873 "paragraph-background-gdk", "e_bgcolor1,
875 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
876 "foreground-gdk", "e_color2,
877 "paragraph-background-gdk", "e_bgcolor2,
879 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
880 "foreground-gdk", "e_color3,
881 "paragraph-background-gdk", "e_bgcolor3,
884 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
885 "foreground-gdk", "e_color1,
887 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
888 "foreground-gdk", "e_color2,
890 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
891 "foreground-gdk", "e_color3,
895 compose->signature_tag = gtk_text_buffer_create_tag(buffer, "signature",
896 "foreground-gdk", &signature_color,
899 compose->uri_tag = gtk_text_buffer_create_tag(buffer, "link",
900 "foreground-gdk", &uri_color,
902 compose->no_wrap_tag = gtk_text_buffer_create_tag(buffer, "no_wrap", NULL);
903 compose->no_join_tag = gtk_text_buffer_create_tag(buffer, "no_join", NULL);
905 #if !GTK_CHECK_VERSION(2, 24, 0)
906 color[0] = quote_color1;
907 color[1] = quote_color2;
908 color[2] = quote_color3;
909 color[3] = quote_bgcolor1;
910 color[4] = quote_bgcolor2;
911 color[5] = quote_bgcolor3;
912 color[6] = signature_color;
913 color[7] = uri_color;
915 cmap = gdk_drawable_get_colormap(gtk_widget_get_window(compose->window));
916 gdk_colormap_alloc_colors(cmap, color, 8, FALSE, TRUE, success);
918 for (i = 0; i < 8; i++) {
919 if (success[i] == FALSE) {
920 g_warning("Compose: color allocation failed.");
921 quote_color1 = quote_color2 = quote_color3 =
922 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 =
923 signature_color = uri_color = black;
929 Compose *compose_new(PrefsAccount *account, const gchar *mailto,
932 return compose_generic_new(account, mailto, NULL, attach_files, NULL);
935 Compose *compose_new_with_folderitem(PrefsAccount *account, FolderItem *item, const gchar *mailto)
937 return compose_generic_new(account, mailto, item, NULL, NULL);
940 Compose *compose_new_with_list( PrefsAccount *account, GList *listAddress )
942 return compose_generic_new( account, NULL, NULL, NULL, listAddress );
945 #define SCROLL_TO_CURSOR(compose) { \
946 GtkTextMark *cmark = gtk_text_buffer_get_insert( \
947 gtk_text_view_get_buffer( \
948 GTK_TEXT_VIEW(compose->text))); \
949 gtk_text_view_scroll_mark_onscreen( \
950 GTK_TEXT_VIEW(compose->text), \
954 static void compose_set_save_to(Compose *compose, const gchar *folderidentifier)
957 if (folderidentifier) {
958 #if !GTK_CHECK_VERSION(2, 24, 0)
959 combobox_unset_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo));
961 combobox_unset_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo));
963 prefs_common.compose_save_to_history = add_history(
964 prefs_common.compose_save_to_history, folderidentifier);
965 #if !GTK_CHECK_VERSION(2, 24, 0)
966 combobox_set_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo),
967 prefs_common.compose_save_to_history);
969 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo),
970 prefs_common.compose_save_to_history);
974 entry = GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(compose->savemsg_combo)));
975 if (folderidentifier)
976 gtk_entry_set_text(GTK_ENTRY(entry), folderidentifier);
978 gtk_entry_set_text(GTK_ENTRY(entry), "");
981 static gchar *compose_get_save_to(Compose *compose)
984 gchar *result = NULL;
985 entry = GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(compose->savemsg_combo)));
986 result = gtk_editable_get_chars(entry, 0, -1);
989 #if !GTK_CHECK_VERSION(2, 24, 0)
990 combobox_unset_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo));
992 combobox_unset_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo));
994 prefs_common.compose_save_to_history = add_history(
995 prefs_common.compose_save_to_history, result);
996 #if !GTK_CHECK_VERSION(2, 24, 0)
997 combobox_set_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo),
998 prefs_common.compose_save_to_history);
1000 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo),
1001 prefs_common.compose_save_to_history);
1007 Compose *compose_generic_new(PrefsAccount *account, const gchar *mailto, FolderItem *item,
1008 GList *attach_files, GList *listAddress )
1011 GtkTextView *textview;
1012 GtkTextBuffer *textbuf;
1014 const gchar *subject_format = NULL;
1015 const gchar *body_format = NULL;
1016 gchar *mailto_from = NULL;
1017 PrefsAccount *mailto_account = NULL;
1018 MsgInfo* dummyinfo = NULL;
1019 gint cursor_pos = -1;
1020 MailField mfield = NO_FIELD_PRESENT;
1024 /* check if mailto defines a from */
1025 if (mailto && *mailto != '\0') {
1026 scan_mailto_url(mailto, &mailto_from, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
1027 /* mailto defines a from, check if we can get account prefs from it,
1028 if not, the account prefs will be guessed using other ways, but we'll keep
1031 mailto_account = account_find_from_address(mailto_from, TRUE);
1032 if (mailto_account == NULL) {
1034 Xstrdup_a(tmp_from, mailto_from, return NULL);
1035 extract_address(tmp_from);
1036 mailto_account = account_find_from_address(tmp_from, TRUE);
1040 account = mailto_account;
1043 /* if no account prefs set from mailto, set if from folder prefs (if any) */
1044 if (!mailto_account && item && item->prefs && item->prefs->enable_default_account)
1045 account = account_find_from_id(item->prefs->default_account);
1047 /* if no account prefs set, fallback to the current one */
1048 if (!account) account = cur_account;
1049 cm_return_val_if_fail(account != NULL, NULL);
1051 compose = compose_create(account, item, COMPOSE_NEW, FALSE);
1053 /* override from name if mailto asked for it */
1055 gtk_entry_set_text(GTK_ENTRY(compose->from_name), mailto_from);
1056 g_free(mailto_from);
1058 /* override from name according to folder properties */
1059 if (item && item->prefs &&
1060 item->prefs->compose_with_format &&
1061 item->prefs->compose_override_from_format &&
1062 *item->prefs->compose_override_from_format != '\0') {
1067 dummyinfo = compose_msginfo_new_from_compose(compose);
1069 /* decode \-escape sequences in the internal representation of the quote format */
1070 tmp = g_malloc(strlen(item->prefs->compose_override_from_format)+1);
1071 pref_get_unescaped_pref(tmp, item->prefs->compose_override_from_format);
1074 quote_fmt_init(dummyinfo, NULL, NULL, FALSE, compose->account, FALSE,
1075 compose->gtkaspell);
1077 quote_fmt_init(dummyinfo, NULL, NULL, FALSE, compose->account, FALSE);
1079 quote_fmt_scan_string(tmp);
1082 buf = quote_fmt_get_buffer();
1084 alertpanel_error(_("New message From format error."));
1086 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1087 quote_fmt_reset_vartable();
1092 compose->replyinfo = NULL;
1093 compose->fwdinfo = NULL;
1095 textview = GTK_TEXT_VIEW(compose->text);
1096 textbuf = gtk_text_view_get_buffer(textview);
1097 compose_create_tags(textview, compose);
1099 undo_block(compose->undostruct);
1101 compose_set_dictionaries_from_folder_prefs(compose, item);
1104 if (account->auto_sig)
1105 compose_insert_sig(compose, FALSE);
1106 gtk_text_buffer_get_start_iter(textbuf, &iter);
1107 gtk_text_buffer_place_cursor(textbuf, &iter);
1109 if (account->protocol != A_NNTP) {
1110 if (mailto && *mailto != '\0') {
1111 mfield = compose_entries_set(compose, mailto, COMPOSE_TO);
1114 compose_set_folder_prefs(compose, item, TRUE);
1116 if (item && item->ret_rcpt) {
1117 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
1120 if (mailto && *mailto != '\0') {
1121 if (!strchr(mailto, '@'))
1122 mfield = compose_entries_set(compose, mailto, COMPOSE_NEWSGROUPS);
1124 mfield = compose_entries_set(compose, mailto, COMPOSE_TO);
1125 } else if (item && FOLDER_CLASS(item->folder) == news_get_class()) {
1126 compose_entry_append(compose, item->path, COMPOSE_NEWSGROUPS, PREF_FOLDER);
1127 mfield = TO_FIELD_PRESENT;
1130 * CLAWS: just don't allow return receipt request, even if the user
1131 * may want to send an email. simple but foolproof.
1133 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", FALSE);
1135 compose_add_field_list( compose, listAddress );
1137 if (item && item->prefs && item->prefs->compose_with_format) {
1138 subject_format = item->prefs->compose_subject_format;
1139 body_format = item->prefs->compose_body_format;
1140 } else if (account->compose_with_format) {
1141 subject_format = account->compose_subject_format;
1142 body_format = account->compose_body_format;
1143 } else if (prefs_common.compose_with_format) {
1144 subject_format = prefs_common.compose_subject_format;
1145 body_format = prefs_common.compose_body_format;
1148 if (subject_format || body_format) {
1151 && *subject_format != '\0' )
1153 gchar *subject = NULL;
1158 dummyinfo = compose_msginfo_new_from_compose(compose);
1160 /* decode \-escape sequences in the internal representation of the quote format */
1161 tmp = g_malloc(strlen(subject_format)+1);
1162 pref_get_unescaped_pref(tmp, subject_format);
1164 subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1166 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account, FALSE,
1167 compose->gtkaspell);
1169 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account, FALSE);
1171 quote_fmt_scan_string(tmp);
1174 buf = quote_fmt_get_buffer();
1176 alertpanel_error(_("New message subject format error."));
1178 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1179 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1180 quote_fmt_reset_vartable();
1184 mfield = SUBJECT_FIELD_PRESENT;
1188 && *body_format != '\0' )
1191 GtkTextBuffer *buffer;
1192 GtkTextIter start, end;
1196 dummyinfo = compose_msginfo_new_from_compose(compose);
1198 text = GTK_TEXT_VIEW(compose->text);
1199 buffer = gtk_text_view_get_buffer(text);
1200 gtk_text_buffer_get_start_iter(buffer, &start);
1201 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
1202 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
1204 compose_quote_fmt(compose, dummyinfo,
1206 NULL, tmp, FALSE, TRUE,
1207 _("The body of the \"New message\" template has an error at line %d."));
1208 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1209 quote_fmt_reset_vartable();
1213 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1214 gtkaspell_highlight_all(compose->gtkaspell);
1216 mfield = BODY_FIELD_PRESENT;
1220 procmsg_msginfo_free( &dummyinfo );
1226 for (curr = attach_files ; curr != NULL ; curr = curr->next) {
1227 ainfo = (AttachInfo *) curr->data;
1228 compose_attach_append(compose, ainfo->file, ainfo->file,
1229 ainfo->content_type, ainfo->charset);
1233 compose_show_first_last_header(compose, TRUE);
1235 /* Set save folder */
1236 if (item && item->prefs && item->prefs->save_copy_to_folder) {
1237 gchar *folderidentifier;
1239 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
1240 folderidentifier = folder_item_get_identifier(item);
1241 compose_set_save_to(compose, folderidentifier);
1242 g_free(folderidentifier);
1245 /* Place cursor according to provided input (mfield) */
1247 case NO_FIELD_PRESENT:
1248 if (compose->header_last)
1249 gtk_widget_grab_focus(compose->header_last->entry);
1251 case TO_FIELD_PRESENT:
1252 buf = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1254 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1257 gtk_widget_grab_focus(compose->subject_entry);
1259 case SUBJECT_FIELD_PRESENT:
1260 textview = GTK_TEXT_VIEW(compose->text);
1263 textbuf = gtk_text_view_get_buffer(textview);
1266 mark = gtk_text_buffer_get_insert(textbuf);
1267 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
1268 gtk_text_buffer_insert(textbuf, &iter, "", -1);
1270 * SUBJECT_FIELD_PRESENT and BODY_FIELD_PRESENT
1271 * only defers where it comes to the variable body
1272 * is not null. If no body is present compose->text
1273 * will be null in which case you cannot place the
1274 * cursor inside the component so. An empty component
1275 * is therefore created before placing the cursor
1277 case BODY_FIELD_PRESENT:
1278 cursor_pos = quote_fmt_get_cursor_pos();
1279 if (cursor_pos == -1)
1280 gtk_widget_grab_focus(compose->header_last->entry);
1282 gtk_widget_grab_focus(compose->text);
1286 undo_unblock(compose->undostruct);
1288 if (prefs_common.auto_exteditor)
1289 compose_exec_ext_editor(compose);
1291 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
1293 SCROLL_TO_CURSOR(compose);
1295 compose->modified = FALSE;
1296 compose_set_title(compose);
1298 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
1303 static void compose_force_encryption(Compose *compose, PrefsAccount *account,
1304 gboolean override_pref, const gchar *system)
1306 const gchar *privacy = NULL;
1308 cm_return_if_fail(compose != NULL);
1309 cm_return_if_fail(account != NULL);
1311 if (override_pref == FALSE && account->default_encrypt_reply == FALSE)
1314 if (account->default_privacy_system && strlen(account->default_privacy_system))
1315 privacy = account->default_privacy_system;
1319 GSList *privacy_avail = privacy_get_system_ids();
1320 if (privacy_avail && g_slist_length(privacy_avail)) {
1321 privacy = (gchar *)(privacy_avail->data);
1324 if (privacy != NULL) {
1326 g_free(compose->privacy_system);
1327 compose->privacy_system = NULL;
1328 g_free(compose->encdata);
1329 compose->encdata = NULL;
1331 if (compose->privacy_system == NULL)
1332 compose->privacy_system = g_strdup(privacy);
1333 else if (*(compose->privacy_system) == '\0') {
1334 g_free(compose->privacy_system);
1335 g_free(compose->encdata);
1336 compose->encdata = NULL;
1337 compose->privacy_system = g_strdup(privacy);
1339 compose_update_privacy_system_menu_item(compose, FALSE);
1340 compose_use_encryption(compose, TRUE);
1344 static void compose_force_signing(Compose *compose, PrefsAccount *account, const gchar *system)
1346 const gchar *privacy = NULL;
1348 if (account->default_privacy_system && strlen(account->default_privacy_system))
1349 privacy = account->default_privacy_system;
1353 GSList *privacy_avail = privacy_get_system_ids();
1354 if (privacy_avail && g_slist_length(privacy_avail)) {
1355 privacy = (gchar *)(privacy_avail->data);
1359 if (privacy != NULL) {
1361 g_free(compose->privacy_system);
1362 compose->privacy_system = NULL;
1363 g_free(compose->encdata);
1364 compose->encdata = NULL;
1366 if (compose->privacy_system == NULL)
1367 compose->privacy_system = g_strdup(privacy);
1368 compose_update_privacy_system_menu_item(compose, FALSE);
1369 compose_use_signing(compose, TRUE);
1373 static Compose *compose_reply_mode(ComposeMode mode, GSList *msginfo_list, gchar *body)
1377 Compose *compose = NULL;
1379 cm_return_val_if_fail(msginfo_list != NULL, NULL);
1381 msginfo = (MsgInfo*)g_slist_nth_data(msginfo_list, 0);
1382 cm_return_val_if_fail(msginfo != NULL, NULL);
1384 list_len = g_slist_length(msginfo_list);
1388 case COMPOSE_REPLY_TO_ADDRESS:
1389 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1390 FALSE, prefs_common.default_reply_list, FALSE, body);
1392 case COMPOSE_REPLY_WITH_QUOTE:
1393 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1394 FALSE, prefs_common.default_reply_list, FALSE, body);
1396 case COMPOSE_REPLY_WITHOUT_QUOTE:
1397 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1398 FALSE, prefs_common.default_reply_list, FALSE, NULL);
1400 case COMPOSE_REPLY_TO_SENDER:
1401 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1402 FALSE, FALSE, TRUE, body);
1404 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1405 compose = compose_followup_and_reply_to(msginfo,
1406 COMPOSE_QUOTE_CHECK,
1407 FALSE, FALSE, body);
1409 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1410 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1411 FALSE, FALSE, TRUE, body);
1413 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1414 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1415 FALSE, FALSE, TRUE, NULL);
1417 case COMPOSE_REPLY_TO_ALL:
1418 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1419 TRUE, FALSE, FALSE, body);
1421 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1422 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1423 TRUE, FALSE, FALSE, body);
1425 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1426 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1427 TRUE, FALSE, FALSE, NULL);
1429 case COMPOSE_REPLY_TO_LIST:
1430 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1431 FALSE, TRUE, FALSE, body);
1433 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1434 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1435 FALSE, TRUE, FALSE, body);
1437 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1438 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1439 FALSE, TRUE, FALSE, NULL);
1441 case COMPOSE_FORWARD:
1442 if (prefs_common.forward_as_attachment) {
1443 compose = compose_reply_mode(COMPOSE_FORWARD_AS_ATTACH, msginfo_list, body);
1446 compose = compose_reply_mode(COMPOSE_FORWARD_INLINE, msginfo_list, body);
1450 case COMPOSE_FORWARD_INLINE:
1451 /* check if we reply to more than one Message */
1452 if (list_len == 1) {
1453 compose = compose_forward(NULL, msginfo, FALSE, body, FALSE, FALSE);
1456 /* more messages FALL THROUGH */
1457 case COMPOSE_FORWARD_AS_ATTACH:
1458 compose = compose_forward_multiple(NULL, msginfo_list);
1460 case COMPOSE_REDIRECT:
1461 compose = compose_redirect(NULL, msginfo, FALSE);
1464 g_warning("compose_reply_mode(): invalid Compose Mode: %d", mode);
1467 if (compose == NULL) {
1468 alertpanel_error(_("Unable to reply. The original email probably doesn't exist."));
1472 compose->rmode = mode;
1473 switch (compose->rmode) {
1475 case COMPOSE_REPLY_WITH_QUOTE:
1476 case COMPOSE_REPLY_WITHOUT_QUOTE:
1477 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1478 debug_print("reply mode Normal\n");
1479 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/Normal", TRUE);
1480 compose_reply_change_mode(compose, COMPOSE_REPLY); /* force update */
1482 case COMPOSE_REPLY_TO_SENDER:
1483 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1484 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1485 debug_print("reply mode Sender\n");
1486 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/Sender", TRUE);
1488 case COMPOSE_REPLY_TO_ALL:
1489 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1490 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1491 debug_print("reply mode All\n");
1492 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/All", TRUE);
1494 case COMPOSE_REPLY_TO_LIST:
1495 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1496 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1497 debug_print("reply mode List\n");
1498 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/List", TRUE);
1500 case COMPOSE_REPLY_TO_ADDRESS:
1501 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", FALSE);
1509 static Compose *compose_reply(MsgInfo *msginfo,
1510 ComposeQuoteMode quote_mode,
1516 return compose_generic_reply(msginfo, quote_mode, to_all, to_ml,
1517 to_sender, FALSE, body);
1520 static Compose *compose_followup_and_reply_to(MsgInfo *msginfo,
1521 ComposeQuoteMode quote_mode,
1526 return compose_generic_reply(msginfo, quote_mode, to_all, FALSE,
1527 to_sender, TRUE, body);
1530 static void compose_extract_original_charset(Compose *compose)
1532 MsgInfo *info = NULL;
1533 if (compose->replyinfo) {
1534 info = compose->replyinfo;
1535 } else if (compose->fwdinfo) {
1536 info = compose->fwdinfo;
1537 } else if (compose->targetinfo) {
1538 info = compose->targetinfo;
1541 MimeInfo *mimeinfo = procmime_scan_message_short(info);
1542 MimeInfo *partinfo = mimeinfo;
1543 while (partinfo && partinfo->type != MIMETYPE_TEXT)
1544 partinfo = procmime_mimeinfo_next(partinfo);
1546 compose->orig_charset =
1547 g_strdup(procmime_mimeinfo_get_parameter(
1548 partinfo, "charset"));
1550 procmime_mimeinfo_free_all(&mimeinfo);
1554 #define SIGNAL_BLOCK(buffer) { \
1555 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1556 G_CALLBACK(compose_changed_cb), \
1558 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1559 G_CALLBACK(text_inserted), \
1563 #define SIGNAL_UNBLOCK(buffer) { \
1564 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1565 G_CALLBACK(compose_changed_cb), \
1567 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1568 G_CALLBACK(text_inserted), \
1572 static Compose *compose_generic_reply(MsgInfo *msginfo,
1573 ComposeQuoteMode quote_mode,
1574 gboolean to_all, gboolean to_ml,
1576 gboolean followup_and_reply_to,
1580 PrefsAccount *account = NULL;
1581 GtkTextView *textview;
1582 GtkTextBuffer *textbuf;
1583 gboolean quote = FALSE;
1584 const gchar *qmark = NULL;
1585 const gchar *body_fmt = NULL;
1586 gchar *s_system = NULL;
1588 cm_return_val_if_fail(msginfo != NULL, NULL);
1589 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
1591 account = account_get_reply_account(msginfo, prefs_common.reply_account_autosel);
1593 cm_return_val_if_fail(account != NULL, NULL);
1595 compose = compose_create(account, msginfo->folder, COMPOSE_REPLY, FALSE);
1597 compose->updating = TRUE;
1599 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
1600 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", TRUE);
1602 compose->replyinfo = procmsg_msginfo_get_full_info(msginfo);
1603 if (!compose->replyinfo)
1604 compose->replyinfo = procmsg_msginfo_copy(msginfo);
1606 compose_extract_original_charset(compose);
1608 if (msginfo->folder && msginfo->folder->ret_rcpt)
1609 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
1611 /* Set save folder */
1612 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1613 gchar *folderidentifier;
1615 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1616 folderidentifier = folder_item_get_identifier(msginfo->folder);
1617 compose_set_save_to(compose, folderidentifier);
1618 g_free(folderidentifier);
1621 if (compose_parse_header(compose, msginfo) < 0) {
1622 compose->updating = FALSE;
1623 compose_destroy(compose);
1627 /* override from name according to folder properties */
1628 if (msginfo->folder && msginfo->folder->prefs &&
1629 msginfo->folder->prefs->reply_with_format &&
1630 msginfo->folder->prefs->reply_override_from_format &&
1631 *msginfo->folder->prefs->reply_override_from_format != '\0') {
1636 /* decode \-escape sequences in the internal representation of the quote format */
1637 tmp = g_malloc(strlen(msginfo->folder->prefs->reply_override_from_format)+1);
1638 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->reply_override_from_format);
1641 quote_fmt_init(compose->replyinfo, NULL, NULL, FALSE, compose->account, FALSE,
1642 compose->gtkaspell);
1644 quote_fmt_init(compose->replyinfo, NULL, NULL, FALSE, compose->account, FALSE);
1646 quote_fmt_scan_string(tmp);
1649 buf = quote_fmt_get_buffer();
1651 alertpanel_error(_("The \"From\" field of the \"Reply\" template contains an invalid email address."));
1653 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1654 quote_fmt_reset_vartable();
1659 textview = (GTK_TEXT_VIEW(compose->text));
1660 textbuf = gtk_text_view_get_buffer(textview);
1661 compose_create_tags(textview, compose);
1663 undo_block(compose->undostruct);
1665 compose_set_dictionaries_from_folder_prefs(compose, msginfo->folder);
1666 gtkaspell_block_check(compose->gtkaspell);
1669 if (quote_mode == COMPOSE_QUOTE_FORCED ||
1670 (quote_mode == COMPOSE_QUOTE_CHECK && prefs_common.reply_with_quote)) {
1671 /* use the reply format of folder (if enabled), or the account's one
1672 (if enabled) or fallback to the global reply format, which is always
1673 enabled (even if empty), and use the relevant quotemark */
1675 if (msginfo->folder && msginfo->folder->prefs &&
1676 msginfo->folder->prefs->reply_with_format) {
1677 qmark = msginfo->folder->prefs->reply_quotemark;
1678 body_fmt = msginfo->folder->prefs->reply_body_format;
1680 } else if (account->reply_with_format) {
1681 qmark = account->reply_quotemark;
1682 body_fmt = account->reply_body_format;
1685 qmark = prefs_common.quotemark;
1686 if (prefs_common.quotefmt && *prefs_common.quotefmt)
1687 body_fmt = gettext(prefs_common.quotefmt);
1694 /* empty quotemark is not allowed */
1695 if (qmark == NULL || *qmark == '\0')
1697 compose_quote_fmt(compose, compose->replyinfo,
1698 body_fmt, qmark, body, FALSE, TRUE,
1699 _("The body of the \"Reply\" template has an error at line %d."));
1700 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1701 quote_fmt_reset_vartable();
1704 if (MSG_IS_ENCRYPTED(compose->replyinfo->flags)) {
1705 compose_force_encryption(compose, account, FALSE, s_system);
1708 privacy_msginfo_get_signed_state(compose->replyinfo, &s_system);
1709 if (MSG_IS_SIGNED(compose->replyinfo->flags) && account->default_sign_reply) {
1710 compose_force_signing(compose, account, s_system);
1714 SIGNAL_BLOCK(textbuf);
1716 if (account->auto_sig)
1717 compose_insert_sig(compose, FALSE);
1719 compose_wrap_all(compose);
1722 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1723 gtkaspell_highlight_all(compose->gtkaspell);
1724 gtkaspell_unblock_check(compose->gtkaspell);
1726 SIGNAL_UNBLOCK(textbuf);
1728 gtk_widget_grab_focus(compose->text);
1730 undo_unblock(compose->undostruct);
1732 if (prefs_common.auto_exteditor)
1733 compose_exec_ext_editor(compose);
1735 compose->modified = FALSE;
1736 compose_set_title(compose);
1738 compose->updating = FALSE;
1739 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
1740 SCROLL_TO_CURSOR(compose);
1742 if (compose->deferred_destroy) {
1743 compose_destroy(compose);
1751 #define INSERT_FW_HEADER(var, hdr) \
1752 if (msginfo->var && *msginfo->var) { \
1753 gtk_stext_insert(text, NULL, NULL, NULL, hdr, -1); \
1754 gtk_stext_insert(text, NULL, NULL, NULL, msginfo->var, -1); \
1755 gtk_stext_insert(text, NULL, NULL, NULL, "\n", 1); \
1758 Compose *compose_forward(PrefsAccount *account, MsgInfo *msginfo,
1759 gboolean as_attach, const gchar *body,
1760 gboolean no_extedit,
1764 GtkTextView *textview;
1765 GtkTextBuffer *textbuf;
1766 gint cursor_pos = -1;
1769 cm_return_val_if_fail(msginfo != NULL, NULL);
1770 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
1772 if (!account && !(account = compose_find_account(msginfo)))
1773 account = cur_account;
1775 if (!prefs_common.forward_as_attachment)
1776 mode = COMPOSE_FORWARD_INLINE;
1778 mode = COMPOSE_FORWARD;
1779 compose = compose_create(account, msginfo->folder, mode, batch);
1781 compose->updating = TRUE;
1782 compose->fwdinfo = procmsg_msginfo_get_full_info(msginfo);
1783 if (!compose->fwdinfo)
1784 compose->fwdinfo = procmsg_msginfo_copy(msginfo);
1786 compose_extract_original_charset(compose);
1788 if (msginfo->subject && *msginfo->subject) {
1789 gchar *buf, *buf2, *p;
1791 buf = p = g_strdup(msginfo->subject);
1792 p += subject_get_prefix_length(p);
1793 memmove(buf, p, strlen(p) + 1);
1795 buf2 = g_strdup_printf("Fw: %s", buf);
1796 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
1802 /* override from name according to folder properties */
1803 if (msginfo->folder && msginfo->folder->prefs &&
1804 msginfo->folder->prefs->forward_with_format &&
1805 msginfo->folder->prefs->forward_override_from_format &&
1806 *msginfo->folder->prefs->forward_override_from_format != '\0') {
1810 MsgInfo *full_msginfo = NULL;
1813 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1815 full_msginfo = procmsg_msginfo_copy(msginfo);
1817 /* decode \-escape sequences in the internal representation of the quote format */
1818 tmp = g_malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
1819 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
1822 gtkaspell_block_check(compose->gtkaspell);
1823 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE,
1824 compose->gtkaspell);
1826 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE);
1828 quote_fmt_scan_string(tmp);
1831 buf = quote_fmt_get_buffer();
1833 alertpanel_error(_("The \"From\" field of the \"Forward\" template contains an invalid email address."));
1835 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1836 quote_fmt_reset_vartable();
1839 procmsg_msginfo_free(&full_msginfo);
1842 textview = GTK_TEXT_VIEW(compose->text);
1843 textbuf = gtk_text_view_get_buffer(textview);
1844 compose_create_tags(textview, compose);
1846 undo_block(compose->undostruct);
1850 msgfile = procmsg_get_message_file(msginfo);
1851 if (!is_file_exist(msgfile))
1852 g_warning("%s: file does not exist", msgfile);
1854 compose_attach_append(compose, msgfile, msgfile,
1855 "message/rfc822", NULL);
1859 const gchar *qmark = NULL;
1860 const gchar *body_fmt = NULL;
1861 MsgInfo *full_msginfo;
1863 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1865 full_msginfo = procmsg_msginfo_copy(msginfo);
1867 /* use the forward format of folder (if enabled), or the account's one
1868 (if enabled) or fallback to the global forward format, which is always
1869 enabled (even if empty), and use the relevant quotemark */
1870 if (msginfo->folder && msginfo->folder->prefs &&
1871 msginfo->folder->prefs->forward_with_format) {
1872 qmark = msginfo->folder->prefs->forward_quotemark;
1873 body_fmt = msginfo->folder->prefs->forward_body_format;
1875 } else if (account->forward_with_format) {
1876 qmark = account->forward_quotemark;
1877 body_fmt = account->forward_body_format;
1880 qmark = prefs_common.fw_quotemark;
1881 if (prefs_common.fw_quotefmt && *prefs_common.fw_quotefmt)
1882 body_fmt = gettext(prefs_common.fw_quotefmt);
1887 /* empty quotemark is not allowed */
1888 if (qmark == NULL || *qmark == '\0')
1891 compose_quote_fmt(compose, full_msginfo,
1892 body_fmt, qmark, body, FALSE, TRUE,
1893 _("The body of the \"Forward\" template has an error at line %d."));
1894 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1895 quote_fmt_reset_vartable();
1896 compose_attach_parts(compose, msginfo);
1898 procmsg_msginfo_free(&full_msginfo);
1901 SIGNAL_BLOCK(textbuf);
1903 if (account->auto_sig)
1904 compose_insert_sig(compose, FALSE);
1906 compose_wrap_all(compose);
1909 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1910 gtkaspell_highlight_all(compose->gtkaspell);
1911 gtkaspell_unblock_check(compose->gtkaspell);
1913 SIGNAL_UNBLOCK(textbuf);
1915 cursor_pos = quote_fmt_get_cursor_pos();
1916 if (cursor_pos == -1)
1917 gtk_widget_grab_focus(compose->header_last->entry);
1919 gtk_widget_grab_focus(compose->text);
1921 if (!no_extedit && prefs_common.auto_exteditor)
1922 compose_exec_ext_editor(compose);
1925 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1926 gchar *folderidentifier;
1928 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1929 folderidentifier = folder_item_get_identifier(msginfo->folder);
1930 compose_set_save_to(compose, folderidentifier);
1931 g_free(folderidentifier);
1934 undo_unblock(compose->undostruct);
1936 compose->modified = FALSE;
1937 compose_set_title(compose);
1939 compose->updating = FALSE;
1940 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
1941 SCROLL_TO_CURSOR(compose);
1943 if (compose->deferred_destroy) {
1944 compose_destroy(compose);
1948 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
1953 #undef INSERT_FW_HEADER
1955 static Compose *compose_forward_multiple(PrefsAccount *account, GSList *msginfo_list)
1958 GtkTextView *textview;
1959 GtkTextBuffer *textbuf;
1963 gboolean single_mail = TRUE;
1965 cm_return_val_if_fail(msginfo_list != NULL, NULL);
1967 if (g_slist_length(msginfo_list) > 1)
1968 single_mail = FALSE;
1970 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next)
1971 if (((MsgInfo *)msginfo->data)->folder == NULL)
1974 /* guess account from first selected message */
1976 !(account = compose_find_account(msginfo_list->data)))
1977 account = cur_account;
1979 cm_return_val_if_fail(account != NULL, NULL);
1981 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
1982 if (msginfo->data) {
1983 MSG_UNSET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_REPLIED);
1984 MSG_SET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_FORWARDED);
1988 if (msginfo_list == NULL || msginfo_list->data == NULL) {
1989 g_warning("no msginfo_list");
1993 compose = compose_create(account, ((MsgInfo *)msginfo_list->data)->folder, COMPOSE_FORWARD, FALSE);
1995 compose->updating = TRUE;
1997 /* override from name according to folder properties */
1998 if (msginfo_list->data) {
1999 MsgInfo *msginfo = msginfo_list->data;
2001 if (msginfo->folder && msginfo->folder->prefs &&
2002 msginfo->folder->prefs->forward_with_format &&
2003 msginfo->folder->prefs->forward_override_from_format &&
2004 *msginfo->folder->prefs->forward_override_from_format != '\0') {
2009 /* decode \-escape sequences in the internal representation of the quote format */
2010 tmp = g_malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
2011 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
2014 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
2015 compose->gtkaspell);
2017 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
2019 quote_fmt_scan_string(tmp);
2022 buf = quote_fmt_get_buffer();
2024 alertpanel_error(_("The \"From\" field of the \"Forward\" template contains an invalid email address."));
2026 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
2027 quote_fmt_reset_vartable();
2033 textview = GTK_TEXT_VIEW(compose->text);
2034 textbuf = gtk_text_view_get_buffer(textview);
2035 compose_create_tags(textview, compose);
2037 undo_block(compose->undostruct);
2038 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
2039 msgfile = procmsg_get_message_file((MsgInfo *)msginfo->data);
2041 if (!is_file_exist(msgfile))
2042 g_warning("%s: file does not exist", msgfile);
2044 compose_attach_append(compose, msgfile, msgfile,
2045 "message/rfc822", NULL);
2050 MsgInfo *info = (MsgInfo *)msginfo_list->data;
2051 if (info->subject && *info->subject) {
2052 gchar *buf, *buf2, *p;
2054 buf = p = g_strdup(info->subject);
2055 p += subject_get_prefix_length(p);
2056 memmove(buf, p, strlen(p) + 1);
2058 buf2 = g_strdup_printf("Fw: %s", buf);
2059 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
2065 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2066 _("Fw: multiple emails"));
2069 SIGNAL_BLOCK(textbuf);
2071 if (account->auto_sig)
2072 compose_insert_sig(compose, FALSE);
2074 compose_wrap_all(compose);
2076 SIGNAL_UNBLOCK(textbuf);
2078 gtk_text_buffer_get_start_iter(textbuf, &iter);
2079 gtk_text_buffer_place_cursor(textbuf, &iter);
2081 if (prefs_common.auto_exteditor)
2082 compose_exec_ext_editor(compose);
2084 gtk_widget_grab_focus(compose->header_last->entry);
2085 undo_unblock(compose->undostruct);
2086 compose->modified = FALSE;
2087 compose_set_title(compose);
2089 compose->updating = FALSE;
2090 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2091 SCROLL_TO_CURSOR(compose);
2093 if (compose->deferred_destroy) {
2094 compose_destroy(compose);
2098 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2103 static gboolean compose_is_sig_separator(Compose *compose, GtkTextBuffer *textbuf, GtkTextIter *iter)
2105 GtkTextIter start = *iter;
2106 GtkTextIter end_iter;
2107 int start_pos = gtk_text_iter_get_offset(&start);
2109 if (!compose->account->sig_sep)
2112 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
2113 start_pos+strlen(compose->account->sig_sep));
2115 /* check sig separator */
2116 str = gtk_text_iter_get_text(&start, &end_iter);
2117 if (!strcmp(str, compose->account->sig_sep)) {
2119 /* check end of line (\n) */
2120 gtk_text_buffer_get_iter_at_offset(textbuf, &start,
2121 start_pos+strlen(compose->account->sig_sep));
2122 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
2123 start_pos+strlen(compose->account->sig_sep)+1);
2124 tmp = gtk_text_iter_get_text(&start, &end_iter);
2125 if (!strcmp(tmp,"\n")) {
2137 static gboolean compose_update_folder_hook(gpointer source, gpointer data)
2139 FolderUpdateData *hookdata = (FolderUpdateData *)source;
2140 Compose *compose = (Compose *)data;
2141 FolderItem *old_item = NULL;
2142 FolderItem *new_item = NULL;
2143 gchar *old_id, *new_id;
2145 if (!(hookdata->update_flags & FOLDER_REMOVE_FOLDERITEM)
2146 && !(hookdata->update_flags & FOLDER_MOVE_FOLDERITEM))
2149 old_item = hookdata->item;
2150 new_item = hookdata->item2;
2152 old_id = folder_item_get_identifier(old_item);
2153 new_id = new_item ? folder_item_get_identifier(new_item) : g_strdup("NULL");
2155 if (compose->targetinfo && compose->targetinfo->folder == old_item) {
2156 debug_print("updating targetinfo folder: %s -> %s\n", old_id, new_id);
2157 compose->targetinfo->folder = new_item;
2160 if (compose->replyinfo && compose->replyinfo->folder == old_item) {
2161 debug_print("updating replyinfo folder: %s -> %s\n", old_id, new_id);
2162 compose->replyinfo->folder = new_item;
2165 if (compose->fwdinfo && compose->fwdinfo->folder == old_item) {
2166 debug_print("updating fwdinfo folder: %s -> %s\n", old_id, new_id);
2167 compose->fwdinfo->folder = new_item;
2175 static void compose_colorize_signature(Compose *compose)
2177 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
2179 GtkTextIter end_iter;
2180 gtk_text_buffer_get_start_iter(buffer, &iter);
2181 while (gtk_text_iter_forward_line(&iter))
2182 if (compose_is_sig_separator(compose, buffer, &iter)) {
2183 gtk_text_buffer_get_end_iter(buffer, &end_iter);
2184 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &end_iter);
2188 #define BLOCK_WRAP() { \
2189 prev_autowrap = compose->autowrap; \
2190 buffer = gtk_text_view_get_buffer( \
2191 GTK_TEXT_VIEW(compose->text)); \
2192 compose->autowrap = FALSE; \
2194 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2195 G_CALLBACK(compose_changed_cb), \
2197 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2198 G_CALLBACK(text_inserted), \
2201 #define UNBLOCK_WRAP() { \
2202 compose->autowrap = prev_autowrap; \
2203 if (compose->autowrap) { \
2204 gint old = compose->draft_timeout_tag; \
2205 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN; \
2206 compose_wrap_all(compose); \
2207 compose->draft_timeout_tag = old; \
2210 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2211 G_CALLBACK(compose_changed_cb), \
2213 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2214 G_CALLBACK(text_inserted), \
2218 Compose *compose_reedit(MsgInfo *msginfo, gboolean batch)
2220 Compose *compose = NULL;
2221 PrefsAccount *account = NULL;
2222 GtkTextView *textview;
2223 GtkTextBuffer *textbuf;
2227 gchar buf[BUFFSIZE];
2228 gboolean use_signing = FALSE;
2229 gboolean use_encryption = FALSE;
2230 gchar *privacy_system = NULL;
2231 int priority = PRIORITY_NORMAL;
2232 MsgInfo *replyinfo = NULL, *fwdinfo = NULL;
2233 gboolean autowrap = prefs_common.autowrap;
2234 gboolean autoindent = prefs_common.auto_indent;
2235 HeaderEntry *manual_headers = NULL;
2237 cm_return_val_if_fail(msginfo != NULL, NULL);
2238 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
2240 if (compose_put_existing_to_front(msginfo)) {
2244 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2245 folder_has_parent_of_type(msginfo->folder, F_DRAFT) ||
2246 folder_has_parent_of_type(msginfo->folder, F_OUTBOX)) {
2247 gchar queueheader_buf[BUFFSIZE];
2250 /* Select Account from queue headers */
2251 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2252 sizeof(queueheader_buf), "X-Claws-Account-Id:")) {
2253 id = atoi(&queueheader_buf[strlen("X-Claws-Account-Id:")]);
2254 account = account_find_from_id(id);
2256 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2257 sizeof(queueheader_buf), "X-Sylpheed-Account-Id:")) {
2258 id = atoi(&queueheader_buf[strlen("X-Sylpheed-Account-Id:")]);
2259 account = account_find_from_id(id);
2261 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2262 sizeof(queueheader_buf), "NAID:")) {
2263 id = atoi(&queueheader_buf[strlen("NAID:")]);
2264 account = account_find_from_id(id);
2266 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2267 sizeof(queueheader_buf), "MAID:")) {
2268 id = atoi(&queueheader_buf[strlen("MAID:")]);
2269 account = account_find_from_id(id);
2271 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2272 sizeof(queueheader_buf), "S:")) {
2273 account = account_find_from_address(queueheader_buf, FALSE);
2275 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2276 sizeof(queueheader_buf), "X-Claws-Sign:")) {
2277 param = atoi(&queueheader_buf[strlen("X-Claws-Sign:")]);
2278 use_signing = param;
2281 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2282 sizeof(queueheader_buf), "X-Sylpheed-Sign:")) {
2283 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Sign:")]);
2284 use_signing = param;
2287 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2288 sizeof(queueheader_buf), "X-Claws-Encrypt:")) {
2289 param = atoi(&queueheader_buf[strlen("X-Claws-Encrypt:")]);
2290 use_encryption = param;
2292 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2293 sizeof(queueheader_buf), "X-Sylpheed-Encrypt:")) {
2294 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Encrypt:")]);
2295 use_encryption = param;
2297 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2298 sizeof(queueheader_buf), "X-Claws-Auto-Wrapping:")) {
2299 param = atoi(&queueheader_buf[strlen("X-Claws-Auto-Wrapping:")]);
2302 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2303 sizeof(queueheader_buf), "X-Claws-Auto-Indent:")) {
2304 param = atoi(&queueheader_buf[strlen("X-Claws-Auto-Indent:")]);
2307 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2308 sizeof(queueheader_buf), "X-Claws-Privacy-System:")) {
2309 privacy_system = g_strdup(&queueheader_buf[strlen("X-Claws-Privacy-System:")]);
2311 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2312 sizeof(queueheader_buf), "X-Sylpheed-Privacy-System:")) {
2313 privacy_system = g_strdup(&queueheader_buf[strlen("X-Sylpheed-Privacy-System:")]);
2315 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2316 sizeof(queueheader_buf), "X-Priority: ")) {
2317 param = atoi(&queueheader_buf[strlen("X-Priority: ")]); /* mind the space */
2320 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2321 sizeof(queueheader_buf), "RMID:")) {
2322 gchar **tokens = g_strsplit(&queueheader_buf[strlen("RMID:")], "\t", 0);
2323 if (tokens[0] && tokens[1] && tokens[2]) {
2324 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2325 if (orig_item != NULL) {
2326 replyinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2331 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2332 sizeof(queueheader_buf), "FMID:")) {
2333 gchar **tokens = g_strsplit(&queueheader_buf[strlen("FMID:")], "\t", 0);
2334 if (tokens[0] && tokens[1] && tokens[2]) {
2335 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2336 if (orig_item != NULL) {
2337 fwdinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2342 /* Get manual headers */
2343 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "X-Claws-Manual-Headers:")) {
2344 gchar *listmh = g_strdup(&queueheader_buf[strlen("X-Claws-Manual-Headers:")]);
2345 if (*listmh != '\0') {
2346 debug_print("Got manual headers: %s\n", listmh);
2347 manual_headers = procheader_entries_from_str(listmh);
2352 account = msginfo->folder->folder->account;
2355 if (!account && prefs_common.reedit_account_autosel) {
2356 gchar from[BUFFSIZE];
2357 if (!procheader_get_header_from_msginfo(msginfo, from, sizeof(from), "FROM:")) {
2358 extract_address(from);
2359 account = account_find_from_address(from, FALSE);
2363 account = cur_account;
2365 cm_return_val_if_fail(account != NULL, NULL);
2367 compose = compose_create(account, msginfo->folder, COMPOSE_REEDIT, batch);
2369 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", autowrap);
2370 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", autoindent);
2371 compose->autowrap = autowrap;
2372 compose->replyinfo = replyinfo;
2373 compose->fwdinfo = fwdinfo;
2375 compose->updating = TRUE;
2376 compose->priority = priority;
2378 if (privacy_system != NULL) {
2379 compose->privacy_system = privacy_system;
2380 compose_use_signing(compose, use_signing);
2381 compose_use_encryption(compose, use_encryption);
2382 compose_update_privacy_system_menu_item(compose, FALSE);
2384 activate_privacy_system(compose, account, FALSE);
2387 compose->targetinfo = procmsg_msginfo_copy(msginfo);
2389 compose_extract_original_charset(compose);
2391 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2392 folder_has_parent_of_type(msginfo->folder, F_DRAFT) ||
2393 folder_has_parent_of_type(msginfo->folder, F_OUTBOX)) {
2394 gchar queueheader_buf[BUFFSIZE];
2396 /* Set message save folder */
2397 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "SCF:")) {
2398 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
2399 compose_set_save_to(compose, &queueheader_buf[4]);
2401 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "RRCPT:")) {
2402 gint active = atoi(&queueheader_buf[strlen("RRCPT:")]);
2404 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
2409 if (compose_parse_header(compose, msginfo) < 0) {
2410 compose->updating = FALSE;
2411 compose_destroy(compose);
2414 compose_reedit_set_entry(compose, msginfo);
2416 textview = GTK_TEXT_VIEW(compose->text);
2417 textbuf = gtk_text_view_get_buffer(textview);
2418 compose_create_tags(textview, compose);
2420 mark = gtk_text_buffer_get_insert(textbuf);
2421 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
2423 g_signal_handlers_block_by_func(G_OBJECT(textbuf),
2424 G_CALLBACK(compose_changed_cb),
2427 if (MSG_IS_ENCRYPTED(msginfo->flags)) {
2428 fp = procmime_get_first_encrypted_text_content(msginfo);
2430 compose_force_encryption(compose, account, TRUE, NULL);
2433 fp = procmime_get_first_text_content(msginfo);
2436 g_warning("Can't get text part");
2440 gboolean prev_autowrap;
2441 GtkTextBuffer *buffer;
2443 while (fgets(buf, sizeof(buf), fp) != NULL) {
2445 gtk_text_buffer_insert(textbuf, &iter, buf, -1);
2451 compose_attach_parts(compose, msginfo);
2453 compose_colorize_signature(compose);
2455 g_signal_handlers_unblock_by_func(G_OBJECT(textbuf),
2456 G_CALLBACK(compose_changed_cb),
2459 if (manual_headers != NULL) {
2460 if (compose_parse_manual_headers(compose, msginfo, manual_headers) < 0) {
2461 procheader_entries_free(manual_headers);
2462 compose->updating = FALSE;
2463 compose_destroy(compose);
2466 procheader_entries_free(manual_headers);
2469 gtk_widget_grab_focus(compose->text);
2471 if (prefs_common.auto_exteditor) {
2472 compose_exec_ext_editor(compose);
2474 compose->modified = FALSE;
2475 compose_set_title(compose);
2477 compose->updating = FALSE;
2478 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2479 SCROLL_TO_CURSOR(compose);
2481 if (compose->deferred_destroy) {
2482 compose_destroy(compose);
2486 compose->sig_str = account_get_signature_str(compose->account);
2488 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2493 Compose *compose_redirect(PrefsAccount *account, MsgInfo *msginfo,
2500 cm_return_val_if_fail(msginfo != NULL, NULL);
2503 account = account_get_reply_account(msginfo,
2504 prefs_common.reply_account_autosel);
2505 cm_return_val_if_fail(account != NULL, NULL);
2507 compose = compose_create(account, msginfo->folder, COMPOSE_REDIRECT, batch);
2509 compose->updating = TRUE;
2511 compose_create_tags(GTK_TEXT_VIEW(compose->text), compose);
2512 compose->replyinfo = NULL;
2513 compose->fwdinfo = NULL;
2515 compose_show_first_last_header(compose, TRUE);
2517 gtk_widget_grab_focus(compose->header_last->entry);
2519 filename = procmsg_get_message_file(msginfo);
2521 if (filename == NULL) {
2522 compose->updating = FALSE;
2523 compose_destroy(compose);
2528 compose->redirect_filename = filename;
2530 /* Set save folder */
2531 item = msginfo->folder;
2532 if (item && item->prefs && item->prefs->save_copy_to_folder) {
2533 gchar *folderidentifier;
2535 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
2536 folderidentifier = folder_item_get_identifier(item);
2537 compose_set_save_to(compose, folderidentifier);
2538 g_free(folderidentifier);
2541 compose_attach_parts(compose, msginfo);
2543 if (msginfo->subject)
2544 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2546 gtk_editable_set_editable(GTK_EDITABLE(compose->subject_entry), FALSE);
2548 compose_quote_fmt(compose, msginfo, "%M", NULL, NULL, FALSE, FALSE,
2549 _("The body of the \"Redirect\" template has an error at line %d."));
2550 quote_fmt_reset_vartable();
2551 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), FALSE);
2553 compose_colorize_signature(compose);
2556 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Add", FALSE);
2557 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", FALSE);
2558 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", FALSE);
2560 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/Save", FALSE);
2561 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertFile", FALSE);
2562 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/AttachFile", FALSE);
2563 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertSig", FALSE);
2564 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/ReplaceSig", FALSE);
2565 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", FALSE);
2566 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", FALSE);
2567 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/ShowRuler", FALSE);
2568 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Actions", FALSE);
2570 if (compose->toolbar->draft_btn)
2571 gtk_widget_set_sensitive(compose->toolbar->draft_btn, FALSE);
2572 if (compose->toolbar->insert_btn)
2573 gtk_widget_set_sensitive(compose->toolbar->insert_btn, FALSE);
2574 if (compose->toolbar->attach_btn)
2575 gtk_widget_set_sensitive(compose->toolbar->attach_btn, FALSE);
2576 if (compose->toolbar->sig_btn)
2577 gtk_widget_set_sensitive(compose->toolbar->sig_btn, FALSE);
2578 if (compose->toolbar->exteditor_btn)
2579 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, FALSE);
2580 if (compose->toolbar->linewrap_current_btn)
2581 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, FALSE);
2582 if (compose->toolbar->linewrap_all_btn)
2583 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, FALSE);
2585 compose->modified = FALSE;
2586 compose_set_title(compose);
2587 compose->updating = FALSE;
2588 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2589 SCROLL_TO_CURSOR(compose);
2591 if (compose->deferred_destroy) {
2592 compose_destroy(compose);
2596 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2601 const GList *compose_get_compose_list(void)
2603 return compose_list;
2606 void compose_entry_append(Compose *compose, const gchar *address,
2607 ComposeEntryType type, ComposePrefType pref_type)
2609 const gchar *header;
2611 gboolean in_quote = FALSE;
2612 if (!address || *address == '\0') return;
2619 header = N_("Bcc:");
2621 case COMPOSE_REPLYTO:
2622 header = N_("Reply-To:");
2624 case COMPOSE_NEWSGROUPS:
2625 header = N_("Newsgroups:");
2627 case COMPOSE_FOLLOWUPTO:
2628 header = N_( "Followup-To:");
2630 case COMPOSE_INREPLYTO:
2631 header = N_( "In-Reply-To:");
2638 header = prefs_common_translated_header_name(header);
2640 cur = begin = (gchar *)address;
2642 /* we separate the line by commas, but not if we're inside a quoted
2644 while (*cur != '\0') {
2646 in_quote = !in_quote;
2647 if (*cur == ',' && !in_quote) {
2648 gchar *tmp = g_strdup(begin);
2650 tmp[cur-begin]='\0';
2653 while (*tmp == ' ' || *tmp == '\t')
2655 compose_add_header_entry(compose, header, tmp, pref_type);
2656 compose_entry_indicate(compose, tmp);
2663 gchar *tmp = g_strdup(begin);
2665 tmp[cur-begin]='\0';
2666 while (*tmp == ' ' || *tmp == '\t')
2668 compose_add_header_entry(compose, header, tmp, pref_type);
2669 compose_entry_indicate(compose, tmp);
2674 static void compose_entry_indicate(Compose *compose, const gchar *mailto)
2679 for (h_list = compose->header_list; h_list != NULL; h_list = h_list->next) {
2680 entry = GTK_ENTRY(((ComposeHeaderEntry *)h_list->data)->entry);
2681 if (gtk_entry_get_text(entry) &&
2682 !g_utf8_collate(gtk_entry_get_text(entry), mailto)) {
2683 gtk_widget_modify_base(
2684 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2685 GTK_STATE_NORMAL, &default_header_bgcolor);
2686 gtk_widget_modify_text(
2687 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2688 GTK_STATE_NORMAL, &default_header_color);
2693 void compose_toolbar_cb(gint action, gpointer data)
2695 ToolbarItem *toolbar_item = (ToolbarItem*)data;
2696 Compose *compose = (Compose*)toolbar_item->parent;
2698 cm_return_if_fail(compose != NULL);
2702 compose_send_cb(NULL, compose);
2705 compose_send_later_cb(NULL, compose);
2708 compose_draft(compose, COMPOSE_QUIT_EDITING);
2711 compose_insert_file_cb(NULL, compose);
2714 compose_attach_cb(NULL, compose);
2717 compose_insert_sig(compose, FALSE);
2720 compose_insert_sig(compose, TRUE);
2723 compose_ext_editor_cb(NULL, compose);
2725 case A_LINEWRAP_CURRENT:
2726 compose_beautify_paragraph(compose, NULL, TRUE);
2728 case A_LINEWRAP_ALL:
2729 compose_wrap_all_full(compose, TRUE);
2732 compose_address_cb(NULL, compose);
2735 case A_CHECK_SPELLING:
2736 compose_check_all(NULL, compose);
2744 static MailField compose_entries_set(Compose *compose, const gchar *mailto, ComposeEntryType to_type)
2749 gchar *subject = NULL;
2753 gchar **attach = NULL;
2754 gchar *inreplyto = NULL;
2755 MailField mfield = NO_FIELD_PRESENT;
2757 /* get mailto parts but skip from */
2758 scan_mailto_url(mailto, NULL, &to, &cc, &bcc, &subject, &body, &attach, &inreplyto);
2761 compose_entry_append(compose, to, to_type, PREF_MAILTO);
2762 mfield = TO_FIELD_PRESENT;
2765 compose_entry_append(compose, cc, COMPOSE_CC, PREF_MAILTO);
2767 compose_entry_append(compose, bcc, COMPOSE_BCC, PREF_MAILTO);
2769 if (!g_utf8_validate (subject, -1, NULL)) {
2770 temp = g_locale_to_utf8 (subject, -1, NULL, &len, NULL);
2771 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), temp);
2774 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), subject);
2776 mfield = SUBJECT_FIELD_PRESENT;
2779 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2780 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2783 gboolean prev_autowrap = compose->autowrap;
2785 compose->autowrap = FALSE;
2787 mark = gtk_text_buffer_get_insert(buffer);
2788 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2790 if (!g_utf8_validate (body, -1, NULL)) {
2791 temp = g_locale_to_utf8 (body, -1, NULL, &len, NULL);
2792 gtk_text_buffer_insert(buffer, &iter, temp, -1);
2795 gtk_text_buffer_insert(buffer, &iter, body, -1);
2797 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
2799 compose->autowrap = prev_autowrap;
2800 if (compose->autowrap)
2801 compose_wrap_all(compose);
2802 mfield = BODY_FIELD_PRESENT;
2806 gint i = 0, att = 0;
2807 gchar *warn_files = NULL;
2808 while (attach[i] != NULL) {
2809 gchar *utf8_filename = conv_filename_to_utf8(attach[i]);
2810 if (utf8_filename) {
2811 if (compose_attach_append(compose, attach[i], utf8_filename, NULL, NULL)) {
2812 gchar *tmp = g_strdup_printf("%s%s\n",
2813 warn_files?warn_files:"",
2819 g_free(utf8_filename);
2821 alertpanel_error(_("Couldn't attach a file (charset conversion failed)."));
2826 alertpanel_notice(ngettext(
2827 "The following file has been attached: \n%s",
2828 "The following files have been attached: \n%s", att), warn_files);
2833 compose_entry_append(compose, inreplyto, COMPOSE_INREPLYTO, PREF_MAILTO);
2846 static gint compose_parse_header(Compose *compose, MsgInfo *msginfo)
2848 static HeaderEntry hentry[] = {
2849 {"Reply-To:", NULL, TRUE },
2850 {"Cc:", NULL, TRUE },
2851 {"References:", NULL, FALSE },
2852 {"Bcc:", NULL, TRUE },
2853 {"Newsgroups:", NULL, TRUE },
2854 {"Followup-To:", NULL, TRUE },
2855 {"List-Post:", NULL, FALSE },
2856 {"X-Priority:", NULL, FALSE },
2857 {NULL, NULL, FALSE }
2874 cm_return_val_if_fail(msginfo != NULL, -1);
2876 if ((fp = procmsg_open_message(msginfo)) == NULL) return -1;
2877 procheader_get_header_fields(fp, hentry);
2880 if (hentry[H_REPLY_TO].body != NULL) {
2881 if (hentry[H_REPLY_TO].body[0] != '\0') {
2883 conv_unmime_header(hentry[H_REPLY_TO].body,
2886 g_free(hentry[H_REPLY_TO].body);
2887 hentry[H_REPLY_TO].body = NULL;
2889 if (hentry[H_CC].body != NULL) {
2890 compose->cc = conv_unmime_header(hentry[H_CC].body, NULL, TRUE);
2891 g_free(hentry[H_CC].body);
2892 hentry[H_CC].body = NULL;
2894 if (hentry[H_REFERENCES].body != NULL) {
2895 if (compose->mode == COMPOSE_REEDIT)
2896 compose->references = hentry[H_REFERENCES].body;
2898 compose->references = compose_parse_references
2899 (hentry[H_REFERENCES].body, msginfo->msgid);
2900 g_free(hentry[H_REFERENCES].body);
2902 hentry[H_REFERENCES].body = NULL;
2904 if (hentry[H_BCC].body != NULL) {
2905 if (compose->mode == COMPOSE_REEDIT)
2907 conv_unmime_header(hentry[H_BCC].body, NULL, TRUE);
2908 g_free(hentry[H_BCC].body);
2909 hentry[H_BCC].body = NULL;
2911 if (hentry[H_NEWSGROUPS].body != NULL) {
2912 compose->newsgroups = hentry[H_NEWSGROUPS].body;
2913 hentry[H_NEWSGROUPS].body = NULL;
2915 if (hentry[H_FOLLOWUP_TO].body != NULL) {
2916 if (hentry[H_FOLLOWUP_TO].body[0] != '\0') {
2917 compose->followup_to =
2918 conv_unmime_header(hentry[H_FOLLOWUP_TO].body,
2921 g_free(hentry[H_FOLLOWUP_TO].body);
2922 hentry[H_FOLLOWUP_TO].body = NULL;
2924 if (hentry[H_LIST_POST].body != NULL) {
2925 gchar *to = NULL, *start = NULL;
2927 extract_address(hentry[H_LIST_POST].body);
2928 if (hentry[H_LIST_POST].body[0] != '\0') {
2929 start = strstr(hentry[H_LIST_POST].body, "mailto:");
2931 scan_mailto_url(start ? start : hentry[H_LIST_POST].body,
2932 NULL, &to, NULL, NULL, NULL, NULL, NULL, NULL);
2935 g_free(compose->ml_post);
2936 compose->ml_post = to;
2939 g_free(hentry[H_LIST_POST].body);
2940 hentry[H_LIST_POST].body = NULL;
2943 /* CLAWS - X-Priority */
2944 if (compose->mode == COMPOSE_REEDIT)
2945 if (hentry[H_X_PRIORITY].body != NULL) {
2948 priority = atoi(hentry[H_X_PRIORITY].body);
2949 g_free(hentry[H_X_PRIORITY].body);
2951 hentry[H_X_PRIORITY].body = NULL;
2953 if (priority < PRIORITY_HIGHEST ||
2954 priority > PRIORITY_LOWEST)
2955 priority = PRIORITY_NORMAL;
2957 compose->priority = priority;
2960 if (compose->mode == COMPOSE_REEDIT) {
2961 if (msginfo->inreplyto && *msginfo->inreplyto)
2962 compose->inreplyto = g_strdup(msginfo->inreplyto);
2964 if (msginfo->msgid && *msginfo->msgid)
2965 compose->msgid = g_strdup(msginfo->msgid);
2967 if (msginfo->msgid && *msginfo->msgid)
2968 compose->inreplyto = g_strdup(msginfo->msgid);
2970 if (!compose->references) {
2971 if (msginfo->msgid && *msginfo->msgid) {
2972 if (msginfo->inreplyto && *msginfo->inreplyto)
2973 compose->references =
2974 g_strdup_printf("<%s>\n\t<%s>",
2978 compose->references =
2979 g_strconcat("<", msginfo->msgid, ">",
2981 } else if (msginfo->inreplyto && *msginfo->inreplyto) {
2982 compose->references =
2983 g_strconcat("<", msginfo->inreplyto, ">",
2992 static gint compose_parse_manual_headers(Compose *compose, MsgInfo *msginfo, HeaderEntry *entries)
2997 cm_return_val_if_fail(msginfo != NULL, -1);
2999 if ((fp = procmsg_open_message(msginfo)) == NULL) return -1;
3000 procheader_get_header_fields(fp, entries);
3004 while (he != NULL && he->name != NULL) {
3006 GtkListStore *model = NULL;
3008 debug_print("Adding manual header: %s with value %s\n", he->name, he->body);
3009 model = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(compose->header_last->combo)));
3010 COMBOBOX_ADD(model, he->name, COMPOSE_TO);
3011 gtk_combo_box_set_active_iter(GTK_COMBO_BOX(compose->header_last->combo), &iter);
3012 gtk_entry_set_text(GTK_ENTRY(compose->header_last->entry), he->body);
3019 static gchar *compose_parse_references(const gchar *ref, const gchar *msgid)
3021 GSList *ref_id_list, *cur;
3025 ref_id_list = references_list_append(NULL, ref);
3026 if (!ref_id_list) return NULL;
3027 if (msgid && *msgid)
3028 ref_id_list = g_slist_append(ref_id_list, g_strdup(msgid));
3033 for (cur = ref_id_list; cur != NULL; cur = cur->next)
3034 /* "<" + Message-ID + ">" + CR+LF+TAB */
3035 len += strlen((gchar *)cur->data) + 5;
3037 if (len > MAX_REFERENCES_LEN) {
3038 /* remove second message-ID */
3039 if (ref_id_list && ref_id_list->next &&
3040 ref_id_list->next->next) {
3041 g_free(ref_id_list->next->data);
3042 ref_id_list = g_slist_remove
3043 (ref_id_list, ref_id_list->next->data);
3045 slist_free_strings_full(ref_id_list);
3052 new_ref = g_string_new("");
3053 for (cur = ref_id_list; cur != NULL; cur = cur->next) {
3054 if (new_ref->len > 0)
3055 g_string_append(new_ref, "\n\t");
3056 g_string_append_printf(new_ref, "<%s>", (gchar *)cur->data);
3059 slist_free_strings_full(ref_id_list);
3061 new_ref_str = new_ref->str;
3062 g_string_free(new_ref, FALSE);
3067 static gchar *compose_quote_fmt(Compose *compose, MsgInfo *msginfo,
3068 const gchar *fmt, const gchar *qmark,
3069 const gchar *body, gboolean rewrap,
3070 gboolean need_unescape,
3071 const gchar *err_msg)
3073 MsgInfo* dummyinfo = NULL;
3074 gchar *quote_str = NULL;
3076 gboolean prev_autowrap;
3077 const gchar *trimmed_body = body;
3078 gint cursor_pos = -1;
3079 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3080 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3085 SIGNAL_BLOCK(buffer);
3088 dummyinfo = compose_msginfo_new_from_compose(compose);
3089 msginfo = dummyinfo;
3092 if (qmark != NULL) {
3094 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
3095 compose->gtkaspell);
3097 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
3099 quote_fmt_scan_string(qmark);
3102 buf = quote_fmt_get_buffer();
3104 alertpanel_error(_("The \"Quotation mark\" of the template is invalid."));
3106 Xstrdup_a(quote_str, buf, goto error)
3109 if (fmt && *fmt != '\0') {
3112 while (*trimmed_body == '\n')
3116 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE,
3117 compose->gtkaspell);
3119 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE);
3121 if (need_unescape) {
3124 /* decode \-escape sequences in the internal representation of the quote format */
3125 tmp = g_malloc(strlen(fmt)+1);
3126 pref_get_unescaped_pref(tmp, fmt);
3127 quote_fmt_scan_string(tmp);
3131 quote_fmt_scan_string(fmt);
3135 buf = quote_fmt_get_buffer();
3137 gint line = quote_fmt_get_line();
3138 alertpanel_error(err_msg, line);
3144 prev_autowrap = compose->autowrap;
3145 compose->autowrap = FALSE;
3147 mark = gtk_text_buffer_get_insert(buffer);
3148 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3149 if (g_utf8_validate(buf, -1, NULL)) {
3150 gtk_text_buffer_insert(buffer, &iter, buf, -1);
3152 gchar *tmpout = NULL;
3153 tmpout = conv_codeset_strdup
3154 (buf, conv_get_locale_charset_str_no_utf8(),
3156 if (!tmpout || !g_utf8_validate(tmpout, -1, NULL)) {
3158 tmpout = g_malloc(strlen(buf)*2+1);
3159 conv_localetodisp(tmpout, strlen(buf)*2+1, buf);
3161 gtk_text_buffer_insert(buffer, &iter, tmpout, -1);
3165 cursor_pos = quote_fmt_get_cursor_pos();
3166 if (cursor_pos == -1)
3167 cursor_pos = gtk_text_iter_get_offset(&iter);
3168 compose->set_cursor_pos = cursor_pos;
3170 gtk_text_buffer_get_start_iter(buffer, &iter);
3171 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
3172 gtk_text_buffer_place_cursor(buffer, &iter);
3174 compose->autowrap = prev_autowrap;
3175 if (compose->autowrap && rewrap)
3176 compose_wrap_all(compose);
3183 SIGNAL_UNBLOCK(buffer);
3185 procmsg_msginfo_free( &dummyinfo );
3190 /* if ml_post is of type addr@host and from is of type
3191 * addr-anything@host, return TRUE
3193 static gboolean is_subscription(const gchar *ml_post, const gchar *from)
3195 gchar *left_ml = NULL;
3196 gchar *right_ml = NULL;
3197 gchar *left_from = NULL;
3198 gchar *right_from = NULL;
3199 gboolean result = FALSE;
3201 if (!ml_post || !from)
3204 left_ml = g_strdup(ml_post);
3205 if (strstr(left_ml, "@")) {
3206 right_ml = strstr(left_ml, "@")+1;
3207 *(strstr(left_ml, "@")) = '\0';
3210 left_from = g_strdup(from);
3211 if (strstr(left_from, "@")) {
3212 right_from = strstr(left_from, "@")+1;
3213 *(strstr(left_from, "@")) = '\0';
3216 if (right_ml && right_from
3217 && !strncmp(left_from, left_ml, strlen(left_ml))
3218 && !strcmp(right_from, right_ml)) {
3227 static void compose_set_folder_prefs(Compose *compose, FolderItem *folder,
3228 gboolean respect_default_to)
3232 if (!folder || !folder->prefs)
3235 if (respect_default_to && folder->prefs->enable_default_to) {
3236 compose_entry_append(compose, folder->prefs->default_to,
3237 COMPOSE_TO, PREF_FOLDER);
3238 compose_entry_indicate(compose, folder->prefs->default_to);
3240 if (folder->prefs->enable_default_cc) {
3241 compose_entry_append(compose, folder->prefs->default_cc,
3242 COMPOSE_CC, PREF_FOLDER);
3243 compose_entry_indicate(compose, folder->prefs->default_cc);
3245 if (folder->prefs->enable_default_bcc) {
3246 compose_entry_append(compose, folder->prefs->default_bcc,
3247 COMPOSE_BCC, PREF_FOLDER);
3248 compose_entry_indicate(compose, folder->prefs->default_bcc);
3250 if (folder->prefs->enable_default_replyto) {
3251 compose_entry_append(compose, folder->prefs->default_replyto,
3252 COMPOSE_REPLYTO, PREF_FOLDER);
3253 compose_entry_indicate(compose, folder->prefs->default_replyto);
3257 static void compose_reply_set_subject(Compose *compose, MsgInfo *msginfo)
3262 if (!compose || !msginfo)
3265 if (msginfo->subject && *msginfo->subject) {
3266 buf = p = g_strdup(msginfo->subject);
3267 p += subject_get_prefix_length(p);
3268 memmove(buf, p, strlen(p) + 1);
3270 buf2 = g_strdup_printf("Re: %s", buf);
3271 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
3276 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), "Re: ");
3279 static void compose_reply_set_entry(Compose *compose, MsgInfo *msginfo,
3280 gboolean to_all, gboolean to_ml,
3282 gboolean followup_and_reply_to)
3284 GSList *cc_list = NULL;
3287 gchar *replyto = NULL;
3288 gchar *ac_email = NULL;
3290 gboolean reply_to_ml = FALSE;
3291 gboolean default_reply_to = FALSE;
3293 cm_return_if_fail(compose->account != NULL);
3294 cm_return_if_fail(msginfo != NULL);
3296 reply_to_ml = to_ml && compose->ml_post;
3298 default_reply_to = msginfo->folder &&
3299 msginfo->folder->prefs->enable_default_reply_to;
3301 if (compose->account->protocol != A_NNTP) {
3302 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
3304 if (reply_to_ml && !default_reply_to) {
3306 gboolean is_subscr = is_subscription(compose->ml_post,
3309 /* normal answer to ml post with a reply-to */
3310 compose_entry_append(compose,
3312 COMPOSE_TO, PREF_ML);
3313 if (compose->replyto)
3314 compose_entry_append(compose,
3316 COMPOSE_CC, PREF_ML);
3318 /* answer to subscription confirmation */
3319 if (compose->replyto)
3320 compose_entry_append(compose,
3322 COMPOSE_TO, PREF_ML);
3323 else if (msginfo->from)
3324 compose_entry_append(compose,
3326 COMPOSE_TO, PREF_ML);
3329 else if (!(to_all || to_sender) && default_reply_to) {
3330 compose_entry_append(compose,
3331 msginfo->folder->prefs->default_reply_to,
3332 COMPOSE_TO, PREF_FOLDER);
3333 compose_entry_indicate(compose,
3334 msginfo->folder->prefs->default_reply_to);
3340 compose_entry_append(compose, msginfo->from,
3341 COMPOSE_TO, PREF_NONE);
3343 Xstrdup_a(tmp1, msginfo->from, return);
3344 extract_address(tmp1);
3345 compose_entry_append(compose,
3346 (!account_find_from_address(tmp1, FALSE))
3349 COMPOSE_TO, PREF_NONE);
3350 if (compose->replyto)
3351 compose_entry_append(compose,
3353 COMPOSE_CC, PREF_NONE);
3355 if (!folder_has_parent_of_type(msginfo->folder, F_QUEUE) &&
3356 !folder_has_parent_of_type(msginfo->folder, F_OUTBOX) &&
3357 !folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
3358 if (compose->replyto) {
3359 compose_entry_append(compose,
3361 COMPOSE_TO, PREF_NONE);
3363 compose_entry_append(compose,
3364 msginfo->from ? msginfo->from : "",
3365 COMPOSE_TO, PREF_NONE);
3368 /* replying to own mail, use original recp */
3369 compose_entry_append(compose,
3370 msginfo->to ? msginfo->to : "",
3371 COMPOSE_TO, PREF_NONE);
3372 compose_entry_append(compose,
3373 msginfo->cc ? msginfo->cc : "",
3374 COMPOSE_CC, PREF_NONE);
3379 if (to_sender || (compose->followup_to &&
3380 !strncmp(compose->followup_to, "poster", 6)))
3381 compose_entry_append
3383 (compose->replyto ? compose->replyto :
3384 msginfo->from ? msginfo->from : ""),
3385 COMPOSE_TO, PREF_NONE);
3387 else if (followup_and_reply_to || to_all) {
3388 compose_entry_append
3390 (compose->replyto ? compose->replyto :
3391 msginfo->from ? msginfo->from : ""),
3392 COMPOSE_TO, PREF_NONE);
3394 compose_entry_append
3396 compose->followup_to ? compose->followup_to :
3397 compose->newsgroups ? compose->newsgroups : "",
3398 COMPOSE_NEWSGROUPS, PREF_NONE);
3400 compose_entry_append
3402 msginfo->cc ? msginfo->cc : "",
3403 COMPOSE_CC, PREF_NONE);
3406 compose_entry_append
3408 compose->followup_to ? compose->followup_to :
3409 compose->newsgroups ? compose->newsgroups : "",
3410 COMPOSE_NEWSGROUPS, PREF_NONE);
3412 compose_reply_set_subject(compose, msginfo);
3414 if (to_ml && compose->ml_post) return;
3415 if (!to_all || compose->account->protocol == A_NNTP) return;
3417 if (compose->replyto) {
3418 Xstrdup_a(replyto, compose->replyto, return);
3419 extract_address(replyto);
3421 if (msginfo->from) {
3422 Xstrdup_a(from, msginfo->from, return);
3423 extract_address(from);
3426 if (replyto && from)
3427 cc_list = address_list_append_with_comments(cc_list, from);
3428 if (to_all && msginfo->folder &&
3429 msginfo->folder->prefs->enable_default_reply_to)
3430 cc_list = address_list_append_with_comments(cc_list,
3431 msginfo->folder->prefs->default_reply_to);
3432 cc_list = address_list_append_with_comments(cc_list, msginfo->to);
3433 cc_list = address_list_append_with_comments(cc_list, compose->cc);
3435 ac_email = g_utf8_strdown(compose->account->address, -1);
3438 for (cur = cc_list; cur != NULL; cur = cur->next) {
3439 gchar *addr = g_utf8_strdown(cur->data, -1);
3440 extract_address(addr);
3442 if (strcmp(ac_email, addr))
3443 compose_entry_append(compose, (gchar *)cur->data,
3444 COMPOSE_CC, PREF_NONE);
3446 debug_print("Cc address same as compose account's, ignoring\n");
3451 slist_free_strings_full(cc_list);
3457 #define SET_ENTRY(entry, str) \
3460 gtk_entry_set_text(GTK_ENTRY(compose->entry), str); \
3463 #define SET_ADDRESS(type, str) \
3466 compose_entry_append(compose, str, type, PREF_NONE); \
3469 static void compose_reedit_set_entry(Compose *compose, MsgInfo *msginfo)
3471 cm_return_if_fail(msginfo != NULL);
3473 SET_ENTRY(subject_entry, msginfo->subject);
3474 SET_ENTRY(from_name, msginfo->from);
3475 SET_ADDRESS(COMPOSE_TO, msginfo->to);
3476 SET_ADDRESS(COMPOSE_CC, compose->cc);
3477 SET_ADDRESS(COMPOSE_BCC, compose->bcc);
3478 SET_ADDRESS(COMPOSE_REPLYTO, compose->replyto);
3479 SET_ADDRESS(COMPOSE_NEWSGROUPS, compose->newsgroups);
3480 SET_ADDRESS(COMPOSE_FOLLOWUPTO, compose->followup_to);
3482 compose_update_priority_menu_item(compose);
3483 compose_update_privacy_system_menu_item(compose, FALSE);
3484 compose_show_first_last_header(compose, TRUE);
3490 static void compose_insert_sig(Compose *compose, gboolean replace)
3492 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3493 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3495 GtkTextIter iter, iter_end;
3496 gint cur_pos, ins_pos;
3497 gboolean prev_autowrap;
3498 gboolean found = FALSE;
3499 gboolean exists = FALSE;
3501 cm_return_if_fail(compose->account != NULL);
3505 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3506 G_CALLBACK(compose_changed_cb),
3509 mark = gtk_text_buffer_get_insert(buffer);
3510 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3511 cur_pos = gtk_text_iter_get_offset (&iter);
3514 gtk_text_buffer_get_end_iter(buffer, &iter);
3516 exists = (compose->sig_str != NULL);
3519 GtkTextIter first_iter, start_iter, end_iter;
3521 gtk_text_buffer_get_start_iter(buffer, &first_iter);
3523 if (!exists || compose->sig_str[0] == '\0')
3526 found = gtk_text_iter_forward_to_tag_toggle(&first_iter,
3527 compose->signature_tag);
3530 /* include previous \n\n */
3531 gtk_text_iter_backward_chars(&first_iter, 1);
3532 start_iter = first_iter;
3533 end_iter = first_iter;
3535 found = gtk_text_iter_forward_to_tag_toggle(&end_iter,
3536 compose->signature_tag);
3537 found &= gtk_text_iter_forward_to_tag_toggle(&end_iter,
3538 compose->signature_tag);
3540 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
3546 g_free(compose->sig_str);
3547 compose->sig_str = account_get_signature_str(compose->account);
3549 cur_pos = gtk_text_iter_get_offset(&iter);
3551 if (!compose->sig_str || (replace && !compose->account->auto_sig)) {
3552 g_free(compose->sig_str);
3553 compose->sig_str = NULL;
3555 if (compose->sig_inserted == FALSE)
3556 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
3557 compose->sig_inserted = TRUE;
3559 cur_pos = gtk_text_iter_get_offset(&iter);
3560 gtk_text_buffer_insert(buffer, &iter, compose->sig_str, -1);
3562 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cur_pos);
3563 gtk_text_iter_forward_chars(&iter, 1);
3564 gtk_text_buffer_get_end_iter(buffer, &iter_end);
3565 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &iter_end);
3567 if (cur_pos > gtk_text_buffer_get_char_count (buffer))
3568 cur_pos = gtk_text_buffer_get_char_count (buffer);
3571 /* put the cursor where it should be
3572 * either where the quote_fmt says, either where it was */
3573 if (compose->set_cursor_pos < 0)
3574 gtk_text_buffer_get_iter_at_offset(buffer, &iter, ins_pos);
3576 gtk_text_buffer_get_iter_at_offset(buffer, &iter,
3577 compose->set_cursor_pos);
3579 compose->set_cursor_pos = -1;
3580 gtk_text_buffer_place_cursor(buffer, &iter);
3581 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3582 G_CALLBACK(compose_changed_cb),
3588 static ComposeInsertResult compose_insert_file(Compose *compose, const gchar *file)
3591 GtkTextBuffer *buffer;
3594 const gchar *cur_encoding;
3595 gchar buf[BUFFSIZE];
3598 gboolean prev_autowrap;
3601 GString *file_contents = NULL;
3602 ComposeInsertResult result = COMPOSE_INSERT_SUCCESS;
3604 cm_return_val_if_fail(file != NULL, COMPOSE_INSERT_NO_FILE);
3606 /* get the size of the file we are about to insert */
3607 ret = g_stat(file, &file_stat);
3609 gchar *shortfile = g_path_get_basename(file);
3610 alertpanel_error(_("Could not get size of file '%s'."), shortfile);
3612 return COMPOSE_INSERT_NO_FILE;
3613 } else if (prefs_common.warn_large_insert == TRUE) {
3615 /* ask user for confirmation if the file is large */
3616 if (prefs_common.warn_large_insert_size < 0 ||
3617 file_stat.st_size > (prefs_common.warn_large_insert_size * 1024)) {
3621 msg = g_strdup_printf(_("You are about to insert a file of %s "
3622 "in the message body. Are you sure you want to do that?"),
3623 to_human_readable(file_stat.st_size));
3624 aval = alertpanel_full(_("Are you sure?"), msg, GTK_STOCK_CANCEL,
3625 g_strconcat("+", _("_Insert"), NULL), NULL, TRUE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
3628 /* do we ask for confirmation next time? */
3629 if (aval & G_ALERTDISABLE) {
3630 /* no confirmation next time, disable feature in preferences */
3631 aval &= ~G_ALERTDISABLE;
3632 prefs_common.warn_large_insert = FALSE;
3635 /* abort file insertion if user canceled action */
3636 if (aval != G_ALERTALTERNATE) {
3637 return COMPOSE_INSERT_NO_FILE;
3643 if ((fp = g_fopen(file, "rb")) == NULL) {
3644 FILE_OP_ERROR(file, "fopen");
3645 return COMPOSE_INSERT_READ_ERROR;
3648 prev_autowrap = compose->autowrap;
3649 compose->autowrap = FALSE;
3651 text = GTK_TEXT_VIEW(compose->text);
3652 buffer = gtk_text_view_get_buffer(text);
3653 mark = gtk_text_buffer_get_insert(buffer);
3654 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3656 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3657 G_CALLBACK(text_inserted),
3660 cur_encoding = conv_get_locale_charset_str_no_utf8();
3662 file_contents = g_string_new("");
3663 while (fgets(buf, sizeof(buf), fp) != NULL) {
3666 if (g_utf8_validate(buf, -1, NULL) == TRUE)
3667 str = g_strdup(buf);
3669 codeconv_set_strict(TRUE);
3670 str = conv_codeset_strdup
3671 (buf, cur_encoding, CS_INTERNAL);
3672 codeconv_set_strict(FALSE);
3675 result = COMPOSE_INSERT_INVALID_CHARACTER;
3681 /* strip <CR> if DOS/Windows file,
3682 replace <CR> with <LF> if Macintosh file. */
3685 if (len > 0 && str[len - 1] != '\n') {
3687 if (str[len] == '\r') str[len] = '\n';
3690 file_contents = g_string_append(file_contents, str);
3694 if (result == COMPOSE_INSERT_SUCCESS) {
3695 gtk_text_buffer_insert(buffer, &iter, file_contents->str, -1);
3697 compose_changed_cb(NULL, compose);
3698 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3699 G_CALLBACK(text_inserted),
3701 compose->autowrap = prev_autowrap;
3702 if (compose->autowrap)
3703 compose_wrap_all(compose);
3706 g_string_free(file_contents, TRUE);
3712 static gboolean compose_attach_append(Compose *compose, const gchar *file,
3713 const gchar *filename,
3714 const gchar *content_type,
3715 const gchar *charset)
3723 GtkListStore *store;
3725 gboolean has_binary = FALSE;
3727 if (!is_file_exist(file)) {
3728 gchar *file_from_uri = g_filename_from_uri(file, NULL, NULL);
3729 gboolean result = FALSE;
3730 if (file_from_uri && is_file_exist(file_from_uri)) {
3731 result = compose_attach_append(
3732 compose, file_from_uri,
3733 filename, content_type,
3736 g_free(file_from_uri);
3739 alertpanel_error("File %s doesn't exist\n", filename);
3742 if ((size = get_file_size(file)) < 0) {
3743 alertpanel_error("Can't get file size of %s\n", filename);
3747 /* In batch mode, we allow 0-length files to be attached no questions asked */
3748 if (size == 0 && !compose->batch) {
3749 gchar * msg = g_strdup_printf(_("File %s is empty."), filename);
3750 AlertValue aval = alertpanel_full(_("Empty file"), msg,
3751 GTK_STOCK_CANCEL, g_strconcat("+", _("_Attach anyway"), NULL), NULL, FALSE,
3752 NULL, ALERT_WARNING, G_ALERTDEFAULT);
3755 if (aval != G_ALERTALTERNATE) {
3759 if ((fp = g_fopen(file, "rb")) == NULL) {
3760 alertpanel_error(_("Can't read %s."), filename);
3765 ainfo = g_new0(AttachInfo, 1);
3766 auto_ainfo = g_auto_pointer_new_with_free
3767 (ainfo, (GFreeFunc) compose_attach_info_free);
3768 ainfo->file = g_strdup(file);
3771 ainfo->content_type = g_strdup(content_type);
3772 if (!g_ascii_strcasecmp(content_type, "message/rfc822")) {
3774 MsgFlags flags = {0, 0};
3776 if (procmime_get_encoding_for_text_file(file, &has_binary) == ENC_7BIT)
3777 ainfo->encoding = ENC_7BIT;
3779 ainfo->encoding = ENC_8BIT;
3781 msginfo = procheader_parse_file(file, flags, FALSE, FALSE);
3782 if (msginfo && msginfo->subject)
3783 name = g_strdup(msginfo->subject);
3785 name = g_path_get_basename(filename ? filename : file);
3787 ainfo->name = g_strdup_printf(_("Message: %s"), name);
3789 procmsg_msginfo_free(&msginfo);
3791 if (!g_ascii_strncasecmp(content_type, "text/", 5)) {
3792 ainfo->charset = g_strdup(charset);
3793 ainfo->encoding = procmime_get_encoding_for_text_file(file, &has_binary);
3795 ainfo->encoding = ENC_BASE64;
3797 name = g_path_get_basename(filename ? filename : file);
3798 ainfo->name = g_strdup(name);
3802 ainfo->content_type = procmime_get_mime_type(file);
3803 if (!ainfo->content_type) {
3804 ainfo->content_type =
3805 g_strdup("application/octet-stream");
3806 ainfo->encoding = ENC_BASE64;
3807 } else if (!g_ascii_strncasecmp(ainfo->content_type, "text/", 5))
3809 procmime_get_encoding_for_text_file(file, &has_binary);
3811 ainfo->encoding = ENC_BASE64;
3812 name = g_path_get_basename(filename ? filename : file);
3813 ainfo->name = g_strdup(name);
3817 if (ainfo->name != NULL
3818 && !strcmp(ainfo->name, ".")) {
3819 g_free(ainfo->name);
3823 if (!strcmp(ainfo->content_type, "unknown") || has_binary) {
3824 g_free(ainfo->content_type);
3825 ainfo->content_type = g_strdup("application/octet-stream");
3826 g_free(ainfo->charset);
3827 ainfo->charset = NULL;
3830 ainfo->size = (goffset)size;
3831 size_text = to_human_readable((goffset)size);
3833 store = GTK_LIST_STORE(gtk_tree_view_get_model
3834 (GTK_TREE_VIEW(compose->attach_clist)));
3836 gtk_list_store_append(store, &iter);
3837 gtk_list_store_set(store, &iter,
3838 COL_MIMETYPE, ainfo->content_type,
3839 COL_SIZE, size_text,
3840 COL_NAME, ainfo->name,
3841 COL_CHARSET, ainfo->charset,
3843 COL_AUTODATA, auto_ainfo,
3846 g_auto_pointer_free(auto_ainfo);
3847 compose_attach_update_label(compose);
3851 static void compose_use_signing(Compose *compose, gboolean use_signing)
3853 compose->use_signing = use_signing;
3854 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", use_signing);
3857 static void compose_use_encryption(Compose *compose, gboolean use_encryption)
3859 compose->use_encryption = use_encryption;
3860 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", use_encryption);
3863 #define NEXT_PART_NOT_CHILD(info) \
3865 node = info->node; \
3866 while (node->children) \
3867 node = g_node_last_child(node); \
3868 info = procmime_mimeinfo_next((MimeInfo *)node->data); \
3871 static void compose_attach_parts(Compose *compose, MsgInfo *msginfo)
3875 MimeInfo *firsttext = NULL;
3876 MimeInfo *encrypted = NULL;
3879 const gchar *partname = NULL;
3881 mimeinfo = procmime_scan_message(msginfo);
3882 if (!mimeinfo) return;
3884 if (mimeinfo->node->children == NULL) {
3885 procmime_mimeinfo_free_all(&mimeinfo);
3889 /* find first content part */
3890 child = (MimeInfo *) mimeinfo->node->children->data;
3891 while (child && child->node->children && (child->type == MIMETYPE_MULTIPART))
3892 child = (MimeInfo *)child->node->children->data;
3895 if (child->type == MIMETYPE_TEXT) {
3897 debug_print("First text part found\n");
3898 } else if (compose->mode == COMPOSE_REEDIT &&
3899 child->type == MIMETYPE_APPLICATION &&
3900 !g_ascii_strcasecmp(child->subtype, "pgp-encrypted")) {
3901 encrypted = (MimeInfo *)child->node->parent->data;
3904 child = (MimeInfo *) mimeinfo->node->children->data;
3905 while (child != NULL) {
3908 if (child == encrypted) {
3909 /* skip this part of tree */
3910 NEXT_PART_NOT_CHILD(child);
3914 if (child->type == MIMETYPE_MULTIPART) {
3915 /* get the actual content */
3916 child = procmime_mimeinfo_next(child);
3920 if (child == firsttext) {
3921 child = procmime_mimeinfo_next(child);
3925 outfile = procmime_get_tmp_file_name(child);
3926 if ((err = procmime_get_part(outfile, child)) < 0)
3927 g_warning("Can't get the part of multipart message. (%s)", g_strerror(-err));
3929 gchar *content_type;
3931 content_type = procmime_get_content_type_str(child->type, child->subtype);
3933 /* if we meet a pgp signature, we don't attach it, but
3934 * we force signing. */
3935 if ((strcmp(content_type, "application/pgp-signature") &&
3936 strcmp(content_type, "application/pkcs7-signature") &&
3937 strcmp(content_type, "application/x-pkcs7-signature"))
3938 || compose->mode == COMPOSE_REDIRECT) {
3939 partname = procmime_mimeinfo_get_parameter(child, "filename");
3940 if (partname == NULL)
3941 partname = procmime_mimeinfo_get_parameter(child, "name");
3942 if (partname == NULL)
3944 compose_attach_append(compose, outfile,
3945 partname, content_type,
3946 procmime_mimeinfo_get_parameter(child, "charset"));
3948 compose_force_signing(compose, compose->account, NULL);
3950 g_free(content_type);
3953 NEXT_PART_NOT_CHILD(child);
3955 procmime_mimeinfo_free_all(&mimeinfo);
3958 #undef NEXT_PART_NOT_CHILD
3963 WAIT_FOR_INDENT_CHAR,
3964 WAIT_FOR_INDENT_CHAR_OR_SPACE,
3967 /* return indent length, we allow:
3968 indent characters followed by indent characters or spaces/tabs,
3969 alphabets and numbers immediately followed by indent characters,
3970 and the repeating sequences of the above
3971 If quote ends with multiple spaces, only the first one is included. */
3972 static gchar *compose_get_quote_str(GtkTextBuffer *buffer,
3973 const GtkTextIter *start, gint *len)
3975 GtkTextIter iter = *start;
3979 IndentState state = WAIT_FOR_INDENT_CHAR;
3982 gint alnum_count = 0;
3983 gint space_count = 0;
3986 if (prefs_common.quote_chars == NULL) {
3990 while (!gtk_text_iter_ends_line(&iter)) {
3991 wc = gtk_text_iter_get_char(&iter);
3992 if (g_unichar_iswide(wc))
3994 clen = g_unichar_to_utf8(wc, ch);
3998 is_indent = strchr(prefs_common.quote_chars, ch[0]) ? TRUE : FALSE;
3999 is_space = g_unichar_isspace(wc);
4001 if (state == WAIT_FOR_INDENT_CHAR) {
4002 if (!is_indent && !g_unichar_isalnum(wc))
4005 quote_len += alnum_count + space_count + 1;
4006 alnum_count = space_count = 0;
4007 state = WAIT_FOR_INDENT_CHAR_OR_SPACE;
4010 } else if (state == WAIT_FOR_INDENT_CHAR_OR_SPACE) {
4011 if (!is_indent && !is_space && !g_unichar_isalnum(wc))
4015 else if (is_indent) {
4016 quote_len += alnum_count + space_count + 1;
4017 alnum_count = space_count = 0;
4020 state = WAIT_FOR_INDENT_CHAR;
4024 gtk_text_iter_forward_char(&iter);
4027 if (quote_len > 0 && space_count > 0)
4033 if (quote_len > 0) {
4035 gtk_text_iter_forward_chars(&iter, quote_len);
4036 return gtk_text_buffer_get_text(buffer, start, &iter, FALSE);
4042 /* return >0 if the line is itemized */
4043 static int compose_itemized_length(GtkTextBuffer *buffer,
4044 const GtkTextIter *start)
4046 GtkTextIter iter = *start;
4051 if (gtk_text_iter_ends_line(&iter))
4056 wc = gtk_text_iter_get_char(&iter);
4057 if (!g_unichar_isspace(wc))
4059 gtk_text_iter_forward_char(&iter);
4060 if (gtk_text_iter_ends_line(&iter))
4064 clen = g_unichar_to_utf8(wc, ch);
4068 if (!strchr("*-+", ch[0]))
4071 gtk_text_iter_forward_char(&iter);
4072 if (gtk_text_iter_ends_line(&iter))
4074 wc = gtk_text_iter_get_char(&iter);
4075 if (g_unichar_isspace(wc)) {
4081 /* return the string at the start of the itemization */
4082 static gchar * compose_get_itemized_chars(GtkTextBuffer *buffer,
4083 const GtkTextIter *start)
4085 GtkTextIter iter = *start;
4088 GString *item_chars = g_string_new("");
4091 if (gtk_text_iter_ends_line(&iter))
4096 wc = gtk_text_iter_get_char(&iter);
4097 if (!g_unichar_isspace(wc))
4099 gtk_text_iter_forward_char(&iter);
4100 if (gtk_text_iter_ends_line(&iter))
4102 g_string_append_unichar(item_chars, wc);
4105 str = item_chars->str;
4106 g_string_free(item_chars, FALSE);
4110 /* return the number of spaces at a line's start */
4111 static int compose_left_offset_length(GtkTextBuffer *buffer,
4112 const GtkTextIter *start)
4114 GtkTextIter iter = *start;
4117 if (gtk_text_iter_ends_line(&iter))
4121 wc = gtk_text_iter_get_char(&iter);
4122 if (!g_unichar_isspace(wc))
4125 gtk_text_iter_forward_char(&iter);
4126 if (gtk_text_iter_ends_line(&iter))
4130 gtk_text_iter_forward_char(&iter);
4131 if (gtk_text_iter_ends_line(&iter))
4136 static gboolean compose_get_line_break_pos(GtkTextBuffer *buffer,
4137 const GtkTextIter *start,
4138 GtkTextIter *break_pos,
4142 GtkTextIter iter = *start, line_end = *start;
4143 PangoLogAttr *attrs;
4150 gboolean can_break = FALSE;
4151 gboolean do_break = FALSE;
4152 gboolean was_white = FALSE;
4153 gboolean prev_dont_break = FALSE;
4155 gtk_text_iter_forward_to_line_end(&line_end);
4156 str = gtk_text_buffer_get_text(buffer, &iter, &line_end, FALSE);
4157 len = g_utf8_strlen(str, -1);
4161 g_warning("compose_get_line_break_pos: len = 0!");
4165 /* g_print("breaking line: %d: %s (len = %d)\n",
4166 gtk_text_iter_get_line(&iter), str, len); */
4168 attrs = g_new(PangoLogAttr, len + 1);
4170 pango_default_break(str, -1, NULL, attrs, len + 1);
4174 /* skip quote and leading spaces */
4175 for (i = 0; *p != '\0' && i < len; i++) {
4178 wc = g_utf8_get_char(p);
4179 if (i >= quote_len && !g_unichar_isspace(wc))
4181 if (g_unichar_iswide(wc))
4183 else if (*p == '\t')
4187 p = g_utf8_next_char(p);
4190 for (; *p != '\0' && i < len; i++) {
4191 PangoLogAttr *attr = attrs + i;
4195 if (attr->is_line_break && can_break && was_white && !prev_dont_break)
4198 was_white = attr->is_white;
4200 /* don't wrap URI */
4201 if ((uri_len = get_uri_len(p)) > 0) {
4203 if (pos > 0 && col > max_col) {
4213 wc = g_utf8_get_char(p);
4214 if (g_unichar_iswide(wc)) {
4216 if (prev_dont_break && can_break && attr->is_line_break)
4218 } else if (*p == '\t')
4222 if (pos > 0 && col > max_col) {
4227 if (*p == '-' || *p == '/')
4228 prev_dont_break = TRUE;
4230 prev_dont_break = FALSE;
4232 p = g_utf8_next_char(p);
4236 /* debug_print("compose_get_line_break_pos(): do_break = %d, pos = %d, col = %d\n", do_break, pos, col); */
4241 *break_pos = *start;
4242 gtk_text_iter_set_line_offset(break_pos, pos);
4247 static gboolean compose_join_next_line(Compose *compose,
4248 GtkTextBuffer *buffer,
4250 const gchar *quote_str)
4252 GtkTextIter iter_ = *iter, cur, prev, next, end;
4253 PangoLogAttr attrs[3];
4255 gchar *next_quote_str;
4258 gboolean keep_cursor = FALSE;
4260 if (!gtk_text_iter_forward_line(&iter_) ||
4261 gtk_text_iter_ends_line(&iter_)) {
4264 next_quote_str = compose_get_quote_str(buffer, &iter_, "e_len);
4266 if ((quote_str || next_quote_str) &&
4267 strcmp2(quote_str, next_quote_str) != 0) {
4268 g_free(next_quote_str);
4271 g_free(next_quote_str);
4274 if (quote_len > 0) {
4275 gtk_text_iter_forward_chars(&end, quote_len);
4276 if (gtk_text_iter_ends_line(&end)) {
4281 /* don't join itemized lines */
4282 if (compose_itemized_length(buffer, &end) > 0) {
4286 /* don't join signature separator */
4287 if (compose_is_sig_separator(compose, buffer, &iter_)) {
4290 /* delete quote str */
4292 gtk_text_buffer_delete(buffer, &iter_, &end);
4294 /* don't join line breaks put by the user */
4296 gtk_text_iter_backward_char(&cur);
4297 if (gtk_text_iter_has_tag(&cur, compose->no_join_tag)) {
4298 gtk_text_iter_forward_char(&cur);
4302 gtk_text_iter_forward_char(&cur);
4303 /* delete linebreak and extra spaces */
4304 while (gtk_text_iter_backward_char(&cur)) {
4305 wc1 = gtk_text_iter_get_char(&cur);
4306 if (!g_unichar_isspace(wc1))
4311 while (!gtk_text_iter_ends_line(&cur)) {
4312 wc1 = gtk_text_iter_get_char(&cur);
4313 if (!g_unichar_isspace(wc1))
4315 gtk_text_iter_forward_char(&cur);
4318 if (!gtk_text_iter_equal(&prev, &next)) {
4321 mark = gtk_text_buffer_get_insert(buffer);
4322 gtk_text_buffer_get_iter_at_mark(buffer, &cur, mark);
4323 if (gtk_text_iter_equal(&prev, &cur))
4325 gtk_text_buffer_delete(buffer, &prev, &next);
4329 /* insert space if required */
4330 gtk_text_iter_backward_char(&prev);
4331 wc1 = gtk_text_iter_get_char(&prev);
4332 wc2 = gtk_text_iter_get_char(&next);
4333 gtk_text_iter_forward_char(&next);
4334 str = gtk_text_buffer_get_text(buffer, &prev, &next, FALSE);
4335 pango_default_break(str, -1, NULL, attrs, 3);
4336 if (!attrs[1].is_line_break ||
4337 (!g_unichar_iswide(wc1) || !g_unichar_iswide(wc2))) {
4338 gtk_text_buffer_insert(buffer, &iter_, " ", 1);
4340 gtk_text_iter_backward_char(&iter_);
4341 gtk_text_buffer_place_cursor(buffer, &iter_);
4350 #define ADD_TXT_POS(bp_, ep_, pti_) \
4351 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
4352 last = last->next; \
4353 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
4354 last->next = NULL; \
4356 g_warning("alloc error scanning URIs"); \
4359 static gboolean compose_beautify_paragraph(Compose *compose, GtkTextIter *par_iter, gboolean force)
4361 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4362 GtkTextBuffer *buffer;
4363 GtkTextIter iter, break_pos, end_of_line;
4364 gchar *quote_str = NULL;
4366 gboolean wrap_quote = force || prefs_common.linewrap_quote;
4367 gboolean prev_autowrap = compose->autowrap;
4368 gint startq_offset = -1, noq_offset = -1;
4369 gint uri_start = -1, uri_stop = -1;
4370 gint nouri_start = -1, nouri_stop = -1;
4371 gint num_blocks = 0;
4372 gint quotelevel = -1;
4373 gboolean modified = force;
4374 gboolean removed = FALSE;
4375 gboolean modified_before_remove = FALSE;
4377 gboolean start = TRUE;
4378 gint itemized_len = 0, rem_item_len = 0;
4379 gchar *itemized_chars = NULL;
4380 gboolean item_continuation = FALSE;
4385 if (compose->draft_timeout_tag == COMPOSE_DRAFT_TIMEOUT_FORBIDDEN) {
4389 compose->autowrap = FALSE;
4391 buffer = gtk_text_view_get_buffer(text);
4392 undo_wrapping(compose->undostruct, TRUE);
4397 mark = gtk_text_buffer_get_insert(buffer);
4398 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
4402 if (compose->draft_timeout_tag == COMPOSE_DRAFT_TIMEOUT_FORBIDDEN) {
4403 if (gtk_text_iter_ends_line(&iter)) {
4404 while (gtk_text_iter_ends_line(&iter) &&
4405 gtk_text_iter_forward_line(&iter))
4408 while (gtk_text_iter_backward_line(&iter)) {
4409 if (gtk_text_iter_ends_line(&iter)) {
4410 gtk_text_iter_forward_line(&iter);
4416 /* move to line start */
4417 gtk_text_iter_set_line_offset(&iter, 0);
4420 itemized_len = compose_itemized_length(buffer, &iter);
4422 if (!itemized_len) {
4423 itemized_len = compose_left_offset_length(buffer, &iter);
4424 item_continuation = TRUE;
4428 itemized_chars = compose_get_itemized_chars(buffer, &iter);
4430 /* go until paragraph end (empty line) */
4431 while (start || !gtk_text_iter_ends_line(&iter)) {
4432 gchar *scanpos = NULL;
4433 /* parse table - in order of priority */
4435 const gchar *needle; /* token */
4437 /* token search function */
4438 gchar *(*search) (const gchar *haystack,
4439 const gchar *needle);
4440 /* part parsing function */
4441 gboolean (*parse) (const gchar *start,
4442 const gchar *scanpos,
4446 /* part to URI function */
4447 gchar *(*build_uri) (const gchar *bp,
4451 static struct table parser[] = {
4452 {"http://", strcasestr, get_uri_part, make_uri_string},
4453 {"https://", strcasestr, get_uri_part, make_uri_string},
4454 {"ftp://", strcasestr, get_uri_part, make_uri_string},
4455 {"sftp://", strcasestr, get_uri_part, make_uri_string},
4456 {"gopher://",strcasestr, get_uri_part, make_uri_string},
4457 {"www.", strcasestr, get_uri_part, make_http_string},
4458 {"mailto:", strcasestr, get_uri_part, make_uri_string},
4459 {"@", strcasestr, get_email_part, make_email_string}
4461 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
4462 gint last_index = PARSE_ELEMS;
4464 gchar *o_walk = NULL, *walk = NULL, *bp = NULL, *ep = NULL;
4468 if (!prev_autowrap && num_blocks == 0) {
4470 g_signal_handlers_block_by_func(G_OBJECT(buffer),
4471 G_CALLBACK(text_inserted),
4474 if (gtk_text_iter_has_tag(&iter, compose->no_wrap_tag) && !force)
4477 uri_start = uri_stop = -1;
4479 quote_str = compose_get_quote_str(buffer, &iter, "e_len);
4482 /* debug_print("compose_beautify_paragraph(): quote_str = '%s'\n", quote_str); */
4483 if (startq_offset == -1)
4484 startq_offset = gtk_text_iter_get_offset(&iter);
4485 quotelevel = get_quote_level(quote_str, prefs_common.quote_chars);
4486 if (quotelevel > 2) {
4487 /* recycle colors */
4488 if (prefs_common.recycle_quote_colors)
4497 if (startq_offset == -1)
4498 noq_offset = gtk_text_iter_get_offset(&iter);
4502 if (prev_autowrap == FALSE && !force && !wrap_quote) {
4505 if (gtk_text_iter_ends_line(&iter)) {
4507 } else if (compose_get_line_break_pos(buffer, &iter, &break_pos,
4508 prefs_common.linewrap_len,
4510 GtkTextIter prev, next, cur;
4511 if (prev_autowrap != FALSE || force) {
4512 compose->automatic_break = TRUE;
4514 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4515 compose->automatic_break = FALSE;
4516 if (itemized_len && compose->autoindent) {
4517 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4518 if (!item_continuation)
4519 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4521 } else if (quote_str && wrap_quote) {
4522 compose->automatic_break = TRUE;
4524 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4525 compose->automatic_break = FALSE;
4526 if (itemized_len && compose->autoindent) {
4527 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4528 if (!item_continuation)
4529 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4533 /* remove trailing spaces */
4535 rem_item_len = itemized_len;
4536 while (compose->autoindent && rem_item_len-- > 0)
4537 gtk_text_iter_backward_char(&cur);
4538 gtk_text_iter_backward_char(&cur);
4541 while (!gtk_text_iter_starts_line(&cur)) {
4544 gtk_text_iter_backward_char(&cur);
4545 wc = gtk_text_iter_get_char(&cur);
4546 if (!g_unichar_isspace(wc))
4550 if (!gtk_text_iter_equal(&prev, &next)) {
4551 gtk_text_buffer_delete(buffer, &prev, &next);
4553 gtk_text_iter_forward_char(&break_pos);
4557 gtk_text_buffer_insert(buffer, &break_pos,
4561 modified |= compose_join_next_line(compose, buffer, &iter, quote_str);
4563 /* move iter to current line start */
4564 gtk_text_iter_set_line_offset(&iter, 0);
4571 /* move iter to next line start */
4577 if (!prev_autowrap && num_blocks > 0) {
4579 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
4580 G_CALLBACK(text_inserted),
4584 while (!gtk_text_iter_ends_line(&end_of_line)) {
4585 gtk_text_iter_forward_char(&end_of_line);
4587 o_walk = walk = gtk_text_buffer_get_text(buffer, &iter, &end_of_line, FALSE);
4589 nouri_start = gtk_text_iter_get_offset(&iter);
4590 nouri_stop = gtk_text_iter_get_offset(&end_of_line);
4592 walk_pos = gtk_text_iter_get_offset(&iter);
4593 /* FIXME: this looks phony. scanning for anything in the parse table */
4594 for (n = 0; n < PARSE_ELEMS; n++) {
4597 tmp = parser[n].search(walk, parser[n].needle);
4599 if (scanpos == NULL || tmp < scanpos) {
4608 /* check if URI can be parsed */
4609 if (parser[last_index].parse(walk, scanpos, (const gchar **)&bp,
4610 (const gchar **)&ep, FALSE)
4611 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
4615 strlen(parser[last_index].needle);
4618 uri_start = walk_pos + (bp - o_walk);
4619 uri_stop = walk_pos + (ep - o_walk);
4623 gtk_text_iter_forward_line(&iter);
4626 if (startq_offset != -1) {
4627 GtkTextIter startquote, endquote;
4628 gtk_text_buffer_get_iter_at_offset(
4629 buffer, &startquote, startq_offset);
4632 switch (quotelevel) {
4634 if (!gtk_text_iter_has_tag(&startquote, compose->quote0_tag) ||
4635 !gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) {
4636 gtk_text_buffer_apply_tag_by_name(
4637 buffer, "quote0", &startquote, &endquote);
4638 gtk_text_buffer_remove_tag_by_name(
4639 buffer, "quote1", &startquote, &endquote);
4640 gtk_text_buffer_remove_tag_by_name(
4641 buffer, "quote2", &startquote, &endquote);
4646 if (!gtk_text_iter_has_tag(&startquote, compose->quote1_tag) ||
4647 !gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) {
4648 gtk_text_buffer_apply_tag_by_name(
4649 buffer, "quote1", &startquote, &endquote);
4650 gtk_text_buffer_remove_tag_by_name(
4651 buffer, "quote0", &startquote, &endquote);
4652 gtk_text_buffer_remove_tag_by_name(
4653 buffer, "quote2", &startquote, &endquote);
4658 if (!gtk_text_iter_has_tag(&startquote, compose->quote2_tag) ||
4659 !gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag)) {
4660 gtk_text_buffer_apply_tag_by_name(
4661 buffer, "quote2", &startquote, &endquote);
4662 gtk_text_buffer_remove_tag_by_name(
4663 buffer, "quote0", &startquote, &endquote);
4664 gtk_text_buffer_remove_tag_by_name(
4665 buffer, "quote1", &startquote, &endquote);
4671 } else if (noq_offset != -1) {
4672 GtkTextIter startnoquote, endnoquote;
4673 gtk_text_buffer_get_iter_at_offset(
4674 buffer, &startnoquote, noq_offset);
4677 if ((gtk_text_iter_has_tag(&startnoquote, compose->quote0_tag)
4678 && gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) ||
4679 (gtk_text_iter_has_tag(&startnoquote, compose->quote1_tag)
4680 && gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) ||
4681 (gtk_text_iter_has_tag(&startnoquote, compose->quote2_tag)
4682 && gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag))) {
4683 gtk_text_buffer_remove_tag_by_name(
4684 buffer, "quote0", &startnoquote, &endnoquote);
4685 gtk_text_buffer_remove_tag_by_name(
4686 buffer, "quote1", &startnoquote, &endnoquote);
4687 gtk_text_buffer_remove_tag_by_name(
4688 buffer, "quote2", &startnoquote, &endnoquote);
4694 if (uri_start != nouri_start && uri_stop != nouri_stop) {
4695 GtkTextIter nouri_start_iter, nouri_end_iter;
4696 gtk_text_buffer_get_iter_at_offset(
4697 buffer, &nouri_start_iter, nouri_start);
4698 gtk_text_buffer_get_iter_at_offset(
4699 buffer, &nouri_end_iter, nouri_stop);
4700 if (gtk_text_iter_has_tag(&nouri_start_iter, compose->uri_tag) &&
4701 gtk_text_iter_has_tag(&nouri_end_iter, compose->uri_tag)) {
4702 gtk_text_buffer_remove_tag_by_name(
4703 buffer, "link", &nouri_start_iter, &nouri_end_iter);
4704 modified_before_remove = modified;
4709 if (uri_start >= 0 && uri_stop > 0) {
4710 GtkTextIter uri_start_iter, uri_end_iter, back;
4711 gtk_text_buffer_get_iter_at_offset(
4712 buffer, &uri_start_iter, uri_start);
4713 gtk_text_buffer_get_iter_at_offset(
4714 buffer, &uri_end_iter, uri_stop);
4715 back = uri_end_iter;
4716 gtk_text_iter_backward_char(&back);
4717 if (!gtk_text_iter_has_tag(&uri_start_iter, compose->uri_tag) ||
4718 !gtk_text_iter_has_tag(&back, compose->uri_tag)) {
4719 gtk_text_buffer_apply_tag_by_name(
4720 buffer, "link", &uri_start_iter, &uri_end_iter);
4722 if (removed && !modified_before_remove) {
4728 /* debug_print("not modified, out after %d lines\n", lines); */
4732 /* debug_print("modified, out after %d lines\n", lines); */
4734 g_free(itemized_chars);
4737 undo_wrapping(compose->undostruct, FALSE);
4738 compose->autowrap = prev_autowrap;
4743 void compose_action_cb(void *data)
4745 Compose *compose = (Compose *)data;
4746 compose_wrap_all(compose);
4749 static void compose_wrap_all(Compose *compose)
4751 compose_wrap_all_full(compose, FALSE);
4754 static void compose_wrap_all_full(Compose *compose, gboolean force)
4756 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4757 GtkTextBuffer *buffer;
4759 gboolean modified = TRUE;
4761 buffer = gtk_text_view_get_buffer(text);
4763 gtk_text_buffer_get_start_iter(buffer, &iter);
4765 undo_wrapping(compose->undostruct, TRUE);
4767 while (!gtk_text_iter_is_end(&iter) && modified)
4768 modified = compose_beautify_paragraph(compose, &iter, force);
4770 undo_wrapping(compose->undostruct, FALSE);
4774 static void compose_set_title(Compose *compose)
4780 edited = compose->modified ? _(" [Edited]") : "";
4782 subject = gtk_editable_get_chars(
4783 GTK_EDITABLE(compose->subject_entry), 0, -1);
4785 #ifndef GENERIC_UMPC
4786 if (subject && strlen(subject))
4787 str = g_strdup_printf(_("%s - Compose message%s"),
4790 str = g_strdup_printf(_("[no subject] - Compose message%s"), edited);
4792 str = g_strdup(_("Compose message"));
4795 gtk_window_set_title(GTK_WINDOW(compose->window), str);
4801 * compose_current_mail_account:
4803 * Find a current mail account (the currently selected account, or the
4804 * default account, if a news account is currently selected). If a
4805 * mail account cannot be found, display an error message.
4807 * Return value: Mail account, or NULL if not found.
4809 static PrefsAccount *
4810 compose_current_mail_account(void)
4814 if (cur_account && cur_account->protocol != A_NNTP)
4817 ac = account_get_default();
4818 if (!ac || ac->protocol == A_NNTP) {
4819 alertpanel_error(_("Account for sending mail is not specified.\n"
4820 "Please select a mail account before sending."));
4827 #define QUOTE_IF_REQUIRED(out, str) \
4829 if (*str != '"' && strpbrk(str, ",.:;[]<>()@\\\"")) { \
4833 len = strlen(str) + 3; \
4834 if ((__tmp = alloca(len)) == NULL) { \
4835 g_warning("can't allocate memory"); \
4836 g_string_free(header, TRUE); \
4839 g_snprintf(__tmp, len, "\"%s\"", str); \
4844 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4845 g_warning("can't allocate memory"); \
4846 g_string_free(header, TRUE); \
4849 strcpy(__tmp, str); \
4855 #define QUOTE_IF_REQUIRED_NORMAL(out, str, errret) \
4857 if (*str != '"' && strpbrk(str, ",.:;[]<>()@\\\"")) { \
4861 len = strlen(str) + 3; \
4862 if ((__tmp = alloca(len)) == NULL) { \
4863 g_warning("can't allocate memory"); \
4866 g_snprintf(__tmp, len, "\"%s\"", str); \
4871 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4872 g_warning("can't allocate memory"); \
4875 strcpy(__tmp, str); \
4881 static void compose_select_account(Compose *compose, PrefsAccount *account,
4884 gchar *from = NULL, *header = NULL;
4885 ComposeHeaderEntry *header_entry;
4886 #if GTK_CHECK_VERSION(2, 24, 0)
4890 cm_return_if_fail(account != NULL);
4892 compose->account = account;
4893 if (account->name && *account->name) {
4895 QUOTE_IF_REQUIRED_NORMAL(buf, account->name, return);
4896 qbuf = escape_internal_quotes(buf, '"');
4897 from = g_strdup_printf("%s <%s>",
4898 qbuf, account->address);
4901 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4903 from = g_strdup_printf("<%s>",
4905 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4910 compose_set_title(compose);
4912 if (account->default_sign && compose->mode != COMPOSE_REDIRECT)
4913 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", TRUE);
4915 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", FALSE);
4916 if (account->default_encrypt && compose->mode != COMPOSE_REDIRECT)
4917 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", TRUE);
4919 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", FALSE);
4921 activate_privacy_system(compose, account, FALSE);
4923 if (!init && compose->mode != COMPOSE_REDIRECT) {
4924 undo_block(compose->undostruct);
4925 compose_insert_sig(compose, TRUE);
4926 undo_unblock(compose->undostruct);
4929 header_entry = (ComposeHeaderEntry *) compose->header_list->data;
4930 #if !GTK_CHECK_VERSION(2, 24, 0)
4931 header = gtk_combo_box_get_active_text(GTK_COMBO_BOX(header_entry->combo));
4933 if (gtk_combo_box_get_active_iter(GTK_COMBO_BOX(header_entry->combo), &iter))
4934 gtk_tree_model_get(gtk_combo_box_get_model(GTK_COMBO_BOX(
4935 header_entry->combo)), &iter, COMBOBOX_TEXT, &header, -1);
4938 if (header && !strlen(gtk_entry_get_text(GTK_ENTRY(header_entry->entry)))) {
4939 if (account->protocol == A_NNTP) {
4940 if (!strcmp(header, _("To:")))
4941 combobox_select_by_text(
4942 GTK_COMBO_BOX(header_entry->combo),
4945 if (!strcmp(header, _("Newsgroups:")))
4946 combobox_select_by_text(
4947 GTK_COMBO_BOX(header_entry->combo),
4955 /* use account's dict info if set */
4956 if (compose->gtkaspell) {
4957 if (account->enable_default_dictionary)
4958 gtkaspell_change_dict(compose->gtkaspell,
4959 account->default_dictionary, FALSE);
4960 if (account->enable_default_alt_dictionary)
4961 gtkaspell_change_alt_dict(compose->gtkaspell,
4962 account->default_alt_dictionary);
4963 if (account->enable_default_dictionary
4964 || account->enable_default_alt_dictionary)
4965 compose_spell_menu_changed(compose);
4970 gboolean compose_check_for_valid_recipient(Compose *compose) {
4971 gchar *recipient_headers_mail[] = {"To:", "Cc:", "Bcc:", NULL};
4972 gchar *recipient_headers_news[] = {"Newsgroups:", NULL};
4973 gboolean recipient_found = FALSE;
4977 /* free to and newsgroup list */
4978 slist_free_strings_full(compose->to_list);
4979 compose->to_list = NULL;
4981 slist_free_strings_full(compose->newsgroup_list);
4982 compose->newsgroup_list = NULL;
4984 /* search header entries for to and newsgroup entries */
4985 for (list = compose->header_list; list; list = list->next) {
4988 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
4989 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4992 if (entry[0] != '\0') {
4993 for (strptr = recipient_headers_mail; *strptr != NULL; strptr++) {
4994 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
4995 compose->to_list = address_list_append(compose->to_list, entry);
4996 recipient_found = TRUE;
4999 for (strptr = recipient_headers_news; *strptr != NULL; strptr++) {
5000 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
5001 compose->newsgroup_list = newsgroup_list_append(compose->newsgroup_list, entry);
5002 recipient_found = TRUE;
5009 return recipient_found;
5012 static gboolean compose_check_for_set_recipients(Compose *compose)
5014 if (compose->account->set_autocc && compose->account->auto_cc) {
5015 gboolean found_other = FALSE;
5017 /* search header entries for to and newsgroup entries */
5018 for (list = compose->header_list; list; list = list->next) {
5021 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5022 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5025 if (strcmp(entry, compose->account->auto_cc)
5026 || strcmp(header, prefs_common_translated_header_name("Cc:"))) {
5037 if (compose->batch) {
5038 gtk_widget_show_all(compose->window);
5040 text = g_strdup_printf(_("The only recipient is the default '%s' address. Send anyway?"),
5041 prefs_common_translated_header_name("Cc"));
5042 aval = alertpanel(_("Send"),
5044 GTK_STOCK_CANCEL, g_strconcat("+", _("_Send"), NULL), NULL);
5046 if (aval != G_ALERTALTERNATE)
5050 if (compose->account->set_autobcc && compose->account->auto_bcc) {
5051 gboolean found_other = FALSE;
5053 /* search header entries for to and newsgroup entries */
5054 for (list = compose->header_list; list; list = list->next) {
5057 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5058 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5061 if (strcmp(entry, compose->account->auto_bcc)
5062 || strcmp(header, prefs_common_translated_header_name("Bcc:"))) {
5074 if (compose->batch) {
5075 gtk_widget_show_all(compose->window);
5077 text = g_strdup_printf(_("The only recipient is the default '%s' address. Send anyway?"),
5078 prefs_common_translated_header_name("Bcc"));
5079 aval = alertpanel(_("Send"),
5081 GTK_STOCK_CANCEL, g_strconcat("+", _("_Send"), NULL), NULL);
5083 if (aval != G_ALERTALTERNATE)
5090 static gboolean compose_check_entries(Compose *compose, gboolean check_everything)
5094 if (compose_check_for_valid_recipient(compose) == FALSE) {
5095 if (compose->batch) {
5096 gtk_widget_show_all(compose->window);
5098 alertpanel_error(_("Recipient is not specified."));
5102 if (compose_check_for_set_recipients(compose) == FALSE) {
5106 if (!compose->batch && prefs_common.warn_empty_subj == TRUE) {
5107 str = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5108 if (*str == '\0' && check_everything == TRUE &&
5109 compose->mode != COMPOSE_REDIRECT) {
5111 gchar *button_label;
5114 if (compose->sending)
5115 button_label = g_strconcat("+", _("_Send"), NULL);
5117 button_label = g_strconcat("+", _("_Queue"), NULL);
5118 message = g_strdup_printf(_("Subject is empty. %s"),
5119 compose->sending?_("Send it anyway?"):
5120 _("Queue it anyway?"));
5122 aval = alertpanel_full(compose->sending?_("Send"):_("Send later"), message,
5123 GTK_STOCK_CANCEL, button_label, NULL, TRUE, NULL,
5124 ALERT_QUESTION, G_ALERTDEFAULT);
5126 if (aval & G_ALERTDISABLE) {
5127 aval &= ~G_ALERTDISABLE;
5128 prefs_common.warn_empty_subj = FALSE;
5130 if (aval != G_ALERTALTERNATE)
5135 if (check_everything && hooks_invoke(COMPOSE_CHECK_BEFORE_SEND_HOOKLIST, compose))
5141 gint compose_send(Compose *compose)
5144 FolderItem *folder = NULL;
5146 gchar *msgpath = NULL;
5147 gboolean discard_window = FALSE;
5148 gchar *errstr = NULL;
5149 gchar *tmsgid = NULL;
5150 MainWindow *mainwin = mainwindow_get_mainwindow();
5151 gboolean queued_removed = FALSE;
5153 if (prefs_common.send_dialog_invisible
5154 || compose->batch == TRUE)
5155 discard_window = TRUE;
5157 compose_allow_user_actions (compose, FALSE);
5158 compose->sending = TRUE;
5160 if (compose_check_entries(compose, TRUE) == FALSE) {
5161 if (compose->batch) {
5162 gtk_widget_show_all(compose->window);
5168 val = compose_queue(compose, &msgnum, &folder, &msgpath, TRUE);
5171 if (compose->batch) {
5172 gtk_widget_show_all(compose->window);
5175 alertpanel_error(_("Could not queue message for sending:\n\n"
5176 "Charset conversion failed."));
5177 } else if (val == -5) {
5178 alertpanel_error(_("Could not queue message for sending:\n\n"
5179 "Couldn't get recipient encryption key."));
5180 } else if (val == -6) {
5182 } else if (val == -3) {
5183 if (privacy_peek_error())
5184 alertpanel_error(_("Could not queue message for sending:\n\n"
5185 "Signature failed: %s"), privacy_get_error());
5186 } else if (val == -2 && errno != 0) {
5187 alertpanel_error(_("Could not queue message for sending:\n\n%s."), g_strerror(errno));
5189 alertpanel_error(_("Could not queue message for sending."));
5194 tmsgid = compose->msgid ? g_strdup(compose->msgid) : NULL;
5195 if (discard_window) {
5196 compose->sending = FALSE;
5197 compose_close(compose);
5198 /* No more compose access in the normal codepath
5199 * after this point! */
5204 alertpanel_error(_("The message was queued but could not be "
5205 "sent.\nUse \"Send queued messages\" from "
5206 "the main window to retry."));
5207 if (!discard_window) {
5214 if (msgpath == NULL) {
5215 msgpath = folder_item_fetch_msg(folder, msgnum);
5216 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5219 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5220 claws_unlink(msgpath);
5223 if (!discard_window) {
5225 if (!queued_removed)
5226 folder_item_remove_msg(folder, msgnum);
5227 folder_item_scan(folder);
5229 /* make sure we delete that */
5230 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5232 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5233 folder_item_remove_msg(folder, tmp->msgnum);
5234 procmsg_msginfo_free(&tmp);
5241 if (!queued_removed)
5242 folder_item_remove_msg(folder, msgnum);
5243 folder_item_scan(folder);
5245 /* make sure we delete that */
5246 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5248 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5249 folder_item_remove_msg(folder, tmp->msgnum);
5250 procmsg_msginfo_free(&tmp);
5253 if (!discard_window) {
5254 compose->sending = FALSE;
5255 compose_allow_user_actions (compose, TRUE);
5256 compose_close(compose);
5260 alertpanel_error_log(_("%s\nUse \"Send queued messages\" from "
5261 "the main window to retry."), errstr);
5264 alertpanel_error_log(_("The message was queued but could not be "
5265 "sent.\nUse \"Send queued messages\" from "
5266 "the main window to retry."));
5268 if (!discard_window) {
5277 toolbar_main_set_sensitive(mainwin);
5278 main_window_set_menu_sensitive(mainwin);
5284 compose_allow_user_actions (compose, TRUE);
5285 compose->sending = FALSE;
5286 compose->modified = TRUE;
5287 toolbar_main_set_sensitive(mainwin);
5288 main_window_set_menu_sensitive(mainwin);
5293 static gboolean compose_use_attach(Compose *compose)
5295 GtkTreeModel *model = gtk_tree_view_get_model
5296 (GTK_TREE_VIEW(compose->attach_clist));
5297 return gtk_tree_model_iter_n_children(model, NULL) > 0;
5300 static gint compose_redirect_write_headers_from_headerlist(Compose *compose,
5303 gchar buf[BUFFSIZE];
5305 gboolean first_to_address;
5306 gboolean first_cc_address;
5308 ComposeHeaderEntry *headerentry;
5309 const gchar *headerentryname;
5310 const gchar *cc_hdr;
5311 const gchar *to_hdr;
5312 gboolean err = FALSE;
5314 debug_print("Writing redirect header\n");
5316 cc_hdr = prefs_common_translated_header_name("Cc:");
5317 to_hdr = prefs_common_translated_header_name("To:");
5319 first_to_address = TRUE;
5320 for (list = compose->header_list; list; list = list->next) {
5321 headerentry = ((ComposeHeaderEntry *)list->data);
5322 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5324 if (g_utf8_collate(headerentryname, to_hdr) == 0) {
5325 const gchar *entstr = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5326 Xstrdup_a(str, entstr, return -1);
5328 if (str[0] != '\0') {
5329 compose_convert_header
5330 (compose, buf, sizeof(buf), str,
5331 strlen("Resent-To") + 2, TRUE);
5333 if (first_to_address) {
5334 err |= (fprintf(fp, "Resent-To: ") < 0);
5335 first_to_address = FALSE;
5337 err |= (fprintf(fp, ",") < 0);
5339 err |= (fprintf(fp, "%s", buf) < 0);
5343 if (!first_to_address) {
5344 err |= (fprintf(fp, "\n") < 0);
5347 first_cc_address = TRUE;
5348 for (list = compose->header_list; list; list = list->next) {
5349 headerentry = ((ComposeHeaderEntry *)list->data);
5350 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5352 if (g_utf8_collate(headerentryname, cc_hdr) == 0) {
5353 const gchar *strg = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5354 Xstrdup_a(str, strg, return -1);
5356 if (str[0] != '\0') {
5357 compose_convert_header
5358 (compose, buf, sizeof(buf), str,
5359 strlen("Resent-Cc") + 2, TRUE);
5361 if (first_cc_address) {
5362 err |= (fprintf(fp, "Resent-Cc: ") < 0);
5363 first_cc_address = FALSE;
5365 err |= (fprintf(fp, ",") < 0);
5367 err |= (fprintf(fp, "%s", buf) < 0);
5371 if (!first_cc_address) {
5372 err |= (fprintf(fp, "\n") < 0);
5375 return (err ? -1:0);
5378 static gint compose_redirect_write_headers(Compose *compose, FILE *fp)
5380 gchar buf[BUFFSIZE];
5382 const gchar *entstr;
5383 /* struct utsname utsbuf; */
5384 gboolean err = FALSE;
5386 cm_return_val_if_fail(fp != NULL, -1);
5387 cm_return_val_if_fail(compose->account != NULL, -1);
5388 cm_return_val_if_fail(compose->account->address != NULL, -1);
5391 if (prefs_common.hide_timezone)
5392 get_rfc822_date_hide_tz(buf, sizeof(buf));
5394 get_rfc822_date(buf, sizeof(buf));
5395 err |= (fprintf(fp, "Resent-Date: %s\n", buf) < 0);
5398 if (compose->account->name && *compose->account->name) {
5399 compose_convert_header
5400 (compose, buf, sizeof(buf), compose->account->name,
5401 strlen("From: "), TRUE);
5402 err |= (fprintf(fp, "Resent-From: %s <%s>\n",
5403 buf, compose->account->address) < 0);
5405 err |= (fprintf(fp, "Resent-From: %s\n", compose->account->address) < 0);
5408 entstr = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5409 if (*entstr != '\0') {
5410 Xstrdup_a(str, entstr, return -1);
5413 compose_convert_header(compose, buf, sizeof(buf), str,
5414 strlen("Subject: "), FALSE);
5415 err |= (fprintf(fp, "Subject: %s\n", buf) < 0);
5419 /* Resent-Message-ID */
5420 if (compose->account->gen_msgid) {
5421 gchar *addr = prefs_account_generate_msgid(compose->account);
5422 err |= (fprintf(fp, "Resent-Message-ID: <%s>\n", addr) < 0);
5424 g_free(compose->msgid);
5425 compose->msgid = addr;
5427 compose->msgid = NULL;
5430 if (compose_redirect_write_headers_from_headerlist(compose, fp))
5433 /* separator between header and body */
5434 err |= (fputs("\n", fp) == EOF);
5436 return (err ? -1:0);
5439 static gint compose_redirect_write_to_file(Compose *compose, FILE *fdest)
5443 gchar buf[BUFFSIZE];
5445 gboolean skip = FALSE;
5446 gboolean err = FALSE;
5447 gchar *not_included[]={
5448 "Return-Path:", "Delivered-To:", "Received:",
5449 "Subject:", "X-UIDL:", "AF:",
5450 "NF:", "PS:", "SRH:",
5451 "SFN:", "DSR:", "MID:",
5452 "CFG:", "PT:", "S:",
5453 "RQ:", "SSV:", "NSV:",
5454 "SSH:", "R:", "MAID:",
5455 "NAID:", "RMID:", "FMID:",
5456 "SCF:", "RRCPT:", "NG:",
5457 "X-Claws-Privacy", "X-Claws-Sign:", "X-Claws-Encrypt",
5458 "X-Claws-End-Special-Headers:", "X-Claws-Account-Id:",
5459 "X-Sylpheed-Privacy", "X-Sylpheed-Sign:", "X-Sylpheed-Encrypt",
5460 "X-Sylpheed-End-Special-Headers:", "X-Sylpheed-Account-Id:",
5461 "X-Claws-Auto-Wrapping:", "X-Claws-Auto-Indent:",
5464 if ((fp = g_fopen(compose->redirect_filename, "rb")) == NULL) {
5465 FILE_OP_ERROR(compose->redirect_filename, "fopen");
5469 while (procheader_get_one_field_asis(buf, sizeof(buf), fp) != -1) {
5471 for (i = 0; not_included[i] != NULL; i++) {
5472 if (g_ascii_strncasecmp(buf, not_included[i],
5473 strlen(not_included[i])) == 0) {
5480 if (fputs(buf, fdest) == -1)
5483 if (!prefs_common.redirect_keep_from) {
5484 if (g_ascii_strncasecmp(buf, "From:",
5485 strlen("From:")) == 0) {
5486 err |= (fputs(" (by way of ", fdest) == EOF);
5487 if (compose->account->name
5488 && *compose->account->name) {
5489 compose_convert_header
5490 (compose, buf, sizeof(buf),
5491 compose->account->name,
5494 err |= (fprintf(fdest, "%s <%s>",
5496 compose->account->address) < 0);
5498 err |= (fprintf(fdest, "%s",
5499 compose->account->address) < 0);
5500 err |= (fputs(")", fdest) == EOF);
5504 if (fputs("\n", fdest) == -1)
5511 if (compose_redirect_write_headers(compose, fdest))
5514 while ((len = fread(buf, sizeof(gchar), sizeof(buf), fp)) > 0) {
5515 if (fwrite(buf, sizeof(gchar), len, fdest) != len)
5528 static gint compose_write_to_file(Compose *compose, FILE *fp, gint action, gboolean attach_parts)
5530 GtkTextBuffer *buffer;
5531 GtkTextIter start, end;
5532 gchar *chars, *tmp_enc_file, *content;
5534 const gchar *out_codeset;
5535 EncodingType encoding = ENC_UNKNOWN;
5536 MimeInfo *mimemsg, *mimetext;
5538 const gchar *src_codeset = CS_INTERNAL;
5539 gchar *from_addr = NULL;
5540 gchar *from_name = NULL;
5543 if (action == COMPOSE_WRITE_FOR_SEND) {
5544 attach_parts = TRUE;
5546 /* We're sending the message, generate a Message-ID
5548 if (compose->msgid == NULL &&
5549 compose->account->gen_msgid) {
5550 compose->msgid = prefs_account_generate_msgid(compose->account);
5554 /* create message MimeInfo */
5555 mimemsg = procmime_mimeinfo_new();
5556 mimemsg->type = MIMETYPE_MESSAGE;
5557 mimemsg->subtype = g_strdup("rfc822");
5558 mimemsg->content = MIMECONTENT_MEM;
5559 mimemsg->tmp = TRUE; /* must free content later */
5560 mimemsg->data.mem = compose_get_header(compose);
5562 /* Create text part MimeInfo */
5563 /* get all composed text */
5564 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5565 gtk_text_buffer_get_start_iter(buffer, &start);
5566 gtk_text_buffer_get_end_iter(buffer, &end);
5567 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5569 out_codeset = conv_get_charset_str(compose->out_encoding);
5571 if (!out_codeset && is_ascii_str(chars)) {
5572 out_codeset = CS_US_ASCII;
5573 } else if (prefs_common.outgoing_fallback_to_ascii &&
5574 is_ascii_str(chars)) {
5575 out_codeset = CS_US_ASCII;
5576 encoding = ENC_7BIT;
5580 gchar *test_conv_global_out = NULL;
5581 gchar *test_conv_reply = NULL;
5583 /* automatic mode. be automatic. */
5584 codeconv_set_strict(TRUE);
5586 out_codeset = conv_get_outgoing_charset_str();
5588 debug_print("trying to convert to %s\n", out_codeset);
5589 test_conv_global_out = conv_codeset_strdup(chars, src_codeset, out_codeset);
5592 if (!test_conv_global_out && compose->orig_charset
5593 && strcmp(compose->orig_charset, CS_US_ASCII)) {
5594 out_codeset = compose->orig_charset;
5595 debug_print("failure; trying to convert to %s\n", out_codeset);
5596 test_conv_reply = conv_codeset_strdup(chars, src_codeset, out_codeset);
5599 if (!test_conv_global_out && !test_conv_reply) {
5601 out_codeset = CS_INTERNAL;
5602 debug_print("failure; finally using %s\n", out_codeset);
5604 g_free(test_conv_global_out);
5605 g_free(test_conv_reply);
5606 codeconv_set_strict(FALSE);
5609 if (encoding == ENC_UNKNOWN) {
5610 if (prefs_common.encoding_method == CTE_BASE64)
5611 encoding = ENC_BASE64;
5612 else if (prefs_common.encoding_method == CTE_QUOTED_PRINTABLE)
5613 encoding = ENC_QUOTED_PRINTABLE;
5614 else if (prefs_common.encoding_method == CTE_8BIT)
5615 encoding = ENC_8BIT;
5617 encoding = procmime_get_encoding_for_charset(out_codeset);
5620 debug_print("src encoding = %s, out encoding = %s, transfer encoding = %s\n",
5621 src_codeset, out_codeset, procmime_get_encoding_str(encoding));
5623 if (action == COMPOSE_WRITE_FOR_SEND) {
5624 codeconv_set_strict(TRUE);
5625 buf = conv_codeset_strdup(chars, src_codeset, out_codeset);
5626 codeconv_set_strict(FALSE);
5631 msg = g_strdup_printf(_("Can't convert the character encoding of the message \n"
5632 "to the specified %s charset.\n"
5633 "Send it as %s?"), out_codeset, src_codeset);
5634 aval = alertpanel_full(_("Error"), msg, GTK_STOCK_CANCEL,
5635 g_strconcat("+", _("_Send"), NULL), NULL, FALSE,
5636 NULL, ALERT_ERROR, G_ALERTDEFAULT);
5639 if (aval != G_ALERTALTERNATE) {
5644 out_codeset = src_codeset;
5650 out_codeset = src_codeset;
5655 if (prefs_common.rewrite_first_from && (encoding == ENC_8BIT || encoding == ENC_7BIT)) {
5656 if (!strncmp(buf, "From ", sizeof("From ")-1) ||
5657 strstr(buf, "\nFrom ") != NULL) {
5658 encoding = ENC_QUOTED_PRINTABLE;
5662 mimetext = procmime_mimeinfo_new();
5663 mimetext->content = MIMECONTENT_MEM;
5664 mimetext->tmp = TRUE; /* must free content later */
5665 /* dup'ed because procmime_encode_content can turn it into a tmpfile
5666 * and free the data, which we need later. */
5667 mimetext->data.mem = g_strdup(buf);
5668 mimetext->type = MIMETYPE_TEXT;
5669 mimetext->subtype = g_strdup("plain");
5670 g_hash_table_insert(mimetext->typeparameters, g_strdup("charset"),
5671 g_strdup(out_codeset));
5673 /* protect trailing spaces when signing message */
5674 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5675 privacy_system_can_sign(compose->privacy_system)) {
5676 encoding = ENC_QUOTED_PRINTABLE;
5680 debug_print("main text: %Id bytes encoded as %s in %d\n",
5682 debug_print("main text: %zd bytes encoded as %s in %d\n",
5684 strlen(buf), out_codeset, encoding);
5686 /* check for line length limit */
5687 if (action == COMPOSE_WRITE_FOR_SEND &&
5688 encoding != ENC_QUOTED_PRINTABLE && encoding != ENC_BASE64 &&
5689 check_line_length(buf, 1000, &line) < 0) {
5692 msg = g_strdup_printf
5693 (_("Line %d exceeds the line length limit (998 bytes).\n"
5694 "The contents of the message might be broken on the way to the delivery.\n"
5696 "Send it anyway?"), line + 1);
5697 aval = alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, GTK_STOCK_OK, NULL);
5699 if (aval != G_ALERTALTERNATE) {
5705 if (encoding != ENC_UNKNOWN)
5706 procmime_encode_content(mimetext, encoding);
5708 /* append attachment parts */
5709 if (compose_use_attach(compose) && attach_parts) {
5710 MimeInfo *mimempart;
5711 gchar *boundary = NULL;
5712 mimempart = procmime_mimeinfo_new();
5713 mimempart->content = MIMECONTENT_EMPTY;
5714 mimempart->type = MIMETYPE_MULTIPART;
5715 mimempart->subtype = g_strdup("mixed");
5719 boundary = generate_mime_boundary(NULL);
5720 } while (strstr(buf, boundary) != NULL);
5722 g_hash_table_insert(mimempart->typeparameters, g_strdup("boundary"),
5725 mimetext->disposition = DISPOSITIONTYPE_INLINE;
5727 g_node_append(mimempart->node, mimetext->node);
5728 g_node_append(mimemsg->node, mimempart->node);
5730 if (compose_add_attachments(compose, mimempart) < 0)
5733 g_node_append(mimemsg->node, mimetext->node);
5737 if (strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) != 0) {
5738 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
5739 /* extract name and address */
5740 if (strstr(spec, " <") && strstr(spec, ">")) {
5741 from_addr = g_strdup(strrchr(spec, '<')+1);
5742 *(strrchr(from_addr, '>')) = '\0';
5743 from_name = g_strdup(spec);
5744 *(strrchr(from_name, '<')) = '\0';
5751 /* sign message if sending */
5752 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5753 privacy_system_can_sign(compose->privacy_system))
5754 if (!privacy_sign(compose->privacy_system, mimemsg,
5755 compose->account, from_addr)) {
5763 if (compose->use_encryption) {
5764 if (compose->encdata != NULL &&
5765 strcmp(compose->encdata, "_DONT_ENCRYPT_")) {
5767 /* First, write an unencrypted copy and save it to outbox, if
5768 * user wants that. */
5769 if (compose->account->save_encrypted_as_clear_text) {
5770 debug_print("saving sent message unencrypted...\n");
5771 FILE *tmpfp = get_tmpfile_in_dir(get_mime_tmp_dir(), &tmp_enc_file);
5775 /* fp now points to a file with headers written,
5776 * let's make a copy. */
5778 content = file_read_stream_to_str(fp);
5780 str_write_to_file(content, tmp_enc_file);
5783 /* Now write the unencrypted body. */
5784 if ((tmpfp = g_fopen(tmp_enc_file, "a")) != NULL) {
5785 procmime_write_mimeinfo(mimemsg, tmpfp);
5788 outbox = folder_find_item_from_identifier(compose_get_save_to(compose));
5790 outbox = folder_get_default_outbox();
5792 procmsg_save_to_outbox(outbox, tmp_enc_file, TRUE);
5793 claws_unlink(tmp_enc_file);
5795 g_warning("Can't open file '%s'", tmp_enc_file);
5798 g_warning("couldn't get tempfile");
5801 if (!privacy_encrypt(compose->privacy_system, mimemsg, compose->encdata)) {
5802 debug_print("Couldn't encrypt mime structure: %s.\n",
5803 privacy_get_error());
5804 alertpanel_error(_("Couldn't encrypt the email: %s"),
5805 privacy_get_error());
5810 procmime_write_mimeinfo(mimemsg, fp);
5812 procmime_mimeinfo_free_all(&mimemsg);
5817 static gint compose_write_body_to_file(Compose *compose, const gchar *file)
5819 GtkTextBuffer *buffer;
5820 GtkTextIter start, end;
5825 if ((fp = g_fopen(file, "wb")) == NULL) {
5826 FILE_OP_ERROR(file, "fopen");
5830 /* chmod for security */
5831 if (change_file_mode_rw(fp, file) < 0) {
5832 FILE_OP_ERROR(file, "chmod");
5833 g_warning("can't change file mode");
5836 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5837 gtk_text_buffer_get_start_iter(buffer, &start);
5838 gtk_text_buffer_get_end_iter(buffer, &end);
5839 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5841 chars = conv_codeset_strdup
5842 (tmp, CS_INTERNAL, conv_get_locale_charset_str());
5851 len = strlen(chars);
5852 if (fwrite(chars, sizeof(gchar), len, fp) != len) {
5853 FILE_OP_ERROR(file, "fwrite");
5862 if (fclose(fp) == EOF) {
5863 FILE_OP_ERROR(file, "fclose");
5870 static gint compose_remove_reedit_target(Compose *compose, gboolean force)
5873 MsgInfo *msginfo = compose->targetinfo;
5875 cm_return_val_if_fail(compose->mode == COMPOSE_REEDIT, -1);
5876 if (!msginfo) return -1;
5878 if (!force && MSG_IS_LOCKED(msginfo->flags))
5881 item = msginfo->folder;
5882 cm_return_val_if_fail(item != NULL, -1);
5884 if (procmsg_msg_exist(msginfo) &&
5885 (folder_has_parent_of_type(item, F_QUEUE) ||
5886 folder_has_parent_of_type(item, F_DRAFT)
5887 || msginfo == compose->autosaved_draft)) {
5888 if (folder_item_remove_msg(item, msginfo->msgnum) < 0) {
5889 g_warning("can't remove the old message");
5892 debug_print("removed reedit target %d\n", msginfo->msgnum);
5899 static void compose_remove_draft(Compose *compose)
5902 MsgInfo *msginfo = compose->targetinfo;
5903 drafts = account_get_special_folder(compose->account, F_DRAFT);
5905 if (procmsg_msg_exist(msginfo)) {
5906 folder_item_remove_msg(drafts, msginfo->msgnum);
5911 gint compose_queue(Compose *compose, gint *msgnum, FolderItem **item, gchar **msgpath,
5912 gboolean remove_reedit_target)
5914 return compose_queue_sub (compose, msgnum, item, msgpath, FALSE, remove_reedit_target);
5917 static gboolean compose_warn_encryption(Compose *compose)
5919 const gchar *warning = privacy_get_encrypt_warning(compose->privacy_system);
5920 AlertValue val = G_ALERTALTERNATE;
5922 if (warning == NULL)
5925 val = alertpanel_full(_("Encryption warning"), warning,
5926 GTK_STOCK_CANCEL, g_strconcat("+", _("C_ontinue"), NULL), NULL,
5927 TRUE, NULL, ALERT_WARNING, G_ALERTALTERNATE);
5928 if (val & G_ALERTDISABLE) {
5929 val &= ~G_ALERTDISABLE;
5930 if (val == G_ALERTALTERNATE)
5931 privacy_inhibit_encrypt_warning(compose->privacy_system,
5935 if (val == G_ALERTALTERNATE) {
5942 static gint compose_queue_sub(Compose *compose, gint *msgnum, FolderItem **item,
5943 gchar **msgpath, gboolean check_subject,
5944 gboolean remove_reedit_target)
5951 PrefsAccount *mailac = NULL, *newsac = NULL;
5952 gboolean err = FALSE;
5954 debug_print("queueing message...\n");
5955 cm_return_val_if_fail(compose->account != NULL, -1);
5957 if (compose_check_entries(compose, check_subject) == FALSE) {
5958 if (compose->batch) {
5959 gtk_widget_show_all(compose->window);
5964 if (!compose->to_list && !compose->newsgroup_list) {
5965 g_warning("can't get recipient list.");
5969 if (compose->to_list) {
5970 if (compose->account->protocol != A_NNTP)
5971 mailac = compose->account;
5972 else if (cur_account && cur_account->protocol != A_NNTP)
5973 mailac = cur_account;
5974 else if (!(mailac = compose_current_mail_account())) {
5975 alertpanel_error(_("No account for sending mails available!"));
5980 if (compose->newsgroup_list) {
5981 if (compose->account->protocol == A_NNTP)
5982 newsac = compose->account;
5984 alertpanel_error(_("Selected account isn't NNTP: Posting is impossible."));
5989 /* write queue header */
5990 tmp = g_strdup_printf("%s%cqueue.%p%08x", get_tmp_dir(),
5991 G_DIR_SEPARATOR, compose, (guint) rand());
5992 debug_print("queuing to %s\n", tmp);
5993 if ((fp = g_fopen(tmp, "w+b")) == NULL) {
5994 FILE_OP_ERROR(tmp, "fopen");
5999 if (change_file_mode_rw(fp, tmp) < 0) {
6000 FILE_OP_ERROR(tmp, "chmod");
6001 g_warning("can't change file mode");
6004 /* queueing variables */
6005 err |= (fprintf(fp, "AF:\n") < 0);
6006 err |= (fprintf(fp, "NF:0\n") < 0);
6007 err |= (fprintf(fp, "PS:10\n") < 0);
6008 err |= (fprintf(fp, "SRH:1\n") < 0);
6009 err |= (fprintf(fp, "SFN:\n") < 0);
6010 err |= (fprintf(fp, "DSR:\n") < 0);
6012 err |= (fprintf(fp, "MID:<%s>\n", compose->msgid) < 0);
6014 err |= (fprintf(fp, "MID:\n") < 0);
6015 err |= (fprintf(fp, "CFG:\n") < 0);
6016 err |= (fprintf(fp, "PT:0\n") < 0);
6017 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
6018 err |= (fprintf(fp, "RQ:\n") < 0);
6020 err |= (fprintf(fp, "SSV:%s\n", mailac->smtp_server) < 0);
6022 err |= (fprintf(fp, "SSV:\n") < 0);
6024 err |= (fprintf(fp, "NSV:%s\n", newsac->nntp_server) < 0);
6026 err |= (fprintf(fp, "NSV:\n") < 0);
6027 err |= (fprintf(fp, "SSH:\n") < 0);
6028 /* write recepient list */
6029 if (compose->to_list) {
6030 err |= (fprintf(fp, "R:<%s>", (gchar *)compose->to_list->data) < 0);
6031 for (cur = compose->to_list->next; cur != NULL;
6033 err |= (fprintf(fp, ",<%s>", (gchar *)cur->data) < 0);
6034 err |= (fprintf(fp, "\n") < 0);
6036 /* write newsgroup list */
6037 if (compose->newsgroup_list) {
6038 err |= (fprintf(fp, "NG:") < 0);
6039 err |= (fprintf(fp, "%s", (gchar *)compose->newsgroup_list->data) < 0);
6040 for (cur = compose->newsgroup_list->next; cur != NULL; cur = cur->next)
6041 err |= (fprintf(fp, ",%s", (gchar *)cur->data) < 0);
6042 err |= (fprintf(fp, "\n") < 0);
6044 /* Sylpheed account IDs */
6046 err |= (fprintf(fp, "MAID:%d\n", mailac->account_id) < 0);
6048 err |= (fprintf(fp, "NAID:%d\n", newsac->account_id) < 0);
6051 if (compose->privacy_system != NULL) {
6052 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
6053 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
6054 if (compose->use_encryption) {
6055 if (!compose_warn_encryption(compose)) {
6061 if (mailac && mailac->encrypt_to_self) {
6062 GSList *tmp_list = g_slist_copy(compose->to_list);
6063 tmp_list = g_slist_append(tmp_list, compose->account->address);
6064 compose->encdata = privacy_get_encrypt_data(compose->privacy_system, tmp_list);
6065 g_slist_free(tmp_list);
6067 compose->encdata = privacy_get_encrypt_data(compose->privacy_system, compose->to_list);
6069 if (compose->encdata != NULL) {
6070 if (strcmp(compose->encdata, "_DONT_ENCRYPT_")) {
6071 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
6072 err |= (fprintf(fp, "X-Claws-Encrypt-Data:%s\n",
6073 compose->encdata) < 0);
6074 } /* else we finally dont want to encrypt */
6076 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
6077 /* and if encdata was null, it means there's been a problem in
6080 g_warning("failed to write queue message");
6089 /* Save copy folder */
6090 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
6091 gchar *savefolderid;
6093 savefolderid = compose_get_save_to(compose);
6094 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
6095 g_free(savefolderid);
6097 /* Save copy folder */
6098 if (compose->return_receipt) {
6099 err |= (fprintf(fp, "RRCPT:1\n") < 0);
6101 /* Message-ID of message replying to */
6102 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
6103 gchar *folderid = NULL;
6105 if (compose->replyinfo->folder)
6106 folderid = folder_item_get_identifier(compose->replyinfo->folder);
6107 if (folderid == NULL)
6108 folderid = g_strdup("NULL");
6110 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
6113 /* Message-ID of message forwarding to */
6114 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
6115 gchar *folderid = NULL;
6117 if (compose->fwdinfo->folder)
6118 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
6119 if (folderid == NULL)
6120 folderid = g_strdup("NULL");
6122 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
6126 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
6127 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
6129 /* end of headers */
6130 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
6132 if (compose->redirect_filename != NULL) {
6133 if (compose_redirect_write_to_file(compose, fp) < 0) {
6141 if ((result = compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_SEND, TRUE)) < 0) {
6145 return result - 1; /* -2 for a generic error, -3 for signing error, -4 for encoding */
6149 g_warning("failed to write queue message");
6155 if (fclose(fp) == EOF) {
6156 FILE_OP_ERROR(tmp, "fclose");
6162 if (item && *item) {
6165 queue = account_get_special_folder(compose->account, F_QUEUE);
6168 g_warning("can't find queue folder");
6173 folder_item_scan(queue);
6174 if ((num = folder_item_add_msg(queue, tmp, NULL, FALSE)) < 0) {
6175 g_warning("can't queue the message");
6181 if (msgpath == NULL) {
6187 if (compose->mode == COMPOSE_REEDIT && remove_reedit_target) {
6188 compose_remove_reedit_target(compose, FALSE);
6191 if ((msgnum != NULL) && (item != NULL)) {
6199 static int compose_add_attachments(Compose *compose, MimeInfo *parent)
6202 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
6205 gchar *type, *subtype;
6206 GtkTreeModel *model;
6209 model = gtk_tree_view_get_model(tree_view);
6211 if (!gtk_tree_model_get_iter_first(model, &iter))
6214 gtk_tree_model_get(model, &iter,
6218 if (!is_file_exist(ainfo->file)) {
6219 gchar *msg = g_strdup_printf(_("Attachment %s doesn't exist anymore. Ignore?"), ainfo->file);
6220 AlertValue val = alertpanel_full(_("Warning"), msg, _("Cancel sending"), _("Ignore attachment"),
6221 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
6223 if (val == G_ALERTDEFAULT) {
6228 if (g_stat(ainfo->file, &statbuf) < 0)
6231 mimepart = procmime_mimeinfo_new();
6232 mimepart->content = MIMECONTENT_FILE;
6233 mimepart->data.filename = g_strdup(ainfo->file);
6234 mimepart->tmp = FALSE; /* or we destroy our attachment */
6235 mimepart->offset = 0;
6236 mimepart->length = statbuf.st_size;
6238 type = g_strdup(ainfo->content_type);
6240 if (!strchr(type, '/')) {
6242 type = g_strdup("application/octet-stream");
6245 subtype = strchr(type, '/') + 1;
6246 *(subtype - 1) = '\0';
6247 mimepart->type = procmime_get_media_type(type);
6248 mimepart->subtype = g_strdup(subtype);
6251 if (mimepart->type == MIMETYPE_MESSAGE &&
6252 !g_ascii_strcasecmp(mimepart->subtype, "rfc822")) {
6253 mimepart->disposition = DISPOSITIONTYPE_INLINE;
6254 } else if (mimepart->type == MIMETYPE_TEXT) {
6255 if (!ainfo->name && g_ascii_strcasecmp(mimepart->subtype, "plain")) {
6256 /* Text parts with no name come from multipart/alternative
6257 * forwards. Make sure the recipient won't look at the
6258 * original HTML part by mistake. */
6259 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6260 ainfo->name = g_strdup_printf(_("Original %s part"),
6264 g_hash_table_insert(mimepart->typeparameters,
6265 g_strdup("charset"), g_strdup(ainfo->charset));
6267 if (ainfo->name && mimepart->type != MIMETYPE_MESSAGE) {
6268 if (mimepart->type == MIMETYPE_APPLICATION &&
6269 !strcmp2(mimepart->subtype, "octet-stream"))
6270 g_hash_table_insert(mimepart->typeparameters,
6271 g_strdup("name"), g_strdup(ainfo->name));
6272 g_hash_table_insert(mimepart->dispositionparameters,
6273 g_strdup("filename"), g_strdup(ainfo->name));
6274 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6277 if (mimepart->type == MIMETYPE_MESSAGE
6278 || mimepart->type == MIMETYPE_MULTIPART)
6279 ainfo->encoding = ENC_BINARY;
6280 else if (compose->use_signing) {
6281 if (ainfo->encoding == ENC_7BIT)
6282 ainfo->encoding = ENC_QUOTED_PRINTABLE;
6283 else if (ainfo->encoding == ENC_8BIT)
6284 ainfo->encoding = ENC_BASE64;
6289 procmime_encode_content(mimepart, ainfo->encoding);
6291 g_node_append(parent->node, mimepart->node);
6292 } while (gtk_tree_model_iter_next(model, &iter));
6297 static gchar *compose_quote_list_of_addresses(gchar *str)
6299 GSList *list = NULL, *item = NULL;
6300 gchar *qname = NULL, *faddr = NULL, *result = NULL;
6302 list = address_list_append_with_comments(list, str);
6303 for (item = list; item != NULL; item = item->next) {
6304 gchar *spec = item->data;
6305 gchar *endofname = strstr(spec, " <");
6306 if (endofname != NULL) {
6309 QUOTE_IF_REQUIRED_NORMAL(qname, spec, return NULL);
6310 qqname = escape_internal_quotes(qname, '"');
6312 if (*qname != *spec || qqname != qname) { /* has been quoted, compute new */
6313 gchar *addr = g_strdup(endofname);
6314 gchar *name = (qqname != qname)? qqname: g_strdup(qname);
6315 faddr = g_strconcat(name, addr, NULL);
6318 debug_print("new auto-quoted address: '%s'\n", faddr);
6322 result = g_strdup((faddr != NULL)? faddr: spec);
6324 result = g_strconcat(result,
6326 (faddr != NULL)? faddr: spec,
6329 if (faddr != NULL) {
6334 slist_free_strings_full(list);
6339 #define IS_IN_CUSTOM_HEADER(header) \
6340 (compose->account->add_customhdr && \
6341 custom_header_find(compose->account->customhdr_list, header) != NULL)
6343 static void compose_add_headerfield_from_headerlist(Compose *compose,
6345 const gchar *fieldname,
6346 const gchar *seperator)
6348 gchar *str, *fieldname_w_colon;
6349 gboolean add_field = FALSE;
6351 ComposeHeaderEntry *headerentry;
6352 const gchar *headerentryname;
6353 const gchar *trans_fieldname;
6356 if (IS_IN_CUSTOM_HEADER(fieldname))
6359 debug_print("Adding %s-fields\n", fieldname);
6361 fieldstr = g_string_sized_new(64);
6363 fieldname_w_colon = g_strconcat(fieldname, ":", NULL);
6364 trans_fieldname = prefs_common_translated_header_name(fieldname_w_colon);
6366 for (list = compose->header_list; list; list = list->next) {
6367 headerentry = ((ComposeHeaderEntry *)list->data);
6368 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
6370 if (!g_utf8_collate(trans_fieldname, headerentryname)) {
6371 gchar * ustr = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
6373 str = compose_quote_list_of_addresses(ustr);
6375 if (str != NULL && str[0] != '\0') {
6377 g_string_append(fieldstr, seperator);
6378 g_string_append(fieldstr, str);
6387 buf = g_new0(gchar, fieldstr->len * 4 + 256);
6388 compose_convert_header
6389 (compose, buf, fieldstr->len * 4 + 256, fieldstr->str,
6390 strlen(fieldname) + 2, TRUE);
6391 g_string_append_printf(header, "%s: %s\n", fieldname, buf);
6395 g_free(fieldname_w_colon);
6396 g_string_free(fieldstr, TRUE);
6401 static gchar *compose_get_manual_headers_info(Compose *compose)
6403 GString *sh_header = g_string_new(" ");
6405 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6407 for (list = compose->header_list; list; list = list->next) {
6408 ComposeHeaderEntry *headerentry;
6411 gchar *headername_wcolon;
6412 const gchar *headername_trans;
6414 gboolean standard_header = FALSE;
6416 headerentry = ((ComposeHeaderEntry *)list->data);
6418 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6420 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6425 if (!strstr(tmp, ":")) {
6426 headername_wcolon = g_strconcat(tmp, ":", NULL);
6427 headername = g_strdup(tmp);
6429 headername_wcolon = g_strdup(tmp);
6430 headername = g_strdup(strtok(tmp, ":"));
6434 string = std_headers;
6435 while (*string != NULL) {
6436 headername_trans = prefs_common_translated_header_name(*string);
6437 if (!strcmp(headername_trans, headername_wcolon))
6438 standard_header = TRUE;
6441 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
6442 g_string_append_printf(sh_header, "%s ", headername);
6444 g_free(headername_wcolon);
6446 g_string_truncate(sh_header, strlen(sh_header->str) - 1); /* remove last space */
6447 return g_string_free(sh_header, FALSE);
6450 static gchar *compose_get_header(Compose *compose)
6452 gchar buf[BUFFSIZE];
6453 const gchar *entry_str;
6457 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6459 gchar *from_name = NULL, *from_address = NULL;
6462 cm_return_val_if_fail(compose->account != NULL, NULL);
6463 cm_return_val_if_fail(compose->account->address != NULL, NULL);
6465 header = g_string_sized_new(64);
6468 if (prefs_common.hide_timezone)
6469 get_rfc822_date_hide_tz(buf, sizeof(buf));
6471 get_rfc822_date(buf, sizeof(buf));
6472 g_string_append_printf(header, "Date: %s\n", buf);
6476 if (compose->account->name && *compose->account->name) {
6478 QUOTE_IF_REQUIRED(buf, compose->account->name);
6479 tmp = g_strdup_printf("%s <%s>",
6480 buf, compose->account->address);
6482 tmp = g_strdup_printf("%s",
6483 compose->account->address);
6485 if (!strcmp(gtk_entry_get_text(GTK_ENTRY(compose->from_name)), tmp)
6486 || strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) == 0) {
6488 from_name = compose->account->name ? g_strdup(compose->account->name):NULL;
6489 from_address = g_strdup(compose->account->address);
6491 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
6492 /* extract name and address */
6493 if (strstr(spec, " <") && strstr(spec, ">")) {
6494 from_address = g_strdup(strrchr(spec, '<')+1);
6495 *(strrchr(from_address, '>')) = '\0';
6496 from_name = g_strdup(spec);
6497 *(strrchr(from_name, '<')) = '\0';
6500 from_address = g_strdup(spec);
6507 if (from_name && *from_name) {
6509 compose_convert_header
6510 (compose, buf, sizeof(buf), from_name,
6511 strlen("From: "), TRUE);
6512 QUOTE_IF_REQUIRED(name, buf);
6513 qname = escape_internal_quotes(name, '"');
6515 g_string_append_printf(header, "From: %s <%s>\n",
6516 qname, from_address);
6517 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To") &&
6518 compose->return_receipt) {
6519 compose_convert_header(compose, buf, sizeof(buf), from_name,
6520 strlen("Disposition-Notification-To: "),
6522 g_string_append_printf(header, "Disposition-Notification-To: %s <%s>\n", buf, from_address);
6527 g_string_append_printf(header, "From: %s\n", from_address);
6528 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To") &&
6529 compose->return_receipt)
6530 g_string_append_printf(header, "Disposition-Notification-To: %s\n", from_address);
6534 g_free(from_address);
6537 compose_add_headerfield_from_headerlist(compose, header, "To", ", ");
6540 compose_add_headerfield_from_headerlist(compose, header, "Newsgroups", ",");
6543 compose_add_headerfield_from_headerlist(compose, header, "Cc", ", ");
6547 * If this account is a NNTP account remove Bcc header from
6548 * message body since it otherwise will be publicly shown
6550 if (compose->account->protocol != A_NNTP)
6551 compose_add_headerfield_from_headerlist(compose, header, "Bcc", ", ");
6554 str = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
6556 if (*str != '\0' && !IS_IN_CUSTOM_HEADER("Subject")) {
6559 compose_convert_header(compose, buf, sizeof(buf), str,
6560 strlen("Subject: "), FALSE);
6561 g_string_append_printf(header, "Subject: %s\n", buf);
6567 if (compose->msgid != NULL && strlen(compose->msgid) > 0) {
6568 g_string_append_printf(header, "Message-ID: <%s>\n",
6572 if (compose->remove_references == FALSE) {
6574 if (compose->inreplyto && compose->to_list)
6575 g_string_append_printf(header, "In-Reply-To: <%s>\n", compose->inreplyto);
6578 if (compose->references)
6579 g_string_append_printf(header, "References: %s\n", compose->references);
6583 compose_add_headerfield_from_headerlist(compose, header, "Followup-To", ",");
6586 compose_add_headerfield_from_headerlist(compose, header, "Reply-To", ", ");
6589 if (compose->account->organization &&
6590 strlen(compose->account->organization) &&
6591 !IS_IN_CUSTOM_HEADER("Organization")) {
6592 compose_convert_header(compose, buf, sizeof(buf),
6593 compose->account->organization,
6594 strlen("Organization: "), FALSE);
6595 g_string_append_printf(header, "Organization: %s\n", buf);
6598 /* Program version and system info */
6599 if (compose->account->gen_xmailer &&
6600 g_slist_length(compose->to_list) && !IS_IN_CUSTOM_HEADER("X-Mailer") &&
6601 !compose->newsgroup_list) {
6602 g_string_append_printf(header, "X-Mailer: %s (GTK+ %d.%d.%d; %s)\n",
6604 gtk_major_version, gtk_minor_version, gtk_micro_version,
6607 if (compose->account->gen_xmailer &&
6608 g_slist_length(compose->newsgroup_list) && !IS_IN_CUSTOM_HEADER("X-Newsreader")) {
6609 g_string_append_printf(header, "X-Newsreader: %s (GTK+ %d.%d.%d; %s)\n",
6611 gtk_major_version, gtk_minor_version, gtk_micro_version,
6615 /* custom headers */
6616 if (compose->account->add_customhdr) {
6619 for (cur = compose->account->customhdr_list; cur != NULL;
6621 CustomHeader *chdr = (CustomHeader *)cur->data;
6623 if (custom_header_is_allowed(chdr->name)
6624 && chdr->value != NULL
6625 && *(chdr->value) != '\0') {
6626 compose_convert_header
6627 (compose, buf, sizeof(buf),
6629 strlen(chdr->name) + 2, FALSE);
6630 g_string_append_printf(header, "%s: %s\n", chdr->name, buf);
6635 /* Automatic Faces and X-Faces */
6636 if (get_account_xface (buf, sizeof(buf), compose->account->account_name) == 0) {
6637 g_string_append_printf(header, "X-Face: %s\n", buf);
6639 else if (get_default_xface (buf, sizeof(buf)) == 0) {
6640 g_string_append_printf(header, "X-Face: %s\n", buf);
6642 if (get_account_face (buf, sizeof(buf), compose->account->account_name) == 0) {
6643 g_string_append_printf(header, "Face: %s\n", buf);
6645 else if (get_default_face (buf, sizeof(buf)) == 0) {
6646 g_string_append_printf(header, "Face: %s\n", buf);
6650 switch (compose->priority) {
6651 case PRIORITY_HIGHEST: g_string_append_printf(header, "Importance: high\n"
6652 "X-Priority: 1 (Highest)\n");
6654 case PRIORITY_HIGH: g_string_append_printf(header, "Importance: high\n"
6655 "X-Priority: 2 (High)\n");
6657 case PRIORITY_NORMAL: break;
6658 case PRIORITY_LOW: g_string_append_printf(header, "Importance: low\n"
6659 "X-Priority: 4 (Low)\n");
6661 case PRIORITY_LOWEST: g_string_append_printf(header, "Importance: low\n"
6662 "X-Priority: 5 (Lowest)\n");
6664 default: debug_print("compose: priority unknown : %d\n",
6668 /* get special headers */
6669 for (list = compose->header_list; list; list = list->next) {
6670 ComposeHeaderEntry *headerentry;
6673 gchar *headername_wcolon;
6674 const gchar *headername_trans;
6677 gboolean standard_header = FALSE;
6679 headerentry = ((ComposeHeaderEntry *)list->data);
6681 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6683 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6688 if (!strstr(tmp, ":")) {
6689 headername_wcolon = g_strconcat(tmp, ":", NULL);
6690 headername = g_strdup(tmp);
6692 headername_wcolon = g_strdup(tmp);
6693 headername = g_strdup(strtok(tmp, ":"));
6697 entry_str = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
6698 Xstrdup_a(headervalue, entry_str, return NULL);
6699 subst_char(headervalue, '\r', ' ');
6700 subst_char(headervalue, '\n', ' ');
6701 string = std_headers;
6702 while (*string != NULL) {
6703 headername_trans = prefs_common_translated_header_name(*string);
6704 if (!strcmp(headername_trans, headername_wcolon))
6705 standard_header = TRUE;
6708 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
6709 g_string_append_printf(header, "%s %s\n", headername_wcolon, headervalue);
6712 g_free(headername_wcolon);
6716 g_string_free(header, FALSE);
6721 #undef IS_IN_CUSTOM_HEADER
6723 static void compose_convert_header(Compose *compose, gchar *dest, gint len, gchar *src,
6724 gint header_len, gboolean addr_field)
6726 gchar *tmpstr = NULL;
6727 const gchar *out_codeset = NULL;
6729 cm_return_if_fail(src != NULL);
6730 cm_return_if_fail(dest != NULL);
6732 if (len < 1) return;
6734 tmpstr = g_strdup(src);
6736 subst_char(tmpstr, '\n', ' ');
6737 subst_char(tmpstr, '\r', ' ');
6740 if (!g_utf8_validate(tmpstr, -1, NULL)) {
6741 gchar *mybuf = g_malloc(strlen(tmpstr)*2 +1);
6742 conv_localetodisp(mybuf, strlen(tmpstr)*2 +1, tmpstr);
6747 codeconv_set_strict(TRUE);
6748 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6749 conv_get_charset_str(compose->out_encoding));
6750 codeconv_set_strict(FALSE);
6752 if (!dest || *dest == '\0') {
6753 gchar *test_conv_global_out = NULL;
6754 gchar *test_conv_reply = NULL;
6756 /* automatic mode. be automatic. */
6757 codeconv_set_strict(TRUE);
6759 out_codeset = conv_get_outgoing_charset_str();
6761 debug_print("trying to convert to %s\n", out_codeset);
6762 test_conv_global_out = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6765 if (!test_conv_global_out && compose->orig_charset
6766 && strcmp(compose->orig_charset, CS_US_ASCII)) {
6767 out_codeset = compose->orig_charset;
6768 debug_print("failure; trying to convert to %s\n", out_codeset);
6769 test_conv_reply = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6772 if (!test_conv_global_out && !test_conv_reply) {
6774 out_codeset = CS_INTERNAL;
6775 debug_print("finally using %s\n", out_codeset);
6777 g_free(test_conv_global_out);
6778 g_free(test_conv_reply);
6779 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6781 codeconv_set_strict(FALSE);
6786 static void compose_add_to_addressbook_cb(GtkMenuItem *menuitem, gpointer user_data)
6790 cm_return_if_fail(user_data != NULL);
6792 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(user_data)));
6793 g_strstrip(address);
6794 if (*address != '\0') {
6795 gchar *name = procheader_get_fromname(address);
6796 extract_address(address);
6797 #ifndef USE_ALT_ADDRBOOK
6798 addressbook_add_contact(name, address, NULL, NULL);
6800 debug_print("%s: %s\n", name, address);
6801 if (addressadd_selection(name, address, NULL, NULL)) {
6802 debug_print( "addressbook_add_contact - added\n" );
6809 static void compose_entry_popup_extend(GtkEntry *entry, GtkMenu *menu, gpointer user_data)
6811 GtkWidget *menuitem;
6814 cm_return_if_fail(menu != NULL);
6815 cm_return_if_fail(GTK_IS_MENU_SHELL(menu));
6817 menuitem = gtk_separator_menu_item_new();
6818 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6819 gtk_widget_show(menuitem);
6821 menuitem = gtk_menu_item_new_with_mnemonic(_("Add to address _book"));
6822 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6824 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
6825 g_strstrip(address);
6826 if (*address == '\0') {
6827 gtk_widget_set_sensitive(GTK_WIDGET(menuitem), FALSE);
6830 g_signal_connect(G_OBJECT(menuitem), "activate",
6831 G_CALLBACK(compose_add_to_addressbook_cb), entry);
6832 gtk_widget_show(menuitem);
6835 void compose_add_extra_header(gchar *header, GtkListStore *model)
6838 if (strcmp(header, "")) {
6839 COMBOBOX_ADD(model, header, COMPOSE_TO);
6843 void compose_add_extra_header_entries(GtkListStore *model)
6847 gchar buf[BUFFSIZE];
6850 if (extra_headers == NULL) {
6851 exhrc = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, "extraheaderrc", NULL);
6852 if ((exh = g_fopen(exhrc, "rb")) == NULL) {
6853 debug_print("extra headers file not found\n");
6854 goto extra_headers_done;
6856 while (fgets(buf, BUFFSIZE, exh) != NULL) {
6857 lastc = strlen(buf) - 1; /* remove trailing control chars */
6858 while (lastc >= 0 && buf[lastc] != ':')
6859 buf[lastc--] = '\0';
6860 if (lastc > 0 && buf[0] != '#' && buf[lastc] == ':') {
6861 buf[lastc] = '\0'; /* remove trailing : for comparison */
6862 if (custom_header_is_allowed(buf)) {
6864 extra_headers = g_slist_prepend(extra_headers, g_strdup(buf));
6867 g_message("disallowed extra header line: %s\n", buf);
6871 g_message("invalid extra header line: %s\n", buf);
6877 extra_headers = g_slist_prepend(extra_headers, g_strdup("")); /* end of list */
6878 extra_headers = g_slist_reverse(extra_headers);
6880 g_slist_foreach(extra_headers, (GFunc)compose_add_extra_header, (gpointer)model);
6883 static void compose_create_header_entry(Compose *compose)
6885 gchar *headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6892 const gchar *header = NULL;
6893 ComposeHeaderEntry *headerentry;
6894 gboolean standard_header = FALSE;
6895 GtkListStore *model;
6898 headerentry = g_new0(ComposeHeaderEntry, 1);
6900 /* Combo box model */
6901 model = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_BOOLEAN);
6902 #if !GTK_CHECK_VERSION(2, 24, 0)
6903 combo = gtk_combo_box_entry_new_with_model(GTK_TREE_MODEL(model), 0);
6905 COMBOBOX_ADD(model, prefs_common_translated_header_name("To:"),
6907 COMBOBOX_ADD(model, prefs_common_translated_header_name("Cc:"),
6909 COMBOBOX_ADD(model, prefs_common_translated_header_name("Bcc:"),
6911 COMBOBOX_ADD(model, prefs_common_translated_header_name("Newsgroups:"),
6912 COMPOSE_NEWSGROUPS);
6913 COMBOBOX_ADD(model, prefs_common_translated_header_name("Reply-To:"),
6915 COMBOBOX_ADD(model, prefs_common_translated_header_name("Followup-To:"),
6916 COMPOSE_FOLLOWUPTO);
6917 compose_add_extra_header_entries(model);
6920 #if GTK_CHECK_VERSION(2, 24, 0)
6921 combo = gtk_combo_box_new_with_model_and_entry(GTK_TREE_MODEL(model));
6922 GtkCellRenderer *cell = gtk_cell_renderer_text_new();
6923 gtk_cell_renderer_set_alignment(cell, 0.0, 0.5);
6924 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), cell, TRUE);
6925 gtk_combo_box_set_entry_text_column(GTK_COMBO_BOX(combo), 0);
6927 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
6928 g_signal_connect(G_OBJECT(gtk_bin_get_child(GTK_BIN(combo))), "grab_focus",
6929 G_CALLBACK(compose_grab_focus_cb), compose);
6930 gtk_widget_show(combo);
6932 /* Putting only the combobox child into focus chain of its parent causes
6933 * the parent to be skipped when changing focus via Tab or Shift+Tab.
6934 * This eliminates need to pres Tab twice in order to really get from the
6935 * combobox to next widget. */
6937 l = g_list_prepend(l, gtk_bin_get_child(GTK_BIN(combo)));
6938 gtk_container_set_focus_chain(GTK_CONTAINER(combo), l);
6941 gtk_table_attach(GTK_TABLE(compose->header_table), combo, 0, 1,
6942 compose->header_nextrow, compose->header_nextrow+1,
6943 GTK_SHRINK, GTK_FILL, 0, 0);
6944 if (compose->header_last && (compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN)) {
6945 const gchar *last_header_entry = gtk_entry_get_text(
6946 GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
6948 while (*string != NULL) {
6949 if (!strcmp(prefs_common_translated_header_name(*string), last_header_entry))
6950 standard_header = TRUE;
6953 if (standard_header)
6954 header = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
6956 if (!compose->header_last || !standard_header) {
6957 switch(compose->account->protocol) {
6959 header = prefs_common_translated_header_name("Newsgroups:");
6962 header = prefs_common_translated_header_name("To:");
6967 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((combo)))), header);
6969 gtk_editable_set_editable(
6970 GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((combo)))),
6971 prefs_common.type_any_header);
6973 g_signal_connect_after(G_OBJECT(gtk_bin_get_child(GTK_BIN((combo)))), "grab_focus",
6974 G_CALLBACK(compose_grab_focus_cb), compose);
6976 /* Entry field with cleanup button */
6977 button = gtk_button_new();
6978 gtk_button_set_image(GTK_BUTTON(button),
6979 gtk_image_new_from_stock(GTK_STOCK_CLEAR, GTK_ICON_SIZE_MENU));
6980 gtk_widget_show(button);
6981 CLAWS_SET_TIP(button,
6982 _("Delete entry contents"));
6983 entry = gtk_entry_new();
6984 gtk_widget_show(entry);
6985 CLAWS_SET_TIP(entry,
6986 _("Use <tab> to autocomplete from addressbook"));
6987 hbox = gtk_hbox_new (FALSE, 0);
6988 gtk_widget_show(hbox);
6989 gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, TRUE, 0);
6990 gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
6991 gtk_table_attach(GTK_TABLE(compose->header_table), hbox, 1, 2,
6992 compose->header_nextrow, compose->header_nextrow+1,
6993 GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
6995 g_signal_connect(G_OBJECT(entry), "key-press-event",
6996 G_CALLBACK(compose_headerentry_key_press_event_cb),
6998 g_signal_connect(G_OBJECT(entry), "changed",
6999 G_CALLBACK(compose_headerentry_changed_cb),
7001 g_signal_connect_after(G_OBJECT(entry), "grab_focus",
7002 G_CALLBACK(compose_grab_focus_cb), compose);
7004 g_signal_connect(G_OBJECT(button), "clicked",
7005 G_CALLBACK(compose_headerentry_button_clicked_cb),
7009 gtk_drag_dest_set(entry, GTK_DEST_DEFAULT_ALL, compose_mime_types,
7010 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7011 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7012 g_signal_connect(G_OBJECT(entry), "drag_data_received",
7013 G_CALLBACK(compose_header_drag_received_cb),
7015 g_signal_connect(G_OBJECT(entry), "drag-drop",
7016 G_CALLBACK(compose_drag_drop),
7018 g_signal_connect(G_OBJECT(entry), "populate-popup",
7019 G_CALLBACK(compose_entry_popup_extend),
7022 address_completion_register_entry(GTK_ENTRY(entry), TRUE);
7024 headerentry->compose = compose;
7025 headerentry->combo = combo;
7026 headerentry->entry = entry;
7027 headerentry->button = button;
7028 headerentry->hbox = hbox;
7029 headerentry->headernum = compose->header_nextrow;
7030 headerentry->type = PREF_NONE;
7032 compose->header_nextrow++;
7033 compose->header_last = headerentry;
7034 compose->header_list =
7035 g_slist_append(compose->header_list,
7039 static void compose_add_header_entry(Compose *compose, const gchar *header,
7040 gchar *text, ComposePrefType pref_type)
7042 ComposeHeaderEntry *last_header = compose->header_last;
7043 gchar *tmp = g_strdup(text), *email;
7044 gboolean replyto_hdr;
7046 replyto_hdr = (!strcasecmp(header,
7047 prefs_common_translated_header_name("Reply-To:")) ||
7049 prefs_common_translated_header_name("Followup-To:")) ||
7051 prefs_common_translated_header_name("In-Reply-To:")));
7053 extract_address(tmp);
7054 email = g_utf8_strdown(tmp, -1);
7056 if (replyto_hdr == FALSE &&
7057 g_hash_table_lookup(compose->email_hashtable, email) != NULL)
7059 debug_print("Ignoring duplicate address - %s %s, pref_type: %d\n",
7060 header, text, (gint) pref_type);
7066 if (!strcasecmp(header, prefs_common_translated_header_name("In-Reply-To:")))
7067 gtk_entry_set_text(GTK_ENTRY(
7068 gtk_bin_get_child(GTK_BIN(last_header->combo))), header);
7070 combobox_select_by_text(GTK_COMBO_BOX(last_header->combo), header);
7071 gtk_entry_set_text(GTK_ENTRY(last_header->entry), text);
7072 last_header->type = pref_type;
7074 if (replyto_hdr == FALSE)
7075 g_hash_table_insert(compose->email_hashtable, email,
7076 GUINT_TO_POINTER(1));
7083 static void compose_destroy_headerentry(Compose *compose,
7084 ComposeHeaderEntry *headerentry)
7086 gchar *text = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
7089 extract_address(text);
7090 email = g_utf8_strdown(text, -1);
7091 g_hash_table_remove(compose->email_hashtable, email);
7095 gtk_widget_destroy(headerentry->combo);
7096 gtk_widget_destroy(headerentry->entry);
7097 gtk_widget_destroy(headerentry->button);
7098 gtk_widget_destroy(headerentry->hbox);
7099 g_free(headerentry);
7102 static void compose_remove_header_entries(Compose *compose)
7105 for (list = compose->header_list; list; list = list->next)
7106 compose_destroy_headerentry(compose, (ComposeHeaderEntry *)list->data);
7108 compose->header_last = NULL;
7109 g_slist_free(compose->header_list);
7110 compose->header_list = NULL;
7111 compose->header_nextrow = 1;
7112 compose_create_header_entry(compose);
7115 static GtkWidget *compose_create_header(Compose *compose)
7117 GtkWidget *from_optmenu_hbox;
7118 GtkWidget *header_table_main;
7119 GtkWidget *header_scrolledwin;
7120 GtkWidget *header_table;
7122 /* parent with account selection and from header */
7123 header_table_main = gtk_table_new(2, 2, FALSE);
7124 gtk_widget_show(header_table_main);
7125 gtk_container_set_border_width(GTK_CONTAINER(header_table_main), BORDER_WIDTH);
7127 from_optmenu_hbox = compose_account_option_menu_create(compose);
7128 gtk_table_attach(GTK_TABLE(header_table_main), from_optmenu_hbox,
7129 0, 2, 0, 1, GTK_EXPAND | GTK_FILL, GTK_SHRINK, 0, 0);
7131 /* child with header labels and entries */
7132 header_scrolledwin = gtk_scrolled_window_new(NULL, NULL);
7133 gtk_widget_show(header_scrolledwin);
7134 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
7136 header_table = gtk_table_new(2, 2, FALSE);
7137 gtk_widget_show(header_table);
7138 gtk_container_set_border_width(GTK_CONTAINER(header_table), 0);
7139 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin), header_table);
7140 gtk_container_set_focus_vadjustment(GTK_CONTAINER(header_table),
7141 gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(header_scrolledwin)));
7142 gtk_viewport_set_shadow_type(GTK_VIEWPORT(gtk_bin_get_child(GTK_BIN(header_scrolledwin))), GTK_SHADOW_NONE);
7144 gtk_table_attach(GTK_TABLE(header_table_main), header_scrolledwin,
7145 0, 2, 1, 2, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 2);
7147 compose->header_table = header_table;
7148 compose->header_list = NULL;
7149 compose->header_nextrow = 0;
7151 compose_create_header_entry(compose);
7153 compose->table = NULL;
7155 return header_table_main;
7158 static gboolean popup_attach_button_pressed(GtkWidget *widget, gpointer data)
7160 Compose *compose = (Compose *)data;
7161 GdkEventButton event;
7164 event.time = gtk_get_current_event_time();
7166 return attach_button_pressed(compose->attach_clist, &event, compose);
7169 static GtkWidget *compose_create_attach(Compose *compose)
7171 GtkWidget *attach_scrwin;
7172 GtkWidget *attach_clist;
7174 GtkListStore *store;
7175 GtkCellRenderer *renderer;
7176 GtkTreeViewColumn *column;
7177 GtkTreeSelection *selection;
7179 /* attachment list */
7180 attach_scrwin = gtk_scrolled_window_new(NULL, NULL);
7181 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(attach_scrwin),
7182 GTK_POLICY_AUTOMATIC,
7183 GTK_POLICY_AUTOMATIC);
7184 gtk_widget_set_size_request(attach_scrwin, -1, 80);
7186 store = gtk_list_store_new(N_ATTACH_COLS,
7192 G_TYPE_AUTO_POINTER,
7194 attach_clist = GTK_WIDGET(gtk_tree_view_new_with_model
7195 (GTK_TREE_MODEL(store)));
7196 gtk_container_add(GTK_CONTAINER(attach_scrwin), attach_clist);
7197 g_object_unref(store);
7199 renderer = gtk_cell_renderer_text_new();
7200 column = gtk_tree_view_column_new_with_attributes
7201 (_("Mime type"), renderer, "text",
7202 COL_MIMETYPE, NULL);
7203 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7205 renderer = gtk_cell_renderer_text_new();
7206 column = gtk_tree_view_column_new_with_attributes
7207 (_("Size"), renderer, "text",
7209 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7211 renderer = gtk_cell_renderer_text_new();
7212 column = gtk_tree_view_column_new_with_attributes
7213 (_("Name"), renderer, "text",
7215 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7217 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(attach_clist),
7218 prefs_common.use_stripes_everywhere);
7219 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(attach_clist));
7220 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
7222 g_signal_connect(G_OBJECT(attach_clist), "row_activated",
7223 G_CALLBACK(attach_selected), compose);
7224 g_signal_connect(G_OBJECT(attach_clist), "button_press_event",
7225 G_CALLBACK(attach_button_pressed), compose);
7226 g_signal_connect(G_OBJECT(attach_clist), "popup-menu",
7227 G_CALLBACK(popup_attach_button_pressed), compose);
7228 g_signal_connect(G_OBJECT(attach_clist), "key_press_event",
7229 G_CALLBACK(attach_key_pressed), compose);
7232 gtk_drag_dest_set(attach_clist,
7233 GTK_DEST_DEFAULT_ALL, compose_mime_types,
7234 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7235 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7236 g_signal_connect(G_OBJECT(attach_clist), "drag_data_received",
7237 G_CALLBACK(compose_attach_drag_received_cb),
7239 g_signal_connect(G_OBJECT(attach_clist), "drag-drop",
7240 G_CALLBACK(compose_drag_drop),
7243 compose->attach_scrwin = attach_scrwin;
7244 compose->attach_clist = attach_clist;
7246 return attach_scrwin;
7249 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose);
7250 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose);
7252 static GtkWidget *compose_create_others(Compose *compose)
7255 GtkWidget *savemsg_checkbtn;
7256 GtkWidget *savemsg_combo;
7257 GtkWidget *savemsg_select;
7260 gchar *folderidentifier;
7262 /* Table for settings */
7263 table = gtk_table_new(3, 1, FALSE);
7264 gtk_container_set_border_width(GTK_CONTAINER(table), BORDER_WIDTH);
7265 gtk_widget_show(table);
7266 gtk_table_set_row_spacings(GTK_TABLE(table), VSPACING_NARROW);
7269 /* Save Message to folder */
7270 savemsg_checkbtn = gtk_check_button_new_with_label(_("Save Message to "));
7271 gtk_widget_show(savemsg_checkbtn);
7272 gtk_table_attach(GTK_TABLE(table), savemsg_checkbtn, 0, 1, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7273 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7274 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), prefs_common.savemsg);
7276 g_signal_connect(G_OBJECT(savemsg_checkbtn), "toggled",
7277 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
7279 #if !GTK_CHECK_VERSION(2, 24, 0)
7280 savemsg_combo = gtk_combo_box_entry_new_text();
7282 savemsg_combo = gtk_combo_box_text_new_with_entry();
7284 compose->savemsg_checkbtn = savemsg_checkbtn;
7285 compose->savemsg_combo = savemsg_combo;
7286 gtk_widget_show(savemsg_combo);
7288 if (prefs_common.compose_save_to_history)
7289 #if !GTK_CHECK_VERSION(2, 24, 0)
7290 combobox_set_popdown_strings(GTK_COMBO_BOX(savemsg_combo),
7291 prefs_common.compose_save_to_history);
7293 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(savemsg_combo),
7294 prefs_common.compose_save_to_history);
7296 gtk_table_attach(GTK_TABLE(table), savemsg_combo, 1, 2, rowcount, rowcount + 1, GTK_FILL|GTK_EXPAND, GTK_SHRINK, 0, 0);
7297 gtk_widget_set_sensitive(GTK_WIDGET(savemsg_combo), prefs_common.savemsg);
7298 g_signal_connect_after(G_OBJECT(savemsg_combo), "grab_focus",
7299 G_CALLBACK(compose_grab_focus_cb), compose);
7300 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7301 folderidentifier = folder_item_get_identifier(account_get_special_folder
7302 (compose->account, F_OUTBOX));
7303 compose_set_save_to(compose, folderidentifier);
7304 g_free(folderidentifier);
7307 savemsg_select = gtkut_get_browse_file_btn(_("_Browse"));
7308 gtk_widget_show(savemsg_select);
7309 gtk_table_attach(GTK_TABLE(table), savemsg_select, 2, 3, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7310 g_signal_connect(G_OBJECT(savemsg_select), "clicked",
7311 G_CALLBACK(compose_savemsg_select_cb),
7317 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose)
7319 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo),
7320 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn)));
7323 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose)
7328 dest = foldersel_folder_sel(NULL, FOLDER_SEL_COPY, NULL, FALSE);
7331 path = folder_item_get_identifier(dest);
7333 compose_set_save_to(compose, path);
7337 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry, gboolean wrap,
7338 GdkAtom clip, GtkTextIter *insert_place);
7341 static gboolean text_clicked(GtkWidget *text, GdkEventButton *event,
7345 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
7347 if (event->button == 3) {
7349 GtkTextIter sel_start, sel_end;
7350 gboolean stuff_selected;
7352 /* move the cursor to allow GtkAspell to check the word
7353 * under the mouse */
7354 if (event->x && event->y) {
7355 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7356 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7358 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7361 GtkTextMark *mark = gtk_text_buffer_get_insert(buffer);
7362 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7365 stuff_selected = gtk_text_buffer_get_selection_bounds(
7367 &sel_start, &sel_end);
7369 gtk_text_buffer_place_cursor (buffer, &iter);
7370 /* reselect stuff */
7372 && gtk_text_iter_in_range(&iter, &sel_start, &sel_end)) {
7373 gtk_text_buffer_select_range(buffer,
7374 &sel_start, &sel_end);
7376 return FALSE; /* pass the event so that the right-click goes through */
7379 if (event->button == 2) {
7384 /* get the middle-click position to paste at the correct place */
7385 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7386 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7388 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7391 entry_paste_clipboard(compose, text,
7392 prefs_common.linewrap_pastes,
7393 GDK_SELECTION_PRIMARY, &iter);
7401 static void compose_spell_menu_changed(void *data)
7403 Compose *compose = (Compose *)data;
7405 GtkWidget *menuitem;
7406 GtkWidget *parent_item;
7407 GtkMenu *menu = GTK_MENU(gtk_menu_new());
7410 if (compose->gtkaspell == NULL)
7413 parent_item = gtk_ui_manager_get_widget(compose->ui_manager,
7414 "/Menu/Spelling/Options");
7416 /* setting the submenu removes /Spelling/Options from the factory
7417 * so we need to save it */
7419 if (parent_item == NULL) {
7420 parent_item = compose->aspell_options_menu;
7421 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), NULL);
7423 compose->aspell_options_menu = parent_item;
7425 spell_menu = gtkaspell_make_config_menu(compose->gtkaspell);
7427 spell_menu = g_slist_reverse(spell_menu);
7428 for (items = spell_menu;
7429 items; items = items->next) {
7430 menuitem = GTK_WIDGET(GTK_MENU_ITEM(items->data));
7431 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), GTK_WIDGET(menuitem));
7432 gtk_widget_show(GTK_WIDGET(menuitem));
7434 g_slist_free(spell_menu);
7436 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), GTK_WIDGET(menu));
7437 gtk_widget_show(parent_item);
7440 static void compose_dict_changed(void *data)
7442 Compose *compose = (Compose *) data;
7444 if(!compose->gtkaspell)
7446 if(compose->gtkaspell->recheck_when_changing_dict == FALSE)
7449 gtkaspell_highlight_all(compose->gtkaspell);
7450 claws_spell_entry_recheck_all(CLAWS_SPELL_ENTRY(compose->subject_entry));
7454 static gboolean compose_popup_menu(GtkWidget *widget, gpointer data)
7456 Compose *compose = (Compose *)data;
7457 GdkEventButton event;
7460 event.time = gtk_get_current_event_time();
7464 return text_clicked(compose->text, &event, compose);
7467 static gboolean compose_force_window_origin = TRUE;
7468 static Compose *compose_create(PrefsAccount *account,
7477 GtkWidget *handlebox;
7479 GtkWidget *notebook;
7481 GtkWidget *attach_hbox;
7482 GtkWidget *attach_lab1;
7483 GtkWidget *attach_lab2;
7488 GtkWidget *subject_hbox;
7489 GtkWidget *subject_frame;
7490 GtkWidget *subject_entry;
7494 GtkWidget *edit_vbox;
7495 GtkWidget *ruler_hbox;
7497 GtkWidget *scrolledwin;
7499 GtkTextBuffer *buffer;
7500 GtkClipboard *clipboard;
7502 UndoMain *undostruct;
7504 GtkWidget *popupmenu;
7505 GtkWidget *tmpl_menu;
7506 GtkActionGroup *action_group = NULL;
7509 GtkAspell * gtkaspell = NULL;
7512 static GdkGeometry geometry;
7514 cm_return_val_if_fail(account != NULL, NULL);
7516 gtkut_convert_int_to_gdk_color(prefs_common.default_header_bgcolor,
7517 &default_header_bgcolor);
7518 gtkut_convert_int_to_gdk_color(prefs_common.default_header_color,
7519 &default_header_color);
7521 debug_print("Creating compose window...\n");
7522 compose = g_new0(Compose, 1);
7524 compose->batch = batch;
7525 compose->account = account;
7526 compose->folder = folder;
7528 compose->mutex = cm_mutex_new();
7529 compose->set_cursor_pos = -1;
7531 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose");
7533 gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
7534 gtk_widget_set_size_request(window, prefs_common.compose_width,
7535 prefs_common.compose_height);
7537 if (!geometry.max_width) {
7538 geometry.max_width = gdk_screen_width();
7539 geometry.max_height = gdk_screen_height();
7542 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7543 &geometry, GDK_HINT_MAX_SIZE);
7544 if (!geometry.min_width) {
7545 geometry.min_width = 600;
7546 geometry.min_height = 440;
7548 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7549 &geometry, GDK_HINT_MIN_SIZE);
7551 #ifndef GENERIC_UMPC
7552 if (compose_force_window_origin)
7553 gtk_window_move(GTK_WINDOW(window), prefs_common.compose_x,
7554 prefs_common.compose_y);
7556 g_signal_connect(G_OBJECT(window), "delete_event",
7557 G_CALLBACK(compose_delete_cb), compose);
7558 MANAGE_WINDOW_SIGNALS_CONNECT(window);
7559 gtk_widget_realize(window);
7561 gtkut_widget_set_composer_icon(window);
7563 vbox = gtk_vbox_new(FALSE, 0);
7564 gtk_container_add(GTK_CONTAINER(window), vbox);
7566 compose->ui_manager = gtk_ui_manager_new();
7567 action_group = cm_menu_create_action_group_full(compose->ui_manager,"Menu", compose_entries,
7568 G_N_ELEMENTS(compose_entries), (gpointer)compose);
7569 gtk_action_group_add_toggle_actions(action_group, compose_toggle_entries,
7570 G_N_ELEMENTS(compose_toggle_entries), (gpointer)compose);
7571 gtk_action_group_add_radio_actions(action_group, compose_radio_rm_entries,
7572 G_N_ELEMENTS(compose_radio_rm_entries), COMPOSE_REPLY, G_CALLBACK(compose_reply_change_mode_cb), (gpointer)compose);
7573 gtk_action_group_add_radio_actions(action_group, compose_radio_prio_entries,
7574 G_N_ELEMENTS(compose_radio_prio_entries), PRIORITY_NORMAL, G_CALLBACK(compose_set_priority_cb), (gpointer)compose);
7575 gtk_action_group_add_radio_actions(action_group, compose_radio_enc_entries,
7576 G_N_ELEMENTS(compose_radio_enc_entries), C_AUTO, G_CALLBACK(compose_set_encoding_cb), (gpointer)compose);
7578 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Menu", NULL, GTK_UI_MANAGER_MENUBAR)
7580 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Message", "Message", GTK_UI_MANAGER_MENU)
7581 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Edit", "Edit", GTK_UI_MANAGER_MENU)
7583 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Spelling", "Spelling", GTK_UI_MANAGER_MENU)
7585 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Options", "Options", GTK_UI_MANAGER_MENU)
7586 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Tools", "Tools", GTK_UI_MANAGER_MENU)
7587 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Help", "Help", GTK_UI_MANAGER_MENU)
7590 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Send", "Message/Send", GTK_UI_MANAGER_MENUITEM)
7591 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "SendLater", "Message/SendLater", GTK_UI_MANAGER_MENUITEM)
7592 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator1", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7593 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "AttachFile", "Message/AttachFile", GTK_UI_MANAGER_MENUITEM)
7594 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertFile", "Message/InsertFile", GTK_UI_MANAGER_MENUITEM)
7595 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertSig", "Message/InsertSig", GTK_UI_MANAGER_MENUITEM)
7596 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "ReplaceSig", "Message/ReplaceSig", GTK_UI_MANAGER_MENUITEM)
7597 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator2", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7598 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Save", "Message/Save", GTK_UI_MANAGER_MENUITEM)
7599 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator3", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7600 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Print", "Message/Print", GTK_UI_MANAGER_MENUITEM)
7601 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator4", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7602 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Close", "Message/Close", GTK_UI_MANAGER_MENUITEM)
7605 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Undo", "Edit/Undo", GTK_UI_MANAGER_MENUITEM)
7606 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Redo", "Edit/Redo", GTK_UI_MANAGER_MENUITEM)
7607 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator1", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7609 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Cut", "Edit/Cut", GTK_UI_MANAGER_MENUITEM)
7610 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Copy", "Edit/Copy", GTK_UI_MANAGER_MENUITEM)
7611 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Paste", "Edit/Paste", GTK_UI_MANAGER_MENUITEM)
7613 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SpecialPaste", "Edit/SpecialPaste", GTK_UI_MANAGER_MENU)
7614 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "AsQuotation", "Edit/SpecialPaste/AsQuotation", GTK_UI_MANAGER_MENUITEM)
7615 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Wrapped", "Edit/SpecialPaste/Wrapped", GTK_UI_MANAGER_MENUITEM)
7616 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Unwrapped", "Edit/SpecialPaste/Unwrapped", GTK_UI_MANAGER_MENUITEM)
7618 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SelectAll", "Edit/SelectAll", GTK_UI_MANAGER_MENUITEM)
7620 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Advanced", "Edit/Advanced", GTK_UI_MANAGER_MENU)
7621 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackChar", "Edit/Advanced/BackChar", GTK_UI_MANAGER_MENUITEM)
7622 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwChar", "Edit/Advanced/ForwChar", GTK_UI_MANAGER_MENUITEM)
7623 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackWord", "Edit/Advanced/BackWord", GTK_UI_MANAGER_MENUITEM)
7624 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwWord", "Edit/Advanced/ForwWord", GTK_UI_MANAGER_MENUITEM)
7625 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BegLine", "Edit/Advanced/BegLine", GTK_UI_MANAGER_MENUITEM)
7626 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "EndLine", "Edit/Advanced/EndLine", GTK_UI_MANAGER_MENUITEM)
7627 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "PrevLine", "Edit/Advanced/PrevLine", GTK_UI_MANAGER_MENUITEM)
7628 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "NextLine", "Edit/Advanced/NextLine", GTK_UI_MANAGER_MENUITEM)
7629 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackChar", "Edit/Advanced/DelBackChar", GTK_UI_MANAGER_MENUITEM)
7630 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwChar", "Edit/Advanced/DelForwChar", GTK_UI_MANAGER_MENUITEM)
7631 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackWord", "Edit/Advanced/DelBackWord", GTK_UI_MANAGER_MENUITEM)
7632 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwWord", "Edit/Advanced/DelForwWord", GTK_UI_MANAGER_MENUITEM)
7633 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelLine", "Edit/Advanced/DelLine", GTK_UI_MANAGER_MENUITEM)
7634 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelEndLine", "Edit/Advanced/DelEndLine", GTK_UI_MANAGER_MENUITEM)
7636 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator2", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7638 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Find", "Edit/Find", GTK_UI_MANAGER_MENUITEM)
7639 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapPara", "Edit/WrapPara", GTK_UI_MANAGER_MENUITEM)
7640 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapAllLines", "Edit/WrapAllLines", GTK_UI_MANAGER_MENUITEM)
7641 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoWrap", "Edit/AutoWrap", GTK_UI_MANAGER_MENUITEM)
7642 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoIndent", "Edit/AutoIndent", GTK_UI_MANAGER_MENUITEM)
7644 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator3", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7646 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "ExtEditor", "Edit/ExtEditor", GTK_UI_MANAGER_MENUITEM)
7650 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckAllSel", "Spelling/CheckAllSel", GTK_UI_MANAGER_MENUITEM)
7651 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "HighlightAll", "Spelling/HighlightAll", GTK_UI_MANAGER_MENUITEM)
7652 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckBackwards", "Spelling/CheckBackwards", GTK_UI_MANAGER_MENUITEM)
7653 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "ForwardNext", "Spelling/ForwardNext", GTK_UI_MANAGER_MENUITEM)
7654 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Separator1", "Spelling/---", GTK_UI_MANAGER_SEPARATOR)
7655 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Options", "Spelling/Options", GTK_UI_MANAGER_MENU)
7659 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "ReplyMode", "Options/ReplyMode", GTK_UI_MANAGER_MENU)
7660 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Normal", "Options/ReplyMode/Normal", GTK_UI_MANAGER_MENUITEM)
7661 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "All", "Options/ReplyMode/All", GTK_UI_MANAGER_MENUITEM)
7662 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Sender", "Options/ReplyMode/Sender", GTK_UI_MANAGER_MENUITEM)
7663 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "List", "Options/ReplyMode/List", GTK_UI_MANAGER_MENUITEM)
7665 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator1", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7666 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "PrivacySystem", "Options/PrivacySystem", GTK_UI_MANAGER_MENU)
7667 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/PrivacySystem", "PlaceHolder", "Options/PrivacySystem/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7668 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Sign", "Options/Sign", GTK_UI_MANAGER_MENUITEM)
7669 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encrypt", "Options/Encrypt", GTK_UI_MANAGER_MENUITEM)
7672 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator2", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7673 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Priority", "Options/Priority", GTK_UI_MANAGER_MENU)
7674 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Highest", "Options/Priority/Highest", GTK_UI_MANAGER_MENUITEM)
7675 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "High", "Options/Priority/High", GTK_UI_MANAGER_MENUITEM)
7676 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Normal", "Options/Priority/Normal", GTK_UI_MANAGER_MENUITEM)
7677 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Low", "Options/Priority/Low", GTK_UI_MANAGER_MENUITEM)
7678 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Lowest", "Options/Priority/Lowest", GTK_UI_MANAGER_MENUITEM)
7680 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator3", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7681 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RequestRetRcpt", "Options/RequestRetRcpt", GTK_UI_MANAGER_MENUITEM)
7682 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator4", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7683 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RemoveReferences", "Options/RemoveReferences", GTK_UI_MANAGER_MENUITEM)
7684 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator5", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7686 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encoding", "Options/Encoding", GTK_UI_MANAGER_MENU)
7688 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_AUTO, "Options/Encoding/"CS_AUTO, GTK_UI_MANAGER_MENUITEM)
7689 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator1", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7690 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_US_ASCII, "Options/Encoding/"CS_US_ASCII, GTK_UI_MANAGER_MENUITEM)
7691 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_UTF_8, "Options/Encoding/"CS_UTF_8, GTK_UI_MANAGER_MENUITEM)
7692 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator2", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7694 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Western", "Options/Encoding/Western", GTK_UI_MANAGER_MENU)
7695 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)
7696 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)
7697 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Western", CS_WINDOWS_1252, "Options/Encoding/Western/"CS_WINDOWS_1252, GTK_UI_MANAGER_MENUITEM)
7699 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_2, "Options/Encoding/"CS_ISO_8859_2, GTK_UI_MANAGER_MENUITEM)
7701 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Baltic", "Options/Encoding/Baltic", GTK_UI_MANAGER_MENU)
7702 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)
7703 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)
7705 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_7, "Options/Encoding/"CS_ISO_8859_7, GTK_UI_MANAGER_MENUITEM)
7707 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Hebrew", "Options/Encoding/Hebrew", GTK_UI_MANAGER_MENU)
7708 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)
7709 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Hebrew", CS_WINDOWS_1255, "Options/Encoding/Hebrew/"CS_WINDOWS_1255, GTK_UI_MANAGER_MENUITEM)
7711 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Arabic", "Options/Encoding/Arabic", GTK_UI_MANAGER_MENU)
7712 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)
7713 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Arabic", CS_WINDOWS_1256, "Options/Encoding/Arabic/"CS_WINDOWS_1256, GTK_UI_MANAGER_MENUITEM)
7715 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_9, "Options/Encoding/"CS_ISO_8859_9, GTK_UI_MANAGER_MENUITEM)
7717 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Cyrillic", "Options/Encoding/Cyrillic", GTK_UI_MANAGER_MENU)
7718 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)
7719 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_R, "Options/Encoding/Cyrillic/"CS_KOI8_R, GTK_UI_MANAGER_MENUITEM)
7720 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_MACCYR, "Options/Encoding/Cyrillic/"CS_MACCYR, GTK_UI_MANAGER_MENUITEM)
7721 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_U, "Options/Encoding/Cyrillic/"CS_KOI8_U, GTK_UI_MANAGER_MENUITEM)
7722 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_WINDOWS_1251, "Options/Encoding/Cyrillic/"CS_WINDOWS_1251, GTK_UI_MANAGER_MENUITEM)
7724 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Japanese", "Options/Encoding/Japanese", GTK_UI_MANAGER_MENU)
7725 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)
7726 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)
7727 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_EUC_JP, "Options/Encoding/Japanese/"CS_EUC_JP, GTK_UI_MANAGER_MENUITEM)
7728 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_SHIFT_JIS, "Options/Encoding/Japanese/"CS_SHIFT_JIS, GTK_UI_MANAGER_MENUITEM)
7730 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Chinese", "Options/Encoding/Chinese", GTK_UI_MANAGER_MENU)
7731 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB18030, "Options/Encoding/Chinese/"CS_GB18030, GTK_UI_MANAGER_MENUITEM)
7732 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB2312, "Options/Encoding/Chinese/"CS_GB2312, GTK_UI_MANAGER_MENUITEM)
7733 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GBK, "Options/Encoding/Chinese/"CS_GBK, GTK_UI_MANAGER_MENUITEM)
7734 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_BIG5, "Options/Encoding/Chinese/"CS_BIG5, GTK_UI_MANAGER_MENUITEM)
7735 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_EUC_TW, "Options/Encoding/Chinese/"CS_EUC_TW, GTK_UI_MANAGER_MENUITEM)
7737 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Korean", "Options/Encoding/Korean", GTK_UI_MANAGER_MENU)
7738 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Korean", CS_EUC_KR, "Options/Encoding/Korean/"CS_EUC_KR, GTK_UI_MANAGER_MENUITEM)
7739 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)
7741 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Thai", "Options/Encoding/Thai", GTK_UI_MANAGER_MENU)
7742 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_TIS_620, "Options/Encoding/Thai/"CS_TIS_620, GTK_UI_MANAGER_MENUITEM)
7743 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_WINDOWS_874, "Options/Encoding/Thai/"CS_WINDOWS_874, GTK_UI_MANAGER_MENUITEM)
7747 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "ShowRuler", "Tools/ShowRuler", GTK_UI_MANAGER_MENUITEM)
7748 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "AddressBook", "Tools/AddressBook", GTK_UI_MANAGER_MENUITEM)
7749 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Template", "Tools/Template", GTK_UI_MANAGER_MENU)
7750 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Template", "PlaceHolder", "Tools/Template/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7751 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Actions", "Tools/Actions", GTK_UI_MANAGER_MENU)
7752 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Actions", "PlaceHolder", "Tools/Actions/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7755 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Help", "About", "Help/About", GTK_UI_MANAGER_MENUITEM)
7757 menubar = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu");
7758 gtk_widget_show_all(menubar);
7760 gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(compose->ui_manager));
7761 gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, TRUE, 0);
7763 if (prefs_common.toolbar_detachable) {
7764 handlebox = gtk_handle_box_new();
7766 handlebox = gtk_hbox_new(FALSE, 0);
7768 gtk_box_pack_start(GTK_BOX(vbox), handlebox, FALSE, FALSE, 0);
7770 gtk_widget_realize(handlebox);
7771 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, handlebox,
7774 vbox2 = gtk_vbox_new(FALSE, 2);
7775 gtk_box_pack_start(GTK_BOX(vbox), vbox2, TRUE, TRUE, 0);
7776 gtk_container_set_border_width(GTK_CONTAINER(vbox2), 0);
7779 notebook = gtk_notebook_new();
7780 gtk_widget_show(notebook);
7782 /* header labels and entries */
7783 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7784 compose_create_header(compose),
7785 gtk_label_new_with_mnemonic(_("Hea_der")));
7786 /* attachment list */
7787 attach_hbox = gtk_hbox_new(FALSE, 0);
7788 gtk_widget_show(attach_hbox);
7790 attach_lab1 = gtk_label_new_with_mnemonic(_("_Attachments"));
7791 gtk_widget_show(attach_lab1);
7792 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab1, TRUE, TRUE, 0);
7794 attach_lab2 = gtk_label_new("");
7795 gtk_widget_show(attach_lab2);
7796 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab2, FALSE, FALSE, 0);
7798 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7799 compose_create_attach(compose),
7802 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7803 compose_create_others(compose),
7804 gtk_label_new_with_mnemonic(_("Othe_rs")));
7807 subject_hbox = gtk_hbox_new(FALSE, 0);
7808 gtk_widget_show(subject_hbox);
7810 subject_frame = gtk_frame_new(NULL);
7811 gtk_frame_set_shadow_type(GTK_FRAME(subject_frame), GTK_SHADOW_NONE);
7812 gtk_box_pack_start(GTK_BOX(subject_hbox), subject_frame, TRUE, TRUE, 0);
7813 gtk_widget_show(subject_frame);
7815 subject = gtk_hbox_new(FALSE, HSPACING_NARROW);
7816 gtk_container_set_border_width(GTK_CONTAINER(subject), 0);
7817 gtk_widget_show(subject);
7819 label = gtk_label_new_with_mnemonic(_("S_ubject:"));
7820 gtk_box_pack_start(GTK_BOX(subject), label, FALSE, FALSE, 0);
7821 gtk_widget_show(label);
7824 subject_entry = claws_spell_entry_new();
7826 subject_entry = gtk_entry_new();
7828 gtk_box_pack_start(GTK_BOX(subject), subject_entry, TRUE, TRUE, 0);
7829 g_signal_connect_after(G_OBJECT(subject_entry), "grab_focus",
7830 G_CALLBACK(compose_grab_focus_cb), compose);
7831 gtk_label_set_mnemonic_widget(GTK_LABEL(label), subject_entry);
7832 gtk_widget_show(subject_entry);
7833 compose->subject_entry = subject_entry;
7834 gtk_container_add(GTK_CONTAINER(subject_frame), subject);
7836 edit_vbox = gtk_vbox_new(FALSE, 0);
7838 gtk_box_pack_start(GTK_BOX(edit_vbox), subject_hbox, FALSE, FALSE, 0);
7841 ruler_hbox = gtk_hbox_new(FALSE, 0);
7842 gtk_box_pack_start(GTK_BOX(edit_vbox), ruler_hbox, FALSE, FALSE, 0);
7844 ruler = gtk_shruler_new(GTK_ORIENTATION_HORIZONTAL);
7845 gtk_shruler_set_range(GTK_SHRULER(ruler), 0.0, 100.0, 1.0);
7846 gtk_box_pack_start(GTK_BOX(ruler_hbox), ruler, TRUE, TRUE,
7850 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
7851 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
7852 GTK_POLICY_AUTOMATIC,
7853 GTK_POLICY_AUTOMATIC);
7854 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
7856 gtk_box_pack_start(GTK_BOX(edit_vbox), scrolledwin, TRUE, TRUE, 0);
7858 text = gtk_text_view_new();
7859 if (prefs_common.show_compose_margin) {
7860 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text), 6);
7861 gtk_text_view_set_right_margin(GTK_TEXT_VIEW(text), 6);
7863 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
7864 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
7865 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), TRUE);
7866 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
7867 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
7869 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
7870 g_signal_connect_after(G_OBJECT(text), "size_allocate",
7871 G_CALLBACK(compose_edit_size_alloc),
7873 g_signal_connect(G_OBJECT(buffer), "changed",
7874 G_CALLBACK(compose_changed_cb), compose);
7875 g_signal_connect(G_OBJECT(text), "grab_focus",
7876 G_CALLBACK(compose_grab_focus_cb), compose);
7877 g_signal_connect(G_OBJECT(buffer), "insert_text",
7878 G_CALLBACK(text_inserted), compose);
7879 g_signal_connect(G_OBJECT(text), "button_press_event",
7880 G_CALLBACK(text_clicked), compose);
7881 g_signal_connect(G_OBJECT(text), "popup-menu",
7882 G_CALLBACK(compose_popup_menu), compose);
7883 g_signal_connect(G_OBJECT(subject_entry), "changed",
7884 G_CALLBACK(compose_changed_cb), compose);
7885 g_signal_connect(G_OBJECT(subject_entry), "activate",
7886 G_CALLBACK(compose_subject_entry_activated), compose);
7889 gtk_drag_dest_set(text, GTK_DEST_DEFAULT_ALL, compose_mime_types,
7890 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7891 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7892 g_signal_connect(G_OBJECT(text), "drag_data_received",
7893 G_CALLBACK(compose_insert_drag_received_cb),
7895 g_signal_connect(G_OBJECT(text), "drag-drop",
7896 G_CALLBACK(compose_drag_drop),
7898 g_signal_connect(G_OBJECT(text), "key-press-event",
7899 G_CALLBACK(completion_set_focus_to_subject),
7901 gtk_widget_show_all(vbox);
7903 /* pane between attach clist and text */
7904 paned = gtk_vpaned_new();
7905 gtk_container_add(GTK_CONTAINER(vbox2), paned);
7906 gtk_paned_pack1(GTK_PANED(paned), notebook, FALSE, FALSE);
7907 gtk_paned_pack2(GTK_PANED(paned), edit_vbox, TRUE, FALSE);
7908 gtk_paned_set_position(GTK_PANED(paned), prefs_common.compose_notebook_height);
7909 g_signal_connect(G_OBJECT(notebook), "size_allocate",
7910 G_CALLBACK(compose_notebook_size_alloc), paned);
7912 gtk_widget_show_all(paned);
7915 if (prefs_common.textfont) {
7916 PangoFontDescription *font_desc;
7918 font_desc = pango_font_description_from_string
7919 (prefs_common.textfont);
7921 gtk_widget_modify_font(text, font_desc);
7922 pango_font_description_free(font_desc);
7926 gtk_action_group_add_actions(action_group, compose_popup_entries,
7927 G_N_ELEMENTS(compose_popup_entries), (gpointer)compose);
7928 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Popup", NULL, GTK_UI_MANAGER_MENUBAR)
7929 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup", "Compose", "Compose", GTK_UI_MANAGER_MENU)
7930 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Add", "Compose/Add", GTK_UI_MANAGER_MENUITEM)
7931 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Remove", "Compose/Remove", GTK_UI_MANAGER_MENUITEM)
7932 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Separator1", "Compose/---", GTK_UI_MANAGER_SEPARATOR)
7933 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Properties", "Compose/Properties", GTK_UI_MANAGER_MENUITEM)
7935 popupmenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(gtk_ui_manager_get_widget(compose->ui_manager, "/Popup/Compose")));
7937 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
7938 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
7939 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
7941 tmpl_menu = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu/Tools/Template");
7943 undostruct = undo_init(text);
7944 undo_set_change_state_func(undostruct, &compose_undo_state_changed,
7947 address_completion_start(window);
7949 compose->window = window;
7950 compose->vbox = vbox;
7951 compose->menubar = menubar;
7952 compose->handlebox = handlebox;
7954 compose->vbox2 = vbox2;
7956 compose->paned = paned;
7958 compose->attach_label = attach_lab2;
7960 compose->notebook = notebook;
7961 compose->edit_vbox = edit_vbox;
7962 compose->ruler_hbox = ruler_hbox;
7963 compose->ruler = ruler;
7964 compose->scrolledwin = scrolledwin;
7965 compose->text = text;
7967 compose->focused_editable = NULL;
7969 compose->popupmenu = popupmenu;
7971 compose->tmpl_menu = tmpl_menu;
7973 compose->mode = mode;
7974 compose->rmode = mode;
7976 compose->targetinfo = NULL;
7977 compose->replyinfo = NULL;
7978 compose->fwdinfo = NULL;
7980 compose->email_hashtable = g_hash_table_new_full(g_str_hash,
7981 g_str_equal, (GDestroyNotify) g_free, NULL);
7983 compose->replyto = NULL;
7985 compose->bcc = NULL;
7986 compose->followup_to = NULL;
7988 compose->ml_post = NULL;
7990 compose->inreplyto = NULL;
7991 compose->references = NULL;
7992 compose->msgid = NULL;
7993 compose->boundary = NULL;
7995 compose->autowrap = prefs_common.autowrap;
7996 compose->autoindent = prefs_common.auto_indent;
7997 compose->use_signing = FALSE;
7998 compose->use_encryption = FALSE;
7999 compose->privacy_system = NULL;
8000 compose->encdata = NULL;
8002 compose->modified = FALSE;
8004 compose->return_receipt = FALSE;
8006 compose->to_list = NULL;
8007 compose->newsgroup_list = NULL;
8009 compose->undostruct = undostruct;
8011 compose->sig_str = NULL;
8013 compose->exteditor_file = NULL;
8014 compose->exteditor_pid = -1;
8015 compose->exteditor_tag = -1;
8016 compose->exteditor_socket = NULL;
8017 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN; /* inhibit auto-drafting while loading */
8019 compose->folder_update_callback_id =
8020 hooks_register_hook(FOLDER_UPDATE_HOOKLIST,
8021 compose_update_folder_hook,
8022 (gpointer) compose);
8025 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
8026 if (mode != COMPOSE_REDIRECT) {
8027 if (prefs_common.enable_aspell && prefs_common.dictionary &&
8028 strcmp(prefs_common.dictionary, "")) {
8029 gtkaspell = gtkaspell_new(prefs_common.dictionary,
8030 prefs_common.alt_dictionary,
8031 conv_get_locale_charset_str(),
8032 prefs_common.misspelled_col,
8033 prefs_common.check_while_typing,
8034 prefs_common.recheck_when_changing_dict,
8035 prefs_common.use_alternate,
8036 prefs_common.use_both_dicts,
8037 GTK_TEXT_VIEW(text),
8038 GTK_WINDOW(compose->window),
8039 compose_dict_changed,
8040 compose_spell_menu_changed,
8043 alertpanel_error(_("Spell checker could not "
8045 gtkaspell_checkers_strerror());
8046 gtkaspell_checkers_reset_error();
8048 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", TRUE);
8052 compose->gtkaspell = gtkaspell;
8053 compose_spell_menu_changed(compose);
8054 claws_spell_entry_set_gtkaspell(CLAWS_SPELL_ENTRY(subject_entry), gtkaspell);
8057 compose_select_account(compose, account, TRUE);
8059 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", prefs_common.autowrap);
8060 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", prefs_common.auto_indent);
8062 if (account->set_autocc && account->auto_cc && mode != COMPOSE_REEDIT)
8063 compose_entry_append(compose, account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8065 if (account->set_autobcc && account->auto_bcc && mode != COMPOSE_REEDIT)
8066 compose_entry_append(compose, account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8068 if (account->set_autoreplyto && account->auto_replyto && mode != COMPOSE_REEDIT)
8069 compose_entry_append(compose, account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8071 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", compose->mode == COMPOSE_REPLY);
8072 if (account->protocol != A_NNTP)
8073 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
8074 prefs_common_translated_header_name("To:"));
8076 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
8077 prefs_common_translated_header_name("Newsgroups:"));
8079 #ifndef USE_ALT_ADDRBOOK
8080 addressbook_set_target_compose(compose);
8082 if (mode != COMPOSE_REDIRECT)
8083 compose_set_template_menu(compose);
8085 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Template", FALSE);
8088 compose_list = g_list_append(compose_list, compose);
8090 if (!prefs_common.show_ruler)
8091 gtk_widget_hide(ruler_hbox);
8093 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Tools/ShowRuler", prefs_common.show_ruler);
8096 compose->priority = PRIORITY_NORMAL;
8097 compose_update_priority_menu_item(compose);
8099 compose_set_out_encoding(compose);
8102 compose_update_actions_menu(compose);
8104 /* Privacy Systems menu */
8105 compose_update_privacy_systems_menu(compose);
8107 activate_privacy_system(compose, account, TRUE);
8108 toolbar_set_style(compose->toolbar->toolbar, compose->handlebox, prefs_common.toolbar_style);
8110 gtk_widget_realize(window);
8112 gtk_widget_show(window);
8118 static GtkWidget *compose_account_option_menu_create(Compose *compose)
8123 GtkWidget *optmenubox;
8124 GtkWidget *fromlabel;
8127 GtkWidget *from_name = NULL;
8129 gint num = 0, def_menu = 0;
8131 accounts = account_get_list();
8132 cm_return_val_if_fail(accounts != NULL, NULL);
8134 optmenubox = gtk_event_box_new();
8135 optmenu = gtkut_sc_combobox_create(optmenubox, FALSE);
8136 menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
8138 hbox = gtk_hbox_new(FALSE, 4);
8139 from_name = gtk_entry_new();
8141 g_signal_connect_after(G_OBJECT(from_name), "grab_focus",
8142 G_CALLBACK(compose_grab_focus_cb), compose);
8143 g_signal_connect_after(G_OBJECT(from_name), "activate",
8144 G_CALLBACK(from_name_activate_cb), optmenu);
8146 for (; accounts != NULL; accounts = accounts->next, num++) {
8147 PrefsAccount *ac = (PrefsAccount *)accounts->data;
8148 gchar *name, *from = NULL;
8150 if (ac == compose->account) def_menu = num;
8152 name = g_markup_printf_escaped("<i>%s</i>",
8155 if (ac == compose->account) {
8156 if (ac->name && *ac->name) {
8158 QUOTE_IF_REQUIRED_NORMAL(buf, ac->name, return NULL);
8159 from = g_strdup_printf("%s <%s>",
8161 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8163 from = g_strdup_printf("%s",
8165 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8167 if (cur_account != compose->account) {
8168 gtk_widget_modify_base(
8169 GTK_WIDGET(from_name),
8170 GTK_STATE_NORMAL, &default_header_bgcolor);
8171 gtk_widget_modify_text(
8172 GTK_WIDGET(from_name),
8173 GTK_STATE_NORMAL, &default_header_color);
8176 COMBOBOX_ADD(menu, name, ac->account_id);
8181 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), def_menu);
8183 g_signal_connect(G_OBJECT(optmenu), "changed",
8184 G_CALLBACK(account_activated),
8186 g_signal_connect(G_OBJECT(from_name), "populate-popup",
8187 G_CALLBACK(compose_entry_popup_extend),
8190 fromlabel = gtk_label_new_with_mnemonic(_("_From:"));
8191 gtk_label_set_mnemonic_widget(GTK_LABEL(fromlabel), from_name);
8193 gtk_box_pack_start(GTK_BOX(hbox), fromlabel, FALSE, FALSE, 4);
8194 gtk_box_pack_start(GTK_BOX(hbox), optmenubox, FALSE, FALSE, 0);
8195 gtk_box_pack_start(GTK_BOX(hbox), from_name, TRUE, TRUE, 0);
8197 /* Putting only the GtkEntry into focus chain of parent hbox causes
8198 * the account selector combobox next to it to be unreachable when
8199 * navigating widgets in GtkTable with up/down arrow keys.
8200 * Note: gtk_widget_set_can_focus() was not enough. */
8202 l = g_list_prepend(l, from_name);
8203 gtk_container_set_focus_chain(GTK_CONTAINER(hbox), l);
8206 CLAWS_SET_TIP(optmenubox,
8207 _("Account to use for this email"));
8208 CLAWS_SET_TIP(from_name,
8209 _("Sender address to be used"));
8211 compose->account_combo = optmenu;
8212 compose->from_name = from_name;
8217 static void compose_set_priority_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8219 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8220 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8221 Compose *compose = (Compose *) data;
8223 compose->priority = value;
8227 static void compose_reply_change_mode(Compose *compose,
8230 gboolean was_modified = compose->modified;
8232 gboolean all = FALSE, ml = FALSE, sender = FALSE, followup = FALSE;
8234 cm_return_if_fail(compose->replyinfo != NULL);
8236 if (action == COMPOSE_REPLY && prefs_common.default_reply_list)
8238 if (action == COMPOSE_REPLY && compose->rmode == COMPOSE_FOLLOWUP_AND_REPLY_TO)
8240 if (action == COMPOSE_REPLY_TO_ALL)
8242 if (action == COMPOSE_REPLY_TO_SENDER)
8244 if (action == COMPOSE_REPLY_TO_LIST)
8247 compose_remove_header_entries(compose);
8248 compose_reply_set_entry(compose, compose->replyinfo, all, ml, sender, followup);
8249 if (compose->account->set_autocc && compose->account->auto_cc)
8250 compose_entry_append(compose, compose->account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8252 if (compose->account->set_autobcc && compose->account->auto_bcc)
8253 compose_entry_append(compose, compose->account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8255 if (compose->account->set_autoreplyto && compose->account->auto_replyto)
8256 compose_entry_append(compose, compose->account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8257 compose_show_first_last_header(compose, TRUE);
8258 compose->modified = was_modified;
8259 compose_set_title(compose);
8262 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8264 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8265 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8266 Compose *compose = (Compose *) data;
8269 compose_reply_change_mode(compose, value);
8272 static void compose_update_priority_menu_item(Compose * compose)
8274 GtkWidget *menuitem = NULL;
8275 switch (compose->priority) {
8276 case PRIORITY_HIGHEST:
8277 menuitem = gtk_ui_manager_get_widget
8278 (compose->ui_manager, "/Menu/Options/Priority/Highest");
8281 menuitem = gtk_ui_manager_get_widget
8282 (compose->ui_manager, "/Menu/Options/Priority/High");
8284 case PRIORITY_NORMAL:
8285 menuitem = gtk_ui_manager_get_widget
8286 (compose->ui_manager, "/Menu/Options/Priority/Normal");
8289 menuitem = gtk_ui_manager_get_widget
8290 (compose->ui_manager, "/Menu/Options/Priority/Low");
8292 case PRIORITY_LOWEST:
8293 menuitem = gtk_ui_manager_get_widget
8294 (compose->ui_manager, "/Menu/Options/Priority/Lowest");
8297 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8300 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data)
8302 Compose *compose = (Compose *) data;
8304 gboolean can_sign = FALSE, can_encrypt = FALSE;
8306 cm_return_if_fail(GTK_IS_CHECK_MENU_ITEM(widget));
8308 if (!gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget)))
8311 systemid = g_object_get_data(G_OBJECT(widget), "privacy_system");
8312 g_free(compose->privacy_system);
8313 compose->privacy_system = NULL;
8314 g_free(compose->encdata);
8315 compose->encdata = NULL;
8316 if (systemid != NULL) {
8317 compose->privacy_system = g_strdup(systemid);
8319 can_sign = privacy_system_can_sign(systemid);
8320 can_encrypt = privacy_system_can_encrypt(systemid);
8323 debug_print("activated privacy system: %s\n", systemid != NULL ? systemid : "None");
8325 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8326 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8329 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn)
8331 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8332 GtkWidget *menuitem = NULL;
8333 GList *children, *amenu;
8334 gboolean can_sign = FALSE, can_encrypt = FALSE;
8335 gboolean found = FALSE;
8337 if (compose->privacy_system != NULL) {
8339 menuitem = gtk_menu_item_get_submenu(GTK_MENU_ITEM(
8340 gtk_ui_manager_get_widget(compose->ui_manager, branch_path)));
8341 cm_return_if_fail(menuitem != NULL);
8343 children = gtk_container_get_children(GTK_CONTAINER(GTK_MENU_SHELL(menuitem)));
8346 while (amenu != NULL) {
8347 systemid = g_object_get_data(G_OBJECT(amenu->data), "privacy_system");
8348 if (systemid != NULL) {
8349 if (strcmp(systemid, compose->privacy_system) == 0 &&
8350 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8351 menuitem = GTK_WIDGET(amenu->data);
8353 can_sign = privacy_system_can_sign(systemid);
8354 can_encrypt = privacy_system_can_encrypt(systemid);
8358 } else if (strlen(compose->privacy_system) == 0 &&
8359 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8360 menuitem = GTK_WIDGET(amenu->data);
8363 can_encrypt = FALSE;
8368 amenu = amenu->next;
8370 g_list_free(children);
8371 if (menuitem != NULL)
8372 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8374 if (warn && !found && strlen(compose->privacy_system)) {
8375 alertpanel_warning(_("The privacy system '%s' cannot be loaded. You "
8376 "will not be able to sign or encrypt this message."),
8377 compose->privacy_system);
8381 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8382 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8385 static void compose_set_out_encoding(Compose *compose)
8387 CharSet out_encoding;
8388 const gchar *branch = NULL;
8389 out_encoding = conv_get_charset_from_str(prefs_common.outgoing_charset);
8391 switch(out_encoding) {
8392 case C_AUTO: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8393 case C_US_ASCII: branch = "Menu/Options/Encoding/" CS_US_ASCII; break;
8394 case C_UTF_8: branch = "Menu/Options/Encoding/" CS_UTF_8; break;
8395 case C_ISO_8859_2: branch = "Menu/Options/Encoding/" CS_ISO_8859_2; break;
8396 case C_ISO_8859_7: branch = "Menu/Options/Encoding/" CS_ISO_8859_7; break;
8397 case C_ISO_8859_9: branch = "Menu/Options/Encoding/" CS_ISO_8859_9; break;
8398 case C_ISO_8859_1: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_1; break;
8399 case C_ISO_8859_15: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_15; break;
8400 case C_WINDOWS_1252: branch = "Menu/Options/Encoding/Western/" CS_WINDOWS_1252; break;
8401 case C_ISO_8859_13: branch = "Menu/Options/Encoding/Baltic/" CS_ISO_8859_13; break;
8402 case C_ISO_8859_4: branch = "Menu/Options/Encoding/Baltic" CS_ISO_8859_4; break;
8403 case C_ISO_8859_8: branch = "Menu/Options/Encoding/Hebrew/" CS_ISO_8859_8; break;
8404 case C_WINDOWS_1255: branch = "Menu/Options/Encoding/Hebrew/" CS_WINDOWS_1255; break;
8405 case C_ISO_8859_6: branch = "Menu/Options/Encoding/Arabic/" CS_ISO_8859_6; break;
8406 case C_WINDOWS_1256: branch = "Menu/Options/Encoding/Arabic/" CS_WINDOWS_1256; break;
8407 case C_ISO_8859_5: branch = "Menu/Options/Encoding/Cyrillic/" CS_ISO_8859_5; break;
8408 case C_KOI8_R: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_R; break;
8409 case C_MACCYR: branch = "Menu/Options/Encoding/Cyrillic/" CS_MACCYR; break;
8410 case C_KOI8_U: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_U; break;
8411 case C_WINDOWS_1251: branch = "Menu/Options/Encoding/Cyrillic/" CS_WINDOWS_1251; break;
8412 case C_ISO_2022_JP: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP; break;
8413 case C_ISO_2022_JP_2: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP_2; break;
8414 case C_EUC_JP: branch = "Menu/Options/Encoding/Japanese/" CS_EUC_JP; break;
8415 case C_SHIFT_JIS: branch = "Menu/Options/Encoding/Japanese/" CS_SHIFT_JIS; break;
8416 case C_GB18030: branch = "Menu/Options/Encoding/Chinese/" CS_GB18030; break;
8417 case C_GB2312: branch = "Menu/Options/Encoding/Chinese/" CS_GB2312; break;
8418 case C_GBK: branch = "Menu/Options/Encoding/Chinese/" CS_GBK; break;
8419 case C_BIG5: branch = "Menu/Options/Encoding/Chinese/" CS_BIG5; break;
8420 case C_EUC_TW: branch = "Menu/Options/Encoding/Chinese/" CS_EUC_TW; break;
8421 case C_EUC_KR: branch = "Menu/Options/Encoding/Korean/" CS_EUC_KR; break;
8422 case C_ISO_2022_KR: branch = "Menu/Options/Encoding/Korean/" CS_ISO_2022_KR; break;
8423 case C_TIS_620: branch = "Menu/Options/Encoding/Thai/" CS_TIS_620; break;
8424 case C_WINDOWS_874: branch = "Menu/Options/Encoding/Thai/" CS_WINDOWS_874; break;
8425 default: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8427 cm_toggle_menu_set_active_full(compose->ui_manager, (gchar *)branch, TRUE);
8430 static void compose_set_template_menu(Compose *compose)
8432 GSList *tmpl_list, *cur;
8436 tmpl_list = template_get_config();
8438 menu = gtk_menu_new();
8440 gtk_menu_set_accel_group (GTK_MENU (menu),
8441 gtk_ui_manager_get_accel_group(compose->ui_manager));
8442 for (cur = tmpl_list; cur != NULL; cur = cur->next) {
8443 Template *tmpl = (Template *)cur->data;
8444 gchar *accel_path = NULL;
8445 item = gtk_menu_item_new_with_label(tmpl->name);
8446 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
8447 g_signal_connect(G_OBJECT(item), "activate",
8448 G_CALLBACK(compose_template_activate_cb),
8450 g_object_set_data(G_OBJECT(item), "template", tmpl);
8451 gtk_widget_show(item);
8452 accel_path = g_strconcat("<ComposeTemplates>" , "/", tmpl->name, NULL);
8453 gtk_menu_item_set_accel_path(GTK_MENU_ITEM(item), accel_path);
8457 gtk_widget_show(menu);
8458 gtk_menu_item_set_submenu(GTK_MENU_ITEM(compose->tmpl_menu), menu);
8461 void compose_update_actions_menu(Compose *compose)
8463 action_update_compose_menu(compose->ui_manager, "/Menu/Tools/Actions", compose);
8466 static void compose_update_privacy_systems_menu(Compose *compose)
8468 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8469 GSList *systems, *cur;
8471 GtkWidget *system_none;
8473 GtkWidget *privacy_menuitem = gtk_ui_manager_get_widget(compose->ui_manager, branch_path);
8474 GtkWidget *privacy_menu = gtk_menu_new();
8476 system_none = gtk_radio_menu_item_new_with_mnemonic(NULL, _("_None"));
8477 g_object_set_data_full(G_OBJECT(system_none), "privacy_system", NULL, NULL);
8479 g_signal_connect(G_OBJECT(system_none), "activate",
8480 G_CALLBACK(compose_set_privacy_system_cb), compose);
8482 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), system_none);
8483 gtk_widget_show(system_none);
8485 systems = privacy_get_system_ids();
8486 for (cur = systems; cur != NULL; cur = g_slist_next(cur)) {
8487 gchar *systemid = cur->data;
8489 group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(system_none));
8490 widget = gtk_radio_menu_item_new_with_label(group,
8491 privacy_system_get_name(systemid));
8492 g_object_set_data_full(G_OBJECT(widget), "privacy_system",
8493 g_strdup(systemid), g_free);
8494 g_signal_connect(G_OBJECT(widget), "activate",
8495 G_CALLBACK(compose_set_privacy_system_cb), compose);
8497 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), widget);
8498 gtk_widget_show(widget);
8501 g_slist_free(systems);
8502 gtk_menu_item_set_submenu(GTK_MENU_ITEM(privacy_menuitem), privacy_menu);
8503 gtk_widget_show_all(privacy_menu);
8504 gtk_widget_show_all(privacy_menuitem);
8507 void compose_reflect_prefs_all(void)
8512 for (cur = compose_list; cur != NULL; cur = cur->next) {
8513 compose = (Compose *)cur->data;
8514 compose_set_template_menu(compose);
8518 void compose_reflect_prefs_pixmap_theme(void)
8523 for (cur = compose_list; cur != NULL; cur = cur->next) {
8524 compose = (Compose *)cur->data;
8525 toolbar_update(TOOLBAR_COMPOSE, compose);
8529 static const gchar *compose_quote_char_from_context(Compose *compose)
8531 const gchar *qmark = NULL;
8533 cm_return_val_if_fail(compose != NULL, NULL);
8535 switch (compose->mode) {
8536 /* use forward-specific quote char */
8537 case COMPOSE_FORWARD:
8538 case COMPOSE_FORWARD_AS_ATTACH:
8539 case COMPOSE_FORWARD_INLINE:
8540 if (compose->folder && compose->folder->prefs &&
8541 compose->folder->prefs->forward_with_format)
8542 qmark = compose->folder->prefs->forward_quotemark;
8543 else if (compose->account->forward_with_format)
8544 qmark = compose->account->forward_quotemark;
8546 qmark = prefs_common.fw_quotemark;
8549 /* use reply-specific quote char in all other modes */
8551 if (compose->folder && compose->folder->prefs &&
8552 compose->folder->prefs->reply_with_format)
8553 qmark = compose->folder->prefs->reply_quotemark;
8554 else if (compose->account->reply_with_format)
8555 qmark = compose->account->reply_quotemark;
8557 qmark = prefs_common.quotemark;
8561 if (qmark == NULL || *qmark == '\0')
8567 static void compose_template_apply(Compose *compose, Template *tmpl,
8571 GtkTextBuffer *buffer;
8575 gchar *parsed_str = NULL;
8576 gint cursor_pos = 0;
8577 const gchar *err_msg = _("The body of the template has an error at line %d.");
8580 /* process the body */
8582 text = GTK_TEXT_VIEW(compose->text);
8583 buffer = gtk_text_view_get_buffer(text);
8586 qmark = compose_quote_char_from_context(compose);
8588 if (compose->replyinfo != NULL) {
8591 gtk_text_buffer_set_text(buffer, "", -1);
8592 mark = gtk_text_buffer_get_insert(buffer);
8593 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8595 parsed_str = compose_quote_fmt(compose, compose->replyinfo,
8596 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8598 } else if (compose->fwdinfo != NULL) {
8601 gtk_text_buffer_set_text(buffer, "", -1);
8602 mark = gtk_text_buffer_get_insert(buffer);
8603 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8605 parsed_str = compose_quote_fmt(compose, compose->fwdinfo,
8606 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8609 MsgInfo* dummyinfo = compose_msginfo_new_from_compose(compose);
8611 GtkTextIter start, end;
8614 gtk_text_buffer_get_start_iter(buffer, &start);
8615 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
8616 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
8618 /* clear the buffer now */
8620 gtk_text_buffer_set_text(buffer, "", -1);
8622 parsed_str = compose_quote_fmt(compose, dummyinfo,
8623 tmpl->value, qmark, tmp, FALSE, FALSE, err_msg);
8624 procmsg_msginfo_free( &dummyinfo );
8630 gtk_text_buffer_set_text(buffer, "", -1);
8631 mark = gtk_text_buffer_get_insert(buffer);
8632 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8635 if (replace && parsed_str && compose->account->auto_sig)
8636 compose_insert_sig(compose, FALSE);
8638 if (replace && parsed_str) {
8639 gtk_text_buffer_get_start_iter(buffer, &iter);
8640 gtk_text_buffer_place_cursor(buffer, &iter);
8644 cursor_pos = quote_fmt_get_cursor_pos();
8645 compose->set_cursor_pos = cursor_pos;
8646 if (cursor_pos == -1)
8648 gtk_text_buffer_get_start_iter(buffer, &iter);
8649 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
8650 gtk_text_buffer_place_cursor(buffer, &iter);
8653 /* process the other fields */
8655 compose_template_apply_fields(compose, tmpl);
8656 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
8657 quote_fmt_reset_vartable();
8658 compose_changed_cb(NULL, compose);
8661 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
8662 gtkaspell_highlight_all(compose->gtkaspell);
8666 static void compose_template_apply_fields_error(const gchar *header)
8671 tr = g_strdup(C_("'%s' stands for a header name",
8672 "Template '%s' format error."));
8673 text = g_strdup_printf(tr, prefs_common_translated_header_name(header));
8674 alertpanel_error(text);
8680 static void compose_template_apply_fields(Compose *compose, Template *tmpl)
8682 MsgInfo* dummyinfo = NULL;
8683 MsgInfo *msginfo = NULL;
8686 if (compose->replyinfo != NULL)
8687 msginfo = compose->replyinfo;
8688 else if (compose->fwdinfo != NULL)
8689 msginfo = compose->fwdinfo;
8691 dummyinfo = compose_msginfo_new_from_compose(compose);
8692 msginfo = dummyinfo;
8695 if (tmpl->from && *tmpl->from != '\0') {
8697 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8698 compose->gtkaspell);
8700 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8702 quote_fmt_scan_string(tmpl->from);
8705 buf = quote_fmt_get_buffer();
8707 compose_template_apply_fields_error("From");
8709 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
8713 if (tmpl->to && *tmpl->to != '\0') {
8715 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8716 compose->gtkaspell);
8718 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8720 quote_fmt_scan_string(tmpl->to);
8723 buf = quote_fmt_get_buffer();
8725 compose_template_apply_fields_error("To");
8727 compose_entry_append(compose, buf, COMPOSE_TO, PREF_TEMPLATE);
8731 if (tmpl->cc && *tmpl->cc != '\0') {
8733 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8734 compose->gtkaspell);
8736 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8738 quote_fmt_scan_string(tmpl->cc);
8741 buf = quote_fmt_get_buffer();
8743 compose_template_apply_fields_error("Cc");
8745 compose_entry_append(compose, buf, COMPOSE_CC, PREF_TEMPLATE);
8749 if (tmpl->bcc && *tmpl->bcc != '\0') {
8751 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8752 compose->gtkaspell);
8754 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8756 quote_fmt_scan_string(tmpl->bcc);
8759 buf = quote_fmt_get_buffer();
8761 compose_template_apply_fields_error("Bcc");
8763 compose_entry_append(compose, buf, COMPOSE_BCC, PREF_TEMPLATE);
8767 if (tmpl->replyto && *tmpl->replyto != '\0') {
8769 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8770 compose->gtkaspell);
8772 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8774 quote_fmt_scan_string(tmpl->replyto);
8777 buf = quote_fmt_get_buffer();
8779 compose_template_apply_fields_error("Reply-To");
8781 compose_entry_append(compose, buf, COMPOSE_REPLYTO, PREF_TEMPLATE);
8785 /* process the subject */
8786 if (tmpl->subject && *tmpl->subject != '\0') {
8788 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8789 compose->gtkaspell);
8791 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8793 quote_fmt_scan_string(tmpl->subject);
8796 buf = quote_fmt_get_buffer();
8798 compose_template_apply_fields_error("Subject");
8800 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
8804 procmsg_msginfo_free( &dummyinfo );
8807 static void compose_destroy(Compose *compose)
8809 GtkAllocation allocation;
8810 GtkTextBuffer *buffer;
8811 GtkClipboard *clipboard;
8813 compose_list = g_list_remove(compose_list, compose);
8815 if (compose->updating) {
8816 debug_print("danger, not destroying anything now\n");
8817 compose->deferred_destroy = TRUE;
8821 /* NOTE: address_completion_end() does nothing with the window
8822 * however this may change. */
8823 address_completion_end(compose->window);
8825 slist_free_strings_full(compose->to_list);
8826 slist_free_strings_full(compose->newsgroup_list);
8827 slist_free_strings_full(compose->header_list);
8829 slist_free_strings_full(extra_headers);
8830 extra_headers = NULL;
8832 compose->header_list = compose->newsgroup_list = compose->to_list = NULL;
8834 g_hash_table_destroy(compose->email_hashtable);
8836 hooks_unregister_hook(FOLDER_UPDATE_HOOKLIST,
8837 compose->folder_update_callback_id);
8839 procmsg_msginfo_free(&(compose->targetinfo));
8840 procmsg_msginfo_free(&(compose->replyinfo));
8841 procmsg_msginfo_free(&(compose->fwdinfo));
8843 g_free(compose->replyto);
8844 g_free(compose->cc);
8845 g_free(compose->bcc);
8846 g_free(compose->newsgroups);
8847 g_free(compose->followup_to);
8849 g_free(compose->ml_post);
8851 g_free(compose->inreplyto);
8852 g_free(compose->references);
8853 g_free(compose->msgid);
8854 g_free(compose->boundary);
8856 g_free(compose->redirect_filename);
8857 if (compose->undostruct)
8858 undo_destroy(compose->undostruct);
8860 g_free(compose->sig_str);
8862 g_free(compose->exteditor_file);
8864 g_free(compose->orig_charset);
8866 g_free(compose->privacy_system);
8867 g_free(compose->encdata);
8869 #ifndef USE_ALT_ADDRBOOK
8870 if (addressbook_get_target_compose() == compose)
8871 addressbook_set_target_compose(NULL);
8874 if (compose->gtkaspell) {
8875 gtkaspell_delete(compose->gtkaspell);
8876 compose->gtkaspell = NULL;
8880 if (!compose->batch) {
8881 gtk_widget_get_allocation(compose->window, &allocation);
8882 prefs_common.compose_width = allocation.width;
8883 prefs_common.compose_height = allocation.height;
8886 if (!gtk_widget_get_parent(compose->paned))
8887 gtk_widget_destroy(compose->paned);
8888 gtk_widget_destroy(compose->popupmenu);
8890 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
8891 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
8892 gtk_text_buffer_remove_selection_clipboard(buffer, clipboard);
8894 gtk_widget_destroy(compose->window);
8895 toolbar_destroy(compose->toolbar);
8896 g_free(compose->toolbar);
8897 cm_mutex_free(compose->mutex);
8901 static void compose_attach_info_free(AttachInfo *ainfo)
8903 g_free(ainfo->file);
8904 g_free(ainfo->content_type);
8905 g_free(ainfo->name);
8906 g_free(ainfo->charset);
8910 static void compose_attach_update_label(Compose *compose)
8915 GtkTreeModel *model;
8920 model = gtk_tree_view_get_model(GTK_TREE_VIEW(compose->attach_clist));
8921 if(!gtk_tree_model_get_iter_first(model, &iter)) {
8922 gtk_label_set_text(GTK_LABEL(compose->attach_label), "");
8926 while(gtk_tree_model_iter_next(model, &iter))
8929 text = g_strdup_printf("(%d)", i);
8930 gtk_label_set_text(GTK_LABEL(compose->attach_label), text);
8934 static void compose_attach_remove_selected(GtkAction *action, gpointer data)
8936 Compose *compose = (Compose *)data;
8937 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
8938 GtkTreeSelection *selection;
8940 GtkTreeModel *model;
8942 selection = gtk_tree_view_get_selection(tree_view);
8943 sel = gtk_tree_selection_get_selected_rows(selection, &model);
8948 for (cur = sel; cur != NULL; cur = cur->next) {
8949 GtkTreePath *path = cur->data;
8950 GtkTreeRowReference *ref = gtk_tree_row_reference_new
8953 gtk_tree_path_free(path);
8956 for (cur = sel; cur != NULL; cur = cur->next) {
8957 GtkTreeRowReference *ref = cur->data;
8958 GtkTreePath *path = gtk_tree_row_reference_get_path(ref);
8961 if (gtk_tree_model_get_iter(model, &iter, path))
8962 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
8964 gtk_tree_path_free(path);
8965 gtk_tree_row_reference_free(ref);
8969 compose_attach_update_label(compose);
8972 static struct _AttachProperty
8975 GtkWidget *mimetype_entry;
8976 GtkWidget *encoding_optmenu;
8977 GtkWidget *path_entry;
8978 GtkWidget *filename_entry;
8980 GtkWidget *cancel_btn;
8983 static void gtk_tree_path_free_(gpointer ptr, gpointer data)
8985 gtk_tree_path_free((GtkTreePath *)ptr);
8988 static void compose_attach_property(GtkAction *action, gpointer data)
8990 Compose *compose = (Compose *)data;
8991 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
8993 GtkComboBox *optmenu;
8994 GtkTreeSelection *selection;
8996 GtkTreeModel *model;
8999 static gboolean cancelled;
9001 /* only if one selected */
9002 selection = gtk_tree_view_get_selection(tree_view);
9003 if (gtk_tree_selection_count_selected_rows(selection) != 1)
9006 sel = gtk_tree_selection_get_selected_rows(selection, &model);
9010 path = (GtkTreePath *) sel->data;
9011 gtk_tree_model_get_iter(model, &iter, path);
9012 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
9015 g_list_foreach(sel, gtk_tree_path_free_, NULL);
9021 if (!attach_prop.window)
9022 compose_attach_property_create(&cancelled);
9023 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), TRUE);
9024 gtk_widget_grab_focus(attach_prop.ok_btn);
9025 gtk_widget_show(attach_prop.window);
9026 gtk_window_set_transient_for(GTK_WINDOW(attach_prop.window),
9027 GTK_WINDOW(compose->window));
9029 optmenu = GTK_COMBO_BOX(attach_prop.encoding_optmenu);
9030 if (ainfo->encoding == ENC_UNKNOWN)
9031 combobox_select_by_data(optmenu, ENC_BASE64);
9033 combobox_select_by_data(optmenu, ainfo->encoding);
9035 gtk_entry_set_text(GTK_ENTRY(attach_prop.mimetype_entry),
9036 ainfo->content_type ? ainfo->content_type : "");
9037 gtk_entry_set_text(GTK_ENTRY(attach_prop.path_entry),
9038 ainfo->file ? ainfo->file : "");
9039 gtk_entry_set_text(GTK_ENTRY(attach_prop.filename_entry),
9040 ainfo->name ? ainfo->name : "");
9043 const gchar *entry_text;
9045 gchar *cnttype = NULL;
9052 gtk_widget_hide(attach_prop.window);
9053 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), FALSE);
9058 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.mimetype_entry));
9059 if (*entry_text != '\0') {
9062 text = g_strstrip(g_strdup(entry_text));
9063 if ((p = strchr(text, '/')) && !strchr(p + 1, '/')) {
9064 cnttype = g_strdup(text);
9067 alertpanel_error(_("Invalid MIME type."));
9073 ainfo->encoding = combobox_get_active_data(optmenu);
9075 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.path_entry));
9076 if (*entry_text != '\0') {
9077 if (is_file_exist(entry_text) &&
9078 (size = get_file_size(entry_text)) > 0)
9079 file = g_strdup(entry_text);
9082 (_("File doesn't exist or is empty."));
9088 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.filename_entry));
9089 if (*entry_text != '\0') {
9090 g_free(ainfo->name);
9091 ainfo->name = g_strdup(entry_text);
9095 g_free(ainfo->content_type);
9096 ainfo->content_type = cnttype;
9099 g_free(ainfo->file);
9103 ainfo->size = (goffset)size;
9105 /* update tree store */
9106 text = to_human_readable(ainfo->size);
9107 gtk_tree_model_get_iter(model, &iter, path);
9108 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
9109 COL_MIMETYPE, ainfo->content_type,
9111 COL_NAME, ainfo->name,
9112 COL_CHARSET, ainfo->charset,
9118 gtk_tree_path_free(path);
9121 #define SET_LABEL_AND_ENTRY(str, entry, top) \
9123 label = gtk_label_new(str); \
9124 gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), \
9125 GTK_FILL, 0, 0, 0); \
9126 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); \
9128 entry = gtk_entry_new(); \
9129 gtk_table_attach(GTK_TABLE(table), entry, 1, 2, top, (top + 1), \
9130 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); \
9133 static void compose_attach_property_create(gboolean *cancelled)
9139 GtkWidget *mimetype_entry;
9142 GtkListStore *optmenu_menu;
9143 GtkWidget *path_entry;
9144 GtkWidget *filename_entry;
9147 GtkWidget *cancel_btn;
9148 GList *mime_type_list, *strlist;
9151 debug_print("Creating attach_property window...\n");
9153 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose_attach_property");
9154 gtk_widget_set_size_request(window, 480, -1);
9155 gtk_container_set_border_width(GTK_CONTAINER(window), 8);
9156 gtk_window_set_title(GTK_WINDOW(window), _("Properties"));
9157 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
9158 g_signal_connect(G_OBJECT(window), "delete_event",
9159 G_CALLBACK(attach_property_delete_event),
9161 g_signal_connect(G_OBJECT(window), "key_press_event",
9162 G_CALLBACK(attach_property_key_pressed),
9165 vbox = gtk_vbox_new(FALSE, 8);
9166 gtk_container_add(GTK_CONTAINER(window), vbox);
9168 table = gtk_table_new(4, 2, FALSE);
9169 gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
9170 gtk_table_set_row_spacings(GTK_TABLE(table), 8);
9171 gtk_table_set_col_spacings(GTK_TABLE(table), 8);
9173 label = gtk_label_new(_("MIME type"));
9174 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, (0 + 1),
9176 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
9177 #if !GTK_CHECK_VERSION(2, 24, 0)
9178 mimetype_entry = gtk_combo_box_entry_new_text();
9180 mimetype_entry = gtk_combo_box_text_new_with_entry();
9182 gtk_table_attach(GTK_TABLE(table), mimetype_entry, 1, 2, 0, (0 + 1),
9183 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
9185 /* stuff with list */
9186 mime_type_list = procmime_get_mime_type_list();
9188 for (; mime_type_list != NULL; mime_type_list = mime_type_list->next) {
9189 MimeType *type = (MimeType *) mime_type_list->data;
9192 tmp = g_strdup_printf("%s/%s", type->type, type->sub_type);
9194 if (g_list_find_custom(strlist, tmp, (GCompareFunc)strcmp2))
9197 strlist = g_list_insert_sorted(strlist, (gpointer)tmp,
9198 (GCompareFunc)strcmp2);
9201 for (mime_type_list = strlist; mime_type_list != NULL;
9202 mime_type_list = mime_type_list->next) {
9203 #if !GTK_CHECK_VERSION(2, 24, 0)
9204 gtk_combo_box_append_text(GTK_COMBO_BOX(mimetype_entry), mime_type_list->data);
9206 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(mimetype_entry), mime_type_list->data);
9208 g_free(mime_type_list->data);
9210 g_list_free(strlist);
9211 gtk_combo_box_set_active(GTK_COMBO_BOX(mimetype_entry), 0);
9212 mimetype_entry = gtk_bin_get_child(GTK_BIN((mimetype_entry)));
9214 label = gtk_label_new(_("Encoding"));
9215 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2,
9217 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
9219 hbox = gtk_hbox_new(FALSE, 0);
9220 gtk_table_attach(GTK_TABLE(table), hbox, 1, 2, 1, 2,
9221 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
9223 optmenu = gtkut_sc_combobox_create(NULL, TRUE);
9224 optmenu_menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
9226 COMBOBOX_ADD(optmenu_menu, "7bit", ENC_7BIT);
9227 COMBOBOX_ADD(optmenu_menu, "8bit", ENC_8BIT);
9228 COMBOBOX_ADD(optmenu_menu, "quoted-printable", ENC_QUOTED_PRINTABLE);
9229 COMBOBOX_ADD(optmenu_menu, "base64", ENC_BASE64);
9230 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), 0);
9232 gtk_box_pack_start(GTK_BOX(hbox), optmenu, TRUE, TRUE, 0);
9234 SET_LABEL_AND_ENTRY(_("Path"), path_entry, 2);
9235 SET_LABEL_AND_ENTRY(_("File name"), filename_entry, 3);
9237 gtkut_stock_button_set_create(&hbbox, &cancel_btn, GTK_STOCK_CANCEL,
9238 &ok_btn, GTK_STOCK_OK,
9240 gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
9241 gtk_widget_grab_default(ok_btn);
9243 g_signal_connect(G_OBJECT(ok_btn), "clicked",
9244 G_CALLBACK(attach_property_ok),
9246 g_signal_connect(G_OBJECT(cancel_btn), "clicked",
9247 G_CALLBACK(attach_property_cancel),
9250 gtk_widget_show_all(vbox);
9252 attach_prop.window = window;
9253 attach_prop.mimetype_entry = mimetype_entry;
9254 attach_prop.encoding_optmenu = optmenu;
9255 attach_prop.path_entry = path_entry;
9256 attach_prop.filename_entry = filename_entry;
9257 attach_prop.ok_btn = ok_btn;
9258 attach_prop.cancel_btn = cancel_btn;
9261 #undef SET_LABEL_AND_ENTRY
9263 static void attach_property_ok(GtkWidget *widget, gboolean *cancelled)
9269 static void attach_property_cancel(GtkWidget *widget, gboolean *cancelled)
9275 static gint attach_property_delete_event(GtkWidget *widget, GdkEventAny *event,
9276 gboolean *cancelled)
9284 static gboolean attach_property_key_pressed(GtkWidget *widget,
9286 gboolean *cancelled)
9288 if (event && event->keyval == GDK_KEY_Escape) {
9292 if (event && event->keyval == GDK_KEY_Return) {
9300 static void compose_exec_ext_editor(Compose *compose)
9305 GdkNativeWindow socket_wid = 0;
9309 tmp = g_strdup_printf("%s%ctmpmsg.%p", get_tmp_dir(),
9310 G_DIR_SEPARATOR, compose);
9312 if (compose_get_ext_editor_uses_socket()) {
9313 /* Only allow one socket */
9314 if (compose->exteditor_socket != NULL) {
9315 if (gtk_widget_is_focus(compose->exteditor_socket)) {
9316 /* Move the focus off of the socket */
9317 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9322 /* Create the receiving GtkSocket */
9323 socket = gtk_socket_new ();
9324 g_signal_connect (GTK_OBJECT(socket), "plug-removed",
9325 G_CALLBACK(compose_ext_editor_plug_removed_cb),
9327 gtk_box_pack_start(GTK_BOX(compose->edit_vbox), socket, TRUE, TRUE, 0);
9328 gtk_widget_set_size_request(socket, prefs_common.compose_width, -1);
9329 /* Realize the socket so that we can use its ID */
9330 gtk_widget_realize(socket);
9331 socket_wid = gtk_socket_get_id(GTK_SOCKET (socket));
9332 compose->exteditor_socket = socket;
9335 if (pipe(pipe_fds) < 0) {
9341 if ((pid = fork()) < 0) {
9348 /* close the write side of the pipe */
9351 compose->exteditor_file = g_strdup(tmp);
9352 compose->exteditor_pid = pid;
9354 compose_set_ext_editor_sensitive(compose, FALSE);
9357 compose->exteditor_ch = g_io_channel_unix_new(pipe_fds[0]);
9359 compose->exteditor_ch = g_io_channel_win32_new_fd(pipe_fds[0]);
9361 compose->exteditor_tag = g_io_add_watch(compose->exteditor_ch,
9365 } else { /* process-monitoring process */
9371 /* close the read side of the pipe */
9374 if (compose_write_body_to_file(compose, tmp) < 0) {
9375 fd_write_all(pipe_fds[1], "2\n", 2);
9379 pid_ed = compose_exec_ext_editor_real(tmp, socket_wid);
9381 fd_write_all(pipe_fds[1], "1\n", 2);
9385 /* wait until editor is terminated */
9386 waitpid(pid_ed, NULL, 0);
9388 fd_write_all(pipe_fds[1], "0\n", 2);
9395 #endif /* G_OS_UNIX */
9398 static gboolean compose_can_autosave(Compose *compose)
9400 if (compose->privacy_system && compose->use_encryption)
9401 return prefs_common.autosave && prefs_common.autosave_encrypted;
9403 return prefs_common.autosave;
9407 static gboolean compose_get_ext_editor_cmd_valid()
9409 gboolean has_s = FALSE;
9410 gboolean has_w = FALSE;
9411 const gchar *p = prefs_common_get_ext_editor_cmd();
9414 while ((p = strchr(p, '%'))) {
9420 } else if (*p == 'w') {
9431 static gint compose_exec_ext_editor_real(const gchar *file, GdkNativeWindow socket_wid)
9438 cm_return_val_if_fail(file != NULL, -1);
9440 if ((pid = fork()) < 0) {
9445 if (pid != 0) return pid;
9447 /* grandchild process */
9449 if (setpgid(0, getppid()))
9452 if (compose_get_ext_editor_cmd_valid()) {
9453 if (compose_get_ext_editor_uses_socket()) {
9454 p = g_strdup(prefs_common_get_ext_editor_cmd());
9455 s = strstr(p, "%w");
9457 if (strstr(p, "%s") < s)
9458 g_snprintf(buf, sizeof(buf), p, file, socket_wid);
9460 g_snprintf(buf, sizeof(buf), p, socket_wid, file);
9463 g_snprintf(buf, sizeof(buf),
9464 prefs_common_get_ext_editor_cmd(), file);
9467 if (prefs_common_get_ext_editor_cmd())
9468 g_warning("External editor command-line is invalid: '%s'",
9469 prefs_common_get_ext_editor_cmd());
9470 g_snprintf(buf, sizeof(buf), DEFAULT_EDITOR_CMD, file);
9473 cmdline = strsplit_with_quote(buf, " ", 1024);
9474 execvp(cmdline[0], cmdline);
9477 g_strfreev(cmdline);
9482 static gboolean compose_ext_editor_kill(Compose *compose)
9484 pid_t pgid = compose->exteditor_pid * -1;
9487 ret = kill(pgid, 0);
9489 if (ret == 0 || (ret == -1 && EPERM == errno)) {
9493 msg = g_strdup_printf
9494 (_("The external editor is still working.\n"
9495 "Force terminating the process?\n"
9496 "process group id: %d"), -pgid);
9497 val = alertpanel_full(_("Notice"), msg, GTK_STOCK_NO, GTK_STOCK_YES,
9498 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
9502 if (val == G_ALERTALTERNATE) {
9503 g_source_remove(compose->exteditor_tag);
9504 g_io_channel_shutdown(compose->exteditor_ch,
9506 g_io_channel_unref(compose->exteditor_ch);
9508 if (kill(pgid, SIGTERM) < 0) perror("kill");
9509 waitpid(compose->exteditor_pid, NULL, 0);
9511 g_warning("Terminated process group id: %d. "
9512 "Temporary file: %s", -pgid, compose->exteditor_file);
9514 compose_set_ext_editor_sensitive(compose, TRUE);
9516 g_free(compose->exteditor_file);
9517 compose->exteditor_file = NULL;
9518 compose->exteditor_pid = -1;
9519 compose->exteditor_ch = NULL;
9520 compose->exteditor_tag = -1;
9528 static gboolean compose_input_cb(GIOChannel *source, GIOCondition condition,
9532 Compose *compose = (Compose *)data;
9535 debug_print("Compose: input from monitoring process\n");
9537 if (g_io_channel_read_chars(source, buf, sizeof(buf), &bytes_read, NULL) != G_IO_STATUS_NORMAL) {
9542 g_io_channel_shutdown(source, FALSE, NULL);
9543 g_io_channel_unref(source);
9545 waitpid(compose->exteditor_pid, NULL, 0);
9547 if (buf[0] == '0') { /* success */
9548 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9549 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
9550 GtkTextIter start, end;
9553 gtk_text_buffer_set_text(buffer, "", -1);
9554 compose_insert_file(compose, compose->exteditor_file);
9555 compose_changed_cb(NULL, compose);
9557 /* Check if we should save the draft or not */
9558 if (compose_can_autosave(compose))
9559 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
9561 if (claws_unlink(compose->exteditor_file) < 0)
9562 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9564 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
9565 gtk_text_buffer_get_start_iter(buffer, &start);
9566 gtk_text_buffer_get_end_iter(buffer, &end);
9567 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
9568 if (chars && strlen(chars) > 0)
9569 compose->modified = TRUE;
9571 } else if (buf[0] == '1') { /* failed */
9572 g_warning("Couldn't exec external editor");
9573 if (claws_unlink(compose->exteditor_file) < 0)
9574 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9575 } else if (buf[0] == '2') {
9576 g_warning("Couldn't write to file");
9577 } else if (buf[0] == '3') {
9578 g_warning("Pipe read failed");
9581 compose_set_ext_editor_sensitive(compose, TRUE);
9583 g_free(compose->exteditor_file);
9584 compose->exteditor_file = NULL;
9585 compose->exteditor_pid = -1;
9586 compose->exteditor_ch = NULL;
9587 compose->exteditor_tag = -1;
9588 if (compose->exteditor_socket) {
9589 gtk_widget_destroy(compose->exteditor_socket);
9590 compose->exteditor_socket = NULL;
9597 static char *ext_editor_menu_entries[] = {
9598 "Menu/Message/Send",
9599 "Menu/Message/SendLater",
9600 "Menu/Message/InsertFile",
9601 "Menu/Message/InsertSig",
9602 "Menu/Message/ReplaceSig",
9603 "Menu/Message/Save",
9604 "Menu/Message/Print",
9609 "Menu/Tools/ShowRuler",
9610 "Menu/Tools/Actions",
9615 static void compose_set_ext_editor_sensitive(Compose *compose,
9620 for (i = 0; ext_editor_menu_entries[i]; ++i) {
9621 cm_menu_set_sensitive_full(compose->ui_manager,
9622 ext_editor_menu_entries[i], sensitive);
9625 if (compose_get_ext_editor_uses_socket()) {
9627 if (compose->exteditor_socket)
9628 gtk_widget_hide(compose->exteditor_socket);
9629 gtk_widget_show(compose->scrolledwin);
9630 if (prefs_common.show_ruler)
9631 gtk_widget_show(compose->ruler_hbox);
9632 /* Fix the focus, as it doesn't go anywhere when the
9633 * socket is hidden or destroyed */
9634 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9636 g_assert (compose->exteditor_socket != NULL);
9637 /* Fix the focus, as it doesn't go anywhere when the
9638 * edit box is hidden */
9639 if (gtk_widget_is_focus(compose->text))
9640 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9641 gtk_widget_hide(compose->scrolledwin);
9642 gtk_widget_hide(compose->ruler_hbox);
9643 gtk_widget_show(compose->exteditor_socket);
9646 gtk_widget_set_sensitive(compose->text, sensitive);
9648 if (compose->toolbar->send_btn)
9649 gtk_widget_set_sensitive(compose->toolbar->send_btn, sensitive);
9650 if (compose->toolbar->sendl_btn)
9651 gtk_widget_set_sensitive(compose->toolbar->sendl_btn, sensitive);
9652 if (compose->toolbar->draft_btn)
9653 gtk_widget_set_sensitive(compose->toolbar->draft_btn, sensitive);
9654 if (compose->toolbar->insert_btn)
9655 gtk_widget_set_sensitive(compose->toolbar->insert_btn, sensitive);
9656 if (compose->toolbar->sig_btn)
9657 gtk_widget_set_sensitive(compose->toolbar->sig_btn, sensitive);
9658 if (compose->toolbar->exteditor_btn)
9659 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, sensitive);
9660 if (compose->toolbar->linewrap_current_btn)
9661 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, sensitive);
9662 if (compose->toolbar->linewrap_all_btn)
9663 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, sensitive);
9666 static gboolean compose_get_ext_editor_uses_socket()
9668 return (prefs_common_get_ext_editor_cmd() &&
9669 strstr(prefs_common_get_ext_editor_cmd(), "%w"));
9672 static gboolean compose_ext_editor_plug_removed_cb(GtkSocket *socket, Compose *compose)
9674 compose->exteditor_socket = NULL;
9675 /* returning FALSE allows destruction of the socket */
9678 #endif /* G_OS_UNIX */
9681 * compose_undo_state_changed:
9683 * Change the sensivity of the menuentries undo and redo
9685 static void compose_undo_state_changed(UndoMain *undostruct, gint undo_state,
9686 gint redo_state, gpointer data)
9688 Compose *compose = (Compose *)data;
9690 switch (undo_state) {
9691 case UNDO_STATE_TRUE:
9692 if (!undostruct->undo_state) {
9693 undostruct->undo_state = TRUE;
9694 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", TRUE);
9697 case UNDO_STATE_FALSE:
9698 if (undostruct->undo_state) {
9699 undostruct->undo_state = FALSE;
9700 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
9703 case UNDO_STATE_UNCHANGED:
9705 case UNDO_STATE_REFRESH:
9706 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", undostruct->undo_state);
9709 g_warning("Undo state not recognized");
9713 switch (redo_state) {
9714 case UNDO_STATE_TRUE:
9715 if (!undostruct->redo_state) {
9716 undostruct->redo_state = TRUE;
9717 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", TRUE);
9720 case UNDO_STATE_FALSE:
9721 if (undostruct->redo_state) {
9722 undostruct->redo_state = FALSE;
9723 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
9726 case UNDO_STATE_UNCHANGED:
9728 case UNDO_STATE_REFRESH:
9729 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", undostruct->redo_state);
9732 g_warning("Redo state not recognized");
9737 /* callback functions */
9739 static void compose_notebook_size_alloc(GtkNotebook *notebook,
9740 GtkAllocation *allocation,
9743 prefs_common.compose_notebook_height = gtk_paned_get_position(paned);
9746 /* compose_edit_size_alloc() - called when resized. don't know whether Gtk
9747 * includes "non-client" (windows-izm) in calculation, so this calculation
9748 * may not be accurate.
9750 static gboolean compose_edit_size_alloc(GtkEditable *widget,
9751 GtkAllocation *allocation,
9752 GtkSHRuler *shruler)
9754 if (prefs_common.show_ruler) {
9755 gint char_width = 0, char_height = 0;
9756 gint line_width_in_chars;
9758 gtkut_get_font_size(GTK_WIDGET(widget),
9759 &char_width, &char_height);
9760 line_width_in_chars =
9761 (allocation->width - allocation->x) / char_width;
9763 /* got the maximum */
9764 gtk_shruler_set_range(GTK_SHRULER(shruler),
9765 0.0, line_width_in_chars, 0);
9774 ComposePrefType type;
9775 gboolean entry_marked;
9778 static void account_activated(GtkComboBox *optmenu, gpointer data)
9780 Compose *compose = (Compose *)data;
9783 gchar *folderidentifier;
9784 gint account_id = 0;
9787 GSList *list, *saved_list = NULL;
9788 HeaderEntryState *state;
9790 /* Get ID of active account in the combo box */
9791 menu = gtk_combo_box_get_model(optmenu);
9792 cm_return_if_fail(gtk_combo_box_get_active_iter(optmenu, &iter));
9793 gtk_tree_model_get(menu, &iter, 1, &account_id, -1);
9795 ac = account_find_from_id(account_id);
9796 cm_return_if_fail(ac != NULL);
9798 if (ac != compose->account) {
9799 compose_select_account(compose, ac, FALSE);
9801 for (list = compose->header_list; list; list = list->next) {
9802 ComposeHeaderEntry *hentry=(ComposeHeaderEntry *)list->data;
9804 if (hentry->type == PREF_ACCOUNT || !list->next) {
9805 compose_destroy_headerentry(compose, hentry);
9808 state = g_malloc0(sizeof(HeaderEntryState));
9809 state->header = gtk_editable_get_chars(GTK_EDITABLE(
9810 gtk_bin_get_child(GTK_BIN(hentry->combo))), 0, -1);
9811 state->entry = gtk_editable_get_chars(
9812 GTK_EDITABLE(hentry->entry), 0, -1);
9813 state->type = hentry->type;
9815 saved_list = g_slist_append(saved_list, state);
9816 compose_destroy_headerentry(compose, hentry);
9819 compose->header_last = NULL;
9820 g_slist_free(compose->header_list);
9821 compose->header_list = NULL;
9822 compose->header_nextrow = 1;
9823 compose_create_header_entry(compose);
9825 if (ac->set_autocc && ac->auto_cc)
9826 compose_entry_append(compose, ac->auto_cc,
9827 COMPOSE_CC, PREF_ACCOUNT);
9828 if (ac->set_autobcc && ac->auto_bcc)
9829 compose_entry_append(compose, ac->auto_bcc,
9830 COMPOSE_BCC, PREF_ACCOUNT);
9831 if (ac->set_autoreplyto && ac->auto_replyto)
9832 compose_entry_append(compose, ac->auto_replyto,
9833 COMPOSE_REPLYTO, PREF_ACCOUNT);
9835 for (list = saved_list; list; list = list->next) {
9836 state = (HeaderEntryState *) list->data;
9838 compose_add_header_entry(compose, state->header,
9839 state->entry, state->type);
9841 g_free(state->header);
9842 g_free(state->entry);
9845 g_slist_free(saved_list);
9847 combobox_select_by_data(GTK_COMBO_BOX(compose->header_last->combo),
9848 (ac->protocol == A_NNTP) ?
9849 COMPOSE_NEWSGROUPS : COMPOSE_TO);
9852 /* Set message save folder */
9853 if (account_get_special_folder(compose->account, F_OUTBOX)) {
9854 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
9856 g_signal_connect(G_OBJECT(compose->savemsg_checkbtn), "toggled",
9857 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
9859 compose_set_save_to(compose, NULL);
9860 if (account_get_special_folder(compose->account, F_OUTBOX)) {
9861 folderidentifier = folder_item_get_identifier(account_get_special_folder
9862 (compose->account, F_OUTBOX));
9863 compose_set_save_to(compose, folderidentifier);
9864 g_free(folderidentifier);
9868 static void attach_selected(GtkTreeView *tree_view, GtkTreePath *tree_path,
9869 GtkTreeViewColumn *column, Compose *compose)
9871 compose_attach_property(NULL, compose);
9874 static gboolean attach_button_pressed(GtkWidget *widget, GdkEventButton *event,
9877 Compose *compose = (Compose *)data;
9878 GtkTreeSelection *attach_selection;
9879 gint attach_nr_selected;
9882 if (!event) return FALSE;
9884 if (event->button == 3) {
9885 attach_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
9886 attach_nr_selected = gtk_tree_selection_count_selected_rows(attach_selection);
9888 /* If no rows, or just one row is selected, right-click should
9889 * open menu relevant to the row being right-clicked on. We
9890 * achieve that by selecting the clicked row first. If more
9891 * than one row is selected, we shouldn't modify the selection,
9892 * as user may want to remove selected rows (attachments). */
9893 if (attach_nr_selected < 2) {
9894 gtk_tree_selection_unselect_all(attach_selection);
9895 attach_nr_selected = 0;
9896 gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget),
9897 event->x, event->y, &path, NULL, NULL, NULL);
9899 gtk_tree_selection_select_path(attach_selection, path);
9900 gtk_tree_path_free(path);
9901 attach_nr_selected++;
9905 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", (attach_nr_selected > 0));
9906 /* Properties menu item makes no sense with more than one row
9907 * selected, the properties dialog can only edit one attachment. */
9908 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", (attach_nr_selected == 1));
9910 gtk_menu_popup(GTK_MENU(compose->popupmenu), NULL, NULL,
9911 NULL, NULL, event->button, event->time);
9918 static gboolean attach_key_pressed(GtkWidget *widget, GdkEventKey *event,
9921 Compose *compose = (Compose *)data;
9923 if (!event) return FALSE;
9925 switch (event->keyval) {
9926 case GDK_KEY_Delete:
9927 compose_attach_remove_selected(NULL, compose);
9933 static void compose_allow_user_actions (Compose *compose, gboolean allow)
9935 toolbar_comp_set_sensitive(compose, allow);
9936 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message", allow);
9937 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", allow);
9939 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", allow);
9941 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", allow);
9942 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools", allow);
9943 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Help", allow);
9945 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), allow);
9949 static void compose_send_cb(GtkAction *action, gpointer data)
9951 Compose *compose = (Compose *)data;
9954 if (compose->exteditor_tag != -1) {
9955 debug_print("ignoring send: external editor still open\n");
9959 if (prefs_common.work_offline &&
9960 !inc_offline_should_override(TRUE,
9961 _("Claws Mail needs network access in order "
9962 "to send this email.")))
9965 if (compose->draft_timeout_tag >= 0) { /* CLAWS: disable draft timeout */
9966 g_source_remove(compose->draft_timeout_tag);
9967 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
9970 compose_send(compose);
9973 static void compose_send_later_cb(GtkAction *action, gpointer data)
9975 Compose *compose = (Compose *)data;
9979 compose_allow_user_actions(compose, FALSE);
9980 val = compose_queue_sub(compose, NULL, NULL, NULL, TRUE, TRUE);
9981 compose_allow_user_actions(compose, TRUE);
9985 compose_close(compose);
9986 } else if (val == -1) {
9987 alertpanel_error(_("Could not queue message."));
9988 } else if (val == -2) {
9989 alertpanel_error(_("Could not queue message:\n\n%s."), g_strerror(errno));
9990 } else if (val == -3) {
9991 if (privacy_peek_error())
9992 alertpanel_error(_("Could not queue message for sending:\n\n"
9993 "Signature failed: %s"), privacy_get_error());
9994 } else if (val == -4) {
9995 alertpanel_error(_("Could not queue message for sending:\n\n"
9996 "Charset conversion failed."));
9997 } else if (val == -5) {
9998 alertpanel_error(_("Could not queue message for sending:\n\n"
9999 "Couldn't get recipient encryption key."));
10000 } else if (val == -6) {
10003 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
10006 #define DRAFTED_AT_EXIT "drafted_at_exit"
10007 static void compose_register_draft(MsgInfo *info)
10009 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10010 DRAFTED_AT_EXIT, NULL);
10011 FILE *fp = g_fopen(filepath, "ab");
10014 fprintf(fp, "%s\t%d\n", folder_item_get_identifier(info->folder),
10022 gboolean compose_draft (gpointer data, guint action)
10024 Compose *compose = (Compose *)data;
10029 MsgFlags flag = {0, 0};
10030 static gboolean lock = FALSE;
10031 MsgInfo *newmsginfo;
10033 gboolean target_locked = FALSE;
10034 gboolean err = FALSE;
10036 if (lock) return FALSE;
10038 if (compose->sending)
10041 draft = account_get_special_folder(compose->account, F_DRAFT);
10042 cm_return_val_if_fail(draft != NULL, FALSE);
10044 if (!g_mutex_trylock(compose->mutex)) {
10045 /* we don't want to lock the mutex once it's available,
10046 * because as the only other part of compose.c locking
10047 * it is compose_close - which means once unlocked,
10048 * the compose struct will be freed */
10049 debug_print("couldn't lock mutex, probably sending\n");
10055 tmp = g_strdup_printf("%s%cdraft.%p", get_tmp_dir(),
10056 G_DIR_SEPARATOR, compose);
10057 if ((fp = g_fopen(tmp, "wb")) == NULL) {
10058 FILE_OP_ERROR(tmp, "fopen");
10062 /* chmod for security */
10063 if (change_file_mode_rw(fp, tmp) < 0) {
10064 FILE_OP_ERROR(tmp, "chmod");
10065 g_warning("can't change file mode");
10068 /* Save draft infos */
10069 err |= (fprintf(fp, "X-Claws-Account-Id:%d\n", compose->account->account_id) < 0);
10070 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
10072 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
10073 gchar *savefolderid;
10075 savefolderid = compose_get_save_to(compose);
10076 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
10077 g_free(savefolderid);
10079 if (compose->return_receipt) {
10080 err |= (fprintf(fp, "RRCPT:1\n") < 0);
10082 if (compose->privacy_system) {
10083 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
10084 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
10085 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
10088 /* Message-ID of message replying to */
10089 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
10090 gchar *folderid = NULL;
10092 if (compose->replyinfo->folder)
10093 folderid = folder_item_get_identifier(compose->replyinfo->folder);
10094 if (folderid == NULL)
10095 folderid = g_strdup("NULL");
10097 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
10100 /* Message-ID of message forwarding to */
10101 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
10102 gchar *folderid = NULL;
10104 if (compose->fwdinfo->folder)
10105 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
10106 if (folderid == NULL)
10107 folderid = g_strdup("NULL");
10109 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
10113 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
10114 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
10116 sheaders = compose_get_manual_headers_info(compose);
10117 err |= (fprintf(fp, "X-Claws-Manual-Headers:%s\n", sheaders) < 0);
10120 /* end of headers */
10121 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
10128 if (compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_STORE, action != COMPOSE_AUTO_SAVE) < 0) {
10132 if (fclose(fp) == EOF) {
10136 flag.perm_flags = MSG_NEW|MSG_UNREAD;
10137 if (compose->targetinfo) {
10138 target_locked = MSG_IS_LOCKED(compose->targetinfo->flags);
10140 flag.perm_flags |= MSG_LOCKED;
10142 flag.tmp_flags = MSG_DRAFT;
10144 folder_item_scan(draft);
10145 if ((msgnum = folder_item_add_msg(draft, tmp, &flag, TRUE)) < 0) {
10146 MsgInfo *tmpinfo = NULL;
10147 debug_print("didn't get msgnum after adding draft [%s]\n", compose->msgid?compose->msgid:"no msgid");
10148 if (compose->msgid) {
10149 tmpinfo = folder_item_get_msginfo_by_msgid(draft, compose->msgid);
10152 msgnum = tmpinfo->msgnum;
10153 procmsg_msginfo_free(&tmpinfo);
10154 debug_print("got draft msgnum %d from scanning\n", msgnum);
10156 debug_print("didn't get draft msgnum after scanning\n");
10159 debug_print("got draft msgnum %d from adding\n", msgnum);
10165 if (action != COMPOSE_AUTO_SAVE) {
10166 if (action != COMPOSE_DRAFT_FOR_EXIT)
10167 alertpanel_error(_("Could not save draft."));
10170 gtkut_window_popup(compose->window);
10171 val = alertpanel_full(_("Could not save draft"),
10172 _("Could not save draft.\n"
10173 "Do you want to cancel exit or discard this email?"),
10174 _("_Cancel exit"), _("_Discard email"), NULL,
10175 FALSE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
10176 if (val == G_ALERTALTERNATE) {
10178 g_mutex_unlock(compose->mutex); /* must be done before closing */
10179 compose_close(compose);
10183 g_mutex_unlock(compose->mutex); /* must be done before closing */
10192 if (compose->mode == COMPOSE_REEDIT) {
10193 compose_remove_reedit_target(compose, TRUE);
10196 newmsginfo = folder_item_get_msginfo(draft, msgnum);
10199 procmsg_msginfo_unset_flags(newmsginfo, ~0, ~0);
10201 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD|MSG_LOCKED, MSG_DRAFT);
10203 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD, MSG_DRAFT);
10204 if (compose_use_attach(compose) && action != COMPOSE_AUTO_SAVE)
10205 procmsg_msginfo_set_flags(newmsginfo, 0,
10206 MSG_HAS_ATTACHMENT);
10208 if (action == COMPOSE_DRAFT_FOR_EXIT) {
10209 compose_register_draft(newmsginfo);
10211 procmsg_msginfo_free(&newmsginfo);
10214 folder_item_scan(draft);
10216 if (action == COMPOSE_QUIT_EDITING || action == COMPOSE_DRAFT_FOR_EXIT) {
10218 g_mutex_unlock(compose->mutex); /* must be done before closing */
10219 compose_close(compose);
10225 path = folder_item_fetch_msg(draft, msgnum);
10226 if (path == NULL) {
10227 debug_print("can't fetch %s:%d\n", draft->path, msgnum);
10230 if (g_stat(path, &s) < 0) {
10231 FILE_OP_ERROR(path, "stat");
10237 procmsg_msginfo_free(&(compose->targetinfo));
10238 compose->targetinfo = procmsg_msginfo_new();
10239 compose->targetinfo->msgnum = msgnum;
10240 compose->targetinfo->size = (goffset)s.st_size;
10241 compose->targetinfo->mtime = s.st_mtime;
10242 compose->targetinfo->folder = draft;
10244 procmsg_msginfo_set_flags(compose->targetinfo, MSG_LOCKED, 0);
10245 compose->mode = COMPOSE_REEDIT;
10247 if (action == COMPOSE_AUTO_SAVE) {
10248 compose->autosaved_draft = compose->targetinfo;
10250 compose->modified = FALSE;
10251 compose_set_title(compose);
10255 g_mutex_unlock(compose->mutex);
10259 void compose_clear_exit_drafts(void)
10261 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10262 DRAFTED_AT_EXIT, NULL);
10263 if (is_file_exist(filepath))
10264 claws_unlink(filepath);
10269 void compose_reopen_exit_drafts(void)
10271 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10272 DRAFTED_AT_EXIT, NULL);
10273 FILE *fp = g_fopen(filepath, "rb");
10277 while (fgets(buf, sizeof(buf), fp)) {
10278 gchar **parts = g_strsplit(buf, "\t", 2);
10279 const gchar *folder = parts[0];
10280 int msgnum = parts[1] ? atoi(parts[1]):-1;
10282 if (folder && *folder && msgnum > -1) {
10283 FolderItem *item = folder_find_item_from_identifier(folder);
10284 MsgInfo *info = folder_item_get_msginfo(item, msgnum);
10286 compose_reedit(info, FALSE);
10293 compose_clear_exit_drafts();
10296 static void compose_save_cb(GtkAction *action, gpointer data)
10298 Compose *compose = (Compose *)data;
10299 compose_draft(compose, COMPOSE_KEEP_EDITING);
10300 compose->rmode = COMPOSE_REEDIT;
10303 void compose_attach_from_list(Compose *compose, GList *file_list, gboolean free_data)
10305 if (compose && file_list) {
10308 for ( tmp = file_list; tmp; tmp = tmp->next) {
10309 gchar *file = (gchar *) tmp->data;
10310 gchar *utf8_filename = conv_filename_to_utf8(file);
10311 compose_attach_append(compose, file, utf8_filename, NULL, NULL);
10312 compose_changed_cb(NULL, compose);
10317 g_free(utf8_filename);
10322 static void compose_attach_cb(GtkAction *action, gpointer data)
10324 Compose *compose = (Compose *)data;
10327 if (compose->redirect_filename != NULL)
10330 /* Set focus_window properly, in case we were called via popup menu,
10331 * which unsets it (via focus_out_event callback on compose window). */
10332 manage_window_focus_in(compose->window, NULL, NULL);
10334 file_list = filesel_select_multiple_files_open(_("Select file"), NULL);
10337 compose_attach_from_list(compose, file_list, TRUE);
10338 g_list_free(file_list);
10342 static void compose_insert_file_cb(GtkAction *action, gpointer data)
10344 Compose *compose = (Compose *)data;
10346 gint files_inserted = 0;
10348 file_list = filesel_select_multiple_files_open(_("Select file"), NULL);
10353 for ( tmp = file_list; tmp; tmp = tmp->next) {
10354 gchar *file = (gchar *) tmp->data;
10355 gchar *filedup = g_strdup(file);
10356 gchar *shortfile = g_path_get_basename(filedup);
10357 ComposeInsertResult res;
10358 /* insert the file if the file is short or if the user confirmed that
10359 he/she wants to insert the large file */
10360 res = compose_insert_file(compose, file);
10361 if (res == COMPOSE_INSERT_READ_ERROR) {
10362 alertpanel_error(_("File '%s' could not be read."), shortfile);
10363 } else if (res == COMPOSE_INSERT_INVALID_CHARACTER) {
10364 alertpanel_error(_("File '%s' contained invalid characters\n"
10365 "for the current encoding, insertion may be incorrect."),
10367 } else if (res == COMPOSE_INSERT_SUCCESS)
10374 g_list_free(file_list);
10378 if (files_inserted > 0 && compose->gtkaspell &&
10379 compose->gtkaspell->check_while_typing)
10380 gtkaspell_highlight_all(compose->gtkaspell);
10384 static void compose_insert_sig_cb(GtkAction *action, gpointer data)
10386 Compose *compose = (Compose *)data;
10388 compose_insert_sig(compose, FALSE);
10391 static void compose_replace_sig_cb(GtkAction *action, gpointer data)
10393 Compose *compose = (Compose *)data;
10395 compose_insert_sig(compose, TRUE);
10398 static gint compose_delete_cb(GtkWidget *widget, GdkEventAny *event,
10402 Compose *compose = (Compose *)data;
10404 gtkut_widget_get_uposition(widget, &x, &y);
10405 if (!compose->batch) {
10406 prefs_common.compose_x = x;
10407 prefs_common.compose_y = y;
10409 if (compose->sending || compose->updating)
10411 compose_close_cb(NULL, compose);
10415 void compose_close_toolbar(Compose *compose)
10417 compose_close_cb(NULL, compose);
10420 static void compose_close_cb(GtkAction *action, gpointer data)
10422 Compose *compose = (Compose *)data;
10426 if (compose->exteditor_tag != -1) {
10427 if (!compose_ext_editor_kill(compose))
10432 if (compose->modified) {
10433 gboolean reedit = (compose->rmode == COMPOSE_REEDIT);
10434 if (!g_mutex_trylock(compose->mutex)) {
10435 /* we don't want to lock the mutex once it's available,
10436 * because as the only other part of compose.c locking
10437 * it is compose_close - which means once unlocked,
10438 * the compose struct will be freed */
10439 debug_print("couldn't lock mutex, probably sending\n");
10443 val = alertpanel(_("Discard message"),
10444 _("This message has been modified. Discard it?"),
10445 _("_Discard"), _("_Save to Drafts"), GTK_STOCK_CANCEL);
10447 val = alertpanel(_("Save changes"),
10448 _("This message has been modified. Save the latest changes?"),
10449 _("_Don't save"), g_strconcat("+", _("_Save to Drafts"), NULL),
10452 g_mutex_unlock(compose->mutex);
10454 case G_ALERTDEFAULT:
10455 if (compose_can_autosave(compose) && !reedit)
10456 compose_remove_draft(compose);
10458 case G_ALERTALTERNATE:
10459 compose_draft(data, COMPOSE_QUIT_EDITING);
10466 compose_close(compose);
10469 static void compose_print_cb(GtkAction *action, gpointer data)
10471 Compose *compose = (Compose *) data;
10473 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
10474 if (compose->targetinfo)
10475 messageview_print(compose->targetinfo, FALSE, -1, -1, 0);
10478 static void compose_set_encoding_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
10480 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
10481 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
10482 Compose *compose = (Compose *) data;
10485 compose->out_encoding = (CharSet)value;
10488 static void compose_address_cb(GtkAction *action, gpointer data)
10490 Compose *compose = (Compose *)data;
10492 #ifndef USE_ALT_ADDRBOOK
10493 addressbook_open(compose);
10495 GError* error = NULL;
10496 addressbook_connect_signals(compose);
10497 addressbook_dbus_open(TRUE, &error);
10499 g_warning("%s", error->message);
10500 g_error_free(error);
10505 static void about_show_cb(GtkAction *action, gpointer data)
10510 static void compose_template_activate_cb(GtkWidget *widget, gpointer data)
10512 Compose *compose = (Compose *)data;
10517 tmpl = g_object_get_data(G_OBJECT(widget), "template");
10518 cm_return_if_fail(tmpl != NULL);
10520 msg = g_strdup_printf(_("Do you want to apply the template '%s'?"),
10522 val = alertpanel(_("Apply template"), msg,
10523 _("_Replace"), _("_Insert"), GTK_STOCK_CANCEL);
10526 if (val == G_ALERTDEFAULT)
10527 compose_template_apply(compose, tmpl, TRUE);
10528 else if (val == G_ALERTALTERNATE)
10529 compose_template_apply(compose, tmpl, FALSE);
10532 static void compose_ext_editor_cb(GtkAction *action, gpointer data)
10534 Compose *compose = (Compose *)data;
10537 if (compose->exteditor_tag != -1) {
10538 debug_print("ignoring open external editor: external editor still open\n");
10542 compose_exec_ext_editor(compose);
10545 static void compose_undo_cb(GtkAction *action, gpointer data)
10547 Compose *compose = (Compose *)data;
10548 gboolean prev_autowrap = compose->autowrap;
10550 compose->autowrap = FALSE;
10551 undo_undo(compose->undostruct);
10552 compose->autowrap = prev_autowrap;
10555 static void compose_redo_cb(GtkAction *action, gpointer data)
10557 Compose *compose = (Compose *)data;
10558 gboolean prev_autowrap = compose->autowrap;
10560 compose->autowrap = FALSE;
10561 undo_redo(compose->undostruct);
10562 compose->autowrap = prev_autowrap;
10565 static void entry_cut_clipboard(GtkWidget *entry)
10567 if (GTK_IS_EDITABLE(entry))
10568 gtk_editable_cut_clipboard (GTK_EDITABLE(entry));
10569 else if (GTK_IS_TEXT_VIEW(entry))
10570 gtk_text_buffer_cut_clipboard(
10571 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10572 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
10576 static void entry_copy_clipboard(GtkWidget *entry)
10578 if (GTK_IS_EDITABLE(entry))
10579 gtk_editable_copy_clipboard (GTK_EDITABLE(entry));
10580 else if (GTK_IS_TEXT_VIEW(entry))
10581 gtk_text_buffer_copy_clipboard(
10582 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10583 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
10586 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry,
10587 gboolean wrap, GdkAtom clip, GtkTextIter *insert_place)
10589 if (GTK_IS_TEXT_VIEW(entry)) {
10590 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10591 GtkTextMark *mark_start = gtk_text_buffer_get_insert(buffer);
10592 GtkTextIter start_iter, end_iter;
10594 gchar *contents = gtk_clipboard_wait_for_text(gtk_clipboard_get(clip));
10596 if (contents == NULL)
10599 /* we shouldn't delete the selection when middle-click-pasting, or we
10600 * can't mid-click-paste our own selection */
10601 if (clip != GDK_SELECTION_PRIMARY) {
10602 undo_paste_clipboard(GTK_TEXT_VIEW(compose->text), compose->undostruct);
10603 gtk_text_buffer_delete_selection(buffer, FALSE, TRUE);
10606 if (insert_place == NULL) {
10607 /* if insert_place isn't specified, insert at the cursor.
10608 * used for Ctrl-V pasting */
10609 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10610 start = gtk_text_iter_get_offset(&start_iter);
10611 gtk_text_buffer_insert(buffer, &start_iter, contents, strlen(contents));
10613 /* if insert_place is specified, paste here.
10614 * used for mid-click-pasting */
10615 start = gtk_text_iter_get_offset(insert_place);
10616 gtk_text_buffer_insert(buffer, insert_place, contents, strlen(contents));
10617 if (prefs_common.primary_paste_unselects)
10618 gtk_text_buffer_select_range(buffer, insert_place, insert_place);
10622 /* paste unwrapped: mark the paste so it's not wrapped later */
10623 end = start + strlen(contents);
10624 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, start);
10625 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, end);
10626 gtk_text_buffer_apply_tag_by_name(buffer, "no_wrap", &start_iter, &end_iter);
10627 } else if (wrap && clip == GDK_SELECTION_PRIMARY) {
10628 /* rewrap paragraph now (after a mid-click-paste) */
10629 mark_start = gtk_text_buffer_get_insert(buffer);
10630 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10631 gtk_text_iter_backward_char(&start_iter);
10632 compose_beautify_paragraph(compose, &start_iter, TRUE);
10634 } else if (GTK_IS_EDITABLE(entry))
10635 gtk_editable_paste_clipboard (GTK_EDITABLE(entry));
10637 compose->modified = TRUE;
10640 static void entry_allsel(GtkWidget *entry)
10642 if (GTK_IS_EDITABLE(entry))
10643 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
10644 else if (GTK_IS_TEXT_VIEW(entry)) {
10645 GtkTextIter startiter, enditer;
10646 GtkTextBuffer *textbuf;
10648 textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10649 gtk_text_buffer_get_start_iter(textbuf, &startiter);
10650 gtk_text_buffer_get_end_iter(textbuf, &enditer);
10652 gtk_text_buffer_move_mark_by_name(textbuf,
10653 "selection_bound", &startiter);
10654 gtk_text_buffer_move_mark_by_name(textbuf,
10655 "insert", &enditer);
10659 static void compose_cut_cb(GtkAction *action, gpointer data)
10661 Compose *compose = (Compose *)data;
10662 if (compose->focused_editable
10663 #ifndef GENERIC_UMPC
10664 && gtk_widget_has_focus(compose->focused_editable)
10667 entry_cut_clipboard(compose->focused_editable);
10670 static void compose_copy_cb(GtkAction *action, gpointer data)
10672 Compose *compose = (Compose *)data;
10673 if (compose->focused_editable
10674 #ifndef GENERIC_UMPC
10675 && gtk_widget_has_focus(compose->focused_editable)
10678 entry_copy_clipboard(compose->focused_editable);
10681 static void compose_paste_cb(GtkAction *action, gpointer data)
10683 Compose *compose = (Compose *)data;
10684 gint prev_autowrap;
10685 GtkTextBuffer *buffer;
10687 if (compose->focused_editable &&
10688 #ifndef GENERIC_UMPC
10689 gtk_widget_has_focus(compose->focused_editable)
10692 entry_paste_clipboard(compose, compose->focused_editable,
10693 prefs_common.linewrap_pastes,
10694 GDK_SELECTION_CLIPBOARD, NULL);
10699 #ifndef GENERIC_UMPC
10700 gtk_widget_has_focus(compose->text) &&
10702 compose->gtkaspell &&
10703 compose->gtkaspell->check_while_typing)
10704 gtkaspell_highlight_all(compose->gtkaspell);
10708 static void compose_paste_as_quote_cb(GtkAction *action, gpointer data)
10710 Compose *compose = (Compose *)data;
10711 gint wrap_quote = prefs_common.linewrap_quote;
10712 if (compose->focused_editable
10713 #ifndef GENERIC_UMPC
10714 && gtk_widget_has_focus(compose->focused_editable)
10717 /* let text_insert() (called directly or at a later time
10718 * after the gtk_editable_paste_clipboard) know that
10719 * text is to be inserted as a quotation. implemented
10720 * by using a simple refcount... */
10721 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data(
10722 G_OBJECT(compose->focused_editable),
10723 "paste_as_quotation"));
10724 g_object_set_data(G_OBJECT(compose->focused_editable),
10725 "paste_as_quotation",
10726 GINT_TO_POINTER(paste_as_quotation + 1));
10727 prefs_common.linewrap_quote = prefs_common.linewrap_pastes;
10728 entry_paste_clipboard(compose, compose->focused_editable,
10729 prefs_common.linewrap_pastes,
10730 GDK_SELECTION_CLIPBOARD, NULL);
10731 prefs_common.linewrap_quote = wrap_quote;
10735 static void compose_paste_no_wrap_cb(GtkAction *action, gpointer data)
10737 Compose *compose = (Compose *)data;
10738 gint prev_autowrap;
10739 GtkTextBuffer *buffer;
10741 if (compose->focused_editable
10742 #ifndef GENERIC_UMPC
10743 && gtk_widget_has_focus(compose->focused_editable)
10746 entry_paste_clipboard(compose, compose->focused_editable, FALSE,
10747 GDK_SELECTION_CLIPBOARD, NULL);
10752 #ifndef GENERIC_UMPC
10753 gtk_widget_has_focus(compose->text) &&
10755 compose->gtkaspell &&
10756 compose->gtkaspell->check_while_typing)
10757 gtkaspell_highlight_all(compose->gtkaspell);
10761 static void compose_paste_wrap_cb(GtkAction *action, gpointer data)
10763 Compose *compose = (Compose *)data;
10764 gint prev_autowrap;
10765 GtkTextBuffer *buffer;
10767 if (compose->focused_editable
10768 #ifndef GENERIC_UMPC
10769 && gtk_widget_has_focus(compose->focused_editable)
10772 entry_paste_clipboard(compose, compose->focused_editable, TRUE,
10773 GDK_SELECTION_CLIPBOARD, NULL);
10778 #ifndef GENERIC_UMPC
10779 gtk_widget_has_focus(compose->text) &&
10781 compose->gtkaspell &&
10782 compose->gtkaspell->check_while_typing)
10783 gtkaspell_highlight_all(compose->gtkaspell);
10787 static void compose_allsel_cb(GtkAction *action, gpointer data)
10789 Compose *compose = (Compose *)data;
10790 if (compose->focused_editable
10791 #ifndef GENERIC_UMPC
10792 && gtk_widget_has_focus(compose->focused_editable)
10795 entry_allsel(compose->focused_editable);
10798 static void textview_move_beginning_of_line (GtkTextView *text)
10800 GtkTextBuffer *buffer;
10804 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10806 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10807 mark = gtk_text_buffer_get_insert(buffer);
10808 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10809 gtk_text_iter_set_line_offset(&ins, 0);
10810 gtk_text_buffer_place_cursor(buffer, &ins);
10813 static void textview_move_forward_character (GtkTextView *text)
10815 GtkTextBuffer *buffer;
10819 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10821 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10822 mark = gtk_text_buffer_get_insert(buffer);
10823 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10824 if (gtk_text_iter_forward_cursor_position(&ins))
10825 gtk_text_buffer_place_cursor(buffer, &ins);
10828 static void textview_move_backward_character (GtkTextView *text)
10830 GtkTextBuffer *buffer;
10834 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10836 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10837 mark = gtk_text_buffer_get_insert(buffer);
10838 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10839 if (gtk_text_iter_backward_cursor_position(&ins))
10840 gtk_text_buffer_place_cursor(buffer, &ins);
10843 static void textview_move_forward_word (GtkTextView *text)
10845 GtkTextBuffer *buffer;
10850 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10852 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10853 mark = gtk_text_buffer_get_insert(buffer);
10854 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10855 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
10856 if (gtk_text_iter_forward_word_ends(&ins, count)) {
10857 gtk_text_iter_backward_word_start(&ins);
10858 gtk_text_buffer_place_cursor(buffer, &ins);
10862 static void textview_move_backward_word (GtkTextView *text)
10864 GtkTextBuffer *buffer;
10868 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10870 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10871 mark = gtk_text_buffer_get_insert(buffer);
10872 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10873 if (gtk_text_iter_backward_word_starts(&ins, 1))
10874 gtk_text_buffer_place_cursor(buffer, &ins);
10877 static void textview_move_end_of_line (GtkTextView *text)
10879 GtkTextBuffer *buffer;
10883 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10885 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10886 mark = gtk_text_buffer_get_insert(buffer);
10887 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10888 if (gtk_text_iter_forward_to_line_end(&ins))
10889 gtk_text_buffer_place_cursor(buffer, &ins);
10892 static void textview_move_next_line (GtkTextView *text)
10894 GtkTextBuffer *buffer;
10899 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10901 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10902 mark = gtk_text_buffer_get_insert(buffer);
10903 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10904 offset = gtk_text_iter_get_line_offset(&ins);
10905 if (gtk_text_iter_forward_line(&ins)) {
10906 gtk_text_iter_set_line_offset(&ins, offset);
10907 gtk_text_buffer_place_cursor(buffer, &ins);
10911 static void textview_move_previous_line (GtkTextView *text)
10913 GtkTextBuffer *buffer;
10918 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10920 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10921 mark = gtk_text_buffer_get_insert(buffer);
10922 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10923 offset = gtk_text_iter_get_line_offset(&ins);
10924 if (gtk_text_iter_backward_line(&ins)) {
10925 gtk_text_iter_set_line_offset(&ins, offset);
10926 gtk_text_buffer_place_cursor(buffer, &ins);
10930 static void textview_delete_forward_character (GtkTextView *text)
10932 GtkTextBuffer *buffer;
10934 GtkTextIter ins, end_iter;
10936 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10938 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10939 mark = gtk_text_buffer_get_insert(buffer);
10940 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10942 if (gtk_text_iter_forward_char(&end_iter)) {
10943 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10947 static void textview_delete_backward_character (GtkTextView *text)
10949 GtkTextBuffer *buffer;
10951 GtkTextIter ins, end_iter;
10953 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10955 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10956 mark = gtk_text_buffer_get_insert(buffer);
10957 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10959 if (gtk_text_iter_backward_char(&end_iter)) {
10960 gtk_text_buffer_delete(buffer, &end_iter, &ins);
10964 static void textview_delete_forward_word (GtkTextView *text)
10966 GtkTextBuffer *buffer;
10968 GtkTextIter ins, end_iter;
10970 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10972 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10973 mark = gtk_text_buffer_get_insert(buffer);
10974 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10976 if (gtk_text_iter_forward_word_end(&end_iter)) {
10977 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10981 static void textview_delete_backward_word (GtkTextView *text)
10983 GtkTextBuffer *buffer;
10985 GtkTextIter ins, end_iter;
10987 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10989 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10990 mark = gtk_text_buffer_get_insert(buffer);
10991 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10993 if (gtk_text_iter_backward_word_start(&end_iter)) {
10994 gtk_text_buffer_delete(buffer, &end_iter, &ins);
10998 static void textview_delete_line (GtkTextView *text)
11000 GtkTextBuffer *buffer;
11002 GtkTextIter ins, start_iter, end_iter;
11004 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11006 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11007 mark = gtk_text_buffer_get_insert(buffer);
11008 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11011 gtk_text_iter_set_line_offset(&start_iter, 0);
11014 if (gtk_text_iter_ends_line(&end_iter)){
11015 if (!gtk_text_iter_forward_char(&end_iter))
11016 gtk_text_iter_backward_char(&start_iter);
11019 gtk_text_iter_forward_to_line_end(&end_iter);
11020 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
11023 static void textview_delete_to_line_end (GtkTextView *text)
11025 GtkTextBuffer *buffer;
11027 GtkTextIter ins, end_iter;
11029 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11031 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11032 mark = gtk_text_buffer_get_insert(buffer);
11033 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11035 if (gtk_text_iter_ends_line(&end_iter))
11036 gtk_text_iter_forward_char(&end_iter);
11038 gtk_text_iter_forward_to_line_end(&end_iter);
11039 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11042 #define DO_ACTION(name, act) { \
11043 if(!strcmp(name, a_name)) { \
11047 static ComposeCallAdvancedAction compose_call_advanced_action_from_path(GtkAction *action)
11049 const gchar *a_name = gtk_action_get_name(action);
11050 DO_ACTION("Edit/Advanced/BackChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER);
11051 DO_ACTION("Edit/Advanced/ForwChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER);
11052 DO_ACTION("Edit/Advanced/BackWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD);
11053 DO_ACTION("Edit/Advanced/ForwWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD);
11054 DO_ACTION("Edit/Advanced/BegLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE);
11055 DO_ACTION("Edit/Advanced/EndLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE);
11056 DO_ACTION("Edit/Advanced/PrevLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE);
11057 DO_ACTION("Edit/Advanced/NextLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE);
11058 DO_ACTION("Edit/Advanced/DelBackChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER);
11059 DO_ACTION("Edit/Advanced/DelForwChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER);
11060 DO_ACTION("Edit/Advanced/DelBackWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD);
11061 DO_ACTION("Edit/Advanced/DelForwWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD);
11062 DO_ACTION("Edit/Advanced/DelLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE);
11063 DO_ACTION("Edit/Advanced/DelEndLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END);
11064 return COMPOSE_CALL_ADVANCED_ACTION_UNDEFINED;
11067 static void compose_advanced_action_cb(GtkAction *gaction, gpointer data)
11069 Compose *compose = (Compose *)data;
11070 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11071 ComposeCallAdvancedAction action = COMPOSE_CALL_ADVANCED_ACTION_UNDEFINED;
11073 action = compose_call_advanced_action_from_path(gaction);
11076 void (*do_action) (GtkTextView *text);
11077 } action_table[] = {
11078 {textview_move_beginning_of_line},
11079 {textview_move_forward_character},
11080 {textview_move_backward_character},
11081 {textview_move_forward_word},
11082 {textview_move_backward_word},
11083 {textview_move_end_of_line},
11084 {textview_move_next_line},
11085 {textview_move_previous_line},
11086 {textview_delete_forward_character},
11087 {textview_delete_backward_character},
11088 {textview_delete_forward_word},
11089 {textview_delete_backward_word},
11090 {textview_delete_line},
11091 {textview_delete_to_line_end}
11094 if (!gtk_widget_has_focus(GTK_WIDGET(text))) return;
11096 if (action >= COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE &&
11097 action <= COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END) {
11098 if (action_table[action].do_action)
11099 action_table[action].do_action(text);
11101 g_warning("Not implemented yet.");
11105 static void compose_grab_focus_cb(GtkWidget *widget, Compose *compose)
11107 GtkAllocation allocation;
11111 if (GTK_IS_EDITABLE(widget)) {
11112 str = gtk_editable_get_chars(GTK_EDITABLE(widget), 0, -1);
11113 gtk_editable_set_position(GTK_EDITABLE(widget),
11116 if ((parent = gtk_widget_get_parent(widget))
11117 && (parent = gtk_widget_get_parent(parent))
11118 && (parent = gtk_widget_get_parent(parent))) {
11119 if (GTK_IS_SCROLLED_WINDOW(parent)) {
11120 gtk_widget_get_allocation(widget, &allocation);
11121 gint y = allocation.y;
11122 gint height = allocation.height;
11123 GtkAdjustment *shown = gtk_scrolled_window_get_vadjustment
11124 (GTK_SCROLLED_WINDOW(parent));
11126 gfloat value = gtk_adjustment_get_value(shown);
11127 gfloat upper = gtk_adjustment_get_upper(shown);
11128 gfloat page_size = gtk_adjustment_get_page_size(shown);
11129 if (y < (int)value) {
11130 gtk_adjustment_set_value(shown, y - 1);
11132 if ((y + height) > ((int)value + (int)page_size)) {
11133 if ((y - height - 1) < ((int)upper - (int)page_size)) {
11134 gtk_adjustment_set_value(shown,
11135 y + height - (int)page_size - 1);
11137 gtk_adjustment_set_value(shown,
11138 (int)upper - (int)page_size - 1);
11145 if (GTK_IS_EDITABLE(widget) || GTK_IS_TEXT_VIEW(widget))
11146 compose->focused_editable = widget;
11148 #ifdef GENERIC_UMPC
11149 if (GTK_IS_TEXT_VIEW(widget)
11150 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->edit_vbox) {
11151 g_object_ref(compose->notebook);
11152 g_object_ref(compose->edit_vbox);
11153 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
11154 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
11155 gtk_paned_add1(GTK_PANED(compose->paned), compose->edit_vbox);
11156 gtk_paned_add2(GTK_PANED(compose->paned), compose->notebook);
11157 g_object_unref(compose->notebook);
11158 g_object_unref(compose->edit_vbox);
11159 g_signal_handlers_block_by_func(G_OBJECT(widget),
11160 G_CALLBACK(compose_grab_focus_cb),
11162 gtk_widget_grab_focus(widget);
11163 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
11164 G_CALLBACK(compose_grab_focus_cb),
11166 } else if (!GTK_IS_TEXT_VIEW(widget)
11167 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->notebook) {
11168 g_object_ref(compose->notebook);
11169 g_object_ref(compose->edit_vbox);
11170 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
11171 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
11172 gtk_paned_add1(GTK_PANED(compose->paned), compose->notebook);
11173 gtk_paned_add2(GTK_PANED(compose->paned), compose->edit_vbox);
11174 g_object_unref(compose->notebook);
11175 g_object_unref(compose->edit_vbox);
11176 g_signal_handlers_block_by_func(G_OBJECT(widget),
11177 G_CALLBACK(compose_grab_focus_cb),
11179 gtk_widget_grab_focus(widget);
11180 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
11181 G_CALLBACK(compose_grab_focus_cb),
11187 static void compose_changed_cb(GtkTextBuffer *textbuf, Compose *compose)
11189 compose->modified = TRUE;
11190 /* compose_beautify_paragraph(compose, NULL, TRUE); */
11191 #ifndef GENERIC_UMPC
11192 compose_set_title(compose);
11196 static void compose_wrap_cb(GtkAction *action, gpointer data)
11198 Compose *compose = (Compose *)data;
11199 compose_beautify_paragraph(compose, NULL, TRUE);
11202 static void compose_wrap_all_cb(GtkAction *action, gpointer data)
11204 Compose *compose = (Compose *)data;
11205 compose_wrap_all_full(compose, TRUE);
11208 static void compose_find_cb(GtkAction *action, gpointer data)
11210 Compose *compose = (Compose *)data;
11212 message_search_compose(compose);
11215 static void compose_toggle_autowrap_cb(GtkToggleAction *action,
11218 Compose *compose = (Compose *)data;
11219 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11220 if (compose->autowrap)
11221 compose_wrap_all_full(compose, TRUE);
11222 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11225 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
11228 Compose *compose = (Compose *)data;
11229 compose->autoindent = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11232 static void compose_toggle_sign_cb(GtkToggleAction *action, gpointer data)
11234 Compose *compose = (Compose *)data;
11236 compose->use_signing = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11239 static void compose_toggle_encrypt_cb(GtkToggleAction *action, gpointer data)
11241 Compose *compose = (Compose *)data;
11243 compose->use_encryption = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11246 static void activate_privacy_system(Compose *compose, PrefsAccount *account, gboolean warn)
11248 g_free(compose->privacy_system);
11249 g_free(compose->encdata);
11251 compose->privacy_system = g_strdup(account->default_privacy_system);
11252 compose_update_privacy_system_menu_item(compose, warn);
11255 static void compose_toggle_ruler_cb(GtkToggleAction *action, gpointer data)
11257 Compose *compose = (Compose *)data;
11259 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action))) {
11260 gtk_widget_show(compose->ruler_hbox);
11261 prefs_common.show_ruler = TRUE;
11263 gtk_widget_hide(compose->ruler_hbox);
11264 gtk_widget_queue_resize(compose->edit_vbox);
11265 prefs_common.show_ruler = FALSE;
11269 static void compose_attach_drag_received_cb (GtkWidget *widget,
11270 GdkDragContext *context,
11273 GtkSelectionData *data,
11276 gpointer user_data)
11278 Compose *compose = (Compose *)user_data;
11282 type = gtk_selection_data_get_data_type(data);
11283 if ((gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list"))
11284 && gtk_drag_get_source_widget(context) !=
11285 summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
11286 list = uri_list_extract_filenames(
11287 (const gchar *)gtk_selection_data_get_data(data));
11288 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11289 gchar *utf8_filename = conv_filename_to_utf8((const gchar *)tmp->data);
11290 compose_attach_append
11291 (compose, (const gchar *)tmp->data,
11292 utf8_filename, NULL, NULL);
11293 g_free(utf8_filename);
11295 if (list) compose_changed_cb(NULL, compose);
11296 list_free_strings(list);
11298 } else if (gtk_drag_get_source_widget(context)
11299 == summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
11300 /* comes from our summaryview */
11301 SummaryView * summaryview = NULL;
11302 GSList * list = NULL, *cur = NULL;
11304 if (mainwindow_get_mainwindow())
11305 summaryview = mainwindow_get_mainwindow()->summaryview;
11308 list = summary_get_selected_msg_list(summaryview);
11310 for (cur = list; cur; cur = cur->next) {
11311 MsgInfo *msginfo = (MsgInfo *)cur->data;
11312 gchar *file = NULL;
11314 file = procmsg_get_message_file_full(msginfo,
11317 compose_attach_append(compose, (const gchar *)file,
11318 (const gchar *)file, "message/rfc822", NULL);
11322 g_slist_free(list);
11326 static gboolean compose_drag_drop(GtkWidget *widget,
11327 GdkDragContext *drag_context,
11329 guint time, gpointer user_data)
11331 /* not handling this signal makes compose_insert_drag_received_cb
11336 static gboolean completion_set_focus_to_subject
11337 (GtkWidget *widget,
11338 GdkEventKey *event,
11341 cm_return_val_if_fail(compose != NULL, FALSE);
11343 /* make backtab move to subject field */
11344 if(event->keyval == GDK_KEY_ISO_Left_Tab) {
11345 gtk_widget_grab_focus(compose->subject_entry);
11351 static void compose_insert_drag_received_cb (GtkWidget *widget,
11352 GdkDragContext *drag_context,
11355 GtkSelectionData *data,
11358 gpointer user_data)
11360 Compose *compose = (Compose *)user_data;
11366 /* strangely, testing data->type == gdk_atom_intern("text/uri-list", TRUE)
11368 type = gtk_selection_data_get_data_type(data);
11369 if (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list")) {
11370 AlertValue val = G_ALERTDEFAULT;
11371 const gchar* ddata = (const gchar *)gtk_selection_data_get_data(data);
11373 list = uri_list_extract_filenames(ddata);
11374 num_files = g_list_length(list);
11375 if (list == NULL && strstr(ddata, "://")) {
11376 /* Assume a list of no files, and data has ://, is a remote link */
11377 gchar *tmpdata = g_strstrip(g_strdup(ddata));
11378 gchar *tmpfile = get_tmp_file();
11379 str_write_to_file(tmpdata, tmpfile);
11381 compose_insert_file(compose, tmpfile);
11382 claws_unlink(tmpfile);
11384 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11385 compose_beautify_paragraph(compose, NULL, TRUE);
11388 switch (prefs_common.compose_dnd_mode) {
11389 case COMPOSE_DND_ASK:
11390 msg = g_strdup_printf(
11392 "Do you want to insert the contents of the file "
11393 "into the message body, or attach it to the email?",
11394 "Do you want to insert the contents of the %d files "
11395 "into the message body, or attach them to the email?",
11398 val = alertpanel_full(_("Insert or attach?"), msg,
11399 GTK_STOCK_CANCEL, g_strconcat("+", _("_Insert"), NULL), _("_Attach"),
11400 TRUE, NULL, ALERT_QUESTION, G_ALERTALTERNATE);
11403 case COMPOSE_DND_INSERT:
11404 val = G_ALERTALTERNATE;
11406 case COMPOSE_DND_ATTACH:
11407 val = G_ALERTOTHER;
11410 /* unexpected case */
11411 g_warning("error: unexpected compose_dnd_mode option value in compose_insert_drag_received_cb()");
11414 if (val & G_ALERTDISABLE) {
11415 val &= ~G_ALERTDISABLE;
11416 /* remember what action to perform by default, only if we don't click Cancel */
11417 if (val == G_ALERTALTERNATE)
11418 prefs_common.compose_dnd_mode = COMPOSE_DND_INSERT;
11419 else if (val == G_ALERTOTHER)
11420 prefs_common.compose_dnd_mode = COMPOSE_DND_ATTACH;
11423 if (val == G_ALERTDEFAULT || val == G_ALERTCANCEL) {
11424 gtk_drag_finish(drag_context, FALSE, FALSE, time);
11425 list_free_strings(list);
11428 } else if (val == G_ALERTOTHER) {
11429 compose_attach_drag_received_cb(widget, drag_context, x, y, data, info, time, user_data);
11430 list_free_strings(list);
11435 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11436 compose_insert_file(compose, (const gchar *)tmp->data);
11438 list_free_strings(list);
11440 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11445 static void compose_header_drag_received_cb (GtkWidget *widget,
11446 GdkDragContext *drag_context,
11449 GtkSelectionData *data,
11452 gpointer user_data)
11454 GtkEditable *entry = (GtkEditable *)user_data;
11455 const gchar *email = (const gchar *)gtk_selection_data_get_data(data);
11457 /* strangely, testing data->type == gdk_atom_intern("text/plain", TRUE)
11460 if (!strncmp(email, "mailto:", strlen("mailto:"))) {
11461 gchar *decoded=g_new(gchar, strlen(email));
11464 decode_uri(decoded, email + strlen("mailto:")); /* will fit */
11465 gtk_editable_delete_text(entry, 0, -1);
11466 gtk_editable_insert_text(entry, decoded, strlen(decoded), &start);
11467 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11471 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11474 static void compose_toggle_return_receipt_cb(GtkToggleAction *action, gpointer data)
11476 Compose *compose = (Compose *)data;
11478 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11479 compose->return_receipt = TRUE;
11481 compose->return_receipt = FALSE;
11484 static void compose_toggle_remove_refs_cb(GtkToggleAction *action, gpointer data)
11486 Compose *compose = (Compose *)data;
11488 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11489 compose->remove_references = TRUE;
11491 compose->remove_references = FALSE;
11494 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
11495 ComposeHeaderEntry *headerentry)
11497 gtk_entry_set_text(GTK_ENTRY(headerentry->entry), "");
11501 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
11502 GdkEventKey *event,
11503 ComposeHeaderEntry *headerentry)
11505 if ((g_slist_length(headerentry->compose->header_list) > 0) &&
11506 ((headerentry->headernum + 1) != headerentry->compose->header_nextrow) &&
11507 !(event->state & GDK_MODIFIER_MASK) &&
11508 (event->keyval == GDK_KEY_BackSpace) &&
11509 (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) == 0)) {
11510 gtk_container_remove
11511 (GTK_CONTAINER(headerentry->compose->header_table),
11512 headerentry->combo);
11513 gtk_container_remove
11514 (GTK_CONTAINER(headerentry->compose->header_table),
11515 headerentry->entry);
11516 headerentry->compose->header_list =
11517 g_slist_remove(headerentry->compose->header_list,
11519 g_free(headerentry);
11520 } else if (event->keyval == GDK_KEY_Tab) {
11521 if (headerentry->compose->header_last == headerentry) {
11522 /* Override default next focus, and give it to subject_entry
11523 * instead of notebook tabs
11525 g_signal_stop_emission_by_name(G_OBJECT(entry), "key-press-event");
11526 gtk_widget_grab_focus(headerentry->compose->subject_entry);
11533 static gboolean scroll_postpone(gpointer data)
11535 Compose *compose = (Compose *)data;
11537 if (compose->batch)
11540 GTK_EVENTS_FLUSH();
11541 compose_show_first_last_header(compose, FALSE);
11545 static void compose_headerentry_changed_cb(GtkWidget *entry,
11546 ComposeHeaderEntry *headerentry)
11548 if (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) != 0) {
11549 compose_create_header_entry(headerentry->compose);
11550 g_signal_handlers_disconnect_matched
11551 (G_OBJECT(entry), G_SIGNAL_MATCH_DATA,
11552 0, 0, NULL, NULL, headerentry);
11554 if (!headerentry->compose->batch)
11555 g_timeout_add(0, scroll_postpone, headerentry->compose);
11559 static gboolean compose_defer_auto_save_draft(Compose *compose)
11561 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
11562 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
11566 static void compose_show_first_last_header(Compose *compose, gboolean show_first)
11568 GtkAdjustment *vadj;
11570 cm_return_if_fail(compose);
11575 cm_return_if_fail(GTK_IS_WIDGET(compose->header_table));
11576 cm_return_if_fail(GTK_IS_VIEWPORT(gtk_widget_get_parent(compose->header_table)));
11577 vadj = gtk_viewport_get_vadjustment(GTK_VIEWPORT(
11578 gtk_widget_get_parent(compose->header_table)));
11579 gtk_adjustment_set_value(vadj, (show_first ?
11580 gtk_adjustment_get_lower(vadj) :
11581 (gtk_adjustment_get_upper(vadj) -
11582 gtk_adjustment_get_page_size(vadj))));
11583 gtk_adjustment_changed(vadj);
11586 static void text_inserted(GtkTextBuffer *buffer, GtkTextIter *iter,
11587 const gchar *text, gint len, Compose *compose)
11589 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data
11590 (G_OBJECT(compose->text), "paste_as_quotation"));
11593 cm_return_if_fail(text != NULL);
11595 g_signal_handlers_block_by_func(G_OBJECT(buffer),
11596 G_CALLBACK(text_inserted),
11598 if (paste_as_quotation) {
11600 const gchar *qmark;
11602 GtkTextIter start_iter;
11605 len = strlen(text);
11607 new_text = g_strndup(text, len);
11609 qmark = compose_quote_char_from_context(compose);
11611 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11612 gtk_text_buffer_place_cursor(buffer, iter);
11614 pos = gtk_text_iter_get_offset(iter);
11616 compose_quote_fmt(compose, NULL, "%Q", qmark, new_text, TRUE, FALSE,
11617 _("Quote format error at line %d."));
11618 quote_fmt_reset_vartable();
11620 g_object_set_data(G_OBJECT(compose->text), "paste_as_quotation",
11621 GINT_TO_POINTER(paste_as_quotation - 1));
11623 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11624 gtk_text_buffer_place_cursor(buffer, iter);
11625 gtk_text_buffer_delete_mark(buffer, mark);
11627 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, pos);
11628 mark = gtk_text_buffer_create_mark(buffer, NULL, &start_iter, FALSE);
11629 compose_beautify_paragraph(compose, &start_iter, FALSE);
11630 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark);
11631 gtk_text_buffer_delete_mark(buffer, mark);
11633 if (strcmp(text, "\n") || compose->automatic_break
11634 || gtk_text_iter_starts_line(iter)) {
11635 GtkTextIter before_ins;
11636 gtk_text_buffer_insert(buffer, iter, text, len);
11637 if (!strstr(text, "\n") && gtk_text_iter_has_tag(iter, compose->no_join_tag)) {
11638 before_ins = *iter;
11639 gtk_text_iter_backward_chars(&before_ins, len);
11640 gtk_text_buffer_remove_tag_by_name(buffer, "no_join", &before_ins, iter);
11643 /* check if the preceding is just whitespace or quote */
11644 GtkTextIter start_line;
11645 gchar *tmp = NULL, *quote = NULL;
11646 gint quote_len = 0, is_normal = 0;
11647 start_line = *iter;
11648 gtk_text_iter_set_line_offset(&start_line, 0);
11649 tmp = gtk_text_buffer_get_text(buffer, &start_line, iter, FALSE);
11652 if (*tmp == '\0') {
11655 quote = compose_get_quote_str(buffer, &start_line, "e_len);
11663 gtk_text_buffer_insert(buffer, iter, text, len);
11665 gtk_text_buffer_insert_with_tags_by_name(buffer,
11666 iter, text, len, "no_join", NULL);
11671 if (!paste_as_quotation) {
11672 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11673 compose_beautify_paragraph(compose, iter, FALSE);
11674 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11675 gtk_text_buffer_delete_mark(buffer, mark);
11678 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
11679 G_CALLBACK(text_inserted),
11681 g_signal_stop_emission_by_name(G_OBJECT(buffer), "insert-text");
11683 if (compose_can_autosave(compose) &&
11684 gtk_text_buffer_get_char_count(buffer) % prefs_common.autosave_length == 0 &&
11685 compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN /* disabled while loading */)
11686 compose->draft_timeout_tag = g_timeout_add
11687 (500, (GSourceFunc) compose_defer_auto_save_draft, compose);
11691 static void compose_check_all(GtkAction *action, gpointer data)
11693 Compose *compose = (Compose *)data;
11694 if (!compose->gtkaspell)
11697 if (gtk_widget_has_focus(compose->subject_entry))
11698 claws_spell_entry_check_all(
11699 CLAWS_SPELL_ENTRY(compose->subject_entry));
11701 gtkaspell_check_all(compose->gtkaspell);
11704 static void compose_highlight_all(GtkAction *action, gpointer data)
11706 Compose *compose = (Compose *)data;
11707 if (compose->gtkaspell) {
11708 claws_spell_entry_recheck_all(
11709 CLAWS_SPELL_ENTRY(compose->subject_entry));
11710 gtkaspell_highlight_all(compose->gtkaspell);
11714 static void compose_check_backwards(GtkAction *action, gpointer data)
11716 Compose *compose = (Compose *)data;
11717 if (!compose->gtkaspell) {
11718 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
11722 if (gtk_widget_has_focus(compose->subject_entry))
11723 claws_spell_entry_check_backwards(
11724 CLAWS_SPELL_ENTRY(compose->subject_entry));
11726 gtkaspell_check_backwards(compose->gtkaspell);
11729 static void compose_check_forwards_go(GtkAction *action, gpointer data)
11731 Compose *compose = (Compose *)data;
11732 if (!compose->gtkaspell) {
11733 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
11737 if (gtk_widget_has_focus(compose->subject_entry))
11738 claws_spell_entry_check_forwards_go(
11739 CLAWS_SPELL_ENTRY(compose->subject_entry));
11741 gtkaspell_check_forwards_go(compose->gtkaspell);
11746 *\brief Guess originating forward account from MsgInfo and several
11747 * "common preference" settings. Return NULL if no guess.
11749 static PrefsAccount *compose_find_account(MsgInfo *msginfo)
11751 PrefsAccount *account = NULL;
11753 cm_return_val_if_fail(msginfo, NULL);
11754 cm_return_val_if_fail(msginfo->folder, NULL);
11755 cm_return_val_if_fail(msginfo->folder->prefs, NULL);
11757 if (msginfo->folder->prefs->enable_default_account)
11758 account = account_find_from_id(msginfo->folder->prefs->default_account);
11760 if (!account && msginfo->to && prefs_common.forward_account_autosel) {
11762 Xstrdup_a(to, msginfo->to, return NULL);
11763 extract_address(to);
11764 account = account_find_from_address(to, FALSE);
11767 if (!account && prefs_common.forward_account_autosel) {
11768 gchar cc[BUFFSIZE];
11769 if (!procheader_get_header_from_msginfo
11770 (msginfo, cc,sizeof cc , "Cc:")) {
11771 gchar *buf = cc + strlen("Cc:");
11772 extract_address(buf);
11773 account = account_find_from_address(buf, FALSE);
11777 if (!account && prefs_common.forward_account_autosel) {
11778 gchar deliveredto[BUFFSIZE];
11779 if (!procheader_get_header_from_msginfo
11780 (msginfo, deliveredto,sizeof deliveredto , "Delivered-To:")) {
11781 gchar *buf = deliveredto + strlen("Delivered-To:");
11782 extract_address(buf);
11783 account = account_find_from_address(buf, FALSE);
11788 account = msginfo->folder->folder->account;
11793 gboolean compose_close(Compose *compose)
11797 cm_return_val_if_fail(compose, FALSE);
11799 if (!g_mutex_trylock(compose->mutex)) {
11800 /* we have to wait for the (possibly deferred by auto-save)
11801 * drafting to be done, before destroying the compose under
11803 debug_print("waiting for drafting to finish...\n");
11804 compose_allow_user_actions(compose, FALSE);
11805 if (compose->close_timeout_tag == 0) {
11806 compose->close_timeout_tag =
11807 g_timeout_add (500, (GSourceFunc) compose_close,
11813 if (compose->draft_timeout_tag >= 0) {
11814 g_source_remove(compose->draft_timeout_tag);
11815 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN;
11818 gtkut_widget_get_uposition(compose->window, &x, &y);
11819 if (!compose->batch) {
11820 prefs_common.compose_x = x;
11821 prefs_common.compose_y = y;
11823 g_mutex_unlock(compose->mutex);
11824 compose_destroy(compose);
11829 * Add entry field for each address in list.
11830 * \param compose E-Mail composition object.
11831 * \param listAddress List of (formatted) E-Mail addresses.
11833 static void compose_add_field_list( Compose *compose, GList *listAddress ) {
11836 node = listAddress;
11838 addr = ( gchar * ) node->data;
11839 compose_entry_append( compose, addr, COMPOSE_TO, PREF_NONE );
11840 node = g_list_next( node );
11844 static void compose_reply_from_messageview_real(MessageView *msgview, GSList *msginfo_list,
11845 guint action, gboolean opening_multiple)
11847 gchar *body = NULL;
11848 GSList *new_msglist = NULL;
11849 MsgInfo *tmp_msginfo = NULL;
11850 gboolean originally_enc = FALSE;
11851 gboolean originally_sig = FALSE;
11852 Compose *compose = NULL;
11853 gchar *s_system = NULL;
11855 cm_return_if_fail(msgview != NULL);
11857 cm_return_if_fail(msginfo_list != NULL);
11859 if (g_slist_length(msginfo_list) == 1 && !opening_multiple) {
11860 MimeInfo *mimeinfo = messageview_get_selected_mime_part(msgview);
11861 MsgInfo *orig_msginfo = (MsgInfo *)msginfo_list->data;
11863 if (mimeinfo != NULL && mimeinfo->type == MIMETYPE_MESSAGE &&
11864 !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
11865 tmp_msginfo = procmsg_msginfo_new_from_mimeinfo(
11866 orig_msginfo, mimeinfo);
11867 if (tmp_msginfo != NULL) {
11868 new_msglist = g_slist_append(NULL, tmp_msginfo);
11870 originally_enc = MSG_IS_ENCRYPTED(orig_msginfo->flags);
11871 privacy_msginfo_get_signed_state(orig_msginfo, &s_system);
11872 originally_sig = MSG_IS_SIGNED(orig_msginfo->flags);
11874 tmp_msginfo->folder = orig_msginfo->folder;
11875 tmp_msginfo->msgnum = orig_msginfo->msgnum;
11876 if (orig_msginfo->tags) {
11877 tmp_msginfo->tags = g_slist_copy(orig_msginfo->tags);
11878 tmp_msginfo->folder->tags_dirty = TRUE;
11884 if (!opening_multiple)
11885 body = messageview_get_selection(msgview);
11888 compose = compose_reply_mode((ComposeMode)action, new_msglist, body);
11889 procmsg_msginfo_free(&tmp_msginfo);
11890 g_slist_free(new_msglist);
11892 compose = compose_reply_mode((ComposeMode)action, msginfo_list, body);
11894 if (compose && originally_enc) {
11895 compose_force_encryption(compose, compose->account, FALSE, s_system);
11898 if (compose && originally_sig && compose->account->default_sign_reply) {
11899 compose_force_signing(compose, compose->account, s_system);
11903 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
11906 void compose_reply_from_messageview(MessageView *msgview, GSList *msginfo_list,
11909 if ((!prefs_common.forward_as_attachment || action != COMPOSE_FORWARD)
11910 && action != COMPOSE_FORWARD_AS_ATTACH && g_slist_length(msginfo_list) > 1) {
11911 GSList *cur = msginfo_list;
11912 gchar *msg = g_strdup_printf(_("You are about to reply to %d "
11913 "messages. Opening the windows "
11914 "could take some time. Do you "
11915 "want to continue?"),
11916 g_slist_length(msginfo_list));
11917 if (g_slist_length(msginfo_list) > 9
11918 && alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, "+" GTK_STOCK_YES, NULL)
11919 != G_ALERTALTERNATE) {
11924 /* We'll open multiple compose windows */
11925 /* let the WM place the next windows */
11926 compose_force_window_origin = FALSE;
11927 for (; cur; cur = cur->next) {
11929 tmplist.data = cur->data;
11930 tmplist.next = NULL;
11931 compose_reply_from_messageview_real(msgview, &tmplist, action, TRUE);
11933 compose_force_window_origin = TRUE;
11935 /* forwarding multiple mails as attachments is done via a
11936 * single compose window */
11937 compose_reply_from_messageview_real(msgview, msginfo_list, action, FALSE);
11941 void compose_check_for_email_account(Compose *compose)
11943 PrefsAccount *ac = NULL, *curr = NULL;
11949 if (compose->account && compose->account->protocol == A_NNTP) {
11950 ac = account_get_cur_account();
11951 if (ac->protocol == A_NNTP) {
11952 list = account_get_list();
11954 for( ; list != NULL ; list = g_list_next(list)) {
11955 curr = (PrefsAccount *) list->data;
11956 if (curr->protocol != A_NNTP) {
11962 combobox_select_by_data(GTK_COMBO_BOX(compose->account_combo),
11967 void compose_reply_to_address(MessageView *msgview, MsgInfo *msginfo,
11968 const gchar *address)
11970 GSList *msginfo_list = NULL;
11971 gchar *body = messageview_get_selection(msgview);
11974 msginfo_list = g_slist_prepend(msginfo_list, msginfo);
11976 compose = compose_reply_mode(COMPOSE_REPLY_TO_ADDRESS, msginfo_list, body);
11977 compose_check_for_email_account(compose);
11978 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
11979 compose_entry_append(compose, address, COMPOSE_TO, PREF_NONE);
11980 compose_reply_set_subject(compose, msginfo);
11983 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
11986 void compose_set_position(Compose *compose, gint pos)
11988 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11990 gtkut_text_view_set_position(text, pos);
11993 gboolean compose_search_string(Compose *compose,
11994 const gchar *str, gboolean case_sens)
11996 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11998 return gtkut_text_view_search_string(text, str, case_sens);
12001 gboolean compose_search_string_backward(Compose *compose,
12002 const gchar *str, gboolean case_sens)
12004 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
12006 return gtkut_text_view_search_string_backward(text, str, case_sens);
12009 /* allocate a msginfo structure and populate its data from a compose data structure */
12010 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose)
12012 MsgInfo *newmsginfo;
12014 gchar buf[BUFFSIZE];
12016 cm_return_val_if_fail( compose != NULL, NULL );
12018 newmsginfo = procmsg_msginfo_new();
12021 get_rfc822_date(buf, sizeof(buf));
12022 newmsginfo->date = g_strdup(buf);
12025 if (compose->from_name) {
12026 newmsginfo->from = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
12027 newmsginfo->fromname = procheader_get_fromname(newmsginfo->from);
12031 if (compose->subject_entry)
12032 newmsginfo->subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
12034 /* to, cc, reply-to, newsgroups */
12035 for (list = compose->header_list; list; list = list->next) {
12036 gchar *header = gtk_editable_get_chars(
12038 gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
12039 gchar *entry = gtk_editable_get_chars(
12040 GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
12042 if ( strcasecmp(header, prefs_common_translated_header_name("To:")) == 0 ) {
12043 if ( newmsginfo->to == NULL ) {
12044 newmsginfo->to = g_strdup(entry);
12045 } else if (entry && *entry) {
12046 gchar *tmp = g_strconcat(newmsginfo->to, ", ", entry, NULL);
12047 g_free(newmsginfo->to);
12048 newmsginfo->to = tmp;
12051 if ( strcasecmp(header, prefs_common_translated_header_name("Cc:")) == 0 ) {
12052 if ( newmsginfo->cc == NULL ) {
12053 newmsginfo->cc = g_strdup(entry);
12054 } else if (entry && *entry) {
12055 gchar *tmp = g_strconcat(newmsginfo->cc, ", ", entry, NULL);
12056 g_free(newmsginfo->cc);
12057 newmsginfo->cc = tmp;
12060 if ( strcasecmp(header,
12061 prefs_common_translated_header_name("Newsgroups:")) == 0 ) {
12062 if ( newmsginfo->newsgroups == NULL ) {
12063 newmsginfo->newsgroups = g_strdup(entry);
12064 } else if (entry && *entry) {
12065 gchar *tmp = g_strconcat(newmsginfo->newsgroups, ", ", entry, NULL);
12066 g_free(newmsginfo->newsgroups);
12067 newmsginfo->newsgroups = tmp;
12075 /* other data is unset */
12081 /* update compose's dictionaries from folder dict settings */
12082 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
12083 FolderItem *folder_item)
12085 cm_return_if_fail(compose != NULL);
12087 if (compose->gtkaspell && folder_item && folder_item->prefs) {
12088 FolderItemPrefs *prefs = folder_item->prefs;
12090 if (prefs->enable_default_dictionary)
12091 gtkaspell_change_dict(compose->gtkaspell,
12092 prefs->default_dictionary, FALSE);
12093 if (folder_item->prefs->enable_default_alt_dictionary)
12094 gtkaspell_change_alt_dict(compose->gtkaspell,
12095 prefs->default_alt_dictionary);
12096 if (prefs->enable_default_dictionary
12097 || prefs->enable_default_alt_dictionary)
12098 compose_spell_menu_changed(compose);
12103 static void compose_subject_entry_activated(GtkWidget *widget, gpointer data)
12105 Compose *compose = (Compose *)data;
12107 cm_return_if_fail(compose != NULL);
12109 gtk_widget_grab_focus(compose->text);
12112 static void from_name_activate_cb(GtkWidget *widget, gpointer data)
12114 gtk_combo_box_popup(GTK_COMBO_BOX(data));