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:"))) {
5073 if (compose->batch) {
5074 gtk_widget_show_all(compose->window);
5076 text = g_strdup_printf(_("The only recipient is the default '%s' address. Send anyway?"),
5077 prefs_common_translated_header_name("Bcc"));
5078 aval = alertpanel(_("Send"),
5080 GTK_STOCK_CANCEL, g_strconcat("+", _("_Send"), NULL), NULL);
5082 if (aval != G_ALERTALTERNATE)
5089 static gboolean compose_check_entries(Compose *compose, gboolean check_everything)
5093 if (compose_check_for_valid_recipient(compose) == FALSE) {
5094 if (compose->batch) {
5095 gtk_widget_show_all(compose->window);
5097 alertpanel_error(_("Recipient is not specified."));
5101 if (compose_check_for_set_recipients(compose) == FALSE) {
5105 if (!compose->batch && prefs_common.warn_empty_subj == TRUE) {
5106 str = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5107 if (*str == '\0' && check_everything == TRUE &&
5108 compose->mode != COMPOSE_REDIRECT) {
5110 gchar *button_label;
5113 if (compose->sending)
5114 button_label = g_strconcat("+", _("_Send"), NULL);
5116 button_label = g_strconcat("+", _("_Queue"), NULL);
5117 message = g_strdup_printf(_("Subject is empty. %s"),
5118 compose->sending?_("Send it anyway?"):
5119 _("Queue it anyway?"));
5121 aval = alertpanel_full(compose->sending?_("Send"):_("Send later"), message,
5122 GTK_STOCK_CANCEL, button_label, NULL, TRUE, NULL,
5123 ALERT_QUESTION, G_ALERTDEFAULT);
5125 if (aval & G_ALERTDISABLE) {
5126 aval &= ~G_ALERTDISABLE;
5127 prefs_common.warn_empty_subj = FALSE;
5129 if (aval != G_ALERTALTERNATE)
5134 if (check_everything && hooks_invoke(COMPOSE_CHECK_BEFORE_SEND_HOOKLIST, compose))
5140 gint compose_send(Compose *compose)
5143 FolderItem *folder = NULL;
5145 gchar *msgpath = NULL;
5146 gboolean discard_window = FALSE;
5147 gchar *errstr = NULL;
5148 gchar *tmsgid = NULL;
5149 MainWindow *mainwin = mainwindow_get_mainwindow();
5150 gboolean queued_removed = FALSE;
5152 if (prefs_common.send_dialog_invisible
5153 || compose->batch == TRUE)
5154 discard_window = TRUE;
5156 compose_allow_user_actions (compose, FALSE);
5157 compose->sending = TRUE;
5159 if (compose_check_entries(compose, TRUE) == FALSE) {
5160 if (compose->batch) {
5161 gtk_widget_show_all(compose->window);
5167 val = compose_queue(compose, &msgnum, &folder, &msgpath, TRUE);
5170 if (compose->batch) {
5171 gtk_widget_show_all(compose->window);
5174 alertpanel_error(_("Could not queue message for sending:\n\n"
5175 "Charset conversion failed."));
5176 } else if (val == -5) {
5177 alertpanel_error(_("Could not queue message for sending:\n\n"
5178 "Couldn't get recipient encryption key."));
5179 } else if (val == -6) {
5181 } else if (val == -3) {
5182 if (privacy_peek_error())
5183 alertpanel_error(_("Could not queue message for sending:\n\n"
5184 "Signature failed: %s"), privacy_get_error());
5185 } else if (val == -2 && errno != 0) {
5186 alertpanel_error(_("Could not queue message for sending:\n\n%s."), g_strerror(errno));
5188 alertpanel_error(_("Could not queue message for sending."));
5193 tmsgid = compose->msgid ? g_strdup(compose->msgid) : NULL;
5194 if (discard_window) {
5195 compose->sending = FALSE;
5196 compose_close(compose);
5197 /* No more compose access in the normal codepath
5198 * after this point! */
5203 alertpanel_error(_("The message was queued but could not be "
5204 "sent.\nUse \"Send queued messages\" from "
5205 "the main window to retry."));
5206 if (!discard_window) {
5213 if (msgpath == NULL) {
5214 msgpath = folder_item_fetch_msg(folder, msgnum);
5215 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5218 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5219 claws_unlink(msgpath);
5222 if (!discard_window) {
5224 if (!queued_removed)
5225 folder_item_remove_msg(folder, msgnum);
5226 folder_item_scan(folder);
5228 /* make sure we delete that */
5229 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5231 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5232 folder_item_remove_msg(folder, tmp->msgnum);
5233 procmsg_msginfo_free(&tmp);
5240 if (!queued_removed)
5241 folder_item_remove_msg(folder, msgnum);
5242 folder_item_scan(folder);
5244 /* make sure we delete that */
5245 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5247 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5248 folder_item_remove_msg(folder, tmp->msgnum);
5249 procmsg_msginfo_free(&tmp);
5252 if (!discard_window) {
5253 compose->sending = FALSE;
5254 compose_allow_user_actions (compose, TRUE);
5255 compose_close(compose);
5259 alertpanel_error_log(_("%s\nUse \"Send queued messages\" from "
5260 "the main window to retry."), errstr);
5263 alertpanel_error_log(_("The message was queued but could not be "
5264 "sent.\nUse \"Send queued messages\" from "
5265 "the main window to retry."));
5267 if (!discard_window) {
5276 toolbar_main_set_sensitive(mainwin);
5277 main_window_set_menu_sensitive(mainwin);
5283 compose_allow_user_actions (compose, TRUE);
5284 compose->sending = FALSE;
5285 compose->modified = TRUE;
5286 toolbar_main_set_sensitive(mainwin);
5287 main_window_set_menu_sensitive(mainwin);
5292 static gboolean compose_use_attach(Compose *compose)
5294 GtkTreeModel *model = gtk_tree_view_get_model
5295 (GTK_TREE_VIEW(compose->attach_clist));
5296 return gtk_tree_model_iter_n_children(model, NULL) > 0;
5299 static gint compose_redirect_write_headers_from_headerlist(Compose *compose,
5302 gchar buf[BUFFSIZE];
5304 gboolean first_to_address;
5305 gboolean first_cc_address;
5307 ComposeHeaderEntry *headerentry;
5308 const gchar *headerentryname;
5309 const gchar *cc_hdr;
5310 const gchar *to_hdr;
5311 gboolean err = FALSE;
5313 debug_print("Writing redirect header\n");
5315 cc_hdr = prefs_common_translated_header_name("Cc:");
5316 to_hdr = prefs_common_translated_header_name("To:");
5318 first_to_address = TRUE;
5319 for (list = compose->header_list; list; list = list->next) {
5320 headerentry = ((ComposeHeaderEntry *)list->data);
5321 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5323 if (g_utf8_collate(headerentryname, to_hdr) == 0) {
5324 const gchar *entstr = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5325 Xstrdup_a(str, entstr, return -1);
5327 if (str[0] != '\0') {
5328 compose_convert_header
5329 (compose, buf, sizeof(buf), str,
5330 strlen("Resent-To") + 2, TRUE);
5332 if (first_to_address) {
5333 err |= (fprintf(fp, "Resent-To: ") < 0);
5334 first_to_address = FALSE;
5336 err |= (fprintf(fp, ",") < 0);
5338 err |= (fprintf(fp, "%s", buf) < 0);
5342 if (!first_to_address) {
5343 err |= (fprintf(fp, "\n") < 0);
5346 first_cc_address = TRUE;
5347 for (list = compose->header_list; list; list = list->next) {
5348 headerentry = ((ComposeHeaderEntry *)list->data);
5349 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5351 if (g_utf8_collate(headerentryname, cc_hdr) == 0) {
5352 const gchar *strg = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5353 Xstrdup_a(str, strg, return -1);
5355 if (str[0] != '\0') {
5356 compose_convert_header
5357 (compose, buf, sizeof(buf), str,
5358 strlen("Resent-Cc") + 2, TRUE);
5360 if (first_cc_address) {
5361 err |= (fprintf(fp, "Resent-Cc: ") < 0);
5362 first_cc_address = FALSE;
5364 err |= (fprintf(fp, ",") < 0);
5366 err |= (fprintf(fp, "%s", buf) < 0);
5370 if (!first_cc_address) {
5371 err |= (fprintf(fp, "\n") < 0);
5374 return (err ? -1:0);
5377 static gint compose_redirect_write_headers(Compose *compose, FILE *fp)
5379 gchar buf[BUFFSIZE];
5381 const gchar *entstr;
5382 /* struct utsname utsbuf; */
5383 gboolean err = FALSE;
5385 cm_return_val_if_fail(fp != NULL, -1);
5386 cm_return_val_if_fail(compose->account != NULL, -1);
5387 cm_return_val_if_fail(compose->account->address != NULL, -1);
5390 if (prefs_common.hide_timezone)
5391 get_rfc822_date_hide_tz(buf, sizeof(buf));
5393 get_rfc822_date(buf, sizeof(buf));
5394 err |= (fprintf(fp, "Resent-Date: %s\n", buf) < 0);
5397 if (compose->account->name && *compose->account->name) {
5398 compose_convert_header
5399 (compose, buf, sizeof(buf), compose->account->name,
5400 strlen("From: "), TRUE);
5401 err |= (fprintf(fp, "Resent-From: %s <%s>\n",
5402 buf, compose->account->address) < 0);
5404 err |= (fprintf(fp, "Resent-From: %s\n", compose->account->address) < 0);
5407 entstr = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5408 if (*entstr != '\0') {
5409 Xstrdup_a(str, entstr, return -1);
5412 compose_convert_header(compose, buf, sizeof(buf), str,
5413 strlen("Subject: "), FALSE);
5414 err |= (fprintf(fp, "Subject: %s\n", buf) < 0);
5418 /* Resent-Message-ID */
5419 if (compose->account->gen_msgid) {
5420 gchar *addr = prefs_account_generate_msgid(compose->account);
5421 err |= (fprintf(fp, "Resent-Message-ID: <%s>\n", addr) < 0);
5423 g_free(compose->msgid);
5424 compose->msgid = addr;
5426 compose->msgid = NULL;
5429 if (compose_redirect_write_headers_from_headerlist(compose, fp))
5432 /* separator between header and body */
5433 err |= (fputs("\n", fp) == EOF);
5435 return (err ? -1:0);
5438 static gint compose_redirect_write_to_file(Compose *compose, FILE *fdest)
5442 gchar buf[BUFFSIZE];
5444 gboolean skip = FALSE;
5445 gboolean err = FALSE;
5446 gchar *not_included[]={
5447 "Return-Path:", "Delivered-To:", "Received:",
5448 "Subject:", "X-UIDL:", "AF:",
5449 "NF:", "PS:", "SRH:",
5450 "SFN:", "DSR:", "MID:",
5451 "CFG:", "PT:", "S:",
5452 "RQ:", "SSV:", "NSV:",
5453 "SSH:", "R:", "MAID:",
5454 "NAID:", "RMID:", "FMID:",
5455 "SCF:", "RRCPT:", "NG:",
5456 "X-Claws-Privacy", "X-Claws-Sign:", "X-Claws-Encrypt",
5457 "X-Claws-End-Special-Headers:", "X-Claws-Account-Id:",
5458 "X-Sylpheed-Privacy", "X-Sylpheed-Sign:", "X-Sylpheed-Encrypt",
5459 "X-Sylpheed-End-Special-Headers:", "X-Sylpheed-Account-Id:",
5460 "X-Claws-Auto-Wrapping:", "X-Claws-Auto-Indent:",
5463 if ((fp = g_fopen(compose->redirect_filename, "rb")) == NULL) {
5464 FILE_OP_ERROR(compose->redirect_filename, "fopen");
5468 while (procheader_get_one_field_asis(buf, sizeof(buf), fp) != -1) {
5470 for (i = 0; not_included[i] != NULL; i++) {
5471 if (g_ascii_strncasecmp(buf, not_included[i],
5472 strlen(not_included[i])) == 0) {
5479 if (fputs(buf, fdest) == -1)
5482 if (!prefs_common.redirect_keep_from) {
5483 if (g_ascii_strncasecmp(buf, "From:",
5484 strlen("From:")) == 0) {
5485 err |= (fputs(" (by way of ", fdest) == EOF);
5486 if (compose->account->name
5487 && *compose->account->name) {
5488 compose_convert_header
5489 (compose, buf, sizeof(buf),
5490 compose->account->name,
5493 err |= (fprintf(fdest, "%s <%s>",
5495 compose->account->address) < 0);
5497 err |= (fprintf(fdest, "%s",
5498 compose->account->address) < 0);
5499 err |= (fputs(")", fdest) == EOF);
5503 if (fputs("\n", fdest) == -1)
5510 if (compose_redirect_write_headers(compose, fdest))
5513 while ((len = fread(buf, sizeof(gchar), sizeof(buf), fp)) > 0) {
5514 if (fwrite(buf, sizeof(gchar), len, fdest) != len)
5527 static gint compose_write_to_file(Compose *compose, FILE *fp, gint action, gboolean attach_parts)
5529 GtkTextBuffer *buffer;
5530 GtkTextIter start, end;
5531 gchar *chars, *tmp_enc_file, *content;
5533 const gchar *out_codeset;
5534 EncodingType encoding = ENC_UNKNOWN;
5535 MimeInfo *mimemsg, *mimetext;
5537 const gchar *src_codeset = CS_INTERNAL;
5538 gchar *from_addr = NULL;
5539 gchar *from_name = NULL;
5542 if (action == COMPOSE_WRITE_FOR_SEND) {
5543 attach_parts = TRUE;
5545 /* We're sending the message, generate a Message-ID
5547 if (compose->msgid == NULL &&
5548 compose->account->gen_msgid) {
5549 compose->msgid = prefs_account_generate_msgid(compose->account);
5553 /* create message MimeInfo */
5554 mimemsg = procmime_mimeinfo_new();
5555 mimemsg->type = MIMETYPE_MESSAGE;
5556 mimemsg->subtype = g_strdup("rfc822");
5557 mimemsg->content = MIMECONTENT_MEM;
5558 mimemsg->tmp = TRUE; /* must free content later */
5559 mimemsg->data.mem = compose_get_header(compose);
5561 /* Create text part MimeInfo */
5562 /* get all composed text */
5563 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5564 gtk_text_buffer_get_start_iter(buffer, &start);
5565 gtk_text_buffer_get_end_iter(buffer, &end);
5566 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5568 out_codeset = conv_get_charset_str(compose->out_encoding);
5570 if (!out_codeset && is_ascii_str(chars)) {
5571 out_codeset = CS_US_ASCII;
5572 } else if (prefs_common.outgoing_fallback_to_ascii &&
5573 is_ascii_str(chars)) {
5574 out_codeset = CS_US_ASCII;
5575 encoding = ENC_7BIT;
5579 gchar *test_conv_global_out = NULL;
5580 gchar *test_conv_reply = NULL;
5582 /* automatic mode. be automatic. */
5583 codeconv_set_strict(TRUE);
5585 out_codeset = conv_get_outgoing_charset_str();
5587 debug_print("trying to convert to %s\n", out_codeset);
5588 test_conv_global_out = conv_codeset_strdup(chars, src_codeset, out_codeset);
5591 if (!test_conv_global_out && compose->orig_charset
5592 && strcmp(compose->orig_charset, CS_US_ASCII)) {
5593 out_codeset = compose->orig_charset;
5594 debug_print("failure; trying to convert to %s\n", out_codeset);
5595 test_conv_reply = conv_codeset_strdup(chars, src_codeset, out_codeset);
5598 if (!test_conv_global_out && !test_conv_reply) {
5600 out_codeset = CS_INTERNAL;
5601 debug_print("failure; finally using %s\n", out_codeset);
5603 g_free(test_conv_global_out);
5604 g_free(test_conv_reply);
5605 codeconv_set_strict(FALSE);
5608 if (encoding == ENC_UNKNOWN) {
5609 if (prefs_common.encoding_method == CTE_BASE64)
5610 encoding = ENC_BASE64;
5611 else if (prefs_common.encoding_method == CTE_QUOTED_PRINTABLE)
5612 encoding = ENC_QUOTED_PRINTABLE;
5613 else if (prefs_common.encoding_method == CTE_8BIT)
5614 encoding = ENC_8BIT;
5616 encoding = procmime_get_encoding_for_charset(out_codeset);
5619 debug_print("src encoding = %s, out encoding = %s, transfer encoding = %s\n",
5620 src_codeset, out_codeset, procmime_get_encoding_str(encoding));
5622 if (action == COMPOSE_WRITE_FOR_SEND) {
5623 codeconv_set_strict(TRUE);
5624 buf = conv_codeset_strdup(chars, src_codeset, out_codeset);
5625 codeconv_set_strict(FALSE);
5630 msg = g_strdup_printf(_("Can't convert the character encoding of the message \n"
5631 "to the specified %s charset.\n"
5632 "Send it as %s?"), out_codeset, src_codeset);
5633 aval = alertpanel_full(_("Error"), msg, GTK_STOCK_CANCEL,
5634 g_strconcat("+", _("_Send"), NULL), NULL, FALSE,
5635 NULL, ALERT_ERROR, G_ALERTDEFAULT);
5638 if (aval != G_ALERTALTERNATE) {
5643 out_codeset = src_codeset;
5649 out_codeset = src_codeset;
5654 if (prefs_common.rewrite_first_from && (encoding == ENC_8BIT || encoding == ENC_7BIT)) {
5655 if (!strncmp(buf, "From ", sizeof("From ")-1) ||
5656 strstr(buf, "\nFrom ") != NULL) {
5657 encoding = ENC_QUOTED_PRINTABLE;
5661 mimetext = procmime_mimeinfo_new();
5662 mimetext->content = MIMECONTENT_MEM;
5663 mimetext->tmp = TRUE; /* must free content later */
5664 /* dup'ed because procmime_encode_content can turn it into a tmpfile
5665 * and free the data, which we need later. */
5666 mimetext->data.mem = g_strdup(buf);
5667 mimetext->type = MIMETYPE_TEXT;
5668 mimetext->subtype = g_strdup("plain");
5669 g_hash_table_insert(mimetext->typeparameters, g_strdup("charset"),
5670 g_strdup(out_codeset));
5672 /* protect trailing spaces when signing message */
5673 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5674 privacy_system_can_sign(compose->privacy_system)) {
5675 encoding = ENC_QUOTED_PRINTABLE;
5679 debug_print("main text: %Id bytes encoded as %s in %d\n",
5681 debug_print("main text: %zd bytes encoded as %s in %d\n",
5683 strlen(buf), out_codeset, encoding);
5685 /* check for line length limit */
5686 if (action == COMPOSE_WRITE_FOR_SEND &&
5687 encoding != ENC_QUOTED_PRINTABLE && encoding != ENC_BASE64 &&
5688 check_line_length(buf, 1000, &line) < 0) {
5691 msg = g_strdup_printf
5692 (_("Line %d exceeds the line length limit (998 bytes).\n"
5693 "The contents of the message might be broken on the way to the delivery.\n"
5695 "Send it anyway?"), line + 1);
5696 aval = alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, GTK_STOCK_OK, NULL);
5698 if (aval != G_ALERTALTERNATE) {
5704 if (encoding != ENC_UNKNOWN)
5705 procmime_encode_content(mimetext, encoding);
5707 /* append attachment parts */
5708 if (compose_use_attach(compose) && attach_parts) {
5709 MimeInfo *mimempart;
5710 gchar *boundary = NULL;
5711 mimempart = procmime_mimeinfo_new();
5712 mimempart->content = MIMECONTENT_EMPTY;
5713 mimempart->type = MIMETYPE_MULTIPART;
5714 mimempart->subtype = g_strdup("mixed");
5718 boundary = generate_mime_boundary(NULL);
5719 } while (strstr(buf, boundary) != NULL);
5721 g_hash_table_insert(mimempart->typeparameters, g_strdup("boundary"),
5724 mimetext->disposition = DISPOSITIONTYPE_INLINE;
5726 g_node_append(mimempart->node, mimetext->node);
5727 g_node_append(mimemsg->node, mimempart->node);
5729 if (compose_add_attachments(compose, mimempart) < 0)
5732 g_node_append(mimemsg->node, mimetext->node);
5736 if (strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) != 0) {
5737 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
5738 /* extract name and address */
5739 if (strstr(spec, " <") && strstr(spec, ">")) {
5740 from_addr = g_strdup(strrchr(spec, '<')+1);
5741 *(strrchr(from_addr, '>')) = '\0';
5742 from_name = g_strdup(spec);
5743 *(strrchr(from_name, '<')) = '\0';
5750 /* sign message if sending */
5751 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5752 privacy_system_can_sign(compose->privacy_system))
5753 if (!privacy_sign(compose->privacy_system, mimemsg,
5754 compose->account, from_addr)) {
5762 if (compose->use_encryption) {
5763 if (compose->encdata != NULL &&
5764 strcmp(compose->encdata, "_DONT_ENCRYPT_")) {
5766 /* First, write an unencrypted copy and save it to outbox, if
5767 * user wants that. */
5768 if (compose->account->save_encrypted_as_clear_text) {
5769 debug_print("saving sent message unencrypted...\n");
5770 FILE *tmpfp = get_tmpfile_in_dir(get_mime_tmp_dir(), &tmp_enc_file);
5774 /* fp now points to a file with headers written,
5775 * let's make a copy. */
5777 content = file_read_stream_to_str(fp);
5779 str_write_to_file(content, tmp_enc_file);
5782 /* Now write the unencrypted body. */
5783 if ((tmpfp = g_fopen(tmp_enc_file, "a")) != NULL) {
5784 procmime_write_mimeinfo(mimemsg, tmpfp);
5787 outbox = folder_find_item_from_identifier(compose_get_save_to(compose));
5789 outbox = folder_get_default_outbox();
5791 procmsg_save_to_outbox(outbox, tmp_enc_file, TRUE);
5792 claws_unlink(tmp_enc_file);
5794 g_warning("Can't open file '%s'", tmp_enc_file);
5797 g_warning("couldn't get tempfile");
5800 if (!privacy_encrypt(compose->privacy_system, mimemsg, compose->encdata)) {
5801 debug_print("Couldn't encrypt mime structure: %s.\n",
5802 privacy_get_error());
5803 alertpanel_error(_("Couldn't encrypt the email: %s"),
5804 privacy_get_error());
5809 procmime_write_mimeinfo(mimemsg, fp);
5811 procmime_mimeinfo_free_all(&mimemsg);
5816 static gint compose_write_body_to_file(Compose *compose, const gchar *file)
5818 GtkTextBuffer *buffer;
5819 GtkTextIter start, end;
5824 if ((fp = g_fopen(file, "wb")) == NULL) {
5825 FILE_OP_ERROR(file, "fopen");
5829 /* chmod for security */
5830 if (change_file_mode_rw(fp, file) < 0) {
5831 FILE_OP_ERROR(file, "chmod");
5832 g_warning("can't change file mode");
5835 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5836 gtk_text_buffer_get_start_iter(buffer, &start);
5837 gtk_text_buffer_get_end_iter(buffer, &end);
5838 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5840 chars = conv_codeset_strdup
5841 (tmp, CS_INTERNAL, conv_get_locale_charset_str());
5850 len = strlen(chars);
5851 if (fwrite(chars, sizeof(gchar), len, fp) != len) {
5852 FILE_OP_ERROR(file, "fwrite");
5861 if (fclose(fp) == EOF) {
5862 FILE_OP_ERROR(file, "fclose");
5869 static gint compose_remove_reedit_target(Compose *compose, gboolean force)
5872 MsgInfo *msginfo = compose->targetinfo;
5874 cm_return_val_if_fail(compose->mode == COMPOSE_REEDIT, -1);
5875 if (!msginfo) return -1;
5877 if (!force && MSG_IS_LOCKED(msginfo->flags))
5880 item = msginfo->folder;
5881 cm_return_val_if_fail(item != NULL, -1);
5883 if (procmsg_msg_exist(msginfo) &&
5884 (folder_has_parent_of_type(item, F_QUEUE) ||
5885 folder_has_parent_of_type(item, F_DRAFT)
5886 || msginfo == compose->autosaved_draft)) {
5887 if (folder_item_remove_msg(item, msginfo->msgnum) < 0) {
5888 g_warning("can't remove the old message");
5891 debug_print("removed reedit target %d\n", msginfo->msgnum);
5898 static void compose_remove_draft(Compose *compose)
5901 MsgInfo *msginfo = compose->targetinfo;
5902 drafts = account_get_special_folder(compose->account, F_DRAFT);
5904 if (procmsg_msg_exist(msginfo)) {
5905 folder_item_remove_msg(drafts, msginfo->msgnum);
5910 gint compose_queue(Compose *compose, gint *msgnum, FolderItem **item, gchar **msgpath,
5911 gboolean remove_reedit_target)
5913 return compose_queue_sub (compose, msgnum, item, msgpath, FALSE, remove_reedit_target);
5916 static gboolean compose_warn_encryption(Compose *compose)
5918 const gchar *warning = privacy_get_encrypt_warning(compose->privacy_system);
5919 AlertValue val = G_ALERTALTERNATE;
5921 if (warning == NULL)
5924 val = alertpanel_full(_("Encryption warning"), warning,
5925 GTK_STOCK_CANCEL, g_strconcat("+", _("C_ontinue"), NULL), NULL,
5926 TRUE, NULL, ALERT_WARNING, G_ALERTALTERNATE);
5927 if (val & G_ALERTDISABLE) {
5928 val &= ~G_ALERTDISABLE;
5929 if (val == G_ALERTALTERNATE)
5930 privacy_inhibit_encrypt_warning(compose->privacy_system,
5934 if (val == G_ALERTALTERNATE) {
5941 static gint compose_queue_sub(Compose *compose, gint *msgnum, FolderItem **item,
5942 gchar **msgpath, gboolean check_subject,
5943 gboolean remove_reedit_target)
5950 PrefsAccount *mailac = NULL, *newsac = NULL;
5951 gboolean err = FALSE;
5953 debug_print("queueing message...\n");
5954 cm_return_val_if_fail(compose->account != NULL, -1);
5956 if (compose_check_entries(compose, check_subject) == FALSE) {
5957 if (compose->batch) {
5958 gtk_widget_show_all(compose->window);
5963 if (!compose->to_list && !compose->newsgroup_list) {
5964 g_warning("can't get recipient list.");
5968 if (compose->to_list) {
5969 if (compose->account->protocol != A_NNTP)
5970 mailac = compose->account;
5971 else if (cur_account && cur_account->protocol != A_NNTP)
5972 mailac = cur_account;
5973 else if (!(mailac = compose_current_mail_account())) {
5974 alertpanel_error(_("No account for sending mails available!"));
5979 if (compose->newsgroup_list) {
5980 if (compose->account->protocol == A_NNTP)
5981 newsac = compose->account;
5983 alertpanel_error(_("Selected account isn't NNTP: Posting is impossible."));
5988 /* write queue header */
5989 tmp = g_strdup_printf("%s%cqueue.%p%08x", get_tmp_dir(),
5990 G_DIR_SEPARATOR, compose, (guint) rand());
5991 debug_print("queuing to %s\n", tmp);
5992 if ((fp = g_fopen(tmp, "w+b")) == NULL) {
5993 FILE_OP_ERROR(tmp, "fopen");
5998 if (change_file_mode_rw(fp, tmp) < 0) {
5999 FILE_OP_ERROR(tmp, "chmod");
6000 g_warning("can't change file mode");
6003 /* queueing variables */
6004 err |= (fprintf(fp, "AF:\n") < 0);
6005 err |= (fprintf(fp, "NF:0\n") < 0);
6006 err |= (fprintf(fp, "PS:10\n") < 0);
6007 err |= (fprintf(fp, "SRH:1\n") < 0);
6008 err |= (fprintf(fp, "SFN:\n") < 0);
6009 err |= (fprintf(fp, "DSR:\n") < 0);
6011 err |= (fprintf(fp, "MID:<%s>\n", compose->msgid) < 0);
6013 err |= (fprintf(fp, "MID:\n") < 0);
6014 err |= (fprintf(fp, "CFG:\n") < 0);
6015 err |= (fprintf(fp, "PT:0\n") < 0);
6016 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
6017 err |= (fprintf(fp, "RQ:\n") < 0);
6019 err |= (fprintf(fp, "SSV:%s\n", mailac->smtp_server) < 0);
6021 err |= (fprintf(fp, "SSV:\n") < 0);
6023 err |= (fprintf(fp, "NSV:%s\n", newsac->nntp_server) < 0);
6025 err |= (fprintf(fp, "NSV:\n") < 0);
6026 err |= (fprintf(fp, "SSH:\n") < 0);
6027 /* write recepient list */
6028 if (compose->to_list) {
6029 err |= (fprintf(fp, "R:<%s>", (gchar *)compose->to_list->data) < 0);
6030 for (cur = compose->to_list->next; cur != NULL;
6032 err |= (fprintf(fp, ",<%s>", (gchar *)cur->data) < 0);
6033 err |= (fprintf(fp, "\n") < 0);
6035 /* write newsgroup list */
6036 if (compose->newsgroup_list) {
6037 err |= (fprintf(fp, "NG:") < 0);
6038 err |= (fprintf(fp, "%s", (gchar *)compose->newsgroup_list->data) < 0);
6039 for (cur = compose->newsgroup_list->next; cur != NULL; cur = cur->next)
6040 err |= (fprintf(fp, ",%s", (gchar *)cur->data) < 0);
6041 err |= (fprintf(fp, "\n") < 0);
6043 /* Sylpheed account IDs */
6045 err |= (fprintf(fp, "MAID:%d\n", mailac->account_id) < 0);
6047 err |= (fprintf(fp, "NAID:%d\n", newsac->account_id) < 0);
6050 if (compose->privacy_system != NULL) {
6051 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
6052 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
6053 if (compose->use_encryption) {
6054 if (!compose_warn_encryption(compose)) {
6060 if (mailac && mailac->encrypt_to_self) {
6061 GSList *tmp_list = g_slist_copy(compose->to_list);
6062 tmp_list = g_slist_append(tmp_list, compose->account->address);
6063 compose->encdata = privacy_get_encrypt_data(compose->privacy_system, tmp_list);
6064 g_slist_free(tmp_list);
6066 compose->encdata = privacy_get_encrypt_data(compose->privacy_system, compose->to_list);
6068 if (compose->encdata != NULL) {
6069 if (strcmp(compose->encdata, "_DONT_ENCRYPT_")) {
6070 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
6071 err |= (fprintf(fp, "X-Claws-Encrypt-Data:%s\n",
6072 compose->encdata) < 0);
6073 } /* else we finally dont want to encrypt */
6075 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
6076 /* and if encdata was null, it means there's been a problem in
6079 g_warning("failed to write queue message");
6088 /* Save copy folder */
6089 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
6090 gchar *savefolderid;
6092 savefolderid = compose_get_save_to(compose);
6093 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
6094 g_free(savefolderid);
6096 /* Save copy folder */
6097 if (compose->return_receipt) {
6098 err |= (fprintf(fp, "RRCPT:1\n") < 0);
6100 /* Message-ID of message replying to */
6101 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
6102 gchar *folderid = NULL;
6104 if (compose->replyinfo->folder)
6105 folderid = folder_item_get_identifier(compose->replyinfo->folder);
6106 if (folderid == NULL)
6107 folderid = g_strdup("NULL");
6109 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
6112 /* Message-ID of message forwarding to */
6113 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
6114 gchar *folderid = NULL;
6116 if (compose->fwdinfo->folder)
6117 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
6118 if (folderid == NULL)
6119 folderid = g_strdup("NULL");
6121 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
6125 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
6126 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
6128 /* end of headers */
6129 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
6131 if (compose->redirect_filename != NULL) {
6132 if (compose_redirect_write_to_file(compose, fp) < 0) {
6140 if ((result = compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_SEND, TRUE)) < 0) {
6144 return result - 1; /* -2 for a generic error, -3 for signing error, -4 for encoding */
6148 g_warning("failed to write queue message");
6154 if (fclose(fp) == EOF) {
6155 FILE_OP_ERROR(tmp, "fclose");
6161 if (item && *item) {
6164 queue = account_get_special_folder(compose->account, F_QUEUE);
6167 g_warning("can't find queue folder");
6172 folder_item_scan(queue);
6173 if ((num = folder_item_add_msg(queue, tmp, NULL, FALSE)) < 0) {
6174 g_warning("can't queue the message");
6180 if (msgpath == NULL) {
6186 if (compose->mode == COMPOSE_REEDIT && remove_reedit_target) {
6187 compose_remove_reedit_target(compose, FALSE);
6190 if ((msgnum != NULL) && (item != NULL)) {
6198 static int compose_add_attachments(Compose *compose, MimeInfo *parent)
6201 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
6204 gchar *type, *subtype;
6205 GtkTreeModel *model;
6208 model = gtk_tree_view_get_model(tree_view);
6210 if (!gtk_tree_model_get_iter_first(model, &iter))
6213 gtk_tree_model_get(model, &iter,
6217 if (!is_file_exist(ainfo->file)) {
6218 gchar *msg = g_strdup_printf(_("Attachment %s doesn't exist anymore. Ignore?"), ainfo->file);
6219 AlertValue val = alertpanel_full(_("Warning"), msg, _("Cancel sending"), _("Ignore attachment"),
6220 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
6222 if (val == G_ALERTDEFAULT) {
6227 if (g_stat(ainfo->file, &statbuf) < 0)
6230 mimepart = procmime_mimeinfo_new();
6231 mimepart->content = MIMECONTENT_FILE;
6232 mimepart->data.filename = g_strdup(ainfo->file);
6233 mimepart->tmp = FALSE; /* or we destroy our attachment */
6234 mimepart->offset = 0;
6235 mimepart->length = statbuf.st_size;
6237 type = g_strdup(ainfo->content_type);
6239 if (!strchr(type, '/')) {
6241 type = g_strdup("application/octet-stream");
6244 subtype = strchr(type, '/') + 1;
6245 *(subtype - 1) = '\0';
6246 mimepart->type = procmime_get_media_type(type);
6247 mimepart->subtype = g_strdup(subtype);
6250 if (mimepart->type == MIMETYPE_MESSAGE &&
6251 !g_ascii_strcasecmp(mimepart->subtype, "rfc822")) {
6252 mimepart->disposition = DISPOSITIONTYPE_INLINE;
6253 } else if (mimepart->type == MIMETYPE_TEXT) {
6254 if (!ainfo->name && g_ascii_strcasecmp(mimepart->subtype, "plain")) {
6255 /* Text parts with no name come from multipart/alternative
6256 * forwards. Make sure the recipient won't look at the
6257 * original HTML part by mistake. */
6258 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6259 ainfo->name = g_strdup_printf(_("Original %s part"),
6263 g_hash_table_insert(mimepart->typeparameters,
6264 g_strdup("charset"), g_strdup(ainfo->charset));
6266 if (ainfo->name && mimepart->type != MIMETYPE_MESSAGE) {
6267 if (mimepart->type == MIMETYPE_APPLICATION &&
6268 !strcmp2(mimepart->subtype, "octet-stream"))
6269 g_hash_table_insert(mimepart->typeparameters,
6270 g_strdup("name"), g_strdup(ainfo->name));
6271 g_hash_table_insert(mimepart->dispositionparameters,
6272 g_strdup("filename"), g_strdup(ainfo->name));
6273 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6276 if (mimepart->type == MIMETYPE_MESSAGE
6277 || mimepart->type == MIMETYPE_MULTIPART)
6278 ainfo->encoding = ENC_BINARY;
6279 else if (compose->use_signing) {
6280 if (ainfo->encoding == ENC_7BIT)
6281 ainfo->encoding = ENC_QUOTED_PRINTABLE;
6282 else if (ainfo->encoding == ENC_8BIT)
6283 ainfo->encoding = ENC_BASE64;
6288 procmime_encode_content(mimepart, ainfo->encoding);
6290 g_node_append(parent->node, mimepart->node);
6291 } while (gtk_tree_model_iter_next(model, &iter));
6296 static gchar *compose_quote_list_of_addresses(gchar *str)
6298 GSList *list = NULL, *item = NULL;
6299 gchar *qname = NULL, *faddr = NULL, *result = NULL;
6301 list = address_list_append_with_comments(list, str);
6302 for (item = list; item != NULL; item = item->next) {
6303 gchar *spec = item->data;
6304 gchar *endofname = strstr(spec, " <");
6305 if (endofname != NULL) {
6308 QUOTE_IF_REQUIRED_NORMAL(qname, spec, return NULL);
6309 qqname = escape_internal_quotes(qname, '"');
6311 if (*qname != *spec || qqname != qname) { /* has been quoted, compute new */
6312 gchar *addr = g_strdup(endofname);
6313 gchar *name = (qqname != qname)? qqname: g_strdup(qname);
6314 faddr = g_strconcat(name, addr, NULL);
6317 debug_print("new auto-quoted address: '%s'\n", faddr);
6321 result = g_strdup((faddr != NULL)? faddr: spec);
6323 result = g_strconcat(result,
6325 (faddr != NULL)? faddr: spec,
6328 if (faddr != NULL) {
6333 slist_free_strings_full(list);
6338 #define IS_IN_CUSTOM_HEADER(header) \
6339 (compose->account->add_customhdr && \
6340 custom_header_find(compose->account->customhdr_list, header) != NULL)
6342 static void compose_add_headerfield_from_headerlist(Compose *compose,
6344 const gchar *fieldname,
6345 const gchar *seperator)
6347 gchar *str, *fieldname_w_colon;
6348 gboolean add_field = FALSE;
6350 ComposeHeaderEntry *headerentry;
6351 const gchar *headerentryname;
6352 const gchar *trans_fieldname;
6355 if (IS_IN_CUSTOM_HEADER(fieldname))
6358 debug_print("Adding %s-fields\n", fieldname);
6360 fieldstr = g_string_sized_new(64);
6362 fieldname_w_colon = g_strconcat(fieldname, ":", NULL);
6363 trans_fieldname = prefs_common_translated_header_name(fieldname_w_colon);
6365 for (list = compose->header_list; list; list = list->next) {
6366 headerentry = ((ComposeHeaderEntry *)list->data);
6367 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
6369 if (!g_utf8_collate(trans_fieldname, headerentryname)) {
6370 gchar * ustr = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
6372 str = compose_quote_list_of_addresses(ustr);
6374 if (str != NULL && str[0] != '\0') {
6376 g_string_append(fieldstr, seperator);
6377 g_string_append(fieldstr, str);
6386 buf = g_new0(gchar, fieldstr->len * 4 + 256);
6387 compose_convert_header
6388 (compose, buf, fieldstr->len * 4 + 256, fieldstr->str,
6389 strlen(fieldname) + 2, TRUE);
6390 g_string_append_printf(header, "%s: %s\n", fieldname, buf);
6394 g_free(fieldname_w_colon);
6395 g_string_free(fieldstr, TRUE);
6400 static gchar *compose_get_manual_headers_info(Compose *compose)
6402 GString *sh_header = g_string_new(" ");
6404 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6406 for (list = compose->header_list; list; list = list->next) {
6407 ComposeHeaderEntry *headerentry;
6410 gchar *headername_wcolon;
6411 const gchar *headername_trans;
6413 gboolean standard_header = FALSE;
6415 headerentry = ((ComposeHeaderEntry *)list->data);
6417 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6419 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6424 if (!strstr(tmp, ":")) {
6425 headername_wcolon = g_strconcat(tmp, ":", NULL);
6426 headername = g_strdup(tmp);
6428 headername_wcolon = g_strdup(tmp);
6429 headername = g_strdup(strtok(tmp, ":"));
6433 string = std_headers;
6434 while (*string != NULL) {
6435 headername_trans = prefs_common_translated_header_name(*string);
6436 if (!strcmp(headername_trans, headername_wcolon))
6437 standard_header = TRUE;
6440 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
6441 g_string_append_printf(sh_header, "%s ", headername);
6443 g_free(headername_wcolon);
6445 g_string_truncate(sh_header, strlen(sh_header->str) - 1); /* remove last space */
6446 return g_string_free(sh_header, FALSE);
6449 static gchar *compose_get_header(Compose *compose)
6451 gchar buf[BUFFSIZE];
6452 const gchar *entry_str;
6456 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6458 gchar *from_name = NULL, *from_address = NULL;
6461 cm_return_val_if_fail(compose->account != NULL, NULL);
6462 cm_return_val_if_fail(compose->account->address != NULL, NULL);
6464 header = g_string_sized_new(64);
6467 if (prefs_common.hide_timezone)
6468 get_rfc822_date_hide_tz(buf, sizeof(buf));
6470 get_rfc822_date(buf, sizeof(buf));
6471 g_string_append_printf(header, "Date: %s\n", buf);
6475 if (compose->account->name && *compose->account->name) {
6477 QUOTE_IF_REQUIRED(buf, compose->account->name);
6478 tmp = g_strdup_printf("%s <%s>",
6479 buf, compose->account->address);
6481 tmp = g_strdup_printf("%s",
6482 compose->account->address);
6484 if (!strcmp(gtk_entry_get_text(GTK_ENTRY(compose->from_name)), tmp)
6485 || strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) == 0) {
6487 from_name = compose->account->name ? g_strdup(compose->account->name):NULL;
6488 from_address = g_strdup(compose->account->address);
6490 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
6491 /* extract name and address */
6492 if (strstr(spec, " <") && strstr(spec, ">")) {
6493 from_address = g_strdup(strrchr(spec, '<')+1);
6494 *(strrchr(from_address, '>')) = '\0';
6495 from_name = g_strdup(spec);
6496 *(strrchr(from_name, '<')) = '\0';
6499 from_address = g_strdup(spec);
6506 if (from_name && *from_name) {
6508 compose_convert_header
6509 (compose, buf, sizeof(buf), from_name,
6510 strlen("From: "), TRUE);
6511 QUOTE_IF_REQUIRED(name, buf);
6512 qname = escape_internal_quotes(name, '"');
6514 g_string_append_printf(header, "From: %s <%s>\n",
6515 qname, from_address);
6516 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To") &&
6517 compose->return_receipt) {
6518 compose_convert_header(compose, buf, sizeof(buf), from_name,
6519 strlen("Disposition-Notification-To: "),
6521 g_string_append_printf(header, "Disposition-Notification-To: %s <%s>\n", buf, from_address);
6526 g_string_append_printf(header, "From: %s\n", from_address);
6527 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To") &&
6528 compose->return_receipt)
6529 g_string_append_printf(header, "Disposition-Notification-To: %s\n", from_address);
6533 g_free(from_address);
6536 compose_add_headerfield_from_headerlist(compose, header, "To", ", ");
6539 compose_add_headerfield_from_headerlist(compose, header, "Newsgroups", ",");
6542 compose_add_headerfield_from_headerlist(compose, header, "Cc", ", ");
6546 * If this account is a NNTP account remove Bcc header from
6547 * message body since it otherwise will be publicly shown
6549 if (compose->account->protocol != A_NNTP)
6550 compose_add_headerfield_from_headerlist(compose, header, "Bcc", ", ");
6553 str = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
6555 if (*str != '\0' && !IS_IN_CUSTOM_HEADER("Subject")) {
6558 compose_convert_header(compose, buf, sizeof(buf), str,
6559 strlen("Subject: "), FALSE);
6560 g_string_append_printf(header, "Subject: %s\n", buf);
6566 if (compose->msgid != NULL && strlen(compose->msgid) > 0) {
6567 g_string_append_printf(header, "Message-ID: <%s>\n",
6571 if (compose->remove_references == FALSE) {
6573 if (compose->inreplyto && compose->to_list)
6574 g_string_append_printf(header, "In-Reply-To: <%s>\n", compose->inreplyto);
6577 if (compose->references)
6578 g_string_append_printf(header, "References: %s\n", compose->references);
6582 compose_add_headerfield_from_headerlist(compose, header, "Followup-To", ",");
6585 compose_add_headerfield_from_headerlist(compose, header, "Reply-To", ", ");
6588 if (compose->account->organization &&
6589 strlen(compose->account->organization) &&
6590 !IS_IN_CUSTOM_HEADER("Organization")) {
6591 compose_convert_header(compose, buf, sizeof(buf),
6592 compose->account->organization,
6593 strlen("Organization: "), FALSE);
6594 g_string_append_printf(header, "Organization: %s\n", buf);
6597 /* Program version and system info */
6598 if (compose->account->gen_xmailer &&
6599 g_slist_length(compose->to_list) && !IS_IN_CUSTOM_HEADER("X-Mailer") &&
6600 !compose->newsgroup_list) {
6601 g_string_append_printf(header, "X-Mailer: %s (GTK+ %d.%d.%d; %s)\n",
6603 gtk_major_version, gtk_minor_version, gtk_micro_version,
6606 if (compose->account->gen_xmailer &&
6607 g_slist_length(compose->newsgroup_list) && !IS_IN_CUSTOM_HEADER("X-Newsreader")) {
6608 g_string_append_printf(header, "X-Newsreader: %s (GTK+ %d.%d.%d; %s)\n",
6610 gtk_major_version, gtk_minor_version, gtk_micro_version,
6614 /* custom headers */
6615 if (compose->account->add_customhdr) {
6618 for (cur = compose->account->customhdr_list; cur != NULL;
6620 CustomHeader *chdr = (CustomHeader *)cur->data;
6622 if (custom_header_is_allowed(chdr->name)
6623 && chdr->value != NULL
6624 && *(chdr->value) != '\0') {
6625 compose_convert_header
6626 (compose, buf, sizeof(buf),
6628 strlen(chdr->name) + 2, FALSE);
6629 g_string_append_printf(header, "%s: %s\n", chdr->name, buf);
6634 /* Automatic Faces and X-Faces */
6635 if (get_account_xface (buf, sizeof(buf), compose->account->account_name) == 0) {
6636 g_string_append_printf(header, "X-Face: %s\n", buf);
6638 else if (get_default_xface (buf, sizeof(buf)) == 0) {
6639 g_string_append_printf(header, "X-Face: %s\n", buf);
6641 if (get_account_face (buf, sizeof(buf), compose->account->account_name) == 0) {
6642 g_string_append_printf(header, "Face: %s\n", buf);
6644 else if (get_default_face (buf, sizeof(buf)) == 0) {
6645 g_string_append_printf(header, "Face: %s\n", buf);
6649 switch (compose->priority) {
6650 case PRIORITY_HIGHEST: g_string_append_printf(header, "Importance: high\n"
6651 "X-Priority: 1 (Highest)\n");
6653 case PRIORITY_HIGH: g_string_append_printf(header, "Importance: high\n"
6654 "X-Priority: 2 (High)\n");
6656 case PRIORITY_NORMAL: break;
6657 case PRIORITY_LOW: g_string_append_printf(header, "Importance: low\n"
6658 "X-Priority: 4 (Low)\n");
6660 case PRIORITY_LOWEST: g_string_append_printf(header, "Importance: low\n"
6661 "X-Priority: 5 (Lowest)\n");
6663 default: debug_print("compose: priority unknown : %d\n",
6667 /* get special headers */
6668 for (list = compose->header_list; list; list = list->next) {
6669 ComposeHeaderEntry *headerentry;
6672 gchar *headername_wcolon;
6673 const gchar *headername_trans;
6676 gboolean standard_header = FALSE;
6678 headerentry = ((ComposeHeaderEntry *)list->data);
6680 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6682 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6687 if (!strstr(tmp, ":")) {
6688 headername_wcolon = g_strconcat(tmp, ":", NULL);
6689 headername = g_strdup(tmp);
6691 headername_wcolon = g_strdup(tmp);
6692 headername = g_strdup(strtok(tmp, ":"));
6696 entry_str = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
6697 Xstrdup_a(headervalue, entry_str, return NULL);
6698 subst_char(headervalue, '\r', ' ');
6699 subst_char(headervalue, '\n', ' ');
6700 string = std_headers;
6701 while (*string != NULL) {
6702 headername_trans = prefs_common_translated_header_name(*string);
6703 if (!strcmp(headername_trans, headername_wcolon))
6704 standard_header = TRUE;
6707 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
6708 g_string_append_printf(header, "%s %s\n", headername_wcolon, headervalue);
6711 g_free(headername_wcolon);
6715 g_string_free(header, FALSE);
6720 #undef IS_IN_CUSTOM_HEADER
6722 static void compose_convert_header(Compose *compose, gchar *dest, gint len, gchar *src,
6723 gint header_len, gboolean addr_field)
6725 gchar *tmpstr = NULL;
6726 const gchar *out_codeset = NULL;
6728 cm_return_if_fail(src != NULL);
6729 cm_return_if_fail(dest != NULL);
6731 if (len < 1) return;
6733 tmpstr = g_strdup(src);
6735 subst_char(tmpstr, '\n', ' ');
6736 subst_char(tmpstr, '\r', ' ');
6739 if (!g_utf8_validate(tmpstr, -1, NULL)) {
6740 gchar *mybuf = g_malloc(strlen(tmpstr)*2 +1);
6741 conv_localetodisp(mybuf, strlen(tmpstr)*2 +1, tmpstr);
6746 codeconv_set_strict(TRUE);
6747 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6748 conv_get_charset_str(compose->out_encoding));
6749 codeconv_set_strict(FALSE);
6751 if (!dest || *dest == '\0') {
6752 gchar *test_conv_global_out = NULL;
6753 gchar *test_conv_reply = NULL;
6755 /* automatic mode. be automatic. */
6756 codeconv_set_strict(TRUE);
6758 out_codeset = conv_get_outgoing_charset_str();
6760 debug_print("trying to convert to %s\n", out_codeset);
6761 test_conv_global_out = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6764 if (!test_conv_global_out && compose->orig_charset
6765 && strcmp(compose->orig_charset, CS_US_ASCII)) {
6766 out_codeset = compose->orig_charset;
6767 debug_print("failure; trying to convert to %s\n", out_codeset);
6768 test_conv_reply = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6771 if (!test_conv_global_out && !test_conv_reply) {
6773 out_codeset = CS_INTERNAL;
6774 debug_print("finally using %s\n", out_codeset);
6776 g_free(test_conv_global_out);
6777 g_free(test_conv_reply);
6778 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6780 codeconv_set_strict(FALSE);
6785 static void compose_add_to_addressbook_cb(GtkMenuItem *menuitem, gpointer user_data)
6789 cm_return_if_fail(user_data != NULL);
6791 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(user_data)));
6792 g_strstrip(address);
6793 if (*address != '\0') {
6794 gchar *name = procheader_get_fromname(address);
6795 extract_address(address);
6796 #ifndef USE_ALT_ADDRBOOK
6797 addressbook_add_contact(name, address, NULL, NULL);
6799 debug_print("%s: %s\n", name, address);
6800 if (addressadd_selection(name, address, NULL, NULL)) {
6801 debug_print( "addressbook_add_contact - added\n" );
6808 static void compose_entry_popup_extend(GtkEntry *entry, GtkMenu *menu, gpointer user_data)
6810 GtkWidget *menuitem;
6813 cm_return_if_fail(menu != NULL);
6814 cm_return_if_fail(GTK_IS_MENU_SHELL(menu));
6816 menuitem = gtk_separator_menu_item_new();
6817 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6818 gtk_widget_show(menuitem);
6820 menuitem = gtk_menu_item_new_with_mnemonic(_("Add to address _book"));
6821 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6823 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
6824 g_strstrip(address);
6825 if (*address == '\0') {
6826 gtk_widget_set_sensitive(GTK_WIDGET(menuitem), FALSE);
6829 g_signal_connect(G_OBJECT(menuitem), "activate",
6830 G_CALLBACK(compose_add_to_addressbook_cb), entry);
6831 gtk_widget_show(menuitem);
6834 void compose_add_extra_header(gchar *header, GtkListStore *model)
6837 if (strcmp(header, "")) {
6838 COMBOBOX_ADD(model, header, COMPOSE_TO);
6842 void compose_add_extra_header_entries(GtkListStore *model)
6846 gchar buf[BUFFSIZE];
6849 if (extra_headers == NULL) {
6850 exhrc = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, "extraheaderrc", NULL);
6851 if ((exh = g_fopen(exhrc, "rb")) == NULL) {
6852 debug_print("extra headers file not found\n");
6853 goto extra_headers_done;
6855 while (fgets(buf, BUFFSIZE, exh) != NULL) {
6856 lastc = strlen(buf) - 1; /* remove trailing control chars */
6857 while (lastc >= 0 && buf[lastc] != ':')
6858 buf[lastc--] = '\0';
6859 if (lastc > 0 && buf[0] != '#' && buf[lastc] == ':') {
6860 buf[lastc] = '\0'; /* remove trailing : for comparison */
6861 if (custom_header_is_allowed(buf)) {
6863 extra_headers = g_slist_prepend(extra_headers, g_strdup(buf));
6866 g_message("disallowed extra header line: %s\n", buf);
6870 g_message("invalid extra header line: %s\n", buf);
6876 extra_headers = g_slist_prepend(extra_headers, g_strdup("")); /* end of list */
6877 extra_headers = g_slist_reverse(extra_headers);
6879 g_slist_foreach(extra_headers, (GFunc)compose_add_extra_header, (gpointer)model);
6882 static void compose_create_header_entry(Compose *compose)
6884 gchar *headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6891 const gchar *header = NULL;
6892 ComposeHeaderEntry *headerentry;
6893 gboolean standard_header = FALSE;
6894 GtkListStore *model;
6897 headerentry = g_new0(ComposeHeaderEntry, 1);
6899 /* Combo box model */
6900 model = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_BOOLEAN);
6901 #if !GTK_CHECK_VERSION(2, 24, 0)
6902 combo = gtk_combo_box_entry_new_with_model(GTK_TREE_MODEL(model), 0);
6904 COMBOBOX_ADD(model, prefs_common_translated_header_name("To:"),
6906 COMBOBOX_ADD(model, prefs_common_translated_header_name("Cc:"),
6908 COMBOBOX_ADD(model, prefs_common_translated_header_name("Bcc:"),
6910 COMBOBOX_ADD(model, prefs_common_translated_header_name("Newsgroups:"),
6911 COMPOSE_NEWSGROUPS);
6912 COMBOBOX_ADD(model, prefs_common_translated_header_name("Reply-To:"),
6914 COMBOBOX_ADD(model, prefs_common_translated_header_name("Followup-To:"),
6915 COMPOSE_FOLLOWUPTO);
6916 compose_add_extra_header_entries(model);
6919 #if GTK_CHECK_VERSION(2, 24, 0)
6920 combo = gtk_combo_box_new_with_model_and_entry(GTK_TREE_MODEL(model));
6921 GtkCellRenderer *cell = gtk_cell_renderer_text_new();
6922 gtk_cell_renderer_set_alignment(cell, 0.0, 0.5);
6923 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), cell, TRUE);
6924 gtk_combo_box_set_entry_text_column(GTK_COMBO_BOX(combo), 0);
6926 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
6927 g_signal_connect(G_OBJECT(gtk_bin_get_child(GTK_BIN(combo))), "grab_focus",
6928 G_CALLBACK(compose_grab_focus_cb), compose);
6929 gtk_widget_show(combo);
6931 /* Putting only the combobox child into focus chain of its parent causes
6932 * the parent to be skipped when changing focus via Tab or Shift+Tab.
6933 * This eliminates need to pres Tab twice in order to really get from the
6934 * combobox to next widget. */
6936 l = g_list_prepend(l, gtk_bin_get_child(GTK_BIN(combo)));
6937 gtk_container_set_focus_chain(GTK_CONTAINER(combo), l);
6940 gtk_table_attach(GTK_TABLE(compose->header_table), combo, 0, 1,
6941 compose->header_nextrow, compose->header_nextrow+1,
6942 GTK_SHRINK, GTK_FILL, 0, 0);
6943 if (compose->header_last && (compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN)) {
6944 const gchar *last_header_entry = gtk_entry_get_text(
6945 GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
6947 while (*string != NULL) {
6948 if (!strcmp(prefs_common_translated_header_name(*string), last_header_entry))
6949 standard_header = TRUE;
6952 if (standard_header)
6953 header = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
6955 if (!compose->header_last || !standard_header) {
6956 switch(compose->account->protocol) {
6958 header = prefs_common_translated_header_name("Newsgroups:");
6961 header = prefs_common_translated_header_name("To:");
6966 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((combo)))), header);
6968 gtk_editable_set_editable(
6969 GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((combo)))),
6970 prefs_common.type_any_header);
6972 g_signal_connect_after(G_OBJECT(gtk_bin_get_child(GTK_BIN((combo)))), "grab_focus",
6973 G_CALLBACK(compose_grab_focus_cb), compose);
6975 /* Entry field with cleanup button */
6976 button = gtk_button_new();
6977 gtk_button_set_image(GTK_BUTTON(button),
6978 gtk_image_new_from_stock(GTK_STOCK_CLEAR, GTK_ICON_SIZE_MENU));
6979 gtk_widget_show(button);
6980 CLAWS_SET_TIP(button,
6981 _("Delete entry contents"));
6982 entry = gtk_entry_new();
6983 gtk_widget_show(entry);
6984 CLAWS_SET_TIP(entry,
6985 _("Use <tab> to autocomplete from addressbook"));
6986 hbox = gtk_hbox_new (FALSE, 0);
6987 gtk_widget_show(hbox);
6988 gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, TRUE, 0);
6989 gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
6990 gtk_table_attach(GTK_TABLE(compose->header_table), hbox, 1, 2,
6991 compose->header_nextrow, compose->header_nextrow+1,
6992 GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
6994 g_signal_connect(G_OBJECT(entry), "key-press-event",
6995 G_CALLBACK(compose_headerentry_key_press_event_cb),
6997 g_signal_connect(G_OBJECT(entry), "changed",
6998 G_CALLBACK(compose_headerentry_changed_cb),
7000 g_signal_connect_after(G_OBJECT(entry), "grab_focus",
7001 G_CALLBACK(compose_grab_focus_cb), compose);
7003 g_signal_connect(G_OBJECT(button), "clicked",
7004 G_CALLBACK(compose_headerentry_button_clicked_cb),
7008 gtk_drag_dest_set(entry, GTK_DEST_DEFAULT_ALL, compose_mime_types,
7009 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7010 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7011 g_signal_connect(G_OBJECT(entry), "drag_data_received",
7012 G_CALLBACK(compose_header_drag_received_cb),
7014 g_signal_connect(G_OBJECT(entry), "drag-drop",
7015 G_CALLBACK(compose_drag_drop),
7017 g_signal_connect(G_OBJECT(entry), "populate-popup",
7018 G_CALLBACK(compose_entry_popup_extend),
7021 address_completion_register_entry(GTK_ENTRY(entry), TRUE);
7023 headerentry->compose = compose;
7024 headerentry->combo = combo;
7025 headerentry->entry = entry;
7026 headerentry->button = button;
7027 headerentry->hbox = hbox;
7028 headerentry->headernum = compose->header_nextrow;
7029 headerentry->type = PREF_NONE;
7031 compose->header_nextrow++;
7032 compose->header_last = headerentry;
7033 compose->header_list =
7034 g_slist_append(compose->header_list,
7038 static void compose_add_header_entry(Compose *compose, const gchar *header,
7039 gchar *text, ComposePrefType pref_type)
7041 ComposeHeaderEntry *last_header = compose->header_last;
7042 gchar *tmp = g_strdup(text), *email;
7043 gboolean replyto_hdr;
7045 replyto_hdr = (!strcasecmp(header,
7046 prefs_common_translated_header_name("Reply-To:")) ||
7048 prefs_common_translated_header_name("Followup-To:")) ||
7050 prefs_common_translated_header_name("In-Reply-To:")));
7052 extract_address(tmp);
7053 email = g_utf8_strdown(tmp, -1);
7055 if (replyto_hdr == FALSE &&
7056 g_hash_table_lookup(compose->email_hashtable, email) != NULL)
7058 debug_print("Ignoring duplicate address - %s %s, pref_type: %d\n",
7059 header, text, (gint) pref_type);
7065 if (!strcasecmp(header, prefs_common_translated_header_name("In-Reply-To:")))
7066 gtk_entry_set_text(GTK_ENTRY(
7067 gtk_bin_get_child(GTK_BIN(last_header->combo))), header);
7069 combobox_select_by_text(GTK_COMBO_BOX(last_header->combo), header);
7070 gtk_entry_set_text(GTK_ENTRY(last_header->entry), text);
7071 last_header->type = pref_type;
7073 if (replyto_hdr == FALSE)
7074 g_hash_table_insert(compose->email_hashtable, email,
7075 GUINT_TO_POINTER(1));
7082 static void compose_destroy_headerentry(Compose *compose,
7083 ComposeHeaderEntry *headerentry)
7085 gchar *text = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
7088 extract_address(text);
7089 email = g_utf8_strdown(text, -1);
7090 g_hash_table_remove(compose->email_hashtable, email);
7094 gtk_widget_destroy(headerentry->combo);
7095 gtk_widget_destroy(headerentry->entry);
7096 gtk_widget_destroy(headerentry->button);
7097 gtk_widget_destroy(headerentry->hbox);
7098 g_free(headerentry);
7101 static void compose_remove_header_entries(Compose *compose)
7104 for (list = compose->header_list; list; list = list->next)
7105 compose_destroy_headerentry(compose, (ComposeHeaderEntry *)list->data);
7107 compose->header_last = NULL;
7108 g_slist_free(compose->header_list);
7109 compose->header_list = NULL;
7110 compose->header_nextrow = 1;
7111 compose_create_header_entry(compose);
7114 static GtkWidget *compose_create_header(Compose *compose)
7116 GtkWidget *from_optmenu_hbox;
7117 GtkWidget *header_table_main;
7118 GtkWidget *header_scrolledwin;
7119 GtkWidget *header_table;
7121 /* parent with account selection and from header */
7122 header_table_main = gtk_table_new(2, 2, FALSE);
7123 gtk_widget_show(header_table_main);
7124 gtk_container_set_border_width(GTK_CONTAINER(header_table_main), BORDER_WIDTH);
7126 from_optmenu_hbox = compose_account_option_menu_create(compose);
7127 gtk_table_attach(GTK_TABLE(header_table_main), from_optmenu_hbox,
7128 0, 2, 0, 1, GTK_EXPAND | GTK_FILL, GTK_SHRINK, 0, 0);
7130 /* child with header labels and entries */
7131 header_scrolledwin = gtk_scrolled_window_new(NULL, NULL);
7132 gtk_widget_show(header_scrolledwin);
7133 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
7135 header_table = gtk_table_new(2, 2, FALSE);
7136 gtk_widget_show(header_table);
7137 gtk_container_set_border_width(GTK_CONTAINER(header_table), 0);
7138 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin), header_table);
7139 gtk_container_set_focus_vadjustment(GTK_CONTAINER(header_table),
7140 gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(header_scrolledwin)));
7141 gtk_viewport_set_shadow_type(GTK_VIEWPORT(gtk_bin_get_child(GTK_BIN(header_scrolledwin))), GTK_SHADOW_NONE);
7143 gtk_table_attach(GTK_TABLE(header_table_main), header_scrolledwin,
7144 0, 2, 1, 2, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 2);
7146 compose->header_table = header_table;
7147 compose->header_list = NULL;
7148 compose->header_nextrow = 0;
7150 compose_create_header_entry(compose);
7152 compose->table = NULL;
7154 return header_table_main;
7157 static gboolean popup_attach_button_pressed(GtkWidget *widget, gpointer data)
7159 Compose *compose = (Compose *)data;
7160 GdkEventButton event;
7163 event.time = gtk_get_current_event_time();
7165 return attach_button_pressed(compose->attach_clist, &event, compose);
7168 static GtkWidget *compose_create_attach(Compose *compose)
7170 GtkWidget *attach_scrwin;
7171 GtkWidget *attach_clist;
7173 GtkListStore *store;
7174 GtkCellRenderer *renderer;
7175 GtkTreeViewColumn *column;
7176 GtkTreeSelection *selection;
7178 /* attachment list */
7179 attach_scrwin = gtk_scrolled_window_new(NULL, NULL);
7180 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(attach_scrwin),
7181 GTK_POLICY_AUTOMATIC,
7182 GTK_POLICY_AUTOMATIC);
7183 gtk_widget_set_size_request(attach_scrwin, -1, 80);
7185 store = gtk_list_store_new(N_ATTACH_COLS,
7191 G_TYPE_AUTO_POINTER,
7193 attach_clist = GTK_WIDGET(gtk_tree_view_new_with_model
7194 (GTK_TREE_MODEL(store)));
7195 gtk_container_add(GTK_CONTAINER(attach_scrwin), attach_clist);
7196 g_object_unref(store);
7198 renderer = gtk_cell_renderer_text_new();
7199 column = gtk_tree_view_column_new_with_attributes
7200 (_("Mime type"), renderer, "text",
7201 COL_MIMETYPE, NULL);
7202 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7204 renderer = gtk_cell_renderer_text_new();
7205 column = gtk_tree_view_column_new_with_attributes
7206 (_("Size"), renderer, "text",
7208 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7210 renderer = gtk_cell_renderer_text_new();
7211 column = gtk_tree_view_column_new_with_attributes
7212 (_("Name"), renderer, "text",
7214 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7216 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(attach_clist),
7217 prefs_common.use_stripes_everywhere);
7218 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(attach_clist));
7219 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
7221 g_signal_connect(G_OBJECT(attach_clist), "row_activated",
7222 G_CALLBACK(attach_selected), compose);
7223 g_signal_connect(G_OBJECT(attach_clist), "button_press_event",
7224 G_CALLBACK(attach_button_pressed), compose);
7225 g_signal_connect(G_OBJECT(attach_clist), "popup-menu",
7226 G_CALLBACK(popup_attach_button_pressed), compose);
7227 g_signal_connect(G_OBJECT(attach_clist), "key_press_event",
7228 G_CALLBACK(attach_key_pressed), compose);
7231 gtk_drag_dest_set(attach_clist,
7232 GTK_DEST_DEFAULT_ALL, compose_mime_types,
7233 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7234 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7235 g_signal_connect(G_OBJECT(attach_clist), "drag_data_received",
7236 G_CALLBACK(compose_attach_drag_received_cb),
7238 g_signal_connect(G_OBJECT(attach_clist), "drag-drop",
7239 G_CALLBACK(compose_drag_drop),
7242 compose->attach_scrwin = attach_scrwin;
7243 compose->attach_clist = attach_clist;
7245 return attach_scrwin;
7248 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose);
7249 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose);
7251 static GtkWidget *compose_create_others(Compose *compose)
7254 GtkWidget *savemsg_checkbtn;
7255 GtkWidget *savemsg_combo;
7256 GtkWidget *savemsg_select;
7259 gchar *folderidentifier;
7261 /* Table for settings */
7262 table = gtk_table_new(3, 1, FALSE);
7263 gtk_container_set_border_width(GTK_CONTAINER(table), BORDER_WIDTH);
7264 gtk_widget_show(table);
7265 gtk_table_set_row_spacings(GTK_TABLE(table), VSPACING_NARROW);
7268 /* Save Message to folder */
7269 savemsg_checkbtn = gtk_check_button_new_with_label(_("Save Message to "));
7270 gtk_widget_show(savemsg_checkbtn);
7271 gtk_table_attach(GTK_TABLE(table), savemsg_checkbtn, 0, 1, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7272 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7273 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), prefs_common.savemsg);
7275 g_signal_connect(G_OBJECT(savemsg_checkbtn), "toggled",
7276 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
7278 #if !GTK_CHECK_VERSION(2, 24, 0)
7279 savemsg_combo = gtk_combo_box_entry_new_text();
7281 savemsg_combo = gtk_combo_box_text_new_with_entry();
7283 compose->savemsg_checkbtn = savemsg_checkbtn;
7284 compose->savemsg_combo = savemsg_combo;
7285 gtk_widget_show(savemsg_combo);
7287 if (prefs_common.compose_save_to_history)
7288 #if !GTK_CHECK_VERSION(2, 24, 0)
7289 combobox_set_popdown_strings(GTK_COMBO_BOX(savemsg_combo),
7290 prefs_common.compose_save_to_history);
7292 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(savemsg_combo),
7293 prefs_common.compose_save_to_history);
7295 gtk_table_attach(GTK_TABLE(table), savemsg_combo, 1, 2, rowcount, rowcount + 1, GTK_FILL|GTK_EXPAND, GTK_SHRINK, 0, 0);
7296 gtk_widget_set_sensitive(GTK_WIDGET(savemsg_combo), prefs_common.savemsg);
7297 g_signal_connect_after(G_OBJECT(savemsg_combo), "grab_focus",
7298 G_CALLBACK(compose_grab_focus_cb), compose);
7299 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7300 folderidentifier = folder_item_get_identifier(account_get_special_folder
7301 (compose->account, F_OUTBOX));
7302 compose_set_save_to(compose, folderidentifier);
7303 g_free(folderidentifier);
7306 savemsg_select = gtkut_get_browse_file_btn(_("_Browse"));
7307 gtk_widget_show(savemsg_select);
7308 gtk_table_attach(GTK_TABLE(table), savemsg_select, 2, 3, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7309 g_signal_connect(G_OBJECT(savemsg_select), "clicked",
7310 G_CALLBACK(compose_savemsg_select_cb),
7316 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose)
7318 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo),
7319 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn)));
7322 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose)
7327 dest = foldersel_folder_sel(NULL, FOLDER_SEL_COPY, NULL, FALSE);
7330 path = folder_item_get_identifier(dest);
7332 compose_set_save_to(compose, path);
7336 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry, gboolean wrap,
7337 GdkAtom clip, GtkTextIter *insert_place);
7340 static gboolean text_clicked(GtkWidget *text, GdkEventButton *event,
7344 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
7346 if (event->button == 3) {
7348 GtkTextIter sel_start, sel_end;
7349 gboolean stuff_selected;
7351 /* move the cursor to allow GtkAspell to check the word
7352 * under the mouse */
7353 if (event->x && event->y) {
7354 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7355 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7357 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7360 GtkTextMark *mark = gtk_text_buffer_get_insert(buffer);
7361 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7364 stuff_selected = gtk_text_buffer_get_selection_bounds(
7366 &sel_start, &sel_end);
7368 gtk_text_buffer_place_cursor (buffer, &iter);
7369 /* reselect stuff */
7371 && gtk_text_iter_in_range(&iter, &sel_start, &sel_end)) {
7372 gtk_text_buffer_select_range(buffer,
7373 &sel_start, &sel_end);
7375 return FALSE; /* pass the event so that the right-click goes through */
7378 if (event->button == 2) {
7383 /* get the middle-click position to paste at the correct place */
7384 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7385 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7387 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7390 entry_paste_clipboard(compose, text,
7391 prefs_common.linewrap_pastes,
7392 GDK_SELECTION_PRIMARY, &iter);
7400 static void compose_spell_menu_changed(void *data)
7402 Compose *compose = (Compose *)data;
7404 GtkWidget *menuitem;
7405 GtkWidget *parent_item;
7406 GtkMenu *menu = GTK_MENU(gtk_menu_new());
7409 if (compose->gtkaspell == NULL)
7412 parent_item = gtk_ui_manager_get_widget(compose->ui_manager,
7413 "/Menu/Spelling/Options");
7415 /* setting the submenu removes /Spelling/Options from the factory
7416 * so we need to save it */
7418 if (parent_item == NULL) {
7419 parent_item = compose->aspell_options_menu;
7420 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), NULL);
7422 compose->aspell_options_menu = parent_item;
7424 spell_menu = gtkaspell_make_config_menu(compose->gtkaspell);
7426 spell_menu = g_slist_reverse(spell_menu);
7427 for (items = spell_menu;
7428 items; items = items->next) {
7429 menuitem = GTK_WIDGET(GTK_MENU_ITEM(items->data));
7430 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), GTK_WIDGET(menuitem));
7431 gtk_widget_show(GTK_WIDGET(menuitem));
7433 g_slist_free(spell_menu);
7435 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), GTK_WIDGET(menu));
7436 gtk_widget_show(parent_item);
7439 static void compose_dict_changed(void *data)
7441 Compose *compose = (Compose *) data;
7443 if(!compose->gtkaspell)
7445 if(compose->gtkaspell->recheck_when_changing_dict == FALSE)
7448 gtkaspell_highlight_all(compose->gtkaspell);
7449 claws_spell_entry_recheck_all(CLAWS_SPELL_ENTRY(compose->subject_entry));
7453 static gboolean compose_popup_menu(GtkWidget *widget, gpointer data)
7455 Compose *compose = (Compose *)data;
7456 GdkEventButton event;
7459 event.time = gtk_get_current_event_time();
7463 return text_clicked(compose->text, &event, compose);
7466 static gboolean compose_force_window_origin = TRUE;
7467 static Compose *compose_create(PrefsAccount *account,
7476 GtkWidget *handlebox;
7478 GtkWidget *notebook;
7480 GtkWidget *attach_hbox;
7481 GtkWidget *attach_lab1;
7482 GtkWidget *attach_lab2;
7487 GtkWidget *subject_hbox;
7488 GtkWidget *subject_frame;
7489 GtkWidget *subject_entry;
7493 GtkWidget *edit_vbox;
7494 GtkWidget *ruler_hbox;
7496 GtkWidget *scrolledwin;
7498 GtkTextBuffer *buffer;
7499 GtkClipboard *clipboard;
7501 UndoMain *undostruct;
7503 GtkWidget *popupmenu;
7504 GtkWidget *tmpl_menu;
7505 GtkActionGroup *action_group = NULL;
7508 GtkAspell * gtkaspell = NULL;
7511 static GdkGeometry geometry;
7513 cm_return_val_if_fail(account != NULL, NULL);
7515 gtkut_convert_int_to_gdk_color(prefs_common.default_header_bgcolor,
7516 &default_header_bgcolor);
7517 gtkut_convert_int_to_gdk_color(prefs_common.default_header_color,
7518 &default_header_color);
7520 debug_print("Creating compose window...\n");
7521 compose = g_new0(Compose, 1);
7523 compose->batch = batch;
7524 compose->account = account;
7525 compose->folder = folder;
7527 compose->mutex = cm_mutex_new();
7528 compose->set_cursor_pos = -1;
7530 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose");
7532 gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
7533 gtk_widget_set_size_request(window, prefs_common.compose_width,
7534 prefs_common.compose_height);
7536 if (!geometry.max_width) {
7537 geometry.max_width = gdk_screen_width();
7538 geometry.max_height = gdk_screen_height();
7541 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7542 &geometry, GDK_HINT_MAX_SIZE);
7543 if (!geometry.min_width) {
7544 geometry.min_width = 600;
7545 geometry.min_height = 440;
7547 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7548 &geometry, GDK_HINT_MIN_SIZE);
7550 #ifndef GENERIC_UMPC
7551 if (compose_force_window_origin)
7552 gtk_window_move(GTK_WINDOW(window), prefs_common.compose_x,
7553 prefs_common.compose_y);
7555 g_signal_connect(G_OBJECT(window), "delete_event",
7556 G_CALLBACK(compose_delete_cb), compose);
7557 MANAGE_WINDOW_SIGNALS_CONNECT(window);
7558 gtk_widget_realize(window);
7560 gtkut_widget_set_composer_icon(window);
7562 vbox = gtk_vbox_new(FALSE, 0);
7563 gtk_container_add(GTK_CONTAINER(window), vbox);
7565 compose->ui_manager = gtk_ui_manager_new();
7566 action_group = cm_menu_create_action_group_full(compose->ui_manager,"Menu", compose_entries,
7567 G_N_ELEMENTS(compose_entries), (gpointer)compose);
7568 gtk_action_group_add_toggle_actions(action_group, compose_toggle_entries,
7569 G_N_ELEMENTS(compose_toggle_entries), (gpointer)compose);
7570 gtk_action_group_add_radio_actions(action_group, compose_radio_rm_entries,
7571 G_N_ELEMENTS(compose_radio_rm_entries), COMPOSE_REPLY, G_CALLBACK(compose_reply_change_mode_cb), (gpointer)compose);
7572 gtk_action_group_add_radio_actions(action_group, compose_radio_prio_entries,
7573 G_N_ELEMENTS(compose_radio_prio_entries), PRIORITY_NORMAL, G_CALLBACK(compose_set_priority_cb), (gpointer)compose);
7574 gtk_action_group_add_radio_actions(action_group, compose_radio_enc_entries,
7575 G_N_ELEMENTS(compose_radio_enc_entries), C_AUTO, G_CALLBACK(compose_set_encoding_cb), (gpointer)compose);
7577 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Menu", NULL, GTK_UI_MANAGER_MENUBAR)
7579 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Message", "Message", GTK_UI_MANAGER_MENU)
7580 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Edit", "Edit", GTK_UI_MANAGER_MENU)
7582 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Spelling", "Spelling", GTK_UI_MANAGER_MENU)
7584 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Options", "Options", GTK_UI_MANAGER_MENU)
7585 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Tools", "Tools", GTK_UI_MANAGER_MENU)
7586 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Help", "Help", GTK_UI_MANAGER_MENU)
7589 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Send", "Message/Send", GTK_UI_MANAGER_MENUITEM)
7590 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "SendLater", "Message/SendLater", GTK_UI_MANAGER_MENUITEM)
7591 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator1", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7592 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "AttachFile", "Message/AttachFile", GTK_UI_MANAGER_MENUITEM)
7593 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertFile", "Message/InsertFile", GTK_UI_MANAGER_MENUITEM)
7594 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertSig", "Message/InsertSig", GTK_UI_MANAGER_MENUITEM)
7595 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "ReplaceSig", "Message/ReplaceSig", GTK_UI_MANAGER_MENUITEM)
7596 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator2", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7597 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Save", "Message/Save", GTK_UI_MANAGER_MENUITEM)
7598 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator3", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7599 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Print", "Message/Print", GTK_UI_MANAGER_MENUITEM)
7600 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator4", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7601 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Close", "Message/Close", GTK_UI_MANAGER_MENUITEM)
7604 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Undo", "Edit/Undo", GTK_UI_MANAGER_MENUITEM)
7605 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Redo", "Edit/Redo", GTK_UI_MANAGER_MENUITEM)
7606 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator1", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7608 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Cut", "Edit/Cut", GTK_UI_MANAGER_MENUITEM)
7609 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Copy", "Edit/Copy", GTK_UI_MANAGER_MENUITEM)
7610 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Paste", "Edit/Paste", GTK_UI_MANAGER_MENUITEM)
7612 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SpecialPaste", "Edit/SpecialPaste", GTK_UI_MANAGER_MENU)
7613 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "AsQuotation", "Edit/SpecialPaste/AsQuotation", GTK_UI_MANAGER_MENUITEM)
7614 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Wrapped", "Edit/SpecialPaste/Wrapped", GTK_UI_MANAGER_MENUITEM)
7615 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Unwrapped", "Edit/SpecialPaste/Unwrapped", GTK_UI_MANAGER_MENUITEM)
7617 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SelectAll", "Edit/SelectAll", GTK_UI_MANAGER_MENUITEM)
7619 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Advanced", "Edit/Advanced", GTK_UI_MANAGER_MENU)
7620 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackChar", "Edit/Advanced/BackChar", GTK_UI_MANAGER_MENUITEM)
7621 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwChar", "Edit/Advanced/ForwChar", GTK_UI_MANAGER_MENUITEM)
7622 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackWord", "Edit/Advanced/BackWord", GTK_UI_MANAGER_MENUITEM)
7623 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwWord", "Edit/Advanced/ForwWord", GTK_UI_MANAGER_MENUITEM)
7624 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BegLine", "Edit/Advanced/BegLine", GTK_UI_MANAGER_MENUITEM)
7625 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "EndLine", "Edit/Advanced/EndLine", GTK_UI_MANAGER_MENUITEM)
7626 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "PrevLine", "Edit/Advanced/PrevLine", GTK_UI_MANAGER_MENUITEM)
7627 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "NextLine", "Edit/Advanced/NextLine", GTK_UI_MANAGER_MENUITEM)
7628 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackChar", "Edit/Advanced/DelBackChar", GTK_UI_MANAGER_MENUITEM)
7629 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwChar", "Edit/Advanced/DelForwChar", GTK_UI_MANAGER_MENUITEM)
7630 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackWord", "Edit/Advanced/DelBackWord", GTK_UI_MANAGER_MENUITEM)
7631 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwWord", "Edit/Advanced/DelForwWord", GTK_UI_MANAGER_MENUITEM)
7632 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelLine", "Edit/Advanced/DelLine", GTK_UI_MANAGER_MENUITEM)
7633 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelEndLine", "Edit/Advanced/DelEndLine", GTK_UI_MANAGER_MENUITEM)
7635 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator2", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7637 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Find", "Edit/Find", GTK_UI_MANAGER_MENUITEM)
7638 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapPara", "Edit/WrapPara", GTK_UI_MANAGER_MENUITEM)
7639 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapAllLines", "Edit/WrapAllLines", GTK_UI_MANAGER_MENUITEM)
7640 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoWrap", "Edit/AutoWrap", GTK_UI_MANAGER_MENUITEM)
7641 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoIndent", "Edit/AutoIndent", GTK_UI_MANAGER_MENUITEM)
7643 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator3", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7645 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "ExtEditor", "Edit/ExtEditor", GTK_UI_MANAGER_MENUITEM)
7649 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckAllSel", "Spelling/CheckAllSel", GTK_UI_MANAGER_MENUITEM)
7650 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "HighlightAll", "Spelling/HighlightAll", GTK_UI_MANAGER_MENUITEM)
7651 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckBackwards", "Spelling/CheckBackwards", GTK_UI_MANAGER_MENUITEM)
7652 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "ForwardNext", "Spelling/ForwardNext", GTK_UI_MANAGER_MENUITEM)
7653 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Separator1", "Spelling/---", GTK_UI_MANAGER_SEPARATOR)
7654 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Options", "Spelling/Options", GTK_UI_MANAGER_MENU)
7658 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "ReplyMode", "Options/ReplyMode", GTK_UI_MANAGER_MENU)
7659 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Normal", "Options/ReplyMode/Normal", GTK_UI_MANAGER_MENUITEM)
7660 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "All", "Options/ReplyMode/All", GTK_UI_MANAGER_MENUITEM)
7661 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Sender", "Options/ReplyMode/Sender", GTK_UI_MANAGER_MENUITEM)
7662 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "List", "Options/ReplyMode/List", GTK_UI_MANAGER_MENUITEM)
7664 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator1", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7665 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "PrivacySystem", "Options/PrivacySystem", GTK_UI_MANAGER_MENU)
7666 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/PrivacySystem", "PlaceHolder", "Options/PrivacySystem/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7667 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Sign", "Options/Sign", GTK_UI_MANAGER_MENUITEM)
7668 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encrypt", "Options/Encrypt", GTK_UI_MANAGER_MENUITEM)
7671 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator2", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7672 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Priority", "Options/Priority", GTK_UI_MANAGER_MENU)
7673 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Highest", "Options/Priority/Highest", GTK_UI_MANAGER_MENUITEM)
7674 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "High", "Options/Priority/High", GTK_UI_MANAGER_MENUITEM)
7675 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Normal", "Options/Priority/Normal", GTK_UI_MANAGER_MENUITEM)
7676 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Low", "Options/Priority/Low", GTK_UI_MANAGER_MENUITEM)
7677 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Lowest", "Options/Priority/Lowest", GTK_UI_MANAGER_MENUITEM)
7679 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator3", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7680 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RequestRetRcpt", "Options/RequestRetRcpt", GTK_UI_MANAGER_MENUITEM)
7681 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator4", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7682 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RemoveReferences", "Options/RemoveReferences", GTK_UI_MANAGER_MENUITEM)
7683 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator5", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7685 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encoding", "Options/Encoding", GTK_UI_MANAGER_MENU)
7687 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_AUTO, "Options/Encoding/"CS_AUTO, GTK_UI_MANAGER_MENUITEM)
7688 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator1", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7689 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_US_ASCII, "Options/Encoding/"CS_US_ASCII, GTK_UI_MANAGER_MENUITEM)
7690 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_UTF_8, "Options/Encoding/"CS_UTF_8, GTK_UI_MANAGER_MENUITEM)
7691 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator2", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7693 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Western", "Options/Encoding/Western", GTK_UI_MANAGER_MENU)
7694 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)
7695 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)
7696 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Western", CS_WINDOWS_1252, "Options/Encoding/Western/"CS_WINDOWS_1252, GTK_UI_MANAGER_MENUITEM)
7698 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_2, "Options/Encoding/"CS_ISO_8859_2, GTK_UI_MANAGER_MENUITEM)
7700 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Baltic", "Options/Encoding/Baltic", GTK_UI_MANAGER_MENU)
7701 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)
7702 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)
7704 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_7, "Options/Encoding/"CS_ISO_8859_7, GTK_UI_MANAGER_MENUITEM)
7706 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Hebrew", "Options/Encoding/Hebrew", GTK_UI_MANAGER_MENU)
7707 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)
7708 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Hebrew", CS_WINDOWS_1255, "Options/Encoding/Hebrew/"CS_WINDOWS_1255, GTK_UI_MANAGER_MENUITEM)
7710 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Arabic", "Options/Encoding/Arabic", GTK_UI_MANAGER_MENU)
7711 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)
7712 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Arabic", CS_WINDOWS_1256, "Options/Encoding/Arabic/"CS_WINDOWS_1256, GTK_UI_MANAGER_MENUITEM)
7714 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_9, "Options/Encoding/"CS_ISO_8859_9, GTK_UI_MANAGER_MENUITEM)
7716 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Cyrillic", "Options/Encoding/Cyrillic", GTK_UI_MANAGER_MENU)
7717 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)
7718 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_R, "Options/Encoding/Cyrillic/"CS_KOI8_R, GTK_UI_MANAGER_MENUITEM)
7719 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_MACCYR, "Options/Encoding/Cyrillic/"CS_MACCYR, GTK_UI_MANAGER_MENUITEM)
7720 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_U, "Options/Encoding/Cyrillic/"CS_KOI8_U, GTK_UI_MANAGER_MENUITEM)
7721 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_WINDOWS_1251, "Options/Encoding/Cyrillic/"CS_WINDOWS_1251, GTK_UI_MANAGER_MENUITEM)
7723 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Japanese", "Options/Encoding/Japanese", GTK_UI_MANAGER_MENU)
7724 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)
7725 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)
7726 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_EUC_JP, "Options/Encoding/Japanese/"CS_EUC_JP, GTK_UI_MANAGER_MENUITEM)
7727 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_SHIFT_JIS, "Options/Encoding/Japanese/"CS_SHIFT_JIS, GTK_UI_MANAGER_MENUITEM)
7729 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Chinese", "Options/Encoding/Chinese", GTK_UI_MANAGER_MENU)
7730 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB18030, "Options/Encoding/Chinese/"CS_GB18030, GTK_UI_MANAGER_MENUITEM)
7731 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB2312, "Options/Encoding/Chinese/"CS_GB2312, GTK_UI_MANAGER_MENUITEM)
7732 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GBK, "Options/Encoding/Chinese/"CS_GBK, GTK_UI_MANAGER_MENUITEM)
7733 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_BIG5, "Options/Encoding/Chinese/"CS_BIG5, GTK_UI_MANAGER_MENUITEM)
7734 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_EUC_TW, "Options/Encoding/Chinese/"CS_EUC_TW, GTK_UI_MANAGER_MENUITEM)
7736 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Korean", "Options/Encoding/Korean", GTK_UI_MANAGER_MENU)
7737 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Korean", CS_EUC_KR, "Options/Encoding/Korean/"CS_EUC_KR, GTK_UI_MANAGER_MENUITEM)
7738 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)
7740 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Thai", "Options/Encoding/Thai", GTK_UI_MANAGER_MENU)
7741 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_TIS_620, "Options/Encoding/Thai/"CS_TIS_620, GTK_UI_MANAGER_MENUITEM)
7742 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_WINDOWS_874, "Options/Encoding/Thai/"CS_WINDOWS_874, GTK_UI_MANAGER_MENUITEM)
7746 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "ShowRuler", "Tools/ShowRuler", GTK_UI_MANAGER_MENUITEM)
7747 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "AddressBook", "Tools/AddressBook", GTK_UI_MANAGER_MENUITEM)
7748 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Template", "Tools/Template", GTK_UI_MANAGER_MENU)
7749 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Template", "PlaceHolder", "Tools/Template/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7750 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Actions", "Tools/Actions", GTK_UI_MANAGER_MENU)
7751 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Actions", "PlaceHolder", "Tools/Actions/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7754 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Help", "About", "Help/About", GTK_UI_MANAGER_MENUITEM)
7756 menubar = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu");
7757 gtk_widget_show_all(menubar);
7759 gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(compose->ui_manager));
7760 gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, TRUE, 0);
7762 if (prefs_common.toolbar_detachable) {
7763 handlebox = gtk_handle_box_new();
7765 handlebox = gtk_hbox_new(FALSE, 0);
7767 gtk_box_pack_start(GTK_BOX(vbox), handlebox, FALSE, FALSE, 0);
7769 gtk_widget_realize(handlebox);
7770 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, handlebox,
7773 vbox2 = gtk_vbox_new(FALSE, 2);
7774 gtk_box_pack_start(GTK_BOX(vbox), vbox2, TRUE, TRUE, 0);
7775 gtk_container_set_border_width(GTK_CONTAINER(vbox2), 0);
7778 notebook = gtk_notebook_new();
7779 gtk_widget_show(notebook);
7781 /* header labels and entries */
7782 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7783 compose_create_header(compose),
7784 gtk_label_new_with_mnemonic(_("Hea_der")));
7785 /* attachment list */
7786 attach_hbox = gtk_hbox_new(FALSE, 0);
7787 gtk_widget_show(attach_hbox);
7789 attach_lab1 = gtk_label_new_with_mnemonic(_("_Attachments"));
7790 gtk_widget_show(attach_lab1);
7791 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab1, TRUE, TRUE, 0);
7793 attach_lab2 = gtk_label_new("");
7794 gtk_widget_show(attach_lab2);
7795 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab2, FALSE, FALSE, 0);
7797 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7798 compose_create_attach(compose),
7801 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7802 compose_create_others(compose),
7803 gtk_label_new_with_mnemonic(_("Othe_rs")));
7806 subject_hbox = gtk_hbox_new(FALSE, 0);
7807 gtk_widget_show(subject_hbox);
7809 subject_frame = gtk_frame_new(NULL);
7810 gtk_frame_set_shadow_type(GTK_FRAME(subject_frame), GTK_SHADOW_NONE);
7811 gtk_box_pack_start(GTK_BOX(subject_hbox), subject_frame, TRUE, TRUE, 0);
7812 gtk_widget_show(subject_frame);
7814 subject = gtk_hbox_new(FALSE, HSPACING_NARROW);
7815 gtk_container_set_border_width(GTK_CONTAINER(subject), 0);
7816 gtk_widget_show(subject);
7818 label = gtk_label_new_with_mnemonic(_("S_ubject:"));
7819 gtk_box_pack_start(GTK_BOX(subject), label, FALSE, FALSE, 0);
7820 gtk_widget_show(label);
7823 subject_entry = claws_spell_entry_new();
7825 subject_entry = gtk_entry_new();
7827 gtk_box_pack_start(GTK_BOX(subject), subject_entry, TRUE, TRUE, 0);
7828 g_signal_connect_after(G_OBJECT(subject_entry), "grab_focus",
7829 G_CALLBACK(compose_grab_focus_cb), compose);
7830 gtk_label_set_mnemonic_widget(GTK_LABEL(label), subject_entry);
7831 gtk_widget_show(subject_entry);
7832 compose->subject_entry = subject_entry;
7833 gtk_container_add(GTK_CONTAINER(subject_frame), subject);
7835 edit_vbox = gtk_vbox_new(FALSE, 0);
7837 gtk_box_pack_start(GTK_BOX(edit_vbox), subject_hbox, FALSE, FALSE, 0);
7840 ruler_hbox = gtk_hbox_new(FALSE, 0);
7841 gtk_box_pack_start(GTK_BOX(edit_vbox), ruler_hbox, FALSE, FALSE, 0);
7843 ruler = gtk_shruler_new(GTK_ORIENTATION_HORIZONTAL);
7844 gtk_shruler_set_range(GTK_SHRULER(ruler), 0.0, 100.0, 1.0);
7845 gtk_box_pack_start(GTK_BOX(ruler_hbox), ruler, TRUE, TRUE,
7849 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
7850 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
7851 GTK_POLICY_AUTOMATIC,
7852 GTK_POLICY_AUTOMATIC);
7853 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
7855 gtk_box_pack_start(GTK_BOX(edit_vbox), scrolledwin, TRUE, TRUE, 0);
7857 text = gtk_text_view_new();
7858 if (prefs_common.show_compose_margin) {
7859 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text), 6);
7860 gtk_text_view_set_right_margin(GTK_TEXT_VIEW(text), 6);
7862 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
7863 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
7864 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), TRUE);
7865 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
7866 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
7868 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
7869 g_signal_connect_after(G_OBJECT(text), "size_allocate",
7870 G_CALLBACK(compose_edit_size_alloc),
7872 g_signal_connect(G_OBJECT(buffer), "changed",
7873 G_CALLBACK(compose_changed_cb), compose);
7874 g_signal_connect(G_OBJECT(text), "grab_focus",
7875 G_CALLBACK(compose_grab_focus_cb), compose);
7876 g_signal_connect(G_OBJECT(buffer), "insert_text",
7877 G_CALLBACK(text_inserted), compose);
7878 g_signal_connect(G_OBJECT(text), "button_press_event",
7879 G_CALLBACK(text_clicked), compose);
7880 g_signal_connect(G_OBJECT(text), "popup-menu",
7881 G_CALLBACK(compose_popup_menu), compose);
7882 g_signal_connect(G_OBJECT(subject_entry), "changed",
7883 G_CALLBACK(compose_changed_cb), compose);
7884 g_signal_connect(G_OBJECT(subject_entry), "activate",
7885 G_CALLBACK(compose_subject_entry_activated), compose);
7888 gtk_drag_dest_set(text, GTK_DEST_DEFAULT_ALL, compose_mime_types,
7889 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7890 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7891 g_signal_connect(G_OBJECT(text), "drag_data_received",
7892 G_CALLBACK(compose_insert_drag_received_cb),
7894 g_signal_connect(G_OBJECT(text), "drag-drop",
7895 G_CALLBACK(compose_drag_drop),
7897 g_signal_connect(G_OBJECT(text), "key-press-event",
7898 G_CALLBACK(completion_set_focus_to_subject),
7900 gtk_widget_show_all(vbox);
7902 /* pane between attach clist and text */
7903 paned = gtk_vpaned_new();
7904 gtk_container_add(GTK_CONTAINER(vbox2), paned);
7905 gtk_paned_pack1(GTK_PANED(paned), notebook, FALSE, FALSE);
7906 gtk_paned_pack2(GTK_PANED(paned), edit_vbox, TRUE, FALSE);
7907 gtk_paned_set_position(GTK_PANED(paned), prefs_common.compose_notebook_height);
7908 g_signal_connect(G_OBJECT(notebook), "size_allocate",
7909 G_CALLBACK(compose_notebook_size_alloc), paned);
7911 gtk_widget_show_all(paned);
7914 if (prefs_common.textfont) {
7915 PangoFontDescription *font_desc;
7917 font_desc = pango_font_description_from_string
7918 (prefs_common.textfont);
7920 gtk_widget_modify_font(text, font_desc);
7921 pango_font_description_free(font_desc);
7925 gtk_action_group_add_actions(action_group, compose_popup_entries,
7926 G_N_ELEMENTS(compose_popup_entries), (gpointer)compose);
7927 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Popup", NULL, GTK_UI_MANAGER_MENUBAR)
7928 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup", "Compose", "Compose", GTK_UI_MANAGER_MENU)
7929 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Add", "Compose/Add", GTK_UI_MANAGER_MENUITEM)
7930 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Remove", "Compose/Remove", GTK_UI_MANAGER_MENUITEM)
7931 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Separator1", "Compose/---", GTK_UI_MANAGER_SEPARATOR)
7932 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Properties", "Compose/Properties", GTK_UI_MANAGER_MENUITEM)
7934 popupmenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(gtk_ui_manager_get_widget(compose->ui_manager, "/Popup/Compose")));
7936 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
7937 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
7938 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
7940 tmpl_menu = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu/Tools/Template");
7942 undostruct = undo_init(text);
7943 undo_set_change_state_func(undostruct, &compose_undo_state_changed,
7946 address_completion_start(window);
7948 compose->window = window;
7949 compose->vbox = vbox;
7950 compose->menubar = menubar;
7951 compose->handlebox = handlebox;
7953 compose->vbox2 = vbox2;
7955 compose->paned = paned;
7957 compose->attach_label = attach_lab2;
7959 compose->notebook = notebook;
7960 compose->edit_vbox = edit_vbox;
7961 compose->ruler_hbox = ruler_hbox;
7962 compose->ruler = ruler;
7963 compose->scrolledwin = scrolledwin;
7964 compose->text = text;
7966 compose->focused_editable = NULL;
7968 compose->popupmenu = popupmenu;
7970 compose->tmpl_menu = tmpl_menu;
7972 compose->mode = mode;
7973 compose->rmode = mode;
7975 compose->targetinfo = NULL;
7976 compose->replyinfo = NULL;
7977 compose->fwdinfo = NULL;
7979 compose->email_hashtable = g_hash_table_new_full(g_str_hash,
7980 g_str_equal, (GDestroyNotify) g_free, NULL);
7982 compose->replyto = NULL;
7984 compose->bcc = NULL;
7985 compose->followup_to = NULL;
7987 compose->ml_post = NULL;
7989 compose->inreplyto = NULL;
7990 compose->references = NULL;
7991 compose->msgid = NULL;
7992 compose->boundary = NULL;
7994 compose->autowrap = prefs_common.autowrap;
7995 compose->autoindent = prefs_common.auto_indent;
7996 compose->use_signing = FALSE;
7997 compose->use_encryption = FALSE;
7998 compose->privacy_system = NULL;
7999 compose->encdata = NULL;
8001 compose->modified = FALSE;
8003 compose->return_receipt = FALSE;
8005 compose->to_list = NULL;
8006 compose->newsgroup_list = NULL;
8008 compose->undostruct = undostruct;
8010 compose->sig_str = NULL;
8012 compose->exteditor_file = NULL;
8013 compose->exteditor_pid = -1;
8014 compose->exteditor_tag = -1;
8015 compose->exteditor_socket = NULL;
8016 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN; /* inhibit auto-drafting while loading */
8018 compose->folder_update_callback_id =
8019 hooks_register_hook(FOLDER_UPDATE_HOOKLIST,
8020 compose_update_folder_hook,
8021 (gpointer) compose);
8024 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
8025 if (mode != COMPOSE_REDIRECT) {
8026 if (prefs_common.enable_aspell && prefs_common.dictionary &&
8027 strcmp(prefs_common.dictionary, "")) {
8028 gtkaspell = gtkaspell_new(prefs_common.dictionary,
8029 prefs_common.alt_dictionary,
8030 conv_get_locale_charset_str(),
8031 prefs_common.misspelled_col,
8032 prefs_common.check_while_typing,
8033 prefs_common.recheck_when_changing_dict,
8034 prefs_common.use_alternate,
8035 prefs_common.use_both_dicts,
8036 GTK_TEXT_VIEW(text),
8037 GTK_WINDOW(compose->window),
8038 compose_dict_changed,
8039 compose_spell_menu_changed,
8042 alertpanel_error(_("Spell checker could not "
8044 gtkaspell_checkers_strerror());
8045 gtkaspell_checkers_reset_error();
8047 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", TRUE);
8051 compose->gtkaspell = gtkaspell;
8052 compose_spell_menu_changed(compose);
8053 claws_spell_entry_set_gtkaspell(CLAWS_SPELL_ENTRY(subject_entry), gtkaspell);
8056 compose_select_account(compose, account, TRUE);
8058 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", prefs_common.autowrap);
8059 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", prefs_common.auto_indent);
8061 if (account->set_autocc && account->auto_cc && mode != COMPOSE_REEDIT)
8062 compose_entry_append(compose, account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8064 if (account->set_autobcc && account->auto_bcc && mode != COMPOSE_REEDIT)
8065 compose_entry_append(compose, account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8067 if (account->set_autoreplyto && account->auto_replyto && mode != COMPOSE_REEDIT)
8068 compose_entry_append(compose, account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8070 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", compose->mode == COMPOSE_REPLY);
8071 if (account->protocol != A_NNTP)
8072 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
8073 prefs_common_translated_header_name("To:"));
8075 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
8076 prefs_common_translated_header_name("Newsgroups:"));
8078 #ifndef USE_ALT_ADDRBOOK
8079 addressbook_set_target_compose(compose);
8081 if (mode != COMPOSE_REDIRECT)
8082 compose_set_template_menu(compose);
8084 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Template", FALSE);
8087 compose_list = g_list_append(compose_list, compose);
8089 if (!prefs_common.show_ruler)
8090 gtk_widget_hide(ruler_hbox);
8092 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Tools/ShowRuler", prefs_common.show_ruler);
8095 compose->priority = PRIORITY_NORMAL;
8096 compose_update_priority_menu_item(compose);
8098 compose_set_out_encoding(compose);
8101 compose_update_actions_menu(compose);
8103 /* Privacy Systems menu */
8104 compose_update_privacy_systems_menu(compose);
8106 activate_privacy_system(compose, account, TRUE);
8107 toolbar_set_style(compose->toolbar->toolbar, compose->handlebox, prefs_common.toolbar_style);
8109 gtk_widget_realize(window);
8111 gtk_widget_show(window);
8117 static GtkWidget *compose_account_option_menu_create(Compose *compose)
8122 GtkWidget *optmenubox;
8123 GtkWidget *fromlabel;
8126 GtkWidget *from_name = NULL;
8128 gint num = 0, def_menu = 0;
8130 accounts = account_get_list();
8131 cm_return_val_if_fail(accounts != NULL, NULL);
8133 optmenubox = gtk_event_box_new();
8134 optmenu = gtkut_sc_combobox_create(optmenubox, FALSE);
8135 menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
8137 hbox = gtk_hbox_new(FALSE, 4);
8138 from_name = gtk_entry_new();
8140 g_signal_connect_after(G_OBJECT(from_name), "grab_focus",
8141 G_CALLBACK(compose_grab_focus_cb), compose);
8142 g_signal_connect_after(G_OBJECT(from_name), "activate",
8143 G_CALLBACK(from_name_activate_cb), optmenu);
8145 for (; accounts != NULL; accounts = accounts->next, num++) {
8146 PrefsAccount *ac = (PrefsAccount *)accounts->data;
8147 gchar *name, *from = NULL;
8149 if (ac == compose->account) def_menu = num;
8151 name = g_markup_printf_escaped("<i>%s</i>",
8154 if (ac == compose->account) {
8155 if (ac->name && *ac->name) {
8157 QUOTE_IF_REQUIRED_NORMAL(buf, ac->name, return NULL);
8158 from = g_strdup_printf("%s <%s>",
8160 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8162 from = g_strdup_printf("%s",
8164 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8166 if (cur_account != compose->account) {
8167 gtk_widget_modify_base(
8168 GTK_WIDGET(from_name),
8169 GTK_STATE_NORMAL, &default_header_bgcolor);
8170 gtk_widget_modify_text(
8171 GTK_WIDGET(from_name),
8172 GTK_STATE_NORMAL, &default_header_color);
8175 COMBOBOX_ADD(menu, name, ac->account_id);
8180 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), def_menu);
8182 g_signal_connect(G_OBJECT(optmenu), "changed",
8183 G_CALLBACK(account_activated),
8185 g_signal_connect(G_OBJECT(from_name), "populate-popup",
8186 G_CALLBACK(compose_entry_popup_extend),
8189 fromlabel = gtk_label_new_with_mnemonic(_("_From:"));
8190 gtk_label_set_mnemonic_widget(GTK_LABEL(fromlabel), from_name);
8192 gtk_box_pack_start(GTK_BOX(hbox), fromlabel, FALSE, FALSE, 4);
8193 gtk_box_pack_start(GTK_BOX(hbox), optmenubox, FALSE, FALSE, 0);
8194 gtk_box_pack_start(GTK_BOX(hbox), from_name, TRUE, TRUE, 0);
8196 /* Putting only the GtkEntry into focus chain of parent hbox causes
8197 * the account selector combobox next to it to be unreachable when
8198 * navigating widgets in GtkTable with up/down arrow keys.
8199 * Note: gtk_widget_set_can_focus() was not enough. */
8201 l = g_list_prepend(l, from_name);
8202 gtk_container_set_focus_chain(GTK_CONTAINER(hbox), l);
8205 CLAWS_SET_TIP(optmenubox,
8206 _("Account to use for this email"));
8207 CLAWS_SET_TIP(from_name,
8208 _("Sender address to be used"));
8210 compose->account_combo = optmenu;
8211 compose->from_name = from_name;
8216 static void compose_set_priority_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8218 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8219 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8220 Compose *compose = (Compose *) data;
8222 compose->priority = value;
8226 static void compose_reply_change_mode(Compose *compose,
8229 gboolean was_modified = compose->modified;
8231 gboolean all = FALSE, ml = FALSE, sender = FALSE, followup = FALSE;
8233 cm_return_if_fail(compose->replyinfo != NULL);
8235 if (action == COMPOSE_REPLY && prefs_common.default_reply_list)
8237 if (action == COMPOSE_REPLY && compose->rmode == COMPOSE_FOLLOWUP_AND_REPLY_TO)
8239 if (action == COMPOSE_REPLY_TO_ALL)
8241 if (action == COMPOSE_REPLY_TO_SENDER)
8243 if (action == COMPOSE_REPLY_TO_LIST)
8246 compose_remove_header_entries(compose);
8247 compose_reply_set_entry(compose, compose->replyinfo, all, ml, sender, followup);
8248 if (compose->account->set_autocc && compose->account->auto_cc)
8249 compose_entry_append(compose, compose->account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8251 if (compose->account->set_autobcc && compose->account->auto_bcc)
8252 compose_entry_append(compose, compose->account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8254 if (compose->account->set_autoreplyto && compose->account->auto_replyto)
8255 compose_entry_append(compose, compose->account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8256 compose_show_first_last_header(compose, TRUE);
8257 compose->modified = was_modified;
8258 compose_set_title(compose);
8261 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8263 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8264 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8265 Compose *compose = (Compose *) data;
8268 compose_reply_change_mode(compose, value);
8271 static void compose_update_priority_menu_item(Compose * compose)
8273 GtkWidget *menuitem = NULL;
8274 switch (compose->priority) {
8275 case PRIORITY_HIGHEST:
8276 menuitem = gtk_ui_manager_get_widget
8277 (compose->ui_manager, "/Menu/Options/Priority/Highest");
8280 menuitem = gtk_ui_manager_get_widget
8281 (compose->ui_manager, "/Menu/Options/Priority/High");
8283 case PRIORITY_NORMAL:
8284 menuitem = gtk_ui_manager_get_widget
8285 (compose->ui_manager, "/Menu/Options/Priority/Normal");
8288 menuitem = gtk_ui_manager_get_widget
8289 (compose->ui_manager, "/Menu/Options/Priority/Low");
8291 case PRIORITY_LOWEST:
8292 menuitem = gtk_ui_manager_get_widget
8293 (compose->ui_manager, "/Menu/Options/Priority/Lowest");
8296 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8299 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data)
8301 Compose *compose = (Compose *) data;
8303 gboolean can_sign = FALSE, can_encrypt = FALSE;
8305 cm_return_if_fail(GTK_IS_CHECK_MENU_ITEM(widget));
8307 if (!gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget)))
8310 systemid = g_object_get_data(G_OBJECT(widget), "privacy_system");
8311 g_free(compose->privacy_system);
8312 compose->privacy_system = NULL;
8313 g_free(compose->encdata);
8314 compose->encdata = NULL;
8315 if (systemid != NULL) {
8316 compose->privacy_system = g_strdup(systemid);
8318 can_sign = privacy_system_can_sign(systemid);
8319 can_encrypt = privacy_system_can_encrypt(systemid);
8322 debug_print("activated privacy system: %s\n", systemid != NULL ? systemid : "None");
8324 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8325 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8328 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn)
8330 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8331 GtkWidget *menuitem = NULL;
8332 GList *children, *amenu;
8333 gboolean can_sign = FALSE, can_encrypt = FALSE;
8334 gboolean found = FALSE;
8336 if (compose->privacy_system != NULL) {
8338 menuitem = gtk_menu_item_get_submenu(GTK_MENU_ITEM(
8339 gtk_ui_manager_get_widget(compose->ui_manager, branch_path)));
8340 cm_return_if_fail(menuitem != NULL);
8342 children = gtk_container_get_children(GTK_CONTAINER(GTK_MENU_SHELL(menuitem)));
8345 while (amenu != NULL) {
8346 systemid = g_object_get_data(G_OBJECT(amenu->data), "privacy_system");
8347 if (systemid != NULL) {
8348 if (strcmp(systemid, compose->privacy_system) == 0 &&
8349 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8350 menuitem = GTK_WIDGET(amenu->data);
8352 can_sign = privacy_system_can_sign(systemid);
8353 can_encrypt = privacy_system_can_encrypt(systemid);
8357 } else if (strlen(compose->privacy_system) == 0 &&
8358 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8359 menuitem = GTK_WIDGET(amenu->data);
8362 can_encrypt = FALSE;
8367 amenu = amenu->next;
8369 g_list_free(children);
8370 if (menuitem != NULL)
8371 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8373 if (warn && !found && strlen(compose->privacy_system)) {
8374 alertpanel_warning(_("The privacy system '%s' cannot be loaded. You "
8375 "will not be able to sign or encrypt this message."),
8376 compose->privacy_system);
8380 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8381 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8384 static void compose_set_out_encoding(Compose *compose)
8386 CharSet out_encoding;
8387 const gchar *branch = NULL;
8388 out_encoding = conv_get_charset_from_str(prefs_common.outgoing_charset);
8390 switch(out_encoding) {
8391 case C_AUTO: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8392 case C_US_ASCII: branch = "Menu/Options/Encoding/" CS_US_ASCII; break;
8393 case C_UTF_8: branch = "Menu/Options/Encoding/" CS_UTF_8; break;
8394 case C_ISO_8859_2: branch = "Menu/Options/Encoding/" CS_ISO_8859_2; break;
8395 case C_ISO_8859_7: branch = "Menu/Options/Encoding/" CS_ISO_8859_7; break;
8396 case C_ISO_8859_9: branch = "Menu/Options/Encoding/" CS_ISO_8859_9; break;
8397 case C_ISO_8859_1: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_1; break;
8398 case C_ISO_8859_15: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_15; break;
8399 case C_WINDOWS_1252: branch = "Menu/Options/Encoding/Western/" CS_WINDOWS_1252; break;
8400 case C_ISO_8859_13: branch = "Menu/Options/Encoding/Baltic/" CS_ISO_8859_13; break;
8401 case C_ISO_8859_4: branch = "Menu/Options/Encoding/Baltic" CS_ISO_8859_4; break;
8402 case C_ISO_8859_8: branch = "Menu/Options/Encoding/Hebrew/" CS_ISO_8859_8; break;
8403 case C_WINDOWS_1255: branch = "Menu/Options/Encoding/Hebrew/" CS_WINDOWS_1255; break;
8404 case C_ISO_8859_6: branch = "Menu/Options/Encoding/Arabic/" CS_ISO_8859_6; break;
8405 case C_WINDOWS_1256: branch = "Menu/Options/Encoding/Arabic/" CS_WINDOWS_1256; break;
8406 case C_ISO_8859_5: branch = "Menu/Options/Encoding/Cyrillic/" CS_ISO_8859_5; break;
8407 case C_KOI8_R: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_R; break;
8408 case C_MACCYR: branch = "Menu/Options/Encoding/Cyrillic/" CS_MACCYR; break;
8409 case C_KOI8_U: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_U; break;
8410 case C_WINDOWS_1251: branch = "Menu/Options/Encoding/Cyrillic/" CS_WINDOWS_1251; break;
8411 case C_ISO_2022_JP: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP; break;
8412 case C_ISO_2022_JP_2: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP_2; break;
8413 case C_EUC_JP: branch = "Menu/Options/Encoding/Japanese/" CS_EUC_JP; break;
8414 case C_SHIFT_JIS: branch = "Menu/Options/Encoding/Japanese/" CS_SHIFT_JIS; break;
8415 case C_GB18030: branch = "Menu/Options/Encoding/Chinese/" CS_GB18030; break;
8416 case C_GB2312: branch = "Menu/Options/Encoding/Chinese/" CS_GB2312; break;
8417 case C_GBK: branch = "Menu/Options/Encoding/Chinese/" CS_GBK; break;
8418 case C_BIG5: branch = "Menu/Options/Encoding/Chinese/" CS_BIG5; break;
8419 case C_EUC_TW: branch = "Menu/Options/Encoding/Chinese/" CS_EUC_TW; break;
8420 case C_EUC_KR: branch = "Menu/Options/Encoding/Korean/" CS_EUC_KR; break;
8421 case C_ISO_2022_KR: branch = "Menu/Options/Encoding/Korean/" CS_ISO_2022_KR; break;
8422 case C_TIS_620: branch = "Menu/Options/Encoding/Thai/" CS_TIS_620; break;
8423 case C_WINDOWS_874: branch = "Menu/Options/Encoding/Thai/" CS_WINDOWS_874; break;
8424 default: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8426 cm_toggle_menu_set_active_full(compose->ui_manager, (gchar *)branch, TRUE);
8429 static void compose_set_template_menu(Compose *compose)
8431 GSList *tmpl_list, *cur;
8435 tmpl_list = template_get_config();
8437 menu = gtk_menu_new();
8439 gtk_menu_set_accel_group (GTK_MENU (menu),
8440 gtk_ui_manager_get_accel_group(compose->ui_manager));
8441 for (cur = tmpl_list; cur != NULL; cur = cur->next) {
8442 Template *tmpl = (Template *)cur->data;
8443 gchar *accel_path = NULL;
8444 item = gtk_menu_item_new_with_label(tmpl->name);
8445 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
8446 g_signal_connect(G_OBJECT(item), "activate",
8447 G_CALLBACK(compose_template_activate_cb),
8449 g_object_set_data(G_OBJECT(item), "template", tmpl);
8450 gtk_widget_show(item);
8451 accel_path = g_strconcat("<ComposeTemplates>" , "/", tmpl->name, NULL);
8452 gtk_menu_item_set_accel_path(GTK_MENU_ITEM(item), accel_path);
8456 gtk_widget_show(menu);
8457 gtk_menu_item_set_submenu(GTK_MENU_ITEM(compose->tmpl_menu), menu);
8460 void compose_update_actions_menu(Compose *compose)
8462 action_update_compose_menu(compose->ui_manager, "/Menu/Tools/Actions", compose);
8465 static void compose_update_privacy_systems_menu(Compose *compose)
8467 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8468 GSList *systems, *cur;
8470 GtkWidget *system_none;
8472 GtkWidget *privacy_menuitem = gtk_ui_manager_get_widget(compose->ui_manager, branch_path);
8473 GtkWidget *privacy_menu = gtk_menu_new();
8475 system_none = gtk_radio_menu_item_new_with_mnemonic(NULL, _("_None"));
8476 g_object_set_data_full(G_OBJECT(system_none), "privacy_system", NULL, NULL);
8478 g_signal_connect(G_OBJECT(system_none), "activate",
8479 G_CALLBACK(compose_set_privacy_system_cb), compose);
8481 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), system_none);
8482 gtk_widget_show(system_none);
8484 systems = privacy_get_system_ids();
8485 for (cur = systems; cur != NULL; cur = g_slist_next(cur)) {
8486 gchar *systemid = cur->data;
8488 group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(system_none));
8489 widget = gtk_radio_menu_item_new_with_label(group,
8490 privacy_system_get_name(systemid));
8491 g_object_set_data_full(G_OBJECT(widget), "privacy_system",
8492 g_strdup(systemid), g_free);
8493 g_signal_connect(G_OBJECT(widget), "activate",
8494 G_CALLBACK(compose_set_privacy_system_cb), compose);
8496 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), widget);
8497 gtk_widget_show(widget);
8500 g_slist_free(systems);
8501 gtk_menu_item_set_submenu(GTK_MENU_ITEM(privacy_menuitem), privacy_menu);
8502 gtk_widget_show_all(privacy_menu);
8503 gtk_widget_show_all(privacy_menuitem);
8506 void compose_reflect_prefs_all(void)
8511 for (cur = compose_list; cur != NULL; cur = cur->next) {
8512 compose = (Compose *)cur->data;
8513 compose_set_template_menu(compose);
8517 void compose_reflect_prefs_pixmap_theme(void)
8522 for (cur = compose_list; cur != NULL; cur = cur->next) {
8523 compose = (Compose *)cur->data;
8524 toolbar_update(TOOLBAR_COMPOSE, compose);
8528 static const gchar *compose_quote_char_from_context(Compose *compose)
8530 const gchar *qmark = NULL;
8532 cm_return_val_if_fail(compose != NULL, NULL);
8534 switch (compose->mode) {
8535 /* use forward-specific quote char */
8536 case COMPOSE_FORWARD:
8537 case COMPOSE_FORWARD_AS_ATTACH:
8538 case COMPOSE_FORWARD_INLINE:
8539 if (compose->folder && compose->folder->prefs &&
8540 compose->folder->prefs->forward_with_format)
8541 qmark = compose->folder->prefs->forward_quotemark;
8542 else if (compose->account->forward_with_format)
8543 qmark = compose->account->forward_quotemark;
8545 qmark = prefs_common.fw_quotemark;
8548 /* use reply-specific quote char in all other modes */
8550 if (compose->folder && compose->folder->prefs &&
8551 compose->folder->prefs->reply_with_format)
8552 qmark = compose->folder->prefs->reply_quotemark;
8553 else if (compose->account->reply_with_format)
8554 qmark = compose->account->reply_quotemark;
8556 qmark = prefs_common.quotemark;
8560 if (qmark == NULL || *qmark == '\0')
8566 static void compose_template_apply(Compose *compose, Template *tmpl,
8570 GtkTextBuffer *buffer;
8574 gchar *parsed_str = NULL;
8575 gint cursor_pos = 0;
8576 const gchar *err_msg = _("The body of the template has an error at line %d.");
8579 /* process the body */
8581 text = GTK_TEXT_VIEW(compose->text);
8582 buffer = gtk_text_view_get_buffer(text);
8585 qmark = compose_quote_char_from_context(compose);
8587 if (compose->replyinfo != NULL) {
8590 gtk_text_buffer_set_text(buffer, "", -1);
8591 mark = gtk_text_buffer_get_insert(buffer);
8592 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8594 parsed_str = compose_quote_fmt(compose, compose->replyinfo,
8595 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8597 } else if (compose->fwdinfo != NULL) {
8600 gtk_text_buffer_set_text(buffer, "", -1);
8601 mark = gtk_text_buffer_get_insert(buffer);
8602 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8604 parsed_str = compose_quote_fmt(compose, compose->fwdinfo,
8605 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8608 MsgInfo* dummyinfo = compose_msginfo_new_from_compose(compose);
8610 GtkTextIter start, end;
8613 gtk_text_buffer_get_start_iter(buffer, &start);
8614 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
8615 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
8617 /* clear the buffer now */
8619 gtk_text_buffer_set_text(buffer, "", -1);
8621 parsed_str = compose_quote_fmt(compose, dummyinfo,
8622 tmpl->value, qmark, tmp, FALSE, FALSE, err_msg);
8623 procmsg_msginfo_free( &dummyinfo );
8629 gtk_text_buffer_set_text(buffer, "", -1);
8630 mark = gtk_text_buffer_get_insert(buffer);
8631 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8634 if (replace && parsed_str && compose->account->auto_sig)
8635 compose_insert_sig(compose, FALSE);
8637 if (replace && parsed_str) {
8638 gtk_text_buffer_get_start_iter(buffer, &iter);
8639 gtk_text_buffer_place_cursor(buffer, &iter);
8643 cursor_pos = quote_fmt_get_cursor_pos();
8644 compose->set_cursor_pos = cursor_pos;
8645 if (cursor_pos == -1)
8647 gtk_text_buffer_get_start_iter(buffer, &iter);
8648 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
8649 gtk_text_buffer_place_cursor(buffer, &iter);
8652 /* process the other fields */
8654 compose_template_apply_fields(compose, tmpl);
8655 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
8656 quote_fmt_reset_vartable();
8657 compose_changed_cb(NULL, compose);
8660 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
8661 gtkaspell_highlight_all(compose->gtkaspell);
8665 static void compose_template_apply_fields_error(const gchar *header)
8670 tr = g_strdup(C_("'%s' stands for a header name",
8671 "Template '%s' format error."));
8672 text = g_strdup_printf(tr, prefs_common_translated_header_name(header));
8673 alertpanel_error(text);
8679 static void compose_template_apply_fields(Compose *compose, Template *tmpl)
8681 MsgInfo* dummyinfo = NULL;
8682 MsgInfo *msginfo = NULL;
8685 if (compose->replyinfo != NULL)
8686 msginfo = compose->replyinfo;
8687 else if (compose->fwdinfo != NULL)
8688 msginfo = compose->fwdinfo;
8690 dummyinfo = compose_msginfo_new_from_compose(compose);
8691 msginfo = dummyinfo;
8694 if (tmpl->from && *tmpl->from != '\0') {
8696 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8697 compose->gtkaspell);
8699 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8701 quote_fmt_scan_string(tmpl->from);
8704 buf = quote_fmt_get_buffer();
8706 compose_template_apply_fields_error("From");
8708 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
8712 if (tmpl->to && *tmpl->to != '\0') {
8714 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8715 compose->gtkaspell);
8717 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8719 quote_fmt_scan_string(tmpl->to);
8722 buf = quote_fmt_get_buffer();
8724 compose_template_apply_fields_error("To");
8726 compose_entry_append(compose, buf, COMPOSE_TO, PREF_TEMPLATE);
8730 if (tmpl->cc && *tmpl->cc != '\0') {
8732 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8733 compose->gtkaspell);
8735 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8737 quote_fmt_scan_string(tmpl->cc);
8740 buf = quote_fmt_get_buffer();
8742 compose_template_apply_fields_error("Cc");
8744 compose_entry_append(compose, buf, COMPOSE_CC, PREF_TEMPLATE);
8748 if (tmpl->bcc && *tmpl->bcc != '\0') {
8750 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8751 compose->gtkaspell);
8753 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8755 quote_fmt_scan_string(tmpl->bcc);
8758 buf = quote_fmt_get_buffer();
8760 compose_template_apply_fields_error("Bcc");
8762 compose_entry_append(compose, buf, COMPOSE_BCC, PREF_TEMPLATE);
8766 if (tmpl->replyto && *tmpl->replyto != '\0') {
8768 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8769 compose->gtkaspell);
8771 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8773 quote_fmt_scan_string(tmpl->replyto);
8776 buf = quote_fmt_get_buffer();
8778 compose_template_apply_fields_error("Reply-To");
8780 compose_entry_append(compose, buf, COMPOSE_REPLYTO, PREF_TEMPLATE);
8784 /* process the subject */
8785 if (tmpl->subject && *tmpl->subject != '\0') {
8787 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8788 compose->gtkaspell);
8790 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8792 quote_fmt_scan_string(tmpl->subject);
8795 buf = quote_fmt_get_buffer();
8797 compose_template_apply_fields_error("Subject");
8799 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
8803 procmsg_msginfo_free( &dummyinfo );
8806 static void compose_destroy(Compose *compose)
8808 GtkAllocation allocation;
8809 GtkTextBuffer *buffer;
8810 GtkClipboard *clipboard;
8812 compose_list = g_list_remove(compose_list, compose);
8814 if (compose->updating) {
8815 debug_print("danger, not destroying anything now\n");
8816 compose->deferred_destroy = TRUE;
8820 /* NOTE: address_completion_end() does nothing with the window
8821 * however this may change. */
8822 address_completion_end(compose->window);
8824 slist_free_strings_full(compose->to_list);
8825 slist_free_strings_full(compose->newsgroup_list);
8826 slist_free_strings_full(compose->header_list);
8828 slist_free_strings_full(extra_headers);
8829 extra_headers = NULL;
8831 compose->header_list = compose->newsgroup_list = compose->to_list = NULL;
8833 g_hash_table_destroy(compose->email_hashtable);
8835 hooks_unregister_hook(FOLDER_UPDATE_HOOKLIST,
8836 compose->folder_update_callback_id);
8838 procmsg_msginfo_free(&(compose->targetinfo));
8839 procmsg_msginfo_free(&(compose->replyinfo));
8840 procmsg_msginfo_free(&(compose->fwdinfo));
8842 g_free(compose->replyto);
8843 g_free(compose->cc);
8844 g_free(compose->bcc);
8845 g_free(compose->newsgroups);
8846 g_free(compose->followup_to);
8848 g_free(compose->ml_post);
8850 g_free(compose->inreplyto);
8851 g_free(compose->references);
8852 g_free(compose->msgid);
8853 g_free(compose->boundary);
8855 g_free(compose->redirect_filename);
8856 if (compose->undostruct)
8857 undo_destroy(compose->undostruct);
8859 g_free(compose->sig_str);
8861 g_free(compose->exteditor_file);
8863 g_free(compose->orig_charset);
8865 g_free(compose->privacy_system);
8866 g_free(compose->encdata);
8868 #ifndef USE_ALT_ADDRBOOK
8869 if (addressbook_get_target_compose() == compose)
8870 addressbook_set_target_compose(NULL);
8873 if (compose->gtkaspell) {
8874 gtkaspell_delete(compose->gtkaspell);
8875 compose->gtkaspell = NULL;
8879 if (!compose->batch) {
8880 gtk_widget_get_allocation(compose->window, &allocation);
8881 prefs_common.compose_width = allocation.width;
8882 prefs_common.compose_height = allocation.height;
8885 if (!gtk_widget_get_parent(compose->paned))
8886 gtk_widget_destroy(compose->paned);
8887 gtk_widget_destroy(compose->popupmenu);
8889 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
8890 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
8891 gtk_text_buffer_remove_selection_clipboard(buffer, clipboard);
8893 gtk_widget_destroy(compose->window);
8894 toolbar_destroy(compose->toolbar);
8895 g_free(compose->toolbar);
8896 cm_mutex_free(compose->mutex);
8900 static void compose_attach_info_free(AttachInfo *ainfo)
8902 g_free(ainfo->file);
8903 g_free(ainfo->content_type);
8904 g_free(ainfo->name);
8905 g_free(ainfo->charset);
8909 static void compose_attach_update_label(Compose *compose)
8914 GtkTreeModel *model;
8919 model = gtk_tree_view_get_model(GTK_TREE_VIEW(compose->attach_clist));
8920 if(!gtk_tree_model_get_iter_first(model, &iter)) {
8921 gtk_label_set_text(GTK_LABEL(compose->attach_label), "");
8925 while(gtk_tree_model_iter_next(model, &iter))
8928 text = g_strdup_printf("(%d)", i);
8929 gtk_label_set_text(GTK_LABEL(compose->attach_label), text);
8933 static void compose_attach_remove_selected(GtkAction *action, gpointer data)
8935 Compose *compose = (Compose *)data;
8936 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
8937 GtkTreeSelection *selection;
8939 GtkTreeModel *model;
8941 selection = gtk_tree_view_get_selection(tree_view);
8942 sel = gtk_tree_selection_get_selected_rows(selection, &model);
8947 for (cur = sel; cur != NULL; cur = cur->next) {
8948 GtkTreePath *path = cur->data;
8949 GtkTreeRowReference *ref = gtk_tree_row_reference_new
8952 gtk_tree_path_free(path);
8955 for (cur = sel; cur != NULL; cur = cur->next) {
8956 GtkTreeRowReference *ref = cur->data;
8957 GtkTreePath *path = gtk_tree_row_reference_get_path(ref);
8960 if (gtk_tree_model_get_iter(model, &iter, path))
8961 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
8963 gtk_tree_path_free(path);
8964 gtk_tree_row_reference_free(ref);
8968 compose_attach_update_label(compose);
8971 static struct _AttachProperty
8974 GtkWidget *mimetype_entry;
8975 GtkWidget *encoding_optmenu;
8976 GtkWidget *path_entry;
8977 GtkWidget *filename_entry;
8979 GtkWidget *cancel_btn;
8982 static void gtk_tree_path_free_(gpointer ptr, gpointer data)
8984 gtk_tree_path_free((GtkTreePath *)ptr);
8987 static void compose_attach_property(GtkAction *action, gpointer data)
8989 Compose *compose = (Compose *)data;
8990 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
8992 GtkComboBox *optmenu;
8993 GtkTreeSelection *selection;
8995 GtkTreeModel *model;
8998 static gboolean cancelled;
9000 /* only if one selected */
9001 selection = gtk_tree_view_get_selection(tree_view);
9002 if (gtk_tree_selection_count_selected_rows(selection) != 1)
9005 sel = gtk_tree_selection_get_selected_rows(selection, &model);
9009 path = (GtkTreePath *) sel->data;
9010 gtk_tree_model_get_iter(model, &iter, path);
9011 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
9014 g_list_foreach(sel, gtk_tree_path_free_, NULL);
9020 if (!attach_prop.window)
9021 compose_attach_property_create(&cancelled);
9022 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), TRUE);
9023 gtk_widget_grab_focus(attach_prop.ok_btn);
9024 gtk_widget_show(attach_prop.window);
9025 gtk_window_set_transient_for(GTK_WINDOW(attach_prop.window),
9026 GTK_WINDOW(compose->window));
9028 optmenu = GTK_COMBO_BOX(attach_prop.encoding_optmenu);
9029 if (ainfo->encoding == ENC_UNKNOWN)
9030 combobox_select_by_data(optmenu, ENC_BASE64);
9032 combobox_select_by_data(optmenu, ainfo->encoding);
9034 gtk_entry_set_text(GTK_ENTRY(attach_prop.mimetype_entry),
9035 ainfo->content_type ? ainfo->content_type : "");
9036 gtk_entry_set_text(GTK_ENTRY(attach_prop.path_entry),
9037 ainfo->file ? ainfo->file : "");
9038 gtk_entry_set_text(GTK_ENTRY(attach_prop.filename_entry),
9039 ainfo->name ? ainfo->name : "");
9042 const gchar *entry_text;
9044 gchar *cnttype = NULL;
9051 gtk_widget_hide(attach_prop.window);
9052 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), FALSE);
9057 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.mimetype_entry));
9058 if (*entry_text != '\0') {
9061 text = g_strstrip(g_strdup(entry_text));
9062 if ((p = strchr(text, '/')) && !strchr(p + 1, '/')) {
9063 cnttype = g_strdup(text);
9066 alertpanel_error(_("Invalid MIME type."));
9072 ainfo->encoding = combobox_get_active_data(optmenu);
9074 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.path_entry));
9075 if (*entry_text != '\0') {
9076 if (is_file_exist(entry_text) &&
9077 (size = get_file_size(entry_text)) > 0)
9078 file = g_strdup(entry_text);
9081 (_("File doesn't exist or is empty."));
9087 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.filename_entry));
9088 if (*entry_text != '\0') {
9089 g_free(ainfo->name);
9090 ainfo->name = g_strdup(entry_text);
9094 g_free(ainfo->content_type);
9095 ainfo->content_type = cnttype;
9098 g_free(ainfo->file);
9102 ainfo->size = (goffset)size;
9104 /* update tree store */
9105 text = to_human_readable(ainfo->size);
9106 gtk_tree_model_get_iter(model, &iter, path);
9107 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
9108 COL_MIMETYPE, ainfo->content_type,
9110 COL_NAME, ainfo->name,
9111 COL_CHARSET, ainfo->charset,
9117 gtk_tree_path_free(path);
9120 #define SET_LABEL_AND_ENTRY(str, entry, top) \
9122 label = gtk_label_new(str); \
9123 gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), \
9124 GTK_FILL, 0, 0, 0); \
9125 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); \
9127 entry = gtk_entry_new(); \
9128 gtk_table_attach(GTK_TABLE(table), entry, 1, 2, top, (top + 1), \
9129 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); \
9132 static void compose_attach_property_create(gboolean *cancelled)
9138 GtkWidget *mimetype_entry;
9141 GtkListStore *optmenu_menu;
9142 GtkWidget *path_entry;
9143 GtkWidget *filename_entry;
9146 GtkWidget *cancel_btn;
9147 GList *mime_type_list, *strlist;
9150 debug_print("Creating attach_property window...\n");
9152 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose_attach_property");
9153 gtk_widget_set_size_request(window, 480, -1);
9154 gtk_container_set_border_width(GTK_CONTAINER(window), 8);
9155 gtk_window_set_title(GTK_WINDOW(window), _("Properties"));
9156 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
9157 g_signal_connect(G_OBJECT(window), "delete_event",
9158 G_CALLBACK(attach_property_delete_event),
9160 g_signal_connect(G_OBJECT(window), "key_press_event",
9161 G_CALLBACK(attach_property_key_pressed),
9164 vbox = gtk_vbox_new(FALSE, 8);
9165 gtk_container_add(GTK_CONTAINER(window), vbox);
9167 table = gtk_table_new(4, 2, FALSE);
9168 gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
9169 gtk_table_set_row_spacings(GTK_TABLE(table), 8);
9170 gtk_table_set_col_spacings(GTK_TABLE(table), 8);
9172 label = gtk_label_new(_("MIME type"));
9173 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, (0 + 1),
9175 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
9176 #if !GTK_CHECK_VERSION(2, 24, 0)
9177 mimetype_entry = gtk_combo_box_entry_new_text();
9179 mimetype_entry = gtk_combo_box_text_new_with_entry();
9181 gtk_table_attach(GTK_TABLE(table), mimetype_entry, 1, 2, 0, (0 + 1),
9182 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
9184 /* stuff with list */
9185 mime_type_list = procmime_get_mime_type_list();
9187 for (; mime_type_list != NULL; mime_type_list = mime_type_list->next) {
9188 MimeType *type = (MimeType *) mime_type_list->data;
9191 tmp = g_strdup_printf("%s/%s", type->type, type->sub_type);
9193 if (g_list_find_custom(strlist, tmp, (GCompareFunc)strcmp2))
9196 strlist = g_list_insert_sorted(strlist, (gpointer)tmp,
9197 (GCompareFunc)strcmp2);
9200 for (mime_type_list = strlist; mime_type_list != NULL;
9201 mime_type_list = mime_type_list->next) {
9202 #if !GTK_CHECK_VERSION(2, 24, 0)
9203 gtk_combo_box_append_text(GTK_COMBO_BOX(mimetype_entry), mime_type_list->data);
9205 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(mimetype_entry), mime_type_list->data);
9207 g_free(mime_type_list->data);
9209 g_list_free(strlist);
9210 gtk_combo_box_set_active(GTK_COMBO_BOX(mimetype_entry), 0);
9211 mimetype_entry = gtk_bin_get_child(GTK_BIN((mimetype_entry)));
9213 label = gtk_label_new(_("Encoding"));
9214 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2,
9216 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
9218 hbox = gtk_hbox_new(FALSE, 0);
9219 gtk_table_attach(GTK_TABLE(table), hbox, 1, 2, 1, 2,
9220 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
9222 optmenu = gtkut_sc_combobox_create(NULL, TRUE);
9223 optmenu_menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
9225 COMBOBOX_ADD(optmenu_menu, "7bit", ENC_7BIT);
9226 COMBOBOX_ADD(optmenu_menu, "8bit", ENC_8BIT);
9227 COMBOBOX_ADD(optmenu_menu, "quoted-printable", ENC_QUOTED_PRINTABLE);
9228 COMBOBOX_ADD(optmenu_menu, "base64", ENC_BASE64);
9229 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), 0);
9231 gtk_box_pack_start(GTK_BOX(hbox), optmenu, TRUE, TRUE, 0);
9233 SET_LABEL_AND_ENTRY(_("Path"), path_entry, 2);
9234 SET_LABEL_AND_ENTRY(_("File name"), filename_entry, 3);
9236 gtkut_stock_button_set_create(&hbbox, &cancel_btn, GTK_STOCK_CANCEL,
9237 &ok_btn, GTK_STOCK_OK,
9239 gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
9240 gtk_widget_grab_default(ok_btn);
9242 g_signal_connect(G_OBJECT(ok_btn), "clicked",
9243 G_CALLBACK(attach_property_ok),
9245 g_signal_connect(G_OBJECT(cancel_btn), "clicked",
9246 G_CALLBACK(attach_property_cancel),
9249 gtk_widget_show_all(vbox);
9251 attach_prop.window = window;
9252 attach_prop.mimetype_entry = mimetype_entry;
9253 attach_prop.encoding_optmenu = optmenu;
9254 attach_prop.path_entry = path_entry;
9255 attach_prop.filename_entry = filename_entry;
9256 attach_prop.ok_btn = ok_btn;
9257 attach_prop.cancel_btn = cancel_btn;
9260 #undef SET_LABEL_AND_ENTRY
9262 static void attach_property_ok(GtkWidget *widget, gboolean *cancelled)
9268 static void attach_property_cancel(GtkWidget *widget, gboolean *cancelled)
9274 static gint attach_property_delete_event(GtkWidget *widget, GdkEventAny *event,
9275 gboolean *cancelled)
9283 static gboolean attach_property_key_pressed(GtkWidget *widget,
9285 gboolean *cancelled)
9287 if (event && event->keyval == GDK_KEY_Escape) {
9291 if (event && event->keyval == GDK_KEY_Return) {
9299 static void compose_exec_ext_editor(Compose *compose)
9304 GdkNativeWindow socket_wid = 0;
9308 tmp = g_strdup_printf("%s%ctmpmsg.%p", get_tmp_dir(),
9309 G_DIR_SEPARATOR, compose);
9311 if (compose_get_ext_editor_uses_socket()) {
9312 /* Only allow one socket */
9313 if (compose->exteditor_socket != NULL) {
9314 if (gtk_widget_is_focus(compose->exteditor_socket)) {
9315 /* Move the focus off of the socket */
9316 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9321 /* Create the receiving GtkSocket */
9322 socket = gtk_socket_new ();
9323 g_signal_connect (GTK_OBJECT(socket), "plug-removed",
9324 G_CALLBACK(compose_ext_editor_plug_removed_cb),
9326 gtk_box_pack_start(GTK_BOX(compose->edit_vbox), socket, TRUE, TRUE, 0);
9327 gtk_widget_set_size_request(socket, prefs_common.compose_width, -1);
9328 /* Realize the socket so that we can use its ID */
9329 gtk_widget_realize(socket);
9330 socket_wid = gtk_socket_get_id(GTK_SOCKET (socket));
9331 compose->exteditor_socket = socket;
9334 if (pipe(pipe_fds) < 0) {
9340 if ((pid = fork()) < 0) {
9347 /* close the write side of the pipe */
9350 compose->exteditor_file = g_strdup(tmp);
9351 compose->exteditor_pid = pid;
9353 compose_set_ext_editor_sensitive(compose, FALSE);
9356 compose->exteditor_ch = g_io_channel_unix_new(pipe_fds[0]);
9358 compose->exteditor_ch = g_io_channel_win32_new_fd(pipe_fds[0]);
9360 compose->exteditor_tag = g_io_add_watch(compose->exteditor_ch,
9364 } else { /* process-monitoring process */
9370 /* close the read side of the pipe */
9373 if (compose_write_body_to_file(compose, tmp) < 0) {
9374 fd_write_all(pipe_fds[1], "2\n", 2);
9378 pid_ed = compose_exec_ext_editor_real(tmp, socket_wid);
9380 fd_write_all(pipe_fds[1], "1\n", 2);
9384 /* wait until editor is terminated */
9385 waitpid(pid_ed, NULL, 0);
9387 fd_write_all(pipe_fds[1], "0\n", 2);
9394 #endif /* G_OS_UNIX */
9397 static gboolean compose_can_autosave(Compose *compose)
9399 if (compose->privacy_system && compose->use_encryption)
9400 return prefs_common.autosave && prefs_common.autosave_encrypted;
9402 return prefs_common.autosave;
9406 static gboolean compose_get_ext_editor_cmd_valid()
9408 gboolean has_s = FALSE;
9409 gboolean has_w = FALSE;
9410 const gchar *p = prefs_common_get_ext_editor_cmd();
9413 while ((p = strchr(p, '%'))) {
9419 } else if (*p == 'w') {
9430 static gint compose_exec_ext_editor_real(const gchar *file, GdkNativeWindow socket_wid)
9437 cm_return_val_if_fail(file != NULL, -1);
9439 if ((pid = fork()) < 0) {
9444 if (pid != 0) return pid;
9446 /* grandchild process */
9448 if (setpgid(0, getppid()))
9451 if (compose_get_ext_editor_cmd_valid()) {
9452 if (compose_get_ext_editor_uses_socket()) {
9453 p = g_strdup(prefs_common_get_ext_editor_cmd());
9454 s = strstr(p, "%w");
9456 if (strstr(p, "%s") < s)
9457 g_snprintf(buf, sizeof(buf), p, file, socket_wid);
9459 g_snprintf(buf, sizeof(buf), p, socket_wid, file);
9462 g_snprintf(buf, sizeof(buf),
9463 prefs_common_get_ext_editor_cmd(), file);
9466 if (prefs_common_get_ext_editor_cmd())
9467 g_warning("External editor command-line is invalid: '%s'",
9468 prefs_common_get_ext_editor_cmd());
9469 g_snprintf(buf, sizeof(buf), DEFAULT_EDITOR_CMD, file);
9472 cmdline = strsplit_with_quote(buf, " ", 1024);
9473 execvp(cmdline[0], cmdline);
9476 g_strfreev(cmdline);
9481 static gboolean compose_ext_editor_kill(Compose *compose)
9483 pid_t pgid = compose->exteditor_pid * -1;
9486 ret = kill(pgid, 0);
9488 if (ret == 0 || (ret == -1 && EPERM == errno)) {
9492 msg = g_strdup_printf
9493 (_("The external editor is still working.\n"
9494 "Force terminating the process?\n"
9495 "process group id: %d"), -pgid);
9496 val = alertpanel_full(_("Notice"), msg, GTK_STOCK_NO, GTK_STOCK_YES,
9497 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
9501 if (val == G_ALERTALTERNATE) {
9502 g_source_remove(compose->exteditor_tag);
9503 g_io_channel_shutdown(compose->exteditor_ch,
9505 g_io_channel_unref(compose->exteditor_ch);
9507 if (kill(pgid, SIGTERM) < 0) perror("kill");
9508 waitpid(compose->exteditor_pid, NULL, 0);
9510 g_warning("Terminated process group id: %d. "
9511 "Temporary file: %s", -pgid, compose->exteditor_file);
9513 compose_set_ext_editor_sensitive(compose, TRUE);
9515 g_free(compose->exteditor_file);
9516 compose->exteditor_file = NULL;
9517 compose->exteditor_pid = -1;
9518 compose->exteditor_ch = NULL;
9519 compose->exteditor_tag = -1;
9527 static gboolean compose_input_cb(GIOChannel *source, GIOCondition condition,
9531 Compose *compose = (Compose *)data;
9534 debug_print("Compose: input from monitoring process\n");
9536 if (g_io_channel_read_chars(source, buf, sizeof(buf), &bytes_read, NULL) != G_IO_STATUS_NORMAL) {
9541 g_io_channel_shutdown(source, FALSE, NULL);
9542 g_io_channel_unref(source);
9544 waitpid(compose->exteditor_pid, NULL, 0);
9546 if (buf[0] == '0') { /* success */
9547 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9548 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
9549 GtkTextIter start, end;
9552 gtk_text_buffer_set_text(buffer, "", -1);
9553 compose_insert_file(compose, compose->exteditor_file);
9554 compose_changed_cb(NULL, compose);
9556 /* Check if we should save the draft or not */
9557 if (compose_can_autosave(compose))
9558 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
9560 if (claws_unlink(compose->exteditor_file) < 0)
9561 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9563 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
9564 gtk_text_buffer_get_start_iter(buffer, &start);
9565 gtk_text_buffer_get_end_iter(buffer, &end);
9566 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
9567 if (chars && strlen(chars) > 0)
9568 compose->modified = TRUE;
9570 } else if (buf[0] == '1') { /* failed */
9571 g_warning("Couldn't exec external editor");
9572 if (claws_unlink(compose->exteditor_file) < 0)
9573 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9574 } else if (buf[0] == '2') {
9575 g_warning("Couldn't write to file");
9576 } else if (buf[0] == '3') {
9577 g_warning("Pipe read failed");
9580 compose_set_ext_editor_sensitive(compose, TRUE);
9582 g_free(compose->exteditor_file);
9583 compose->exteditor_file = NULL;
9584 compose->exteditor_pid = -1;
9585 compose->exteditor_ch = NULL;
9586 compose->exteditor_tag = -1;
9587 if (compose->exteditor_socket) {
9588 gtk_widget_destroy(compose->exteditor_socket);
9589 compose->exteditor_socket = NULL;
9596 static char *ext_editor_menu_entries[] = {
9597 "Menu/Message/Send",
9598 "Menu/Message/SendLater",
9599 "Menu/Message/InsertFile",
9600 "Menu/Message/InsertSig",
9601 "Menu/Message/ReplaceSig",
9602 "Menu/Message/Save",
9603 "Menu/Message/Print",
9608 "Menu/Tools/ShowRuler",
9609 "Menu/Tools/Actions",
9614 static void compose_set_ext_editor_sensitive(Compose *compose,
9619 for (i = 0; ext_editor_menu_entries[i]; ++i) {
9620 cm_menu_set_sensitive_full(compose->ui_manager,
9621 ext_editor_menu_entries[i], sensitive);
9624 if (compose_get_ext_editor_uses_socket()) {
9626 if (compose->exteditor_socket)
9627 gtk_widget_hide(compose->exteditor_socket);
9628 gtk_widget_show(compose->scrolledwin);
9629 if (prefs_common.show_ruler)
9630 gtk_widget_show(compose->ruler_hbox);
9631 /* Fix the focus, as it doesn't go anywhere when the
9632 * socket is hidden or destroyed */
9633 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9635 g_assert (compose->exteditor_socket != NULL);
9636 /* Fix the focus, as it doesn't go anywhere when the
9637 * edit box is hidden */
9638 if (gtk_widget_is_focus(compose->text))
9639 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9640 gtk_widget_hide(compose->scrolledwin);
9641 gtk_widget_hide(compose->ruler_hbox);
9642 gtk_widget_show(compose->exteditor_socket);
9645 gtk_widget_set_sensitive(compose->text, sensitive);
9647 if (compose->toolbar->send_btn)
9648 gtk_widget_set_sensitive(compose->toolbar->send_btn, sensitive);
9649 if (compose->toolbar->sendl_btn)
9650 gtk_widget_set_sensitive(compose->toolbar->sendl_btn, sensitive);
9651 if (compose->toolbar->draft_btn)
9652 gtk_widget_set_sensitive(compose->toolbar->draft_btn, sensitive);
9653 if (compose->toolbar->insert_btn)
9654 gtk_widget_set_sensitive(compose->toolbar->insert_btn, sensitive);
9655 if (compose->toolbar->sig_btn)
9656 gtk_widget_set_sensitive(compose->toolbar->sig_btn, sensitive);
9657 if (compose->toolbar->exteditor_btn)
9658 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, sensitive);
9659 if (compose->toolbar->linewrap_current_btn)
9660 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, sensitive);
9661 if (compose->toolbar->linewrap_all_btn)
9662 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, sensitive);
9665 static gboolean compose_get_ext_editor_uses_socket()
9667 return (prefs_common_get_ext_editor_cmd() &&
9668 strstr(prefs_common_get_ext_editor_cmd(), "%w"));
9671 static gboolean compose_ext_editor_plug_removed_cb(GtkSocket *socket, Compose *compose)
9673 compose->exteditor_socket = NULL;
9674 /* returning FALSE allows destruction of the socket */
9677 #endif /* G_OS_UNIX */
9680 * compose_undo_state_changed:
9682 * Change the sensivity of the menuentries undo and redo
9684 static void compose_undo_state_changed(UndoMain *undostruct, gint undo_state,
9685 gint redo_state, gpointer data)
9687 Compose *compose = (Compose *)data;
9689 switch (undo_state) {
9690 case UNDO_STATE_TRUE:
9691 if (!undostruct->undo_state) {
9692 undostruct->undo_state = TRUE;
9693 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", TRUE);
9696 case UNDO_STATE_FALSE:
9697 if (undostruct->undo_state) {
9698 undostruct->undo_state = FALSE;
9699 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
9702 case UNDO_STATE_UNCHANGED:
9704 case UNDO_STATE_REFRESH:
9705 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", undostruct->undo_state);
9708 g_warning("Undo state not recognized");
9712 switch (redo_state) {
9713 case UNDO_STATE_TRUE:
9714 if (!undostruct->redo_state) {
9715 undostruct->redo_state = TRUE;
9716 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", TRUE);
9719 case UNDO_STATE_FALSE:
9720 if (undostruct->redo_state) {
9721 undostruct->redo_state = FALSE;
9722 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
9725 case UNDO_STATE_UNCHANGED:
9727 case UNDO_STATE_REFRESH:
9728 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", undostruct->redo_state);
9731 g_warning("Redo state not recognized");
9736 /* callback functions */
9738 static void compose_notebook_size_alloc(GtkNotebook *notebook,
9739 GtkAllocation *allocation,
9742 prefs_common.compose_notebook_height = gtk_paned_get_position(paned);
9745 /* compose_edit_size_alloc() - called when resized. don't know whether Gtk
9746 * includes "non-client" (windows-izm) in calculation, so this calculation
9747 * may not be accurate.
9749 static gboolean compose_edit_size_alloc(GtkEditable *widget,
9750 GtkAllocation *allocation,
9751 GtkSHRuler *shruler)
9753 if (prefs_common.show_ruler) {
9754 gint char_width = 0, char_height = 0;
9755 gint line_width_in_chars;
9757 gtkut_get_font_size(GTK_WIDGET(widget),
9758 &char_width, &char_height);
9759 line_width_in_chars =
9760 (allocation->width - allocation->x) / char_width;
9762 /* got the maximum */
9763 gtk_shruler_set_range(GTK_SHRULER(shruler),
9764 0.0, line_width_in_chars, 0);
9773 ComposePrefType type;
9774 gboolean entry_marked;
9777 static void account_activated(GtkComboBox *optmenu, gpointer data)
9779 Compose *compose = (Compose *)data;
9782 gchar *folderidentifier;
9783 gint account_id = 0;
9786 GSList *list, *saved_list = NULL;
9787 HeaderEntryState *state;
9789 /* Get ID of active account in the combo box */
9790 menu = gtk_combo_box_get_model(optmenu);
9791 cm_return_if_fail(gtk_combo_box_get_active_iter(optmenu, &iter));
9792 gtk_tree_model_get(menu, &iter, 1, &account_id, -1);
9794 ac = account_find_from_id(account_id);
9795 cm_return_if_fail(ac != NULL);
9797 if (ac != compose->account) {
9798 compose_select_account(compose, ac, FALSE);
9800 for (list = compose->header_list; list; list = list->next) {
9801 ComposeHeaderEntry *hentry=(ComposeHeaderEntry *)list->data;
9803 if (hentry->type == PREF_ACCOUNT || !list->next) {
9804 compose_destroy_headerentry(compose, hentry);
9807 state = g_malloc0(sizeof(HeaderEntryState));
9808 state->header = gtk_editable_get_chars(GTK_EDITABLE(
9809 gtk_bin_get_child(GTK_BIN(hentry->combo))), 0, -1);
9810 state->entry = gtk_editable_get_chars(
9811 GTK_EDITABLE(hentry->entry), 0, -1);
9812 state->type = hentry->type;
9814 saved_list = g_slist_append(saved_list, state);
9815 compose_destroy_headerentry(compose, hentry);
9818 compose->header_last = NULL;
9819 g_slist_free(compose->header_list);
9820 compose->header_list = NULL;
9821 compose->header_nextrow = 1;
9822 compose_create_header_entry(compose);
9824 if (ac->set_autocc && ac->auto_cc)
9825 compose_entry_append(compose, ac->auto_cc,
9826 COMPOSE_CC, PREF_ACCOUNT);
9827 if (ac->set_autobcc && ac->auto_bcc)
9828 compose_entry_append(compose, ac->auto_bcc,
9829 COMPOSE_BCC, PREF_ACCOUNT);
9830 if (ac->set_autoreplyto && ac->auto_replyto)
9831 compose_entry_append(compose, ac->auto_replyto,
9832 COMPOSE_REPLYTO, PREF_ACCOUNT);
9834 for (list = saved_list; list; list = list->next) {
9835 state = (HeaderEntryState *) list->data;
9837 compose_add_header_entry(compose, state->header,
9838 state->entry, state->type);
9840 g_free(state->header);
9841 g_free(state->entry);
9844 g_slist_free(saved_list);
9846 combobox_select_by_data(GTK_COMBO_BOX(compose->header_last->combo),
9847 (ac->protocol == A_NNTP) ?
9848 COMPOSE_NEWSGROUPS : COMPOSE_TO);
9851 /* Set message save folder */
9852 if (account_get_special_folder(compose->account, F_OUTBOX)) {
9853 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
9855 g_signal_connect(G_OBJECT(compose->savemsg_checkbtn), "toggled",
9856 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
9858 compose_set_save_to(compose, NULL);
9859 if (account_get_special_folder(compose->account, F_OUTBOX)) {
9860 folderidentifier = folder_item_get_identifier(account_get_special_folder
9861 (compose->account, F_OUTBOX));
9862 compose_set_save_to(compose, folderidentifier);
9863 g_free(folderidentifier);
9867 static void attach_selected(GtkTreeView *tree_view, GtkTreePath *tree_path,
9868 GtkTreeViewColumn *column, Compose *compose)
9870 compose_attach_property(NULL, compose);
9873 static gboolean attach_button_pressed(GtkWidget *widget, GdkEventButton *event,
9876 Compose *compose = (Compose *)data;
9877 GtkTreeSelection *attach_selection;
9878 gint attach_nr_selected;
9881 if (!event) return FALSE;
9883 if (event->button == 3) {
9884 attach_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
9885 attach_nr_selected = gtk_tree_selection_count_selected_rows(attach_selection);
9887 /* If no rows, or just one row is selected, right-click should
9888 * open menu relevant to the row being right-clicked on. We
9889 * achieve that by selecting the clicked row first. If more
9890 * than one row is selected, we shouldn't modify the selection,
9891 * as user may want to remove selected rows (attachments). */
9892 if (attach_nr_selected < 2) {
9893 gtk_tree_selection_unselect_all(attach_selection);
9894 attach_nr_selected = 0;
9895 gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget),
9896 event->x, event->y, &path, NULL, NULL, NULL);
9898 gtk_tree_selection_select_path(attach_selection, path);
9899 gtk_tree_path_free(path);
9900 attach_nr_selected++;
9904 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", (attach_nr_selected > 0));
9905 /* Properties menu item makes no sense with more than one row
9906 * selected, the properties dialog can only edit one attachment. */
9907 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", (attach_nr_selected == 1));
9909 gtk_menu_popup(GTK_MENU(compose->popupmenu), NULL, NULL,
9910 NULL, NULL, event->button, event->time);
9917 static gboolean attach_key_pressed(GtkWidget *widget, GdkEventKey *event,
9920 Compose *compose = (Compose *)data;
9922 if (!event) return FALSE;
9924 switch (event->keyval) {
9925 case GDK_KEY_Delete:
9926 compose_attach_remove_selected(NULL, compose);
9932 static void compose_allow_user_actions (Compose *compose, gboolean allow)
9934 toolbar_comp_set_sensitive(compose, allow);
9935 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message", allow);
9936 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", allow);
9938 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", allow);
9940 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", allow);
9941 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools", allow);
9942 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Help", allow);
9944 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), allow);
9948 static void compose_send_cb(GtkAction *action, gpointer data)
9950 Compose *compose = (Compose *)data;
9953 if (compose->exteditor_tag != -1) {
9954 debug_print("ignoring send: external editor still open\n");
9958 if (prefs_common.work_offline &&
9959 !inc_offline_should_override(TRUE,
9960 _("Claws Mail needs network access in order "
9961 "to send this email.")))
9964 if (compose->draft_timeout_tag >= 0) { /* CLAWS: disable draft timeout */
9965 g_source_remove(compose->draft_timeout_tag);
9966 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
9969 compose_send(compose);
9972 static void compose_send_later_cb(GtkAction *action, gpointer data)
9974 Compose *compose = (Compose *)data;
9978 compose_allow_user_actions(compose, FALSE);
9979 val = compose_queue_sub(compose, NULL, NULL, NULL, TRUE, TRUE);
9980 compose_allow_user_actions(compose, TRUE);
9984 compose_close(compose);
9985 } else if (val == -1) {
9986 alertpanel_error(_("Could not queue message."));
9987 } else if (val == -2) {
9988 alertpanel_error(_("Could not queue message:\n\n%s."), g_strerror(errno));
9989 } else if (val == -3) {
9990 if (privacy_peek_error())
9991 alertpanel_error(_("Could not queue message for sending:\n\n"
9992 "Signature failed: %s"), privacy_get_error());
9993 } else if (val == -4) {
9994 alertpanel_error(_("Could not queue message for sending:\n\n"
9995 "Charset conversion failed."));
9996 } else if (val == -5) {
9997 alertpanel_error(_("Could not queue message for sending:\n\n"
9998 "Couldn't get recipient encryption key."));
9999 } else if (val == -6) {
10002 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
10005 #define DRAFTED_AT_EXIT "drafted_at_exit"
10006 static void compose_register_draft(MsgInfo *info)
10008 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10009 DRAFTED_AT_EXIT, NULL);
10010 FILE *fp = g_fopen(filepath, "ab");
10013 fprintf(fp, "%s\t%d\n", folder_item_get_identifier(info->folder),
10021 gboolean compose_draft (gpointer data, guint action)
10023 Compose *compose = (Compose *)data;
10028 MsgFlags flag = {0, 0};
10029 static gboolean lock = FALSE;
10030 MsgInfo *newmsginfo;
10032 gboolean target_locked = FALSE;
10033 gboolean err = FALSE;
10035 if (lock) return FALSE;
10037 if (compose->sending)
10040 draft = account_get_special_folder(compose->account, F_DRAFT);
10041 cm_return_val_if_fail(draft != NULL, FALSE);
10043 if (!g_mutex_trylock(compose->mutex)) {
10044 /* we don't want to lock the mutex once it's available,
10045 * because as the only other part of compose.c locking
10046 * it is compose_close - which means once unlocked,
10047 * the compose struct will be freed */
10048 debug_print("couldn't lock mutex, probably sending\n");
10054 tmp = g_strdup_printf("%s%cdraft.%p", get_tmp_dir(),
10055 G_DIR_SEPARATOR, compose);
10056 if ((fp = g_fopen(tmp, "wb")) == NULL) {
10057 FILE_OP_ERROR(tmp, "fopen");
10061 /* chmod for security */
10062 if (change_file_mode_rw(fp, tmp) < 0) {
10063 FILE_OP_ERROR(tmp, "chmod");
10064 g_warning("can't change file mode");
10067 /* Save draft infos */
10068 err |= (fprintf(fp, "X-Claws-Account-Id:%d\n", compose->account->account_id) < 0);
10069 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
10071 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
10072 gchar *savefolderid;
10074 savefolderid = compose_get_save_to(compose);
10075 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
10076 g_free(savefolderid);
10078 if (compose->return_receipt) {
10079 err |= (fprintf(fp, "RRCPT:1\n") < 0);
10081 if (compose->privacy_system) {
10082 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
10083 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
10084 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
10087 /* Message-ID of message replying to */
10088 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
10089 gchar *folderid = NULL;
10091 if (compose->replyinfo->folder)
10092 folderid = folder_item_get_identifier(compose->replyinfo->folder);
10093 if (folderid == NULL)
10094 folderid = g_strdup("NULL");
10096 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
10099 /* Message-ID of message forwarding to */
10100 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
10101 gchar *folderid = NULL;
10103 if (compose->fwdinfo->folder)
10104 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
10105 if (folderid == NULL)
10106 folderid = g_strdup("NULL");
10108 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
10112 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
10113 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
10115 sheaders = compose_get_manual_headers_info(compose);
10116 err |= (fprintf(fp, "X-Claws-Manual-Headers:%s\n", sheaders) < 0);
10119 /* end of headers */
10120 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
10127 if (compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_STORE, action != COMPOSE_AUTO_SAVE) < 0) {
10131 if (fclose(fp) == EOF) {
10135 flag.perm_flags = MSG_NEW|MSG_UNREAD;
10136 if (compose->targetinfo) {
10137 target_locked = MSG_IS_LOCKED(compose->targetinfo->flags);
10139 flag.perm_flags |= MSG_LOCKED;
10141 flag.tmp_flags = MSG_DRAFT;
10143 folder_item_scan(draft);
10144 if ((msgnum = folder_item_add_msg(draft, tmp, &flag, TRUE)) < 0) {
10145 MsgInfo *tmpinfo = NULL;
10146 debug_print("didn't get msgnum after adding draft [%s]\n", compose->msgid?compose->msgid:"no msgid");
10147 if (compose->msgid) {
10148 tmpinfo = folder_item_get_msginfo_by_msgid(draft, compose->msgid);
10151 msgnum = tmpinfo->msgnum;
10152 procmsg_msginfo_free(&tmpinfo);
10153 debug_print("got draft msgnum %d from scanning\n", msgnum);
10155 debug_print("didn't get draft msgnum after scanning\n");
10158 debug_print("got draft msgnum %d from adding\n", msgnum);
10164 if (action != COMPOSE_AUTO_SAVE) {
10165 if (action != COMPOSE_DRAFT_FOR_EXIT)
10166 alertpanel_error(_("Could not save draft."));
10169 gtkut_window_popup(compose->window);
10170 val = alertpanel_full(_("Could not save draft"),
10171 _("Could not save draft.\n"
10172 "Do you want to cancel exit or discard this email?"),
10173 _("_Cancel exit"), _("_Discard email"), NULL,
10174 FALSE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
10175 if (val == G_ALERTALTERNATE) {
10177 g_mutex_unlock(compose->mutex); /* must be done before closing */
10178 compose_close(compose);
10182 g_mutex_unlock(compose->mutex); /* must be done before closing */
10191 if (compose->mode == COMPOSE_REEDIT) {
10192 compose_remove_reedit_target(compose, TRUE);
10195 newmsginfo = folder_item_get_msginfo(draft, msgnum);
10198 procmsg_msginfo_unset_flags(newmsginfo, ~0, ~0);
10200 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD|MSG_LOCKED, MSG_DRAFT);
10202 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD, MSG_DRAFT);
10203 if (compose_use_attach(compose) && action != COMPOSE_AUTO_SAVE)
10204 procmsg_msginfo_set_flags(newmsginfo, 0,
10205 MSG_HAS_ATTACHMENT);
10207 if (action == COMPOSE_DRAFT_FOR_EXIT) {
10208 compose_register_draft(newmsginfo);
10210 procmsg_msginfo_free(&newmsginfo);
10213 folder_item_scan(draft);
10215 if (action == COMPOSE_QUIT_EDITING || action == COMPOSE_DRAFT_FOR_EXIT) {
10217 g_mutex_unlock(compose->mutex); /* must be done before closing */
10218 compose_close(compose);
10224 path = folder_item_fetch_msg(draft, msgnum);
10225 if (path == NULL) {
10226 debug_print("can't fetch %s:%d\n", draft->path, msgnum);
10229 if (g_stat(path, &s) < 0) {
10230 FILE_OP_ERROR(path, "stat");
10236 procmsg_msginfo_free(&(compose->targetinfo));
10237 compose->targetinfo = procmsg_msginfo_new();
10238 compose->targetinfo->msgnum = msgnum;
10239 compose->targetinfo->size = (goffset)s.st_size;
10240 compose->targetinfo->mtime = s.st_mtime;
10241 compose->targetinfo->folder = draft;
10243 procmsg_msginfo_set_flags(compose->targetinfo, MSG_LOCKED, 0);
10244 compose->mode = COMPOSE_REEDIT;
10246 if (action == COMPOSE_AUTO_SAVE) {
10247 compose->autosaved_draft = compose->targetinfo;
10249 compose->modified = FALSE;
10250 compose_set_title(compose);
10254 g_mutex_unlock(compose->mutex);
10258 void compose_clear_exit_drafts(void)
10260 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10261 DRAFTED_AT_EXIT, NULL);
10262 if (is_file_exist(filepath))
10263 claws_unlink(filepath);
10268 void compose_reopen_exit_drafts(void)
10270 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10271 DRAFTED_AT_EXIT, NULL);
10272 FILE *fp = g_fopen(filepath, "rb");
10276 while (fgets(buf, sizeof(buf), fp)) {
10277 gchar **parts = g_strsplit(buf, "\t", 2);
10278 const gchar *folder = parts[0];
10279 int msgnum = parts[1] ? atoi(parts[1]):-1;
10281 if (folder && *folder && msgnum > -1) {
10282 FolderItem *item = folder_find_item_from_identifier(folder);
10283 MsgInfo *info = folder_item_get_msginfo(item, msgnum);
10285 compose_reedit(info, FALSE);
10292 compose_clear_exit_drafts();
10295 static void compose_save_cb(GtkAction *action, gpointer data)
10297 Compose *compose = (Compose *)data;
10298 compose_draft(compose, COMPOSE_KEEP_EDITING);
10299 compose->rmode = COMPOSE_REEDIT;
10302 void compose_attach_from_list(Compose *compose, GList *file_list, gboolean free_data)
10304 if (compose && file_list) {
10307 for ( tmp = file_list; tmp; tmp = tmp->next) {
10308 gchar *file = (gchar *) tmp->data;
10309 gchar *utf8_filename = conv_filename_to_utf8(file);
10310 compose_attach_append(compose, file, utf8_filename, NULL, NULL);
10311 compose_changed_cb(NULL, compose);
10316 g_free(utf8_filename);
10321 static void compose_attach_cb(GtkAction *action, gpointer data)
10323 Compose *compose = (Compose *)data;
10326 if (compose->redirect_filename != NULL)
10329 /* Set focus_window properly, in case we were called via popup menu,
10330 * which unsets it (via focus_out_event callback on compose window). */
10331 manage_window_focus_in(compose->window, NULL, NULL);
10333 file_list = filesel_select_multiple_files_open(_("Select file"), NULL);
10336 compose_attach_from_list(compose, file_list, TRUE);
10337 g_list_free(file_list);
10341 static void compose_insert_file_cb(GtkAction *action, gpointer data)
10343 Compose *compose = (Compose *)data;
10345 gint files_inserted = 0;
10347 file_list = filesel_select_multiple_files_open(_("Select file"), NULL);
10352 for ( tmp = file_list; tmp; tmp = tmp->next) {
10353 gchar *file = (gchar *) tmp->data;
10354 gchar *filedup = g_strdup(file);
10355 gchar *shortfile = g_path_get_basename(filedup);
10356 ComposeInsertResult res;
10357 /* insert the file if the file is short or if the user confirmed that
10358 he/she wants to insert the large file */
10359 res = compose_insert_file(compose, file);
10360 if (res == COMPOSE_INSERT_READ_ERROR) {
10361 alertpanel_error(_("File '%s' could not be read."), shortfile);
10362 } else if (res == COMPOSE_INSERT_INVALID_CHARACTER) {
10363 alertpanel_error(_("File '%s' contained invalid characters\n"
10364 "for the current encoding, insertion may be incorrect."),
10366 } else if (res == COMPOSE_INSERT_SUCCESS)
10373 g_list_free(file_list);
10377 if (files_inserted > 0 && compose->gtkaspell &&
10378 compose->gtkaspell->check_while_typing)
10379 gtkaspell_highlight_all(compose->gtkaspell);
10383 static void compose_insert_sig_cb(GtkAction *action, gpointer data)
10385 Compose *compose = (Compose *)data;
10387 compose_insert_sig(compose, FALSE);
10390 static void compose_replace_sig_cb(GtkAction *action, gpointer data)
10392 Compose *compose = (Compose *)data;
10394 compose_insert_sig(compose, TRUE);
10397 static gint compose_delete_cb(GtkWidget *widget, GdkEventAny *event,
10401 Compose *compose = (Compose *)data;
10403 gtkut_widget_get_uposition(widget, &x, &y);
10404 if (!compose->batch) {
10405 prefs_common.compose_x = x;
10406 prefs_common.compose_y = y;
10408 if (compose->sending || compose->updating)
10410 compose_close_cb(NULL, compose);
10414 void compose_close_toolbar(Compose *compose)
10416 compose_close_cb(NULL, compose);
10419 static void compose_close_cb(GtkAction *action, gpointer data)
10421 Compose *compose = (Compose *)data;
10425 if (compose->exteditor_tag != -1) {
10426 if (!compose_ext_editor_kill(compose))
10431 if (compose->modified) {
10432 gboolean reedit = (compose->rmode == COMPOSE_REEDIT);
10433 if (!g_mutex_trylock(compose->mutex)) {
10434 /* we don't want to lock the mutex once it's available,
10435 * because as the only other part of compose.c locking
10436 * it is compose_close - which means once unlocked,
10437 * the compose struct will be freed */
10438 debug_print("couldn't lock mutex, probably sending\n");
10442 val = alertpanel(_("Discard message"),
10443 _("This message has been modified. Discard it?"),
10444 _("_Discard"), _("_Save to Drafts"), GTK_STOCK_CANCEL);
10446 val = alertpanel(_("Save changes"),
10447 _("This message has been modified. Save the latest changes?"),
10448 _("_Don't save"), g_strconcat("+", _("_Save to Drafts"), NULL),
10451 g_mutex_unlock(compose->mutex);
10453 case G_ALERTDEFAULT:
10454 if (compose_can_autosave(compose) && !reedit)
10455 compose_remove_draft(compose);
10457 case G_ALERTALTERNATE:
10458 compose_draft(data, COMPOSE_QUIT_EDITING);
10465 compose_close(compose);
10468 static void compose_print_cb(GtkAction *action, gpointer data)
10470 Compose *compose = (Compose *) data;
10472 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
10473 if (compose->targetinfo)
10474 messageview_print(compose->targetinfo, FALSE, -1, -1, 0);
10477 static void compose_set_encoding_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
10479 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
10480 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
10481 Compose *compose = (Compose *) data;
10484 compose->out_encoding = (CharSet)value;
10487 static void compose_address_cb(GtkAction *action, gpointer data)
10489 Compose *compose = (Compose *)data;
10491 #ifndef USE_ALT_ADDRBOOK
10492 addressbook_open(compose);
10494 GError* error = NULL;
10495 addressbook_connect_signals(compose);
10496 addressbook_dbus_open(TRUE, &error);
10498 g_warning("%s", error->message);
10499 g_error_free(error);
10504 static void about_show_cb(GtkAction *action, gpointer data)
10509 static void compose_template_activate_cb(GtkWidget *widget, gpointer data)
10511 Compose *compose = (Compose *)data;
10516 tmpl = g_object_get_data(G_OBJECT(widget), "template");
10517 cm_return_if_fail(tmpl != NULL);
10519 msg = g_strdup_printf(_("Do you want to apply the template '%s'?"),
10521 val = alertpanel(_("Apply template"), msg,
10522 _("_Replace"), _("_Insert"), GTK_STOCK_CANCEL);
10525 if (val == G_ALERTDEFAULT)
10526 compose_template_apply(compose, tmpl, TRUE);
10527 else if (val == G_ALERTALTERNATE)
10528 compose_template_apply(compose, tmpl, FALSE);
10531 static void compose_ext_editor_cb(GtkAction *action, gpointer data)
10533 Compose *compose = (Compose *)data;
10536 if (compose->exteditor_tag != -1) {
10537 debug_print("ignoring open external editor: external editor still open\n");
10541 compose_exec_ext_editor(compose);
10544 static void compose_undo_cb(GtkAction *action, gpointer data)
10546 Compose *compose = (Compose *)data;
10547 gboolean prev_autowrap = compose->autowrap;
10549 compose->autowrap = FALSE;
10550 undo_undo(compose->undostruct);
10551 compose->autowrap = prev_autowrap;
10554 static void compose_redo_cb(GtkAction *action, gpointer data)
10556 Compose *compose = (Compose *)data;
10557 gboolean prev_autowrap = compose->autowrap;
10559 compose->autowrap = FALSE;
10560 undo_redo(compose->undostruct);
10561 compose->autowrap = prev_autowrap;
10564 static void entry_cut_clipboard(GtkWidget *entry)
10566 if (GTK_IS_EDITABLE(entry))
10567 gtk_editable_cut_clipboard (GTK_EDITABLE(entry));
10568 else if (GTK_IS_TEXT_VIEW(entry))
10569 gtk_text_buffer_cut_clipboard(
10570 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10571 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
10575 static void entry_copy_clipboard(GtkWidget *entry)
10577 if (GTK_IS_EDITABLE(entry))
10578 gtk_editable_copy_clipboard (GTK_EDITABLE(entry));
10579 else if (GTK_IS_TEXT_VIEW(entry))
10580 gtk_text_buffer_copy_clipboard(
10581 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10582 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
10585 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry,
10586 gboolean wrap, GdkAtom clip, GtkTextIter *insert_place)
10588 if (GTK_IS_TEXT_VIEW(entry)) {
10589 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10590 GtkTextMark *mark_start = gtk_text_buffer_get_insert(buffer);
10591 GtkTextIter start_iter, end_iter;
10593 gchar *contents = gtk_clipboard_wait_for_text(gtk_clipboard_get(clip));
10595 if (contents == NULL)
10598 /* we shouldn't delete the selection when middle-click-pasting, or we
10599 * can't mid-click-paste our own selection */
10600 if (clip != GDK_SELECTION_PRIMARY) {
10601 undo_paste_clipboard(GTK_TEXT_VIEW(compose->text), compose->undostruct);
10602 gtk_text_buffer_delete_selection(buffer, FALSE, TRUE);
10605 if (insert_place == NULL) {
10606 /* if insert_place isn't specified, insert at the cursor.
10607 * used for Ctrl-V pasting */
10608 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10609 start = gtk_text_iter_get_offset(&start_iter);
10610 gtk_text_buffer_insert(buffer, &start_iter, contents, strlen(contents));
10612 /* if insert_place is specified, paste here.
10613 * used for mid-click-pasting */
10614 start = gtk_text_iter_get_offset(insert_place);
10615 gtk_text_buffer_insert(buffer, insert_place, contents, strlen(contents));
10616 if (prefs_common.primary_paste_unselects)
10617 gtk_text_buffer_select_range(buffer, insert_place, insert_place);
10621 /* paste unwrapped: mark the paste so it's not wrapped later */
10622 end = start + strlen(contents);
10623 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, start);
10624 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, end);
10625 gtk_text_buffer_apply_tag_by_name(buffer, "no_wrap", &start_iter, &end_iter);
10626 } else if (wrap && clip == GDK_SELECTION_PRIMARY) {
10627 /* rewrap paragraph now (after a mid-click-paste) */
10628 mark_start = gtk_text_buffer_get_insert(buffer);
10629 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10630 gtk_text_iter_backward_char(&start_iter);
10631 compose_beautify_paragraph(compose, &start_iter, TRUE);
10633 } else if (GTK_IS_EDITABLE(entry))
10634 gtk_editable_paste_clipboard (GTK_EDITABLE(entry));
10636 compose->modified = TRUE;
10639 static void entry_allsel(GtkWidget *entry)
10641 if (GTK_IS_EDITABLE(entry))
10642 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
10643 else if (GTK_IS_TEXT_VIEW(entry)) {
10644 GtkTextIter startiter, enditer;
10645 GtkTextBuffer *textbuf;
10647 textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10648 gtk_text_buffer_get_start_iter(textbuf, &startiter);
10649 gtk_text_buffer_get_end_iter(textbuf, &enditer);
10651 gtk_text_buffer_move_mark_by_name(textbuf,
10652 "selection_bound", &startiter);
10653 gtk_text_buffer_move_mark_by_name(textbuf,
10654 "insert", &enditer);
10658 static void compose_cut_cb(GtkAction *action, gpointer data)
10660 Compose *compose = (Compose *)data;
10661 if (compose->focused_editable
10662 #ifndef GENERIC_UMPC
10663 && gtk_widget_has_focus(compose->focused_editable)
10666 entry_cut_clipboard(compose->focused_editable);
10669 static void compose_copy_cb(GtkAction *action, gpointer data)
10671 Compose *compose = (Compose *)data;
10672 if (compose->focused_editable
10673 #ifndef GENERIC_UMPC
10674 && gtk_widget_has_focus(compose->focused_editable)
10677 entry_copy_clipboard(compose->focused_editable);
10680 static void compose_paste_cb(GtkAction *action, gpointer data)
10682 Compose *compose = (Compose *)data;
10683 gint prev_autowrap;
10684 GtkTextBuffer *buffer;
10686 if (compose->focused_editable &&
10687 #ifndef GENERIC_UMPC
10688 gtk_widget_has_focus(compose->focused_editable)
10691 entry_paste_clipboard(compose, compose->focused_editable,
10692 prefs_common.linewrap_pastes,
10693 GDK_SELECTION_CLIPBOARD, NULL);
10698 #ifndef GENERIC_UMPC
10699 gtk_widget_has_focus(compose->text) &&
10701 compose->gtkaspell &&
10702 compose->gtkaspell->check_while_typing)
10703 gtkaspell_highlight_all(compose->gtkaspell);
10707 static void compose_paste_as_quote_cb(GtkAction *action, gpointer data)
10709 Compose *compose = (Compose *)data;
10710 gint wrap_quote = prefs_common.linewrap_quote;
10711 if (compose->focused_editable
10712 #ifndef GENERIC_UMPC
10713 && gtk_widget_has_focus(compose->focused_editable)
10716 /* let text_insert() (called directly or at a later time
10717 * after the gtk_editable_paste_clipboard) know that
10718 * text is to be inserted as a quotation. implemented
10719 * by using a simple refcount... */
10720 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data(
10721 G_OBJECT(compose->focused_editable),
10722 "paste_as_quotation"));
10723 g_object_set_data(G_OBJECT(compose->focused_editable),
10724 "paste_as_quotation",
10725 GINT_TO_POINTER(paste_as_quotation + 1));
10726 prefs_common.linewrap_quote = prefs_common.linewrap_pastes;
10727 entry_paste_clipboard(compose, compose->focused_editable,
10728 prefs_common.linewrap_pastes,
10729 GDK_SELECTION_CLIPBOARD, NULL);
10730 prefs_common.linewrap_quote = wrap_quote;
10734 static void compose_paste_no_wrap_cb(GtkAction *action, gpointer data)
10736 Compose *compose = (Compose *)data;
10737 gint prev_autowrap;
10738 GtkTextBuffer *buffer;
10740 if (compose->focused_editable
10741 #ifndef GENERIC_UMPC
10742 && gtk_widget_has_focus(compose->focused_editable)
10745 entry_paste_clipboard(compose, compose->focused_editable, FALSE,
10746 GDK_SELECTION_CLIPBOARD, NULL);
10751 #ifndef GENERIC_UMPC
10752 gtk_widget_has_focus(compose->text) &&
10754 compose->gtkaspell &&
10755 compose->gtkaspell->check_while_typing)
10756 gtkaspell_highlight_all(compose->gtkaspell);
10760 static void compose_paste_wrap_cb(GtkAction *action, gpointer data)
10762 Compose *compose = (Compose *)data;
10763 gint prev_autowrap;
10764 GtkTextBuffer *buffer;
10766 if (compose->focused_editable
10767 #ifndef GENERIC_UMPC
10768 && gtk_widget_has_focus(compose->focused_editable)
10771 entry_paste_clipboard(compose, compose->focused_editable, TRUE,
10772 GDK_SELECTION_CLIPBOARD, NULL);
10777 #ifndef GENERIC_UMPC
10778 gtk_widget_has_focus(compose->text) &&
10780 compose->gtkaspell &&
10781 compose->gtkaspell->check_while_typing)
10782 gtkaspell_highlight_all(compose->gtkaspell);
10786 static void compose_allsel_cb(GtkAction *action, gpointer data)
10788 Compose *compose = (Compose *)data;
10789 if (compose->focused_editable
10790 #ifndef GENERIC_UMPC
10791 && gtk_widget_has_focus(compose->focused_editable)
10794 entry_allsel(compose->focused_editable);
10797 static void textview_move_beginning_of_line (GtkTextView *text)
10799 GtkTextBuffer *buffer;
10803 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10805 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10806 mark = gtk_text_buffer_get_insert(buffer);
10807 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10808 gtk_text_iter_set_line_offset(&ins, 0);
10809 gtk_text_buffer_place_cursor(buffer, &ins);
10812 static void textview_move_forward_character (GtkTextView *text)
10814 GtkTextBuffer *buffer;
10818 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10820 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10821 mark = gtk_text_buffer_get_insert(buffer);
10822 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10823 if (gtk_text_iter_forward_cursor_position(&ins))
10824 gtk_text_buffer_place_cursor(buffer, &ins);
10827 static void textview_move_backward_character (GtkTextView *text)
10829 GtkTextBuffer *buffer;
10833 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10835 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10836 mark = gtk_text_buffer_get_insert(buffer);
10837 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10838 if (gtk_text_iter_backward_cursor_position(&ins))
10839 gtk_text_buffer_place_cursor(buffer, &ins);
10842 static void textview_move_forward_word (GtkTextView *text)
10844 GtkTextBuffer *buffer;
10849 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10851 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10852 mark = gtk_text_buffer_get_insert(buffer);
10853 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10854 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
10855 if (gtk_text_iter_forward_word_ends(&ins, count)) {
10856 gtk_text_iter_backward_word_start(&ins);
10857 gtk_text_buffer_place_cursor(buffer, &ins);
10861 static void textview_move_backward_word (GtkTextView *text)
10863 GtkTextBuffer *buffer;
10867 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10869 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10870 mark = gtk_text_buffer_get_insert(buffer);
10871 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10872 if (gtk_text_iter_backward_word_starts(&ins, 1))
10873 gtk_text_buffer_place_cursor(buffer, &ins);
10876 static void textview_move_end_of_line (GtkTextView *text)
10878 GtkTextBuffer *buffer;
10882 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10884 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10885 mark = gtk_text_buffer_get_insert(buffer);
10886 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10887 if (gtk_text_iter_forward_to_line_end(&ins))
10888 gtk_text_buffer_place_cursor(buffer, &ins);
10891 static void textview_move_next_line (GtkTextView *text)
10893 GtkTextBuffer *buffer;
10898 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10900 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10901 mark = gtk_text_buffer_get_insert(buffer);
10902 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10903 offset = gtk_text_iter_get_line_offset(&ins);
10904 if (gtk_text_iter_forward_line(&ins)) {
10905 gtk_text_iter_set_line_offset(&ins, offset);
10906 gtk_text_buffer_place_cursor(buffer, &ins);
10910 static void textview_move_previous_line (GtkTextView *text)
10912 GtkTextBuffer *buffer;
10917 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10919 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10920 mark = gtk_text_buffer_get_insert(buffer);
10921 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10922 offset = gtk_text_iter_get_line_offset(&ins);
10923 if (gtk_text_iter_backward_line(&ins)) {
10924 gtk_text_iter_set_line_offset(&ins, offset);
10925 gtk_text_buffer_place_cursor(buffer, &ins);
10929 static void textview_delete_forward_character (GtkTextView *text)
10931 GtkTextBuffer *buffer;
10933 GtkTextIter ins, end_iter;
10935 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10937 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10938 mark = gtk_text_buffer_get_insert(buffer);
10939 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10941 if (gtk_text_iter_forward_char(&end_iter)) {
10942 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10946 static void textview_delete_backward_character (GtkTextView *text)
10948 GtkTextBuffer *buffer;
10950 GtkTextIter ins, end_iter;
10952 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10954 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10955 mark = gtk_text_buffer_get_insert(buffer);
10956 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10958 if (gtk_text_iter_backward_char(&end_iter)) {
10959 gtk_text_buffer_delete(buffer, &end_iter, &ins);
10963 static void textview_delete_forward_word (GtkTextView *text)
10965 GtkTextBuffer *buffer;
10967 GtkTextIter ins, end_iter;
10969 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10971 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10972 mark = gtk_text_buffer_get_insert(buffer);
10973 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10975 if (gtk_text_iter_forward_word_end(&end_iter)) {
10976 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10980 static void textview_delete_backward_word (GtkTextView *text)
10982 GtkTextBuffer *buffer;
10984 GtkTextIter ins, end_iter;
10986 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10988 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10989 mark = gtk_text_buffer_get_insert(buffer);
10990 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10992 if (gtk_text_iter_backward_word_start(&end_iter)) {
10993 gtk_text_buffer_delete(buffer, &end_iter, &ins);
10997 static void textview_delete_line (GtkTextView *text)
10999 GtkTextBuffer *buffer;
11001 GtkTextIter ins, start_iter, end_iter;
11003 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11005 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11006 mark = gtk_text_buffer_get_insert(buffer);
11007 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11010 gtk_text_iter_set_line_offset(&start_iter, 0);
11013 if (gtk_text_iter_ends_line(&end_iter)){
11014 if (!gtk_text_iter_forward_char(&end_iter))
11015 gtk_text_iter_backward_char(&start_iter);
11018 gtk_text_iter_forward_to_line_end(&end_iter);
11019 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
11022 static void textview_delete_to_line_end (GtkTextView *text)
11024 GtkTextBuffer *buffer;
11026 GtkTextIter ins, end_iter;
11028 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11030 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11031 mark = gtk_text_buffer_get_insert(buffer);
11032 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11034 if (gtk_text_iter_ends_line(&end_iter))
11035 gtk_text_iter_forward_char(&end_iter);
11037 gtk_text_iter_forward_to_line_end(&end_iter);
11038 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11041 #define DO_ACTION(name, act) { \
11042 if(!strcmp(name, a_name)) { \
11046 static ComposeCallAdvancedAction compose_call_advanced_action_from_path(GtkAction *action)
11048 const gchar *a_name = gtk_action_get_name(action);
11049 DO_ACTION("Edit/Advanced/BackChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER);
11050 DO_ACTION("Edit/Advanced/ForwChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER);
11051 DO_ACTION("Edit/Advanced/BackWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD);
11052 DO_ACTION("Edit/Advanced/ForwWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD);
11053 DO_ACTION("Edit/Advanced/BegLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE);
11054 DO_ACTION("Edit/Advanced/EndLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE);
11055 DO_ACTION("Edit/Advanced/PrevLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE);
11056 DO_ACTION("Edit/Advanced/NextLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE);
11057 DO_ACTION("Edit/Advanced/DelBackChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER);
11058 DO_ACTION("Edit/Advanced/DelForwChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER);
11059 DO_ACTION("Edit/Advanced/DelBackWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD);
11060 DO_ACTION("Edit/Advanced/DelForwWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD);
11061 DO_ACTION("Edit/Advanced/DelLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE);
11062 DO_ACTION("Edit/Advanced/DelEndLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END);
11063 return COMPOSE_CALL_ADVANCED_ACTION_UNDEFINED;
11066 static void compose_advanced_action_cb(GtkAction *gaction, gpointer data)
11068 Compose *compose = (Compose *)data;
11069 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11070 ComposeCallAdvancedAction action = COMPOSE_CALL_ADVANCED_ACTION_UNDEFINED;
11072 action = compose_call_advanced_action_from_path(gaction);
11075 void (*do_action) (GtkTextView *text);
11076 } action_table[] = {
11077 {textview_move_beginning_of_line},
11078 {textview_move_forward_character},
11079 {textview_move_backward_character},
11080 {textview_move_forward_word},
11081 {textview_move_backward_word},
11082 {textview_move_end_of_line},
11083 {textview_move_next_line},
11084 {textview_move_previous_line},
11085 {textview_delete_forward_character},
11086 {textview_delete_backward_character},
11087 {textview_delete_forward_word},
11088 {textview_delete_backward_word},
11089 {textview_delete_line},
11090 {textview_delete_to_line_end}
11093 if (!gtk_widget_has_focus(GTK_WIDGET(text))) return;
11095 if (action >= COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE &&
11096 action <= COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END) {
11097 if (action_table[action].do_action)
11098 action_table[action].do_action(text);
11100 g_warning("Not implemented yet.");
11104 static void compose_grab_focus_cb(GtkWidget *widget, Compose *compose)
11106 GtkAllocation allocation;
11110 if (GTK_IS_EDITABLE(widget)) {
11111 str = gtk_editable_get_chars(GTK_EDITABLE(widget), 0, -1);
11112 gtk_editable_set_position(GTK_EDITABLE(widget),
11115 if ((parent = gtk_widget_get_parent(widget))
11116 && (parent = gtk_widget_get_parent(parent))
11117 && (parent = gtk_widget_get_parent(parent))) {
11118 if (GTK_IS_SCROLLED_WINDOW(parent)) {
11119 gtk_widget_get_allocation(widget, &allocation);
11120 gint y = allocation.y;
11121 gint height = allocation.height;
11122 GtkAdjustment *shown = gtk_scrolled_window_get_vadjustment
11123 (GTK_SCROLLED_WINDOW(parent));
11125 gfloat value = gtk_adjustment_get_value(shown);
11126 gfloat upper = gtk_adjustment_get_upper(shown);
11127 gfloat page_size = gtk_adjustment_get_page_size(shown);
11128 if (y < (int)value) {
11129 gtk_adjustment_set_value(shown, y - 1);
11131 if ((y + height) > ((int)value + (int)page_size)) {
11132 if ((y - height - 1) < ((int)upper - (int)page_size)) {
11133 gtk_adjustment_set_value(shown,
11134 y + height - (int)page_size - 1);
11136 gtk_adjustment_set_value(shown,
11137 (int)upper - (int)page_size - 1);
11144 if (GTK_IS_EDITABLE(widget) || GTK_IS_TEXT_VIEW(widget))
11145 compose->focused_editable = widget;
11147 #ifdef GENERIC_UMPC
11148 if (GTK_IS_TEXT_VIEW(widget)
11149 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->edit_vbox) {
11150 g_object_ref(compose->notebook);
11151 g_object_ref(compose->edit_vbox);
11152 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
11153 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
11154 gtk_paned_add1(GTK_PANED(compose->paned), compose->edit_vbox);
11155 gtk_paned_add2(GTK_PANED(compose->paned), compose->notebook);
11156 g_object_unref(compose->notebook);
11157 g_object_unref(compose->edit_vbox);
11158 g_signal_handlers_block_by_func(G_OBJECT(widget),
11159 G_CALLBACK(compose_grab_focus_cb),
11161 gtk_widget_grab_focus(widget);
11162 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
11163 G_CALLBACK(compose_grab_focus_cb),
11165 } else if (!GTK_IS_TEXT_VIEW(widget)
11166 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->notebook) {
11167 g_object_ref(compose->notebook);
11168 g_object_ref(compose->edit_vbox);
11169 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
11170 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
11171 gtk_paned_add1(GTK_PANED(compose->paned), compose->notebook);
11172 gtk_paned_add2(GTK_PANED(compose->paned), compose->edit_vbox);
11173 g_object_unref(compose->notebook);
11174 g_object_unref(compose->edit_vbox);
11175 g_signal_handlers_block_by_func(G_OBJECT(widget),
11176 G_CALLBACK(compose_grab_focus_cb),
11178 gtk_widget_grab_focus(widget);
11179 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
11180 G_CALLBACK(compose_grab_focus_cb),
11186 static void compose_changed_cb(GtkTextBuffer *textbuf, Compose *compose)
11188 compose->modified = TRUE;
11189 /* compose_beautify_paragraph(compose, NULL, TRUE); */
11190 #ifndef GENERIC_UMPC
11191 compose_set_title(compose);
11195 static void compose_wrap_cb(GtkAction *action, gpointer data)
11197 Compose *compose = (Compose *)data;
11198 compose_beautify_paragraph(compose, NULL, TRUE);
11201 static void compose_wrap_all_cb(GtkAction *action, gpointer data)
11203 Compose *compose = (Compose *)data;
11204 compose_wrap_all_full(compose, TRUE);
11207 static void compose_find_cb(GtkAction *action, gpointer data)
11209 Compose *compose = (Compose *)data;
11211 message_search_compose(compose);
11214 static void compose_toggle_autowrap_cb(GtkToggleAction *action,
11217 Compose *compose = (Compose *)data;
11218 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11219 if (compose->autowrap)
11220 compose_wrap_all_full(compose, TRUE);
11221 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11224 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
11227 Compose *compose = (Compose *)data;
11228 compose->autoindent = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11231 static void compose_toggle_sign_cb(GtkToggleAction *action, gpointer data)
11233 Compose *compose = (Compose *)data;
11235 compose->use_signing = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11238 static void compose_toggle_encrypt_cb(GtkToggleAction *action, gpointer data)
11240 Compose *compose = (Compose *)data;
11242 compose->use_encryption = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11245 static void activate_privacy_system(Compose *compose, PrefsAccount *account, gboolean warn)
11247 g_free(compose->privacy_system);
11248 g_free(compose->encdata);
11250 compose->privacy_system = g_strdup(account->default_privacy_system);
11251 compose_update_privacy_system_menu_item(compose, warn);
11254 static void compose_toggle_ruler_cb(GtkToggleAction *action, gpointer data)
11256 Compose *compose = (Compose *)data;
11258 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action))) {
11259 gtk_widget_show(compose->ruler_hbox);
11260 prefs_common.show_ruler = TRUE;
11262 gtk_widget_hide(compose->ruler_hbox);
11263 gtk_widget_queue_resize(compose->edit_vbox);
11264 prefs_common.show_ruler = FALSE;
11268 static void compose_attach_drag_received_cb (GtkWidget *widget,
11269 GdkDragContext *context,
11272 GtkSelectionData *data,
11275 gpointer user_data)
11277 Compose *compose = (Compose *)user_data;
11281 type = gtk_selection_data_get_data_type(data);
11282 if ((gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list"))
11283 && gtk_drag_get_source_widget(context) !=
11284 summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
11285 list = uri_list_extract_filenames(
11286 (const gchar *)gtk_selection_data_get_data(data));
11287 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11288 gchar *utf8_filename = conv_filename_to_utf8((const gchar *)tmp->data);
11289 compose_attach_append
11290 (compose, (const gchar *)tmp->data,
11291 utf8_filename, NULL, NULL);
11292 g_free(utf8_filename);
11294 if (list) compose_changed_cb(NULL, compose);
11295 list_free_strings(list);
11297 } else if (gtk_drag_get_source_widget(context)
11298 == summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
11299 /* comes from our summaryview */
11300 SummaryView * summaryview = NULL;
11301 GSList * list = NULL, *cur = NULL;
11303 if (mainwindow_get_mainwindow())
11304 summaryview = mainwindow_get_mainwindow()->summaryview;
11307 list = summary_get_selected_msg_list(summaryview);
11309 for (cur = list; cur; cur = cur->next) {
11310 MsgInfo *msginfo = (MsgInfo *)cur->data;
11311 gchar *file = NULL;
11313 file = procmsg_get_message_file_full(msginfo,
11316 compose_attach_append(compose, (const gchar *)file,
11317 (const gchar *)file, "message/rfc822", NULL);
11321 g_slist_free(list);
11325 static gboolean compose_drag_drop(GtkWidget *widget,
11326 GdkDragContext *drag_context,
11328 guint time, gpointer user_data)
11330 /* not handling this signal makes compose_insert_drag_received_cb
11335 static gboolean completion_set_focus_to_subject
11336 (GtkWidget *widget,
11337 GdkEventKey *event,
11340 cm_return_val_if_fail(compose != NULL, FALSE);
11342 /* make backtab move to subject field */
11343 if(event->keyval == GDK_KEY_ISO_Left_Tab) {
11344 gtk_widget_grab_focus(compose->subject_entry);
11350 static void compose_insert_drag_received_cb (GtkWidget *widget,
11351 GdkDragContext *drag_context,
11354 GtkSelectionData *data,
11357 gpointer user_data)
11359 Compose *compose = (Compose *)user_data;
11365 /* strangely, testing data->type == gdk_atom_intern("text/uri-list", TRUE)
11367 type = gtk_selection_data_get_data_type(data);
11368 if (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list")) {
11369 AlertValue val = G_ALERTDEFAULT;
11370 const gchar* ddata = (const gchar *)gtk_selection_data_get_data(data);
11372 list = uri_list_extract_filenames(ddata);
11373 num_files = g_list_length(list);
11374 if (list == NULL && strstr(ddata, "://")) {
11375 /* Assume a list of no files, and data has ://, is a remote link */
11376 gchar *tmpdata = g_strstrip(g_strdup(ddata));
11377 gchar *tmpfile = get_tmp_file();
11378 str_write_to_file(tmpdata, tmpfile);
11380 compose_insert_file(compose, tmpfile);
11381 claws_unlink(tmpfile);
11383 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11384 compose_beautify_paragraph(compose, NULL, TRUE);
11387 switch (prefs_common.compose_dnd_mode) {
11388 case COMPOSE_DND_ASK:
11389 msg = g_strdup_printf(
11391 "Do you want to insert the contents of the file "
11392 "into the message body, or attach it to the email?",
11393 "Do you want to insert the contents of the %d files "
11394 "into the message body, or attach them to the email?",
11397 val = alertpanel_full(_("Insert or attach?"), msg,
11398 GTK_STOCK_CANCEL, g_strconcat("+", _("_Insert"), NULL), _("_Attach"),
11399 TRUE, NULL, ALERT_QUESTION, G_ALERTALTERNATE);
11402 case COMPOSE_DND_INSERT:
11403 val = G_ALERTALTERNATE;
11405 case COMPOSE_DND_ATTACH:
11406 val = G_ALERTOTHER;
11409 /* unexpected case */
11410 g_warning("error: unexpected compose_dnd_mode option value in compose_insert_drag_received_cb()");
11413 if (val & G_ALERTDISABLE) {
11414 val &= ~G_ALERTDISABLE;
11415 /* remember what action to perform by default, only if we don't click Cancel */
11416 if (val == G_ALERTALTERNATE)
11417 prefs_common.compose_dnd_mode = COMPOSE_DND_INSERT;
11418 else if (val == G_ALERTOTHER)
11419 prefs_common.compose_dnd_mode = COMPOSE_DND_ATTACH;
11422 if (val == G_ALERTDEFAULT || val == G_ALERTCANCEL) {
11423 gtk_drag_finish(drag_context, FALSE, FALSE, time);
11424 list_free_strings(list);
11427 } else if (val == G_ALERTOTHER) {
11428 compose_attach_drag_received_cb(widget, drag_context, x, y, data, info, time, user_data);
11429 list_free_strings(list);
11434 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11435 compose_insert_file(compose, (const gchar *)tmp->data);
11437 list_free_strings(list);
11439 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11444 static void compose_header_drag_received_cb (GtkWidget *widget,
11445 GdkDragContext *drag_context,
11448 GtkSelectionData *data,
11451 gpointer user_data)
11453 GtkEditable *entry = (GtkEditable *)user_data;
11454 const gchar *email = (const gchar *)gtk_selection_data_get_data(data);
11456 /* strangely, testing data->type == gdk_atom_intern("text/plain", TRUE)
11459 if (!strncmp(email, "mailto:", strlen("mailto:"))) {
11460 gchar *decoded=g_new(gchar, strlen(email));
11463 decode_uri(decoded, email + strlen("mailto:")); /* will fit */
11464 gtk_editable_delete_text(entry, 0, -1);
11465 gtk_editable_insert_text(entry, decoded, strlen(decoded), &start);
11466 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11470 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11473 static void compose_toggle_return_receipt_cb(GtkToggleAction *action, gpointer data)
11475 Compose *compose = (Compose *)data;
11477 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11478 compose->return_receipt = TRUE;
11480 compose->return_receipt = FALSE;
11483 static void compose_toggle_remove_refs_cb(GtkToggleAction *action, gpointer data)
11485 Compose *compose = (Compose *)data;
11487 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11488 compose->remove_references = TRUE;
11490 compose->remove_references = FALSE;
11493 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
11494 ComposeHeaderEntry *headerentry)
11496 gtk_entry_set_text(GTK_ENTRY(headerentry->entry), "");
11500 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
11501 GdkEventKey *event,
11502 ComposeHeaderEntry *headerentry)
11504 if ((g_slist_length(headerentry->compose->header_list) > 0) &&
11505 ((headerentry->headernum + 1) != headerentry->compose->header_nextrow) &&
11506 !(event->state & GDK_MODIFIER_MASK) &&
11507 (event->keyval == GDK_KEY_BackSpace) &&
11508 (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) == 0)) {
11509 gtk_container_remove
11510 (GTK_CONTAINER(headerentry->compose->header_table),
11511 headerentry->combo);
11512 gtk_container_remove
11513 (GTK_CONTAINER(headerentry->compose->header_table),
11514 headerentry->entry);
11515 headerentry->compose->header_list =
11516 g_slist_remove(headerentry->compose->header_list,
11518 g_free(headerentry);
11519 } else if (event->keyval == GDK_KEY_Tab) {
11520 if (headerentry->compose->header_last == headerentry) {
11521 /* Override default next focus, and give it to subject_entry
11522 * instead of notebook tabs
11524 g_signal_stop_emission_by_name(G_OBJECT(entry), "key-press-event");
11525 gtk_widget_grab_focus(headerentry->compose->subject_entry);
11532 static gboolean scroll_postpone(gpointer data)
11534 Compose *compose = (Compose *)data;
11536 if (compose->batch)
11539 GTK_EVENTS_FLUSH();
11540 compose_show_first_last_header(compose, FALSE);
11544 static void compose_headerentry_changed_cb(GtkWidget *entry,
11545 ComposeHeaderEntry *headerentry)
11547 if (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) != 0) {
11548 compose_create_header_entry(headerentry->compose);
11549 g_signal_handlers_disconnect_matched
11550 (G_OBJECT(entry), G_SIGNAL_MATCH_DATA,
11551 0, 0, NULL, NULL, headerentry);
11553 if (!headerentry->compose->batch)
11554 g_timeout_add(0, scroll_postpone, headerentry->compose);
11558 static gboolean compose_defer_auto_save_draft(Compose *compose)
11560 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
11561 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
11565 static void compose_show_first_last_header(Compose *compose, gboolean show_first)
11567 GtkAdjustment *vadj;
11569 cm_return_if_fail(compose);
11574 cm_return_if_fail(GTK_IS_WIDGET(compose->header_table));
11575 cm_return_if_fail(GTK_IS_VIEWPORT(gtk_widget_get_parent(compose->header_table)));
11576 vadj = gtk_viewport_get_vadjustment(GTK_VIEWPORT(
11577 gtk_widget_get_parent(compose->header_table)));
11578 gtk_adjustment_set_value(vadj, (show_first ?
11579 gtk_adjustment_get_lower(vadj) :
11580 (gtk_adjustment_get_upper(vadj) -
11581 gtk_adjustment_get_page_size(vadj))));
11582 gtk_adjustment_changed(vadj);
11585 static void text_inserted(GtkTextBuffer *buffer, GtkTextIter *iter,
11586 const gchar *text, gint len, Compose *compose)
11588 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data
11589 (G_OBJECT(compose->text), "paste_as_quotation"));
11592 cm_return_if_fail(text != NULL);
11594 g_signal_handlers_block_by_func(G_OBJECT(buffer),
11595 G_CALLBACK(text_inserted),
11597 if (paste_as_quotation) {
11599 const gchar *qmark;
11601 GtkTextIter start_iter;
11604 len = strlen(text);
11606 new_text = g_strndup(text, len);
11608 qmark = compose_quote_char_from_context(compose);
11610 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11611 gtk_text_buffer_place_cursor(buffer, iter);
11613 pos = gtk_text_iter_get_offset(iter);
11615 compose_quote_fmt(compose, NULL, "%Q", qmark, new_text, TRUE, FALSE,
11616 _("Quote format error at line %d."));
11617 quote_fmt_reset_vartable();
11619 g_object_set_data(G_OBJECT(compose->text), "paste_as_quotation",
11620 GINT_TO_POINTER(paste_as_quotation - 1));
11622 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11623 gtk_text_buffer_place_cursor(buffer, iter);
11624 gtk_text_buffer_delete_mark(buffer, mark);
11626 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, pos);
11627 mark = gtk_text_buffer_create_mark(buffer, NULL, &start_iter, FALSE);
11628 compose_beautify_paragraph(compose, &start_iter, FALSE);
11629 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark);
11630 gtk_text_buffer_delete_mark(buffer, mark);
11632 if (strcmp(text, "\n") || compose->automatic_break
11633 || gtk_text_iter_starts_line(iter)) {
11634 GtkTextIter before_ins;
11635 gtk_text_buffer_insert(buffer, iter, text, len);
11636 if (!strstr(text, "\n") && gtk_text_iter_has_tag(iter, compose->no_join_tag)) {
11637 before_ins = *iter;
11638 gtk_text_iter_backward_chars(&before_ins, len);
11639 gtk_text_buffer_remove_tag_by_name(buffer, "no_join", &before_ins, iter);
11642 /* check if the preceding is just whitespace or quote */
11643 GtkTextIter start_line;
11644 gchar *tmp = NULL, *quote = NULL;
11645 gint quote_len = 0, is_normal = 0;
11646 start_line = *iter;
11647 gtk_text_iter_set_line_offset(&start_line, 0);
11648 tmp = gtk_text_buffer_get_text(buffer, &start_line, iter, FALSE);
11651 if (*tmp == '\0') {
11654 quote = compose_get_quote_str(buffer, &start_line, "e_len);
11662 gtk_text_buffer_insert(buffer, iter, text, len);
11664 gtk_text_buffer_insert_with_tags_by_name(buffer,
11665 iter, text, len, "no_join", NULL);
11670 if (!paste_as_quotation) {
11671 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11672 compose_beautify_paragraph(compose, iter, FALSE);
11673 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11674 gtk_text_buffer_delete_mark(buffer, mark);
11677 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
11678 G_CALLBACK(text_inserted),
11680 g_signal_stop_emission_by_name(G_OBJECT(buffer), "insert-text");
11682 if (compose_can_autosave(compose) &&
11683 gtk_text_buffer_get_char_count(buffer) % prefs_common.autosave_length == 0 &&
11684 compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN /* disabled while loading */)
11685 compose->draft_timeout_tag = g_timeout_add
11686 (500, (GSourceFunc) compose_defer_auto_save_draft, compose);
11690 static void compose_check_all(GtkAction *action, gpointer data)
11692 Compose *compose = (Compose *)data;
11693 if (!compose->gtkaspell)
11696 if (gtk_widget_has_focus(compose->subject_entry))
11697 claws_spell_entry_check_all(
11698 CLAWS_SPELL_ENTRY(compose->subject_entry));
11700 gtkaspell_check_all(compose->gtkaspell);
11703 static void compose_highlight_all(GtkAction *action, gpointer data)
11705 Compose *compose = (Compose *)data;
11706 if (compose->gtkaspell) {
11707 claws_spell_entry_recheck_all(
11708 CLAWS_SPELL_ENTRY(compose->subject_entry));
11709 gtkaspell_highlight_all(compose->gtkaspell);
11713 static void compose_check_backwards(GtkAction *action, gpointer data)
11715 Compose *compose = (Compose *)data;
11716 if (!compose->gtkaspell) {
11717 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
11721 if (gtk_widget_has_focus(compose->subject_entry))
11722 claws_spell_entry_check_backwards(
11723 CLAWS_SPELL_ENTRY(compose->subject_entry));
11725 gtkaspell_check_backwards(compose->gtkaspell);
11728 static void compose_check_forwards_go(GtkAction *action, gpointer data)
11730 Compose *compose = (Compose *)data;
11731 if (!compose->gtkaspell) {
11732 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
11736 if (gtk_widget_has_focus(compose->subject_entry))
11737 claws_spell_entry_check_forwards_go(
11738 CLAWS_SPELL_ENTRY(compose->subject_entry));
11740 gtkaspell_check_forwards_go(compose->gtkaspell);
11745 *\brief Guess originating forward account from MsgInfo and several
11746 * "common preference" settings. Return NULL if no guess.
11748 static PrefsAccount *compose_find_account(MsgInfo *msginfo)
11750 PrefsAccount *account = NULL;
11752 cm_return_val_if_fail(msginfo, NULL);
11753 cm_return_val_if_fail(msginfo->folder, NULL);
11754 cm_return_val_if_fail(msginfo->folder->prefs, NULL);
11756 if (msginfo->folder->prefs->enable_default_account)
11757 account = account_find_from_id(msginfo->folder->prefs->default_account);
11759 if (!account && msginfo->to && prefs_common.forward_account_autosel) {
11761 Xstrdup_a(to, msginfo->to, return NULL);
11762 extract_address(to);
11763 account = account_find_from_address(to, FALSE);
11766 if (!account && prefs_common.forward_account_autosel) {
11767 gchar cc[BUFFSIZE];
11768 if (!procheader_get_header_from_msginfo
11769 (msginfo, cc,sizeof cc , "Cc:")) {
11770 gchar *buf = cc + strlen("Cc:");
11771 extract_address(buf);
11772 account = account_find_from_address(buf, FALSE);
11776 if (!account && prefs_common.forward_account_autosel) {
11777 gchar deliveredto[BUFFSIZE];
11778 if (!procheader_get_header_from_msginfo
11779 (msginfo, deliveredto,sizeof deliveredto , "Delivered-To:")) {
11780 gchar *buf = deliveredto + strlen("Delivered-To:");
11781 extract_address(buf);
11782 account = account_find_from_address(buf, FALSE);
11787 account = msginfo->folder->folder->account;
11792 gboolean compose_close(Compose *compose)
11796 cm_return_val_if_fail(compose, FALSE);
11798 if (!g_mutex_trylock(compose->mutex)) {
11799 /* we have to wait for the (possibly deferred by auto-save)
11800 * drafting to be done, before destroying the compose under
11802 debug_print("waiting for drafting to finish...\n");
11803 compose_allow_user_actions(compose, FALSE);
11804 if (compose->close_timeout_tag == 0) {
11805 compose->close_timeout_tag =
11806 g_timeout_add (500, (GSourceFunc) compose_close,
11812 if (compose->draft_timeout_tag >= 0) {
11813 g_source_remove(compose->draft_timeout_tag);
11814 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN;
11817 gtkut_widget_get_uposition(compose->window, &x, &y);
11818 if (!compose->batch) {
11819 prefs_common.compose_x = x;
11820 prefs_common.compose_y = y;
11822 g_mutex_unlock(compose->mutex);
11823 compose_destroy(compose);
11828 * Add entry field for each address in list.
11829 * \param compose E-Mail composition object.
11830 * \param listAddress List of (formatted) E-Mail addresses.
11832 static void compose_add_field_list( Compose *compose, GList *listAddress ) {
11835 node = listAddress;
11837 addr = ( gchar * ) node->data;
11838 compose_entry_append( compose, addr, COMPOSE_TO, PREF_NONE );
11839 node = g_list_next( node );
11843 static void compose_reply_from_messageview_real(MessageView *msgview, GSList *msginfo_list,
11844 guint action, gboolean opening_multiple)
11846 gchar *body = NULL;
11847 GSList *new_msglist = NULL;
11848 MsgInfo *tmp_msginfo = NULL;
11849 gboolean originally_enc = FALSE;
11850 gboolean originally_sig = FALSE;
11851 Compose *compose = NULL;
11852 gchar *s_system = NULL;
11854 cm_return_if_fail(msgview != NULL);
11856 cm_return_if_fail(msginfo_list != NULL);
11858 if (g_slist_length(msginfo_list) == 1 && !opening_multiple) {
11859 MimeInfo *mimeinfo = messageview_get_selected_mime_part(msgview);
11860 MsgInfo *orig_msginfo = (MsgInfo *)msginfo_list->data;
11862 if (mimeinfo != NULL && mimeinfo->type == MIMETYPE_MESSAGE &&
11863 !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
11864 tmp_msginfo = procmsg_msginfo_new_from_mimeinfo(
11865 orig_msginfo, mimeinfo);
11866 if (tmp_msginfo != NULL) {
11867 new_msglist = g_slist_append(NULL, tmp_msginfo);
11869 originally_enc = MSG_IS_ENCRYPTED(orig_msginfo->flags);
11870 privacy_msginfo_get_signed_state(orig_msginfo, &s_system);
11871 originally_sig = MSG_IS_SIGNED(orig_msginfo->flags);
11873 tmp_msginfo->folder = orig_msginfo->folder;
11874 tmp_msginfo->msgnum = orig_msginfo->msgnum;
11875 if (orig_msginfo->tags) {
11876 tmp_msginfo->tags = g_slist_copy(orig_msginfo->tags);
11877 tmp_msginfo->folder->tags_dirty = TRUE;
11883 if (!opening_multiple)
11884 body = messageview_get_selection(msgview);
11887 compose = compose_reply_mode((ComposeMode)action, new_msglist, body);
11888 procmsg_msginfo_free(&tmp_msginfo);
11889 g_slist_free(new_msglist);
11891 compose = compose_reply_mode((ComposeMode)action, msginfo_list, body);
11893 if (compose && originally_enc) {
11894 compose_force_encryption(compose, compose->account, FALSE, s_system);
11897 if (compose && originally_sig && compose->account->default_sign_reply) {
11898 compose_force_signing(compose, compose->account, s_system);
11902 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
11905 void compose_reply_from_messageview(MessageView *msgview, GSList *msginfo_list,
11908 if ((!prefs_common.forward_as_attachment || action != COMPOSE_FORWARD)
11909 && action != COMPOSE_FORWARD_AS_ATTACH && g_slist_length(msginfo_list) > 1) {
11910 GSList *cur = msginfo_list;
11911 gchar *msg = g_strdup_printf(_("You are about to reply to %d "
11912 "messages. Opening the windows "
11913 "could take some time. Do you "
11914 "want to continue?"),
11915 g_slist_length(msginfo_list));
11916 if (g_slist_length(msginfo_list) > 9
11917 && alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, "+" GTK_STOCK_YES, NULL)
11918 != G_ALERTALTERNATE) {
11923 /* We'll open multiple compose windows */
11924 /* let the WM place the next windows */
11925 compose_force_window_origin = FALSE;
11926 for (; cur; cur = cur->next) {
11928 tmplist.data = cur->data;
11929 tmplist.next = NULL;
11930 compose_reply_from_messageview_real(msgview, &tmplist, action, TRUE);
11932 compose_force_window_origin = TRUE;
11934 /* forwarding multiple mails as attachments is done via a
11935 * single compose window */
11936 compose_reply_from_messageview_real(msgview, msginfo_list, action, FALSE);
11940 void compose_check_for_email_account(Compose *compose)
11942 PrefsAccount *ac = NULL, *curr = NULL;
11948 if (compose->account && compose->account->protocol == A_NNTP) {
11949 ac = account_get_cur_account();
11950 if (ac->protocol == A_NNTP) {
11951 list = account_get_list();
11953 for( ; list != NULL ; list = g_list_next(list)) {
11954 curr = (PrefsAccount *) list->data;
11955 if (curr->protocol != A_NNTP) {
11961 combobox_select_by_data(GTK_COMBO_BOX(compose->account_combo),
11966 void compose_reply_to_address(MessageView *msgview, MsgInfo *msginfo,
11967 const gchar *address)
11969 GSList *msginfo_list = NULL;
11970 gchar *body = messageview_get_selection(msgview);
11973 msginfo_list = g_slist_prepend(msginfo_list, msginfo);
11975 compose = compose_reply_mode(COMPOSE_REPLY_TO_ADDRESS, msginfo_list, body);
11976 compose_check_for_email_account(compose);
11977 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
11978 compose_entry_append(compose, address, COMPOSE_TO, PREF_NONE);
11979 compose_reply_set_subject(compose, msginfo);
11982 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
11985 void compose_set_position(Compose *compose, gint pos)
11987 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11989 gtkut_text_view_set_position(text, pos);
11992 gboolean compose_search_string(Compose *compose,
11993 const gchar *str, gboolean case_sens)
11995 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11997 return gtkut_text_view_search_string(text, str, case_sens);
12000 gboolean compose_search_string_backward(Compose *compose,
12001 const gchar *str, gboolean case_sens)
12003 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
12005 return gtkut_text_view_search_string_backward(text, str, case_sens);
12008 /* allocate a msginfo structure and populate its data from a compose data structure */
12009 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose)
12011 MsgInfo *newmsginfo;
12013 gchar buf[BUFFSIZE];
12015 cm_return_val_if_fail( compose != NULL, NULL );
12017 newmsginfo = procmsg_msginfo_new();
12020 get_rfc822_date(buf, sizeof(buf));
12021 newmsginfo->date = g_strdup(buf);
12024 if (compose->from_name) {
12025 newmsginfo->from = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
12026 newmsginfo->fromname = procheader_get_fromname(newmsginfo->from);
12030 if (compose->subject_entry)
12031 newmsginfo->subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
12033 /* to, cc, reply-to, newsgroups */
12034 for (list = compose->header_list; list; list = list->next) {
12035 gchar *header = gtk_editable_get_chars(
12037 gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
12038 gchar *entry = gtk_editable_get_chars(
12039 GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
12041 if ( strcasecmp(header, prefs_common_translated_header_name("To:")) == 0 ) {
12042 if ( newmsginfo->to == NULL ) {
12043 newmsginfo->to = g_strdup(entry);
12044 } else if (entry && *entry) {
12045 gchar *tmp = g_strconcat(newmsginfo->to, ", ", entry, NULL);
12046 g_free(newmsginfo->to);
12047 newmsginfo->to = tmp;
12050 if ( strcasecmp(header, prefs_common_translated_header_name("Cc:")) == 0 ) {
12051 if ( newmsginfo->cc == NULL ) {
12052 newmsginfo->cc = g_strdup(entry);
12053 } else if (entry && *entry) {
12054 gchar *tmp = g_strconcat(newmsginfo->cc, ", ", entry, NULL);
12055 g_free(newmsginfo->cc);
12056 newmsginfo->cc = tmp;
12059 if ( strcasecmp(header,
12060 prefs_common_translated_header_name("Newsgroups:")) == 0 ) {
12061 if ( newmsginfo->newsgroups == NULL ) {
12062 newmsginfo->newsgroups = g_strdup(entry);
12063 } else if (entry && *entry) {
12064 gchar *tmp = g_strconcat(newmsginfo->newsgroups, ", ", entry, NULL);
12065 g_free(newmsginfo->newsgroups);
12066 newmsginfo->newsgroups = tmp;
12074 /* other data is unset */
12080 /* update compose's dictionaries from folder dict settings */
12081 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
12082 FolderItem *folder_item)
12084 cm_return_if_fail(compose != NULL);
12086 if (compose->gtkaspell && folder_item && folder_item->prefs) {
12087 FolderItemPrefs *prefs = folder_item->prefs;
12089 if (prefs->enable_default_dictionary)
12090 gtkaspell_change_dict(compose->gtkaspell,
12091 prefs->default_dictionary, FALSE);
12092 if (folder_item->prefs->enable_default_alt_dictionary)
12093 gtkaspell_change_alt_dict(compose->gtkaspell,
12094 prefs->default_alt_dictionary);
12095 if (prefs->enable_default_dictionary
12096 || prefs->enable_default_alt_dictionary)
12097 compose_spell_menu_changed(compose);
12102 static void compose_subject_entry_activated(GtkWidget *widget, gpointer data)
12104 Compose *compose = (Compose *)data;
12106 cm_return_if_fail(compose != NULL);
12108 gtk_widget_grab_focus(compose->text);
12111 static void from_name_activate_cb(GtkWidget *widget, gpointer data)
12113 gtk_combo_box_popup(GTK_COMBO_BOX(data));