2 * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2015 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/>.
22 #include "claws-features.h"
27 #ifndef PANGO_ENABLE_ENGINE
28 # define PANGO_ENABLE_ENGINE
32 #include <glib/gi18n.h>
33 #include <gdk/gdkkeysyms.h>
36 #include <pango/pango-break.h>
41 #include <sys/types.h>
47 # include <sys/wait.h>
51 #ifndef G_OS_WIN32 /* fixme we should have a configure test. */
55 #if (HAVE_WCTYPE_H && HAVE_WCHAR_H)
62 #include "mainwindow.h"
64 #ifndef USE_ALT_ADDRBOOK
65 #include "addressbook.h"
67 #include "addressbook-dbus.h"
68 #include "addressadd.h"
70 #include "folderview.h"
73 #include "stock_pixmap.h"
74 #include "send_message.h"
77 #include "customheader.h"
78 #include "prefs_common.h"
79 #include "prefs_account.h"
83 #include "procheader.h"
85 #include "statusbar.h"
87 #include "quoted-printable.h"
91 #include "gtkshruler.h"
93 #include "alertpanel.h"
94 #include "manage_window.h"
96 #include "folder_item_prefs.h"
97 #include "addr_compl.h"
98 #include "quote_fmt.h"
100 #include "foldersel.h"
103 #include "message_search.h"
104 #include "combobox.h"
108 #include "autofaces.h"
109 #include "spell_entry.h"
122 #define N_ATTACH_COLS (N_COL_COLUMNS)
126 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE,
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 GList *compose_list = NULL;
188 static GSList *extra_headers = NULL;
190 static Compose *compose_generic_new (PrefsAccount *account,
194 GList *listAddress );
196 static Compose *compose_create (PrefsAccount *account,
201 static void compose_entry_mark_default_to (Compose *compose,
202 const gchar *address);
203 static Compose *compose_followup_and_reply_to (MsgInfo *msginfo,
204 ComposeQuoteMode quote_mode,
208 static Compose *compose_forward_multiple (PrefsAccount *account,
209 GSList *msginfo_list);
210 static Compose *compose_reply (MsgInfo *msginfo,
211 ComposeQuoteMode quote_mode,
216 static Compose *compose_reply_mode (ComposeMode mode,
217 GSList *msginfo_list,
219 static void compose_template_apply_fields(Compose *compose, Template *tmpl);
220 static void compose_update_privacy_systems_menu(Compose *compose);
222 static GtkWidget *compose_account_option_menu_create
224 static void compose_set_out_encoding (Compose *compose);
225 static void compose_set_template_menu (Compose *compose);
226 static void compose_destroy (Compose *compose);
228 static MailField compose_entries_set (Compose *compose,
230 ComposeEntryType to_type);
231 static gint compose_parse_header (Compose *compose,
233 static gint compose_parse_manual_headers (Compose *compose,
235 HeaderEntry *entries);
236 static gchar *compose_parse_references (const gchar *ref,
239 static gchar *compose_quote_fmt (Compose *compose,
245 gboolean need_unescape,
246 const gchar *err_msg);
248 static void compose_reply_set_entry (Compose *compose,
254 followup_and_reply_to);
255 static void compose_reedit_set_entry (Compose *compose,
258 static void compose_insert_sig (Compose *compose,
260 static ComposeInsertResult compose_insert_file (Compose *compose,
263 static gboolean compose_attach_append (Compose *compose,
266 const gchar *content_type,
267 const gchar *charset);
268 static void compose_attach_parts (Compose *compose,
271 static gboolean compose_beautify_paragraph (Compose *compose,
272 GtkTextIter *par_iter,
274 static void compose_wrap_all (Compose *compose);
275 static void compose_wrap_all_full (Compose *compose,
278 static void compose_set_title (Compose *compose);
279 static void compose_select_account (Compose *compose,
280 PrefsAccount *account,
283 static PrefsAccount *compose_current_mail_account(void);
284 /* static gint compose_send (Compose *compose); */
285 static gboolean compose_check_for_valid_recipient
287 static gboolean compose_check_entries (Compose *compose,
288 gboolean check_everything);
289 static gint compose_write_to_file (Compose *compose,
292 gboolean attach_parts);
293 static gint compose_write_body_to_file (Compose *compose,
295 static gint compose_remove_reedit_target (Compose *compose,
297 static void compose_remove_draft (Compose *compose);
298 static gint compose_queue_sub (Compose *compose,
302 gboolean check_subject,
303 gboolean remove_reedit_target);
304 static int compose_add_attachments (Compose *compose,
306 static gchar *compose_get_header (Compose *compose);
307 static gchar *compose_get_manual_headers_info (Compose *compose);
309 static void compose_convert_header (Compose *compose,
314 gboolean addr_field);
316 static void compose_attach_info_free (AttachInfo *ainfo);
317 static void compose_attach_remove_selected (GtkAction *action,
320 static void compose_template_apply (Compose *compose,
323 static void compose_attach_property (GtkAction *action,
325 static void compose_attach_property_create (gboolean *cancelled);
326 static void attach_property_ok (GtkWidget *widget,
327 gboolean *cancelled);
328 static void attach_property_cancel (GtkWidget *widget,
329 gboolean *cancelled);
330 static gint attach_property_delete_event (GtkWidget *widget,
332 gboolean *cancelled);
333 static gboolean attach_property_key_pressed (GtkWidget *widget,
335 gboolean *cancelled);
337 static void compose_exec_ext_editor (Compose *compose);
339 static gint compose_exec_ext_editor_real (const gchar *file,
340 GdkNativeWindow socket_wid);
341 static gboolean compose_ext_editor_kill (Compose *compose);
342 static gboolean compose_input_cb (GIOChannel *source,
343 GIOCondition condition,
345 static void compose_set_ext_editor_sensitive (Compose *compose,
347 static gboolean compose_get_ext_editor_cmd_valid();
348 static gboolean compose_get_ext_editor_uses_socket();
349 static gboolean compose_ext_editor_plug_removed_cb
352 #endif /* G_OS_UNIX */
354 static void compose_undo_state_changed (UndoMain *undostruct,
359 static void compose_create_header_entry (Compose *compose);
360 static void compose_add_header_entry (Compose *compose, const gchar *header,
361 gchar *text, ComposePrefType pref_type);
362 static void compose_remove_header_entries(Compose *compose);
364 static void compose_update_priority_menu_item(Compose * compose);
366 static void compose_spell_menu_changed (void *data);
367 static void compose_dict_changed (void *data);
369 static void compose_add_field_list ( Compose *compose,
370 GList *listAddress );
372 /* callback functions */
374 static void compose_notebook_size_alloc (GtkNotebook *notebook,
375 GtkAllocation *allocation,
377 static gboolean compose_edit_size_alloc (GtkEditable *widget,
378 GtkAllocation *allocation,
379 GtkSHRuler *shruler);
380 static void account_activated (GtkComboBox *optmenu,
382 static void attach_selected (GtkTreeView *tree_view,
383 GtkTreePath *tree_path,
384 GtkTreeViewColumn *column,
386 static gboolean attach_button_pressed (GtkWidget *widget,
387 GdkEventButton *event,
389 static gboolean attach_key_pressed (GtkWidget *widget,
392 static void compose_send_cb (GtkAction *action, gpointer data);
393 static void compose_send_later_cb (GtkAction *action, gpointer data);
395 static void compose_save_cb (GtkAction *action,
398 static void compose_attach_cb (GtkAction *action,
400 static void compose_insert_file_cb (GtkAction *action,
402 static void compose_insert_sig_cb (GtkAction *action,
404 static void compose_replace_sig_cb (GtkAction *action,
407 static void compose_close_cb (GtkAction *action,
409 static void compose_print_cb (GtkAction *action,
412 static void compose_set_encoding_cb (GtkAction *action, GtkRadioAction *current, gpointer data);
414 static void compose_address_cb (GtkAction *action,
416 static void about_show_cb (GtkAction *action,
418 static void compose_template_activate_cb(GtkWidget *widget,
421 static void compose_ext_editor_cb (GtkAction *action,
424 static gint compose_delete_cb (GtkWidget *widget,
428 static void compose_undo_cb (GtkAction *action,
430 static void compose_redo_cb (GtkAction *action,
432 static void compose_cut_cb (GtkAction *action,
434 static void compose_copy_cb (GtkAction *action,
436 static void compose_paste_cb (GtkAction *action,
438 static void compose_paste_as_quote_cb (GtkAction *action,
440 static void compose_paste_no_wrap_cb (GtkAction *action,
442 static void compose_paste_wrap_cb (GtkAction *action,
444 static void compose_allsel_cb (GtkAction *action,
447 static void compose_advanced_action_cb (GtkAction *action,
450 static void compose_grab_focus_cb (GtkWidget *widget,
453 static void compose_changed_cb (GtkTextBuffer *textbuf,
456 static void compose_wrap_cb (GtkAction *action,
458 static void compose_wrap_all_cb (GtkAction *action,
460 static void compose_find_cb (GtkAction *action,
462 static void compose_toggle_autowrap_cb (GtkToggleAction *action,
464 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
467 static void compose_toggle_ruler_cb (GtkToggleAction *action,
469 static void compose_toggle_sign_cb (GtkToggleAction *action,
471 static void compose_toggle_encrypt_cb (GtkToggleAction *action,
473 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data);
474 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn);
475 static void activate_privacy_system (Compose *compose,
476 PrefsAccount *account,
478 static void compose_use_signing(Compose *compose, gboolean use_signing);
479 static void compose_use_encryption(Compose *compose, gboolean use_encryption);
480 static void compose_toggle_return_receipt_cb(GtkToggleAction *action,
482 static void compose_toggle_remove_refs_cb(GtkToggleAction *action,
484 static void compose_set_priority_cb (GtkAction *action, GtkRadioAction *current, gpointer data);
485 static void compose_reply_change_mode (Compose *compose, ComposeMode action);
486 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data);
488 static void compose_attach_drag_received_cb (GtkWidget *widget,
489 GdkDragContext *drag_context,
492 GtkSelectionData *data,
496 static void compose_insert_drag_received_cb (GtkWidget *widget,
497 GdkDragContext *drag_context,
500 GtkSelectionData *data,
504 static void compose_header_drag_received_cb (GtkWidget *widget,
505 GdkDragContext *drag_context,
508 GtkSelectionData *data,
513 static gboolean compose_drag_drop (GtkWidget *widget,
514 GdkDragContext *drag_context,
516 guint time, gpointer user_data);
517 static gboolean completion_set_focus_to_subject
522 static void text_inserted (GtkTextBuffer *buffer,
527 static Compose *compose_generic_reply(MsgInfo *msginfo,
528 ComposeQuoteMode quote_mode,
532 gboolean followup_and_reply_to,
535 static void compose_headerentry_changed_cb (GtkWidget *entry,
536 ComposeHeaderEntry *headerentry);
537 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
539 ComposeHeaderEntry *headerentry);
540 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
541 ComposeHeaderEntry *headerentry);
543 static void compose_show_first_last_header (Compose *compose, gboolean show_first);
545 static void compose_allow_user_actions (Compose *compose, gboolean allow);
547 static void compose_nothing_cb (GtkAction *action, gpointer data)
553 static void compose_check_all (GtkAction *action, gpointer data);
554 static void compose_highlight_all (GtkAction *action, gpointer data);
555 static void compose_check_backwards (GtkAction *action, gpointer data);
556 static void compose_check_forwards_go (GtkAction *action, gpointer data);
559 static PrefsAccount *compose_guess_forward_account_from_msginfo (MsgInfo *msginfo);
561 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose);
564 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
565 FolderItem *folder_item);
567 static void compose_attach_update_label(Compose *compose);
568 static void compose_set_folder_prefs(Compose *compose, FolderItem *folder,
569 gboolean respect_default_to);
570 static void compose_subject_entry_activated(GtkWidget *widget, gpointer data);
571 static void from_name_activate_cb(GtkWidget *widget, gpointer data);
573 static GtkActionEntry compose_popup_entries[] =
575 {"Compose", NULL, "Compose" },
576 {"Compose/Add", NULL, N_("_Add..."), NULL, NULL, G_CALLBACK(compose_attach_cb) },
577 {"Compose/Remove", NULL, N_("_Remove"), NULL, NULL, G_CALLBACK(compose_attach_remove_selected) },
578 {"Compose/---", NULL, "---", NULL, NULL, NULL },
579 {"Compose/Properties", NULL, N_("_Properties..."), NULL, NULL, G_CALLBACK(compose_attach_property) },
582 static GtkActionEntry compose_entries[] =
584 {"Menu", NULL, "Menu" },
586 {"Message", NULL, N_("_Message") },
587 {"Edit", NULL, N_("_Edit") },
589 {"Spelling", NULL, N_("_Spelling") },
591 {"Options", NULL, N_("_Options") },
592 {"Tools", NULL, N_("_Tools") },
593 {"Help", NULL, N_("_Help") },
595 {"Message/Send", NULL, N_("S_end"), "<control>Return", NULL, G_CALLBACK(compose_send_cb) },
596 {"Message/SendLater", NULL, N_("Send _later"), "<shift><control>S", NULL, G_CALLBACK(compose_send_later_cb) },
597 {"Message/---", NULL, "---" },
599 {"Message/AttachFile", NULL, N_("_Attach file"), "<control>M", NULL, G_CALLBACK(compose_attach_cb) },
600 {"Message/InsertFile", NULL, N_("_Insert file"), "<control>I", NULL, G_CALLBACK(compose_insert_file_cb) },
601 {"Message/InsertSig", NULL, N_("Insert si_gnature"), "<control>G", NULL, G_CALLBACK(compose_insert_sig_cb) },
602 {"Message/ReplaceSig", NULL, N_("_Replace signature"), NULL, NULL, G_CALLBACK(compose_replace_sig_cb) },
603 /* {"Message/---", NULL, "---" }, */
604 {"Message/Save", NULL, N_("_Save"), "<control>S", NULL, G_CALLBACK(compose_save_cb) }, /*COMPOSE_KEEP_EDITING*/
605 /* {"Message/---", NULL, "---" }, */
606 {"Message/Print", NULL, N_("_Print"), NULL, NULL, G_CALLBACK(compose_print_cb) },
607 /* {"Message/---", NULL, "---" }, */
608 {"Message/Close", NULL, N_("_Close"), "<control>W", NULL, G_CALLBACK(compose_close_cb) },
611 {"Edit/Undo", NULL, N_("_Undo"), "<control>Z", NULL, G_CALLBACK(compose_undo_cb) },
612 {"Edit/Redo", NULL, N_("_Redo"), "<control>Y", NULL, G_CALLBACK(compose_redo_cb) },
613 {"Edit/---", NULL, "---" },
615 {"Edit/Cut", NULL, N_("Cu_t"), "<control>X", NULL, G_CALLBACK(compose_cut_cb) },
616 {"Edit/Copy", NULL, N_("_Copy"), "<control>C", NULL, G_CALLBACK(compose_copy_cb) },
617 {"Edit/Paste", NULL, N_("_Paste"), "<control>V", NULL, G_CALLBACK(compose_paste_cb) },
619 {"Edit/SpecialPaste", NULL, N_("_Special paste") },
620 {"Edit/SpecialPaste/AsQuotation", NULL, N_("As _quotation"), NULL, NULL, G_CALLBACK(compose_paste_as_quote_cb) },
621 {"Edit/SpecialPaste/Wrapped", NULL, N_("_Wrapped"), NULL, NULL, G_CALLBACK(compose_paste_wrap_cb) },
622 {"Edit/SpecialPaste/Unwrapped", NULL, N_("_Unwrapped"), NULL, NULL, G_CALLBACK(compose_paste_no_wrap_cb) },
624 {"Edit/SelectAll", NULL, N_("Select _all"), "<control>A", NULL, G_CALLBACK(compose_allsel_cb) },
626 {"Edit/Advanced", NULL, N_("A_dvanced") },
627 {"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*/
628 {"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*/
629 {"Edit/Advanced/BackWord", NULL, N_("Move a word backward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD*/
630 {"Edit/Advanced/ForwWord", NULL, N_("Move a word forward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD*/
631 {"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*/
632 {"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*/
633 {"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*/
634 {"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*/
635 {"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*/
636 {"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*/
637 {"Edit/Advanced/DelBackWord", NULL, N_("Delete a word backward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD*/
638 {"Edit/Advanced/DelForwWord", NULL, N_("Delete a word forward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD*/
639 {"Edit/Advanced/DelLine", NULL, N_("Delete line"), "<control>U", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE*/
640 {"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*/
642 /* {"Edit/---", NULL, "---" }, */
643 {"Edit/Find", NULL, N_("_Find"), "<control>F", NULL, G_CALLBACK(compose_find_cb) },
645 /* {"Edit/---", NULL, "---" }, */
646 {"Edit/WrapPara", NULL, N_("_Wrap current paragraph"), "<control>L", NULL, G_CALLBACK(compose_wrap_cb) }, /* 0 */
647 {"Edit/WrapAllLines", NULL, N_("Wrap all long _lines"), "<control><alt>L", NULL, G_CALLBACK(compose_wrap_all_cb) }, /* 1 */
648 /* {"Edit/---", NULL, "---" }, */
649 {"Edit/ExtEditor", NULL, N_("Edit with e_xternal editor"), "<shift><control>X", NULL, G_CALLBACK(compose_ext_editor_cb) },
652 {"Spelling/CheckAllSel", NULL, N_("_Check all or check selection"), NULL, NULL, G_CALLBACK(compose_check_all) },
653 {"Spelling/HighlightAll", NULL, N_("_Highlight all misspelled words"), NULL, NULL, G_CALLBACK(compose_highlight_all) },
654 {"Spelling/CheckBackwards", NULL, N_("Check _backwards misspelled word"), NULL, NULL, G_CALLBACK(compose_check_backwards) },
655 {"Spelling/ForwardNext", NULL, N_("_Forward to next misspelled word"), NULL, NULL, G_CALLBACK(compose_check_forwards_go) },
657 {"Spelling/---", NULL, "---" },
658 {"Spelling/Options", NULL, N_("_Options") },
663 {"Options/ReplyMode", NULL, N_("Reply _mode") },
664 {"Options/---", NULL, "---" },
665 {"Options/PrivacySystem", NULL, N_("Privacy _System") },
666 {"Options/PrivacySystem/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
668 /* {"Options/---", NULL, "---" }, */
670 {"Options/Priority", NULL, N_("_Priority") },
672 {"Options/Encoding", NULL, N_("Character _encoding") },
673 {"Options/Encoding/---", NULL, "---" },
674 #define ENC_ACTION(cs_char,c_char,string) \
675 { "Options/Encoding/" cs_char, NULL, N_(string), NULL, NULL, c_char }
677 {"Options/Encoding/Western", NULL, N_("Western European") },
678 {"Options/Encoding/Baltic", NULL, N_("Baltic") },
679 {"Options/Encoding/Hebrew", NULL, N_("Hebrew") },
680 {"Options/Encoding/Arabic", NULL, N_("Arabic") },
681 {"Options/Encoding/Cyrillic", NULL, N_("Cyrillic") },
682 {"Options/Encoding/Japanese", NULL, N_("Japanese") },
683 {"Options/Encoding/Chinese", NULL, N_("Chinese") },
684 {"Options/Encoding/Korean", NULL, N_("Korean") },
685 {"Options/Encoding/Thai", NULL, N_("Thai") },
688 {"Tools/AddressBook", NULL, N_("_Address book"), NULL, NULL, G_CALLBACK(compose_address_cb) },
690 {"Tools/Template", NULL, N_("_Template") },
691 {"Tools/Template/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
692 {"Tools/Actions", NULL, N_("Actio_ns") },
693 {"Tools/Actions/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
696 {"Help/About", NULL, N_("_About"), NULL, NULL, G_CALLBACK(about_show_cb) },
699 static GtkToggleActionEntry compose_toggle_entries[] =
701 {"Edit/AutoWrap", NULL, N_("Aut_o wrapping"), "<shift><control>L", NULL, G_CALLBACK(compose_toggle_autowrap_cb) }, /* TOGGLE */
702 {"Edit/AutoIndent", NULL, N_("Auto _indent"), NULL, NULL, G_CALLBACK(compose_toggle_autoindent_cb) }, /* TOGGLE */
703 {"Options/Sign", NULL, N_("Si_gn"), NULL, NULL, G_CALLBACK(compose_toggle_sign_cb) }, /* Toggle */
704 {"Options/Encrypt", NULL, N_("_Encrypt"), NULL, NULL, G_CALLBACK(compose_toggle_encrypt_cb) }, /* Toggle */
705 {"Options/RequestRetRcpt", NULL, N_("_Request Return Receipt"), NULL, NULL, G_CALLBACK(compose_toggle_return_receipt_cb) }, /* TOGGLE */
706 {"Options/RemoveReferences", NULL, N_("Remo_ve references"), NULL, NULL, G_CALLBACK(compose_toggle_remove_refs_cb) }, /* TOGGLE */
707 {"Tools/ShowRuler", NULL, N_("Show _ruler"), NULL, NULL, G_CALLBACK(compose_toggle_ruler_cb) }, /* Toggle */
710 static GtkRadioActionEntry compose_radio_rm_entries[] =
712 {"Options/ReplyMode/Normal", NULL, N_("_Normal"), NULL, NULL, COMPOSE_REPLY }, /* RADIO compose_reply_change_mode_cb */
713 {"Options/ReplyMode/All", NULL, N_("_All"), NULL, NULL, COMPOSE_REPLY_TO_ALL }, /* RADIO compose_reply_change_mode_cb */
714 {"Options/ReplyMode/Sender", NULL, N_("_Sender"), NULL, NULL, COMPOSE_REPLY_TO_SENDER }, /* RADIO compose_reply_change_mode_cb */
715 {"Options/ReplyMode/List", NULL, N_("_Mailing-list"), NULL, NULL, COMPOSE_REPLY_TO_LIST }, /* RADIO compose_reply_change_mode_cb */
718 static GtkRadioActionEntry compose_radio_prio_entries[] =
720 {"Options/Priority/Highest", NULL, N_("_Highest"), NULL, NULL, PRIORITY_HIGHEST }, /* RADIO compose_set_priority_cb */
721 {"Options/Priority/High", NULL, N_("Hi_gh"), NULL, NULL, PRIORITY_HIGH }, /* RADIO compose_set_priority_cb */
722 {"Options/Priority/Normal", NULL, N_("_Normal"), NULL, NULL, PRIORITY_NORMAL }, /* RADIO compose_set_priority_cb */
723 {"Options/Priority/Low", NULL, N_("Lo_w"), NULL, NULL, PRIORITY_LOW }, /* RADIO compose_set_priority_cb */
724 {"Options/Priority/Lowest", NULL, N_("_Lowest"), NULL, NULL, PRIORITY_LOWEST }, /* RADIO compose_set_priority_cb */
727 static GtkRadioActionEntry compose_radio_enc_entries[] =
729 ENC_ACTION(CS_AUTO, C_AUTO, N_("_Automatic")), /* RADIO compose_set_encoding_cb */
730 ENC_ACTION(CS_US_ASCII, C_US_ASCII, N_("7bit ASCII (US-ASC_II)")), /* RADIO compose_set_encoding_cb */
731 ENC_ACTION(CS_UTF_8, C_UTF_8, N_("Unicode (_UTF-8)")), /* RADIO compose_set_encoding_cb */
732 ENC_ACTION("Western/"CS_ISO_8859_1, C_ISO_8859_1, "ISO-8859-_1"), /* RADIO compose_set_encoding_cb */
733 ENC_ACTION("Western/"CS_ISO_8859_15, C_ISO_8859_15, "ISO-8859-15"), /* RADIO compose_set_encoding_cb */
734 ENC_ACTION("Western/"CS_WINDOWS_1252, C_WINDOWS_1252, "Windows-1252"), /* RADIO compose_set_encoding_cb */
735 ENC_ACTION(CS_ISO_8859_2, C_ISO_8859_2, N_("Central European (ISO-8859-_2)")), /* RADIO compose_set_encoding_cb */
736 ENC_ACTION("Baltic/"CS_ISO_8859_13, C_ISO_8859_13, "ISO-8859-13"), /* RADIO compose_set_encoding_cb */
737 ENC_ACTION("Baltic/"CS_ISO_8859_4, C_ISO_8859_14, "ISO-8859-_4"), /* RADIO compose_set_encoding_cb */
738 ENC_ACTION(CS_ISO_8859_7, C_ISO_8859_7, N_("Greek (ISO-8859-_7)")), /* RADIO compose_set_encoding_cb */
739 ENC_ACTION("Hebrew/"CS_ISO_8859_8, C_ISO_8859_8, "ISO-8859-_8"), /* RADIO compose_set_encoding_cb */
740 ENC_ACTION("Hebrew/"CS_WINDOWS_1255, C_WINDOWS_1255, "Windows-1255"), /* RADIO compose_set_encoding_cb */
741 ENC_ACTION("Arabic/"CS_ISO_8859_6, C_ISO_8859_6, "ISO-8859-_6"), /* RADIO compose_set_encoding_cb */
742 ENC_ACTION("Arabic/"CS_WINDOWS_1256, C_WINDOWS_1256, "Windows-1256"), /* RADIO compose_set_encoding_cb */
743 ENC_ACTION(CS_ISO_8859_9, C_ISO_8859_9, N_("Turkish (ISO-8859-_9)")), /* RADIO compose_set_encoding_cb */
744 ENC_ACTION("Cyrillic/"CS_ISO_8859_5, C_ISO_8859_5, "ISO-8859-_5"), /* RADIO compose_set_encoding_cb */
745 ENC_ACTION("Cyrillic/"CS_KOI8_R, C_KOI8_R, "KOI8-_R"), /* RADIO compose_set_encoding_cb */
746 ENC_ACTION("Cyrillic/"CS_MACCYR, C_MACCYR, "_Mac-Cyrillic"), /* RADIO compose_set_encoding_cb */
747 ENC_ACTION("Cyrillic/"CS_KOI8_U, C_KOI8_U, "KOI8-_U"), /* RADIO compose_set_encoding_cb */
748 ENC_ACTION("Cyrillic/"CS_WINDOWS_1251, C_WINDOWS_1251, "Windows-1251"), /* RADIO compose_set_encoding_cb */
749 ENC_ACTION("Japanese/"CS_ISO_2022_JP, C_ISO_2022_JP, "ISO-2022-_JP"), /* RADIO compose_set_encoding_cb */
750 ENC_ACTION("Japanese/"CS_ISO_2022_JP_2, C_ISO_2022_JP_2, "ISO-2022-JP-_2"), /* RADIO compose_set_encoding_cb */
751 ENC_ACTION("Japanese/"CS_EUC_JP, C_EUC_JP, "_EUC-JP"), /* RADIO compose_set_encoding_cb */
752 ENC_ACTION("Japanese/"CS_SHIFT_JIS, C_SHIFT_JIS, "_Shift-JIS"), /* RADIO compose_set_encoding_cb */
753 ENC_ACTION("Chinese/"CS_GB18030, C_GB18030, "_GB18030"), /* RADIO compose_set_encoding_cb */
754 ENC_ACTION("Chinese/"CS_GB2312, C_GB2312, "_GB2312"), /* RADIO compose_set_encoding_cb */
755 ENC_ACTION("Chinese/"CS_GBK, C_GBK, "GB_K"), /* RADIO compose_set_encoding_cb */
756 ENC_ACTION("Chinese/"CS_BIG5, C_BIG5, "_Big5-JP"), /* RADIO compose_set_encoding_cb */
757 ENC_ACTION("Chinese/"CS_EUC_TW, C_EUC_TW, "EUC-_TW"), /* RADIO compose_set_encoding_cb */
758 ENC_ACTION("Korean/"CS_EUC_KR, C_EUC_KR, "_EUC-KR"), /* RADIO compose_set_encoding_cb */
759 ENC_ACTION("Korean/"CS_ISO_2022_KR, C_ISO_2022_KR, "_ISO-2022-KR"), /* RADIO compose_set_encoding_cb */
760 ENC_ACTION("Thai/"CS_TIS_620, C_TIS_620, "_TIS-620-KR"), /* RADIO compose_set_encoding_cb */
761 ENC_ACTION("Thai/"CS_WINDOWS_874, C_WINDOWS_874, "_Windows-874"), /* RADIO compose_set_encoding_cb */
764 static GtkTargetEntry compose_mime_types[] =
766 {"text/uri-list", 0, 0},
767 {"UTF8_STRING", 0, 0},
771 static gboolean compose_put_existing_to_front(MsgInfo *info)
773 const GList *compose_list = compose_get_compose_list();
774 const GList *elem = NULL;
777 for (elem = compose_list; elem != NULL && elem->data != NULL;
779 Compose *c = (Compose*)elem->data;
781 if (!c->targetinfo || !c->targetinfo->msgid ||
785 if (!strcmp(c->targetinfo->msgid, info->msgid)) {
786 gtkut_window_popup(c->window);
794 static GdkColor quote_color1 =
795 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
796 static GdkColor quote_color2 =
797 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
798 static GdkColor quote_color3 =
799 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
801 static GdkColor quote_bgcolor1 =
802 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
803 static GdkColor quote_bgcolor2 =
804 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
805 static GdkColor quote_bgcolor3 =
806 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
808 static GdkColor signature_color = {
815 static GdkColor uri_color = {
822 static void compose_create_tags(GtkTextView *text, Compose *compose)
824 GtkTextBuffer *buffer;
825 GdkColor black = {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
826 #if !GTK_CHECK_VERSION(2, 24, 0)
833 buffer = gtk_text_view_get_buffer(text);
835 if (prefs_common.enable_color) {
836 /* grab the quote colors, converting from an int to a GdkColor */
837 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_col,
839 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_col,
841 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_col,
843 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_bgcol,
845 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_bgcol,
847 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_bgcol,
849 gtkut_convert_int_to_gdk_color(prefs_common.signature_col,
851 gtkut_convert_int_to_gdk_color(prefs_common.uri_col,
854 signature_color = quote_color1 = quote_color2 = quote_color3 =
855 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 = uri_color = black;
858 if (prefs_common.enable_color && prefs_common.enable_bgcolor) {
859 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
860 "foreground-gdk", "e_color1,
861 "paragraph-background-gdk", "e_bgcolor1,
863 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
864 "foreground-gdk", "e_color2,
865 "paragraph-background-gdk", "e_bgcolor2,
867 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
868 "foreground-gdk", "e_color3,
869 "paragraph-background-gdk", "e_bgcolor3,
872 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
873 "foreground-gdk", "e_color1,
875 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
876 "foreground-gdk", "e_color2,
878 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
879 "foreground-gdk", "e_color3,
883 compose->signature_tag = gtk_text_buffer_create_tag(buffer, "signature",
884 "foreground-gdk", &signature_color,
887 compose->uri_tag = gtk_text_buffer_create_tag(buffer, "link",
888 "foreground-gdk", &uri_color,
890 compose->no_wrap_tag = gtk_text_buffer_create_tag(buffer, "no_wrap", NULL);
891 compose->no_join_tag = gtk_text_buffer_create_tag(buffer, "no_join", NULL);
893 #if !GTK_CHECK_VERSION(2, 24, 0)
894 color[0] = quote_color1;
895 color[1] = quote_color2;
896 color[2] = quote_color3;
897 color[3] = quote_bgcolor1;
898 color[4] = quote_bgcolor2;
899 color[5] = quote_bgcolor3;
900 color[6] = signature_color;
901 color[7] = uri_color;
903 cmap = gdk_drawable_get_colormap(gtk_widget_get_window(compose->window));
904 gdk_colormap_alloc_colors(cmap, color, 8, FALSE, TRUE, success);
906 for (i = 0; i < 8; i++) {
907 if (success[i] == FALSE) {
908 g_warning("Compose: color allocation failed.");
909 quote_color1 = quote_color2 = quote_color3 =
910 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 =
911 signature_color = uri_color = black;
917 Compose *compose_new(PrefsAccount *account, const gchar *mailto,
920 return compose_generic_new(account, mailto, NULL, attach_files, NULL);
923 Compose *compose_new_with_folderitem(PrefsAccount *account, FolderItem *item, const gchar *mailto)
925 return compose_generic_new(account, mailto, item, NULL, NULL);
928 Compose *compose_new_with_list( PrefsAccount *account, GList *listAddress )
930 return compose_generic_new( account, NULL, NULL, NULL, listAddress );
933 #define SCROLL_TO_CURSOR(compose) { \
934 GtkTextMark *cmark = gtk_text_buffer_get_insert( \
935 gtk_text_view_get_buffer( \
936 GTK_TEXT_VIEW(compose->text))); \
937 gtk_text_view_scroll_mark_onscreen( \
938 GTK_TEXT_VIEW(compose->text), \
942 static void compose_set_save_to(Compose *compose, const gchar *folderidentifier)
945 if (folderidentifier) {
946 #if !GTK_CHECK_VERSION(2, 24, 0)
947 combobox_unset_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo));
949 combobox_unset_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo));
951 prefs_common.compose_save_to_history = add_history(
952 prefs_common.compose_save_to_history, folderidentifier);
953 #if !GTK_CHECK_VERSION(2, 24, 0)
954 combobox_set_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo),
955 prefs_common.compose_save_to_history);
957 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo),
958 prefs_common.compose_save_to_history);
962 entry = GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(compose->savemsg_combo)));
963 if (folderidentifier)
964 gtk_entry_set_text(GTK_ENTRY(entry), folderidentifier);
966 gtk_entry_set_text(GTK_ENTRY(entry), "");
969 static gchar *compose_get_save_to(Compose *compose)
972 gchar *result = NULL;
973 entry = GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(compose->savemsg_combo)));
974 result = gtk_editable_get_chars(entry, 0, -1);
977 #if !GTK_CHECK_VERSION(2, 24, 0)
978 combobox_unset_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo));
980 combobox_unset_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo));
982 prefs_common.compose_save_to_history = add_history(
983 prefs_common.compose_save_to_history, result);
984 #if !GTK_CHECK_VERSION(2, 24, 0)
985 combobox_set_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo),
986 prefs_common.compose_save_to_history);
988 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo),
989 prefs_common.compose_save_to_history);
995 Compose *compose_generic_new(PrefsAccount *account, const gchar *mailto, FolderItem *item,
996 GList *attach_files, GList *listAddress )
999 GtkTextView *textview;
1000 GtkTextBuffer *textbuf;
1002 const gchar *subject_format = NULL;
1003 const gchar *body_format = NULL;
1004 gchar *mailto_from = NULL;
1005 PrefsAccount *mailto_account = NULL;
1006 MsgInfo* dummyinfo = NULL;
1007 gint cursor_pos = -1;
1008 MailField mfield = NO_FIELD_PRESENT;
1012 /* check if mailto defines a from */
1013 if (mailto && *mailto != '\0') {
1014 scan_mailto_url(mailto, &mailto_from, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
1015 /* mailto defines a from, check if we can get account prefs from it,
1016 if not, the account prefs will be guessed using other ways, but we'll keep
1019 mailto_account = account_find_from_address(mailto_from, TRUE);
1020 if (mailto_account == NULL) {
1022 Xstrdup_a(tmp_from, mailto_from, return NULL);
1023 extract_address(tmp_from);
1024 mailto_account = account_find_from_address(tmp_from, TRUE);
1028 account = mailto_account;
1031 /* if no account prefs set from mailto, set if from folder prefs (if any) */
1032 if (!mailto_account && item && item->prefs && item->prefs->enable_default_account)
1033 account = account_find_from_id(item->prefs->default_account);
1035 /* if no account prefs set, fallback to the current one */
1036 if (!account) account = cur_account;
1037 cm_return_val_if_fail(account != NULL, NULL);
1039 compose = compose_create(account, item, COMPOSE_NEW, FALSE);
1041 /* override from name if mailto asked for it */
1043 gtk_entry_set_text(GTK_ENTRY(compose->from_name), mailto_from);
1044 g_free(mailto_from);
1046 /* override from name according to folder properties */
1047 if (item && item->prefs &&
1048 item->prefs->compose_with_format &&
1049 item->prefs->compose_override_from_format &&
1050 *item->prefs->compose_override_from_format != '\0') {
1055 dummyinfo = compose_msginfo_new_from_compose(compose);
1057 /* decode \-escape sequences in the internal representation of the quote format */
1058 tmp = g_malloc(strlen(item->prefs->compose_override_from_format)+1);
1059 pref_get_unescaped_pref(tmp, item->prefs->compose_override_from_format);
1062 quote_fmt_init(dummyinfo, NULL, NULL, FALSE, compose->account, FALSE,
1063 compose->gtkaspell);
1065 quote_fmt_init(dummyinfo, NULL, NULL, FALSE, compose->account, FALSE);
1067 quote_fmt_scan_string(tmp);
1070 buf = quote_fmt_get_buffer();
1072 alertpanel_error(_("New message From format error."));
1074 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1075 quote_fmt_reset_vartable();
1080 compose->replyinfo = NULL;
1081 compose->fwdinfo = NULL;
1083 textview = GTK_TEXT_VIEW(compose->text);
1084 textbuf = gtk_text_view_get_buffer(textview);
1085 compose_create_tags(textview, compose);
1087 undo_block(compose->undostruct);
1089 compose_set_dictionaries_from_folder_prefs(compose, item);
1092 if (account->auto_sig)
1093 compose_insert_sig(compose, FALSE);
1094 gtk_text_buffer_get_start_iter(textbuf, &iter);
1095 gtk_text_buffer_place_cursor(textbuf, &iter);
1097 if (account->protocol != A_NNTP) {
1098 if (mailto && *mailto != '\0') {
1099 mfield = compose_entries_set(compose, mailto, COMPOSE_TO);
1102 compose_set_folder_prefs(compose, item, TRUE);
1104 if (item && item->ret_rcpt) {
1105 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
1108 if (mailto && *mailto != '\0') {
1109 if (!strchr(mailto, '@'))
1110 mfield = compose_entries_set(compose, mailto, COMPOSE_NEWSGROUPS);
1112 mfield = compose_entries_set(compose, mailto, COMPOSE_TO);
1113 } else if (item && FOLDER_CLASS(item->folder) == news_get_class()) {
1114 compose_entry_append(compose, item->path, COMPOSE_NEWSGROUPS, PREF_FOLDER);
1115 mfield = TO_FIELD_PRESENT;
1118 * CLAWS: just don't allow return receipt request, even if the user
1119 * may want to send an email. simple but foolproof.
1121 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", FALSE);
1123 compose_add_field_list( compose, listAddress );
1125 if (item && item->prefs && item->prefs->compose_with_format) {
1126 subject_format = item->prefs->compose_subject_format;
1127 body_format = item->prefs->compose_body_format;
1128 } else if (account->compose_with_format) {
1129 subject_format = account->compose_subject_format;
1130 body_format = account->compose_body_format;
1131 } else if (prefs_common.compose_with_format) {
1132 subject_format = prefs_common.compose_subject_format;
1133 body_format = prefs_common.compose_body_format;
1136 if (subject_format || body_format) {
1139 && *subject_format != '\0' )
1141 gchar *subject = NULL;
1146 dummyinfo = compose_msginfo_new_from_compose(compose);
1148 /* decode \-escape sequences in the internal representation of the quote format */
1149 tmp = g_malloc(strlen(subject_format)+1);
1150 pref_get_unescaped_pref(tmp, subject_format);
1152 subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1154 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account, FALSE,
1155 compose->gtkaspell);
1157 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account, FALSE);
1159 quote_fmt_scan_string(tmp);
1162 buf = quote_fmt_get_buffer();
1164 alertpanel_error(_("New message subject format error."));
1166 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1167 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1168 quote_fmt_reset_vartable();
1172 mfield = SUBJECT_FIELD_PRESENT;
1176 && *body_format != '\0' )
1179 GtkTextBuffer *buffer;
1180 GtkTextIter start, end;
1184 dummyinfo = compose_msginfo_new_from_compose(compose);
1186 text = GTK_TEXT_VIEW(compose->text);
1187 buffer = gtk_text_view_get_buffer(text);
1188 gtk_text_buffer_get_start_iter(buffer, &start);
1189 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
1190 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
1192 compose_quote_fmt(compose, dummyinfo,
1194 NULL, tmp, FALSE, TRUE,
1195 _("The body of the \"New message\" template has an error at line %d."));
1196 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1197 quote_fmt_reset_vartable();
1201 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1202 gtkaspell_highlight_all(compose->gtkaspell);
1204 mfield = BODY_FIELD_PRESENT;
1208 procmsg_msginfo_free( &dummyinfo );
1214 for (curr = attach_files ; curr != NULL ; curr = curr->next) {
1215 ainfo = (AttachInfo *) curr->data;
1216 compose_attach_append(compose, ainfo->file, ainfo->file,
1217 ainfo->content_type, ainfo->charset);
1221 compose_show_first_last_header(compose, TRUE);
1223 /* Set save folder */
1224 if (item && item->prefs && item->prefs->save_copy_to_folder) {
1225 gchar *folderidentifier;
1227 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
1228 folderidentifier = folder_item_get_identifier(item);
1229 compose_set_save_to(compose, folderidentifier);
1230 g_free(folderidentifier);
1233 /* Place cursor according to provided input (mfield) */
1235 case NO_FIELD_PRESENT:
1236 if (compose->header_last)
1237 gtk_widget_grab_focus(compose->header_last->entry);
1239 case TO_FIELD_PRESENT:
1240 buf = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1242 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1245 gtk_widget_grab_focus(compose->subject_entry);
1247 case SUBJECT_FIELD_PRESENT:
1248 textview = GTK_TEXT_VIEW(compose->text);
1251 textbuf = gtk_text_view_get_buffer(textview);
1254 mark = gtk_text_buffer_get_insert(textbuf);
1255 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
1256 gtk_text_buffer_insert(textbuf, &iter, "", -1);
1258 * SUBJECT_FIELD_PRESENT and BODY_FIELD_PRESENT
1259 * only defers where it comes to the variable body
1260 * is not null. If no body is present compose->text
1261 * will be null in which case you cannot place the
1262 * cursor inside the component so. An empty component
1263 * is therefore created before placing the cursor
1265 case BODY_FIELD_PRESENT:
1266 cursor_pos = quote_fmt_get_cursor_pos();
1267 if (cursor_pos == -1)
1268 gtk_widget_grab_focus(compose->header_last->entry);
1270 gtk_widget_grab_focus(compose->text);
1274 undo_unblock(compose->undostruct);
1276 if (prefs_common.auto_exteditor)
1277 compose_exec_ext_editor(compose);
1279 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
1281 SCROLL_TO_CURSOR(compose);
1283 compose->modified = FALSE;
1284 compose_set_title(compose);
1286 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
1291 static void compose_force_encryption(Compose *compose, PrefsAccount *account,
1292 gboolean override_pref, const gchar *system)
1294 const gchar *privacy = NULL;
1296 cm_return_if_fail(compose != NULL);
1297 cm_return_if_fail(account != NULL);
1299 if (override_pref == FALSE && account->default_encrypt_reply == FALSE)
1302 if (account->default_privacy_system && strlen(account->default_privacy_system))
1303 privacy = account->default_privacy_system;
1307 GSList *privacy_avail = privacy_get_system_ids();
1308 if (privacy_avail && g_slist_length(privacy_avail)) {
1309 privacy = (gchar *)(privacy_avail->data);
1312 if (privacy != NULL) {
1314 g_free(compose->privacy_system);
1315 compose->privacy_system = NULL;
1316 g_free(compose->encdata);
1317 compose->encdata = NULL;
1319 if (compose->privacy_system == NULL)
1320 compose->privacy_system = g_strdup(privacy);
1321 else if (*(compose->privacy_system) == '\0') {
1322 g_free(compose->privacy_system);
1323 g_free(compose->encdata);
1324 compose->encdata = NULL;
1325 compose->privacy_system = g_strdup(privacy);
1327 compose_update_privacy_system_menu_item(compose, FALSE);
1328 compose_use_encryption(compose, TRUE);
1332 static void compose_force_signing(Compose *compose, PrefsAccount *account, const gchar *system)
1334 const gchar *privacy = NULL;
1336 if (account->default_privacy_system && strlen(account->default_privacy_system))
1337 privacy = account->default_privacy_system;
1341 GSList *privacy_avail = privacy_get_system_ids();
1342 if (privacy_avail && g_slist_length(privacy_avail)) {
1343 privacy = (gchar *)(privacy_avail->data);
1347 if (privacy != NULL) {
1349 g_free(compose->privacy_system);
1350 compose->privacy_system = NULL;
1351 g_free(compose->encdata);
1352 compose->encdata = NULL;
1354 if (compose->privacy_system == NULL)
1355 compose->privacy_system = g_strdup(privacy);
1356 compose_update_privacy_system_menu_item(compose, FALSE);
1357 compose_use_signing(compose, TRUE);
1361 static Compose *compose_reply_mode(ComposeMode mode, GSList *msginfo_list, gchar *body)
1365 Compose *compose = NULL;
1367 cm_return_val_if_fail(msginfo_list != NULL, NULL);
1369 msginfo = (MsgInfo*)g_slist_nth_data(msginfo_list, 0);
1370 cm_return_val_if_fail(msginfo != NULL, NULL);
1372 list_len = g_slist_length(msginfo_list);
1376 case COMPOSE_REPLY_TO_ADDRESS:
1377 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1378 FALSE, prefs_common.default_reply_list, FALSE, body);
1380 case COMPOSE_REPLY_WITH_QUOTE:
1381 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1382 FALSE, prefs_common.default_reply_list, FALSE, body);
1384 case COMPOSE_REPLY_WITHOUT_QUOTE:
1385 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1386 FALSE, prefs_common.default_reply_list, FALSE, NULL);
1388 case COMPOSE_REPLY_TO_SENDER:
1389 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1390 FALSE, FALSE, TRUE, body);
1392 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1393 compose = compose_followup_and_reply_to(msginfo,
1394 COMPOSE_QUOTE_CHECK,
1395 FALSE, FALSE, body);
1397 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1398 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1399 FALSE, FALSE, TRUE, body);
1401 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1402 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1403 FALSE, FALSE, TRUE, NULL);
1405 case COMPOSE_REPLY_TO_ALL:
1406 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1407 TRUE, FALSE, FALSE, body);
1409 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1410 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1411 TRUE, FALSE, FALSE, body);
1413 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1414 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1415 TRUE, FALSE, FALSE, NULL);
1417 case COMPOSE_REPLY_TO_LIST:
1418 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1419 FALSE, TRUE, FALSE, body);
1421 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1422 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1423 FALSE, TRUE, FALSE, body);
1425 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1426 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1427 FALSE, TRUE, FALSE, NULL);
1429 case COMPOSE_FORWARD:
1430 if (prefs_common.forward_as_attachment) {
1431 compose = compose_reply_mode(COMPOSE_FORWARD_AS_ATTACH, msginfo_list, body);
1434 compose = compose_reply_mode(COMPOSE_FORWARD_INLINE, msginfo_list, body);
1438 case COMPOSE_FORWARD_INLINE:
1439 /* check if we reply to more than one Message */
1440 if (list_len == 1) {
1441 compose = compose_forward(NULL, msginfo, FALSE, body, FALSE, FALSE);
1444 /* more messages FALL THROUGH */
1445 case COMPOSE_FORWARD_AS_ATTACH:
1446 compose = compose_forward_multiple(NULL, msginfo_list);
1448 case COMPOSE_REDIRECT:
1449 compose = compose_redirect(NULL, msginfo, FALSE);
1452 g_warning("compose_reply_mode(): invalid Compose Mode: %d", mode);
1455 if (compose == NULL) {
1456 alertpanel_error(_("Unable to reply. The original email probably doesn't exist."));
1460 compose->rmode = mode;
1461 switch (compose->rmode) {
1463 case COMPOSE_REPLY_WITH_QUOTE:
1464 case COMPOSE_REPLY_WITHOUT_QUOTE:
1465 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1466 debug_print("reply mode Normal\n");
1467 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/Normal", TRUE);
1468 compose_reply_change_mode(compose, COMPOSE_REPLY); /* force update */
1470 case COMPOSE_REPLY_TO_SENDER:
1471 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1472 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1473 debug_print("reply mode Sender\n");
1474 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/Sender", TRUE);
1476 case COMPOSE_REPLY_TO_ALL:
1477 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1478 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1479 debug_print("reply mode All\n");
1480 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/All", TRUE);
1482 case COMPOSE_REPLY_TO_LIST:
1483 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1484 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1485 debug_print("reply mode List\n");
1486 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/List", TRUE);
1488 case COMPOSE_REPLY_TO_ADDRESS:
1489 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", FALSE);
1497 static Compose *compose_reply(MsgInfo *msginfo,
1498 ComposeQuoteMode quote_mode,
1504 return compose_generic_reply(msginfo, quote_mode, to_all, to_ml,
1505 to_sender, FALSE, body);
1508 static Compose *compose_followup_and_reply_to(MsgInfo *msginfo,
1509 ComposeQuoteMode quote_mode,
1514 return compose_generic_reply(msginfo, quote_mode, to_all, FALSE,
1515 to_sender, TRUE, body);
1518 static void compose_extract_original_charset(Compose *compose)
1520 MsgInfo *info = NULL;
1521 if (compose->replyinfo) {
1522 info = compose->replyinfo;
1523 } else if (compose->fwdinfo) {
1524 info = compose->fwdinfo;
1525 } else if (compose->targetinfo) {
1526 info = compose->targetinfo;
1529 MimeInfo *mimeinfo = procmime_scan_message_short(info);
1530 MimeInfo *partinfo = mimeinfo;
1531 while (partinfo && partinfo->type != MIMETYPE_TEXT)
1532 partinfo = procmime_mimeinfo_next(partinfo);
1534 compose->orig_charset =
1535 g_strdup(procmime_mimeinfo_get_parameter(
1536 partinfo, "charset"));
1538 procmime_mimeinfo_free_all(&mimeinfo);
1542 #define SIGNAL_BLOCK(buffer) { \
1543 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1544 G_CALLBACK(compose_changed_cb), \
1546 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1547 G_CALLBACK(text_inserted), \
1551 #define SIGNAL_UNBLOCK(buffer) { \
1552 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1553 G_CALLBACK(compose_changed_cb), \
1555 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1556 G_CALLBACK(text_inserted), \
1560 static Compose *compose_generic_reply(MsgInfo *msginfo,
1561 ComposeQuoteMode quote_mode,
1562 gboolean to_all, gboolean to_ml,
1564 gboolean followup_and_reply_to,
1568 PrefsAccount *account = NULL;
1569 GtkTextView *textview;
1570 GtkTextBuffer *textbuf;
1571 gboolean quote = FALSE;
1572 const gchar *qmark = NULL;
1573 const gchar *body_fmt = NULL;
1574 gchar *s_system = NULL;
1576 cm_return_val_if_fail(msginfo != NULL, NULL);
1577 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
1579 account = account_get_reply_account(msginfo, prefs_common.reply_account_autosel);
1581 cm_return_val_if_fail(account != NULL, NULL);
1583 compose = compose_create(account, msginfo->folder, COMPOSE_REPLY, FALSE);
1585 compose->updating = TRUE;
1587 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
1588 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", TRUE);
1590 compose->replyinfo = procmsg_msginfo_get_full_info(msginfo);
1591 if (!compose->replyinfo)
1592 compose->replyinfo = procmsg_msginfo_copy(msginfo);
1594 compose_extract_original_charset(compose);
1596 if (msginfo->folder && msginfo->folder->ret_rcpt)
1597 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
1599 /* Set save folder */
1600 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1601 gchar *folderidentifier;
1603 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1604 folderidentifier = folder_item_get_identifier(msginfo->folder);
1605 compose_set_save_to(compose, folderidentifier);
1606 g_free(folderidentifier);
1609 if (compose_parse_header(compose, msginfo) < 0) {
1610 compose->updating = FALSE;
1611 compose_destroy(compose);
1615 /* override from name according to folder properties */
1616 if (msginfo->folder && msginfo->folder->prefs &&
1617 msginfo->folder->prefs->reply_with_format &&
1618 msginfo->folder->prefs->reply_override_from_format &&
1619 *msginfo->folder->prefs->reply_override_from_format != '\0') {
1624 /* decode \-escape sequences in the internal representation of the quote format */
1625 tmp = g_malloc(strlen(msginfo->folder->prefs->reply_override_from_format)+1);
1626 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->reply_override_from_format);
1629 quote_fmt_init(compose->replyinfo, NULL, NULL, FALSE, compose->account, FALSE,
1630 compose->gtkaspell);
1632 quote_fmt_init(compose->replyinfo, NULL, NULL, FALSE, compose->account, FALSE);
1634 quote_fmt_scan_string(tmp);
1637 buf = quote_fmt_get_buffer();
1639 alertpanel_error(_("The \"From\" field of the \"Reply\" template contains an invalid email address."));
1641 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1642 quote_fmt_reset_vartable();
1647 textview = (GTK_TEXT_VIEW(compose->text));
1648 textbuf = gtk_text_view_get_buffer(textview);
1649 compose_create_tags(textview, compose);
1651 undo_block(compose->undostruct);
1653 compose_set_dictionaries_from_folder_prefs(compose, msginfo->folder);
1654 gtkaspell_block_check(compose->gtkaspell);
1657 if (quote_mode == COMPOSE_QUOTE_FORCED ||
1658 (quote_mode == COMPOSE_QUOTE_CHECK && prefs_common.reply_with_quote)) {
1659 /* use the reply format of folder (if enabled), or the account's one
1660 (if enabled) or fallback to the global reply format, which is always
1661 enabled (even if empty), and use the relevant quotemark */
1663 if (msginfo->folder && msginfo->folder->prefs &&
1664 msginfo->folder->prefs->reply_with_format) {
1665 qmark = msginfo->folder->prefs->reply_quotemark;
1666 body_fmt = msginfo->folder->prefs->reply_body_format;
1668 } else if (account->reply_with_format) {
1669 qmark = account->reply_quotemark;
1670 body_fmt = account->reply_body_format;
1673 qmark = prefs_common.quotemark;
1674 if (prefs_common.quotefmt && *prefs_common.quotefmt)
1675 body_fmt = gettext(prefs_common.quotefmt);
1682 /* empty quotemark is not allowed */
1683 if (qmark == NULL || *qmark == '\0')
1685 compose_quote_fmt(compose, compose->replyinfo,
1686 body_fmt, qmark, body, FALSE, TRUE,
1687 _("The body of the \"Reply\" template has an error at line %d."));
1688 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1689 quote_fmt_reset_vartable();
1692 if (MSG_IS_ENCRYPTED(compose->replyinfo->flags)) {
1693 compose_force_encryption(compose, account, FALSE, s_system);
1696 privacy_msginfo_get_signed_state(compose->replyinfo, &s_system);
1697 if (MSG_IS_SIGNED(compose->replyinfo->flags) && account->default_sign_reply) {
1698 compose_force_signing(compose, account, s_system);
1702 SIGNAL_BLOCK(textbuf);
1704 if (account->auto_sig)
1705 compose_insert_sig(compose, FALSE);
1707 compose_wrap_all(compose);
1710 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1711 gtkaspell_highlight_all(compose->gtkaspell);
1712 gtkaspell_unblock_check(compose->gtkaspell);
1714 SIGNAL_UNBLOCK(textbuf);
1716 gtk_widget_grab_focus(compose->text);
1718 undo_unblock(compose->undostruct);
1720 if (prefs_common.auto_exteditor)
1721 compose_exec_ext_editor(compose);
1723 compose->modified = FALSE;
1724 compose_set_title(compose);
1726 compose->updating = FALSE;
1727 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
1728 SCROLL_TO_CURSOR(compose);
1730 if (compose->deferred_destroy) {
1731 compose_destroy(compose);
1739 #define INSERT_FW_HEADER(var, hdr) \
1740 if (msginfo->var && *msginfo->var) { \
1741 gtk_stext_insert(text, NULL, NULL, NULL, hdr, -1); \
1742 gtk_stext_insert(text, NULL, NULL, NULL, msginfo->var, -1); \
1743 gtk_stext_insert(text, NULL, NULL, NULL, "\n", 1); \
1746 Compose *compose_forward(PrefsAccount *account, MsgInfo *msginfo,
1747 gboolean as_attach, const gchar *body,
1748 gboolean no_extedit,
1752 GtkTextView *textview;
1753 GtkTextBuffer *textbuf;
1754 gint cursor_pos = -1;
1757 cm_return_val_if_fail(msginfo != NULL, NULL);
1758 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
1761 !(account = compose_guess_forward_account_from_msginfo
1763 account = cur_account;
1765 if (!prefs_common.forward_as_attachment)
1766 mode = COMPOSE_FORWARD_INLINE;
1768 mode = COMPOSE_FORWARD;
1769 compose = compose_create(account, msginfo->folder, mode, batch);
1771 compose->updating = TRUE;
1772 compose->fwdinfo = procmsg_msginfo_get_full_info(msginfo);
1773 if (!compose->fwdinfo)
1774 compose->fwdinfo = procmsg_msginfo_copy(msginfo);
1776 compose_extract_original_charset(compose);
1778 if (msginfo->subject && *msginfo->subject) {
1779 gchar *buf, *buf2, *p;
1781 buf = p = g_strdup(msginfo->subject);
1782 p += subject_get_prefix_length(p);
1783 memmove(buf, p, strlen(p) + 1);
1785 buf2 = g_strdup_printf("Fw: %s", buf);
1786 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
1792 /* override from name according to folder properties */
1793 if (msginfo->folder && msginfo->folder->prefs &&
1794 msginfo->folder->prefs->forward_with_format &&
1795 msginfo->folder->prefs->forward_override_from_format &&
1796 *msginfo->folder->prefs->forward_override_from_format != '\0') {
1800 MsgInfo *full_msginfo = NULL;
1803 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1805 full_msginfo = procmsg_msginfo_copy(msginfo);
1807 /* decode \-escape sequences in the internal representation of the quote format */
1808 tmp = g_malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
1809 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
1812 gtkaspell_block_check(compose->gtkaspell);
1813 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE,
1814 compose->gtkaspell);
1816 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE);
1818 quote_fmt_scan_string(tmp);
1821 buf = quote_fmt_get_buffer();
1823 alertpanel_error(_("The \"From\" field of the \"Forward\" template contains an invalid email address."));
1825 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1826 quote_fmt_reset_vartable();
1829 procmsg_msginfo_free(&full_msginfo);
1832 textview = GTK_TEXT_VIEW(compose->text);
1833 textbuf = gtk_text_view_get_buffer(textview);
1834 compose_create_tags(textview, compose);
1836 undo_block(compose->undostruct);
1840 msgfile = procmsg_get_message_file(msginfo);
1841 if (!is_file_exist(msgfile))
1842 g_warning("%s: file does not exist", msgfile);
1844 compose_attach_append(compose, msgfile, msgfile,
1845 "message/rfc822", NULL);
1849 const gchar *qmark = NULL;
1850 const gchar *body_fmt = NULL;
1851 MsgInfo *full_msginfo;
1853 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1855 full_msginfo = procmsg_msginfo_copy(msginfo);
1857 /* use the forward format of folder (if enabled), or the account's one
1858 (if enabled) or fallback to the global forward format, which is always
1859 enabled (even if empty), and use the relevant quotemark */
1860 if (msginfo->folder && msginfo->folder->prefs &&
1861 msginfo->folder->prefs->forward_with_format) {
1862 qmark = msginfo->folder->prefs->forward_quotemark;
1863 body_fmt = msginfo->folder->prefs->forward_body_format;
1865 } else if (account->forward_with_format) {
1866 qmark = account->forward_quotemark;
1867 body_fmt = account->forward_body_format;
1870 qmark = prefs_common.fw_quotemark;
1871 if (prefs_common.fw_quotefmt && *prefs_common.fw_quotefmt)
1872 body_fmt = gettext(prefs_common.fw_quotefmt);
1877 /* empty quotemark is not allowed */
1878 if (qmark == NULL || *qmark == '\0')
1881 compose_quote_fmt(compose, full_msginfo,
1882 body_fmt, qmark, body, FALSE, TRUE,
1883 _("The body of the \"Forward\" template has an error at line %d."));
1884 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1885 quote_fmt_reset_vartable();
1886 compose_attach_parts(compose, msginfo);
1888 procmsg_msginfo_free(&full_msginfo);
1891 SIGNAL_BLOCK(textbuf);
1893 if (account->auto_sig)
1894 compose_insert_sig(compose, FALSE);
1896 compose_wrap_all(compose);
1899 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1900 gtkaspell_highlight_all(compose->gtkaspell);
1901 gtkaspell_unblock_check(compose->gtkaspell);
1903 SIGNAL_UNBLOCK(textbuf);
1905 cursor_pos = quote_fmt_get_cursor_pos();
1906 if (cursor_pos == -1)
1907 gtk_widget_grab_focus(compose->header_last->entry);
1909 gtk_widget_grab_focus(compose->text);
1911 if (!no_extedit && prefs_common.auto_exteditor)
1912 compose_exec_ext_editor(compose);
1915 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1916 gchar *folderidentifier;
1918 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1919 folderidentifier = folder_item_get_identifier(msginfo->folder);
1920 compose_set_save_to(compose, folderidentifier);
1921 g_free(folderidentifier);
1924 undo_unblock(compose->undostruct);
1926 compose->modified = FALSE;
1927 compose_set_title(compose);
1929 compose->updating = FALSE;
1930 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
1931 SCROLL_TO_CURSOR(compose);
1933 if (compose->deferred_destroy) {
1934 compose_destroy(compose);
1938 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
1943 #undef INSERT_FW_HEADER
1945 static Compose *compose_forward_multiple(PrefsAccount *account, GSList *msginfo_list)
1948 GtkTextView *textview;
1949 GtkTextBuffer *textbuf;
1953 gboolean single_mail = TRUE;
1955 cm_return_val_if_fail(msginfo_list != NULL, NULL);
1957 if (g_slist_length(msginfo_list) > 1)
1958 single_mail = FALSE;
1960 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next)
1961 if (((MsgInfo *)msginfo->data)->folder == NULL)
1964 /* guess account from first selected message */
1966 !(account = compose_guess_forward_account_from_msginfo
1967 (msginfo_list->data)))
1968 account = cur_account;
1970 cm_return_val_if_fail(account != NULL, NULL);
1972 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
1973 if (msginfo->data) {
1974 MSG_UNSET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_REPLIED);
1975 MSG_SET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_FORWARDED);
1979 if (msginfo_list == NULL || msginfo_list->data == NULL) {
1980 g_warning("no msginfo_list");
1984 compose = compose_create(account, ((MsgInfo *)msginfo_list->data)->folder, COMPOSE_FORWARD, FALSE);
1986 compose->updating = TRUE;
1988 /* override from name according to folder properties */
1989 if (msginfo_list->data) {
1990 MsgInfo *msginfo = msginfo_list->data;
1992 if (msginfo->folder && msginfo->folder->prefs &&
1993 msginfo->folder->prefs->forward_with_format &&
1994 msginfo->folder->prefs->forward_override_from_format &&
1995 *msginfo->folder->prefs->forward_override_from_format != '\0') {
2000 /* decode \-escape sequences in the internal representation of the quote format */
2001 tmp = g_malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
2002 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
2005 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
2006 compose->gtkaspell);
2008 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
2010 quote_fmt_scan_string(tmp);
2013 buf = quote_fmt_get_buffer();
2015 alertpanel_error(_("The \"From\" field of the \"Forward\" template contains an invalid email address."));
2017 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
2018 quote_fmt_reset_vartable();
2024 textview = GTK_TEXT_VIEW(compose->text);
2025 textbuf = gtk_text_view_get_buffer(textview);
2026 compose_create_tags(textview, compose);
2028 undo_block(compose->undostruct);
2029 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
2030 msgfile = procmsg_get_message_file((MsgInfo *)msginfo->data);
2032 if (!is_file_exist(msgfile))
2033 g_warning("%s: file does not exist", msgfile);
2035 compose_attach_append(compose, msgfile, msgfile,
2036 "message/rfc822", NULL);
2041 MsgInfo *info = (MsgInfo *)msginfo_list->data;
2042 if (info->subject && *info->subject) {
2043 gchar *buf, *buf2, *p;
2045 buf = p = g_strdup(info->subject);
2046 p += subject_get_prefix_length(p);
2047 memmove(buf, p, strlen(p) + 1);
2049 buf2 = g_strdup_printf("Fw: %s", buf);
2050 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
2056 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2057 _("Fw: multiple emails"));
2060 SIGNAL_BLOCK(textbuf);
2062 if (account->auto_sig)
2063 compose_insert_sig(compose, FALSE);
2065 compose_wrap_all(compose);
2067 SIGNAL_UNBLOCK(textbuf);
2069 gtk_text_buffer_get_start_iter(textbuf, &iter);
2070 gtk_text_buffer_place_cursor(textbuf, &iter);
2072 if (prefs_common.auto_exteditor)
2073 compose_exec_ext_editor(compose);
2075 gtk_widget_grab_focus(compose->header_last->entry);
2076 undo_unblock(compose->undostruct);
2077 compose->modified = FALSE;
2078 compose_set_title(compose);
2080 compose->updating = FALSE;
2081 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2082 SCROLL_TO_CURSOR(compose);
2084 if (compose->deferred_destroy) {
2085 compose_destroy(compose);
2089 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2094 static gboolean compose_is_sig_separator(Compose *compose, GtkTextBuffer *textbuf, GtkTextIter *iter)
2096 GtkTextIter start = *iter;
2097 GtkTextIter end_iter;
2098 int start_pos = gtk_text_iter_get_offset(&start);
2100 if (!compose->account->sig_sep)
2103 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
2104 start_pos+strlen(compose->account->sig_sep));
2106 /* check sig separator */
2107 str = gtk_text_iter_get_text(&start, &end_iter);
2108 if (!strcmp(str, compose->account->sig_sep)) {
2110 /* check end of line (\n) */
2111 gtk_text_buffer_get_iter_at_offset(textbuf, &start,
2112 start_pos+strlen(compose->account->sig_sep));
2113 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
2114 start_pos+strlen(compose->account->sig_sep)+1);
2115 tmp = gtk_text_iter_get_text(&start, &end_iter);
2116 if (!strcmp(tmp,"\n")) {
2128 static gboolean compose_update_folder_hook(gpointer source, gpointer data)
2130 FolderUpdateData *hookdata = (FolderUpdateData *)source;
2131 Compose *compose = (Compose *)data;
2132 FolderItem *old_item = NULL;
2133 FolderItem *new_item = NULL;
2134 gchar *old_id, *new_id;
2136 if (!(hookdata->update_flags & FOLDER_REMOVE_FOLDERITEM)
2137 && !(hookdata->update_flags & FOLDER_MOVE_FOLDERITEM))
2140 old_item = hookdata->item;
2141 new_item = hookdata->item2;
2143 old_id = folder_item_get_identifier(old_item);
2144 new_id = new_item ? folder_item_get_identifier(new_item) : g_strdup("NULL");
2146 if (compose->targetinfo && compose->targetinfo->folder == old_item) {
2147 debug_print("updating targetinfo folder: %s -> %s\n", old_id, new_id);
2148 compose->targetinfo->folder = new_item;
2151 if (compose->replyinfo && compose->replyinfo->folder == old_item) {
2152 debug_print("updating replyinfo folder: %s -> %s\n", old_id, new_id);
2153 compose->replyinfo->folder = new_item;
2156 if (compose->fwdinfo && compose->fwdinfo->folder == old_item) {
2157 debug_print("updating fwdinfo folder: %s -> %s\n", old_id, new_id);
2158 compose->fwdinfo->folder = new_item;
2166 static void compose_colorize_signature(Compose *compose)
2168 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
2170 GtkTextIter end_iter;
2171 gtk_text_buffer_get_start_iter(buffer, &iter);
2172 while (gtk_text_iter_forward_line(&iter))
2173 if (compose_is_sig_separator(compose, buffer, &iter)) {
2174 gtk_text_buffer_get_end_iter(buffer, &end_iter);
2175 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &end_iter);
2179 #define BLOCK_WRAP() { \
2180 prev_autowrap = compose->autowrap; \
2181 buffer = gtk_text_view_get_buffer( \
2182 GTK_TEXT_VIEW(compose->text)); \
2183 compose->autowrap = FALSE; \
2185 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2186 G_CALLBACK(compose_changed_cb), \
2188 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2189 G_CALLBACK(text_inserted), \
2192 #define UNBLOCK_WRAP() { \
2193 compose->autowrap = prev_autowrap; \
2194 if (compose->autowrap) { \
2195 gint old = compose->draft_timeout_tag; \
2196 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN; \
2197 compose_wrap_all(compose); \
2198 compose->draft_timeout_tag = old; \
2201 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2202 G_CALLBACK(compose_changed_cb), \
2204 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2205 G_CALLBACK(text_inserted), \
2209 Compose *compose_reedit(MsgInfo *msginfo, gboolean batch)
2211 Compose *compose = NULL;
2212 PrefsAccount *account = NULL;
2213 GtkTextView *textview;
2214 GtkTextBuffer *textbuf;
2218 gchar buf[BUFFSIZE];
2219 gboolean use_signing = FALSE;
2220 gboolean use_encryption = FALSE;
2221 gchar *privacy_system = NULL;
2222 int priority = PRIORITY_NORMAL;
2223 MsgInfo *replyinfo = NULL, *fwdinfo = NULL;
2224 gboolean autowrap = prefs_common.autowrap;
2225 gboolean autoindent = prefs_common.auto_indent;
2226 HeaderEntry *manual_headers = NULL;
2228 cm_return_val_if_fail(msginfo != NULL, NULL);
2229 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
2231 if (compose_put_existing_to_front(msginfo)) {
2235 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2236 folder_has_parent_of_type(msginfo->folder, F_DRAFT) ||
2237 folder_has_parent_of_type(msginfo->folder, F_OUTBOX)) {
2238 gchar queueheader_buf[BUFFSIZE];
2241 /* Select Account from queue headers */
2242 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2243 sizeof(queueheader_buf), "X-Claws-Account-Id:")) {
2244 id = atoi(&queueheader_buf[strlen("X-Claws-Account-Id:")]);
2245 account = account_find_from_id(id);
2247 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2248 sizeof(queueheader_buf), "X-Sylpheed-Account-Id:")) {
2249 id = atoi(&queueheader_buf[strlen("X-Sylpheed-Account-Id:")]);
2250 account = account_find_from_id(id);
2252 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2253 sizeof(queueheader_buf), "NAID:")) {
2254 id = atoi(&queueheader_buf[strlen("NAID:")]);
2255 account = account_find_from_id(id);
2257 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2258 sizeof(queueheader_buf), "MAID:")) {
2259 id = atoi(&queueheader_buf[strlen("MAID:")]);
2260 account = account_find_from_id(id);
2262 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2263 sizeof(queueheader_buf), "S:")) {
2264 account = account_find_from_address(queueheader_buf, FALSE);
2266 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2267 sizeof(queueheader_buf), "X-Claws-Sign:")) {
2268 param = atoi(&queueheader_buf[strlen("X-Claws-Sign:")]);
2269 use_signing = param;
2272 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2273 sizeof(queueheader_buf), "X-Sylpheed-Sign:")) {
2274 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Sign:")]);
2275 use_signing = param;
2278 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2279 sizeof(queueheader_buf), "X-Claws-Encrypt:")) {
2280 param = atoi(&queueheader_buf[strlen("X-Claws-Encrypt:")]);
2281 use_encryption = param;
2283 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2284 sizeof(queueheader_buf), "X-Sylpheed-Encrypt:")) {
2285 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Encrypt:")]);
2286 use_encryption = param;
2288 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2289 sizeof(queueheader_buf), "X-Claws-Auto-Wrapping:")) {
2290 param = atoi(&queueheader_buf[strlen("X-Claws-Auto-Wrapping:")]);
2293 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2294 sizeof(queueheader_buf), "X-Claws-Auto-Indent:")) {
2295 param = atoi(&queueheader_buf[strlen("X-Claws-Auto-Indent:")]);
2298 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2299 sizeof(queueheader_buf), "X-Claws-Privacy-System:")) {
2300 privacy_system = g_strdup(&queueheader_buf[strlen("X-Claws-Privacy-System:")]);
2302 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2303 sizeof(queueheader_buf), "X-Sylpheed-Privacy-System:")) {
2304 privacy_system = g_strdup(&queueheader_buf[strlen("X-Sylpheed-Privacy-System:")]);
2306 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2307 sizeof(queueheader_buf), "X-Priority: ")) {
2308 param = atoi(&queueheader_buf[strlen("X-Priority: ")]); /* mind the space */
2311 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2312 sizeof(queueheader_buf), "RMID:")) {
2313 gchar **tokens = g_strsplit(&queueheader_buf[strlen("RMID:")], "\t", 0);
2314 if (tokens[0] && tokens[1] && tokens[2]) {
2315 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2316 if (orig_item != NULL) {
2317 replyinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2322 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2323 sizeof(queueheader_buf), "FMID:")) {
2324 gchar **tokens = g_strsplit(&queueheader_buf[strlen("FMID:")], "\t", 0);
2325 if (tokens[0] && tokens[1] && tokens[2]) {
2326 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2327 if (orig_item != NULL) {
2328 fwdinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2333 /* Get manual headers */
2334 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "X-Claws-Manual-Headers:")) {
2335 gchar *listmh = g_strdup(&queueheader_buf[strlen("X-Claws-Manual-Headers:")]);
2336 if (*listmh != '\0') {
2337 debug_print("Got manual headers: %s\n", listmh);
2338 manual_headers = procheader_entries_from_str(listmh);
2343 account = msginfo->folder->folder->account;
2346 if (!account && prefs_common.reedit_account_autosel) {
2347 gchar from[BUFFSIZE];
2348 if (!procheader_get_header_from_msginfo(msginfo, from, sizeof(from), "FROM:")) {
2349 extract_address(from);
2350 account = account_find_from_address(from, FALSE);
2354 account = cur_account;
2356 cm_return_val_if_fail(account != NULL, NULL);
2358 compose = compose_create(account, msginfo->folder, COMPOSE_REEDIT, batch);
2360 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", autowrap);
2361 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", autoindent);
2362 compose->autowrap = autowrap;
2363 compose->replyinfo = replyinfo;
2364 compose->fwdinfo = fwdinfo;
2366 compose->updating = TRUE;
2367 compose->priority = priority;
2369 if (privacy_system != NULL) {
2370 compose->privacy_system = privacy_system;
2371 compose_use_signing(compose, use_signing);
2372 compose_use_encryption(compose, use_encryption);
2373 compose_update_privacy_system_menu_item(compose, FALSE);
2375 activate_privacy_system(compose, account, FALSE);
2378 compose->targetinfo = procmsg_msginfo_copy(msginfo);
2380 compose_extract_original_charset(compose);
2382 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2383 folder_has_parent_of_type(msginfo->folder, F_DRAFT) ||
2384 folder_has_parent_of_type(msginfo->folder, F_OUTBOX)) {
2385 gchar queueheader_buf[BUFFSIZE];
2387 /* Set message save folder */
2388 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "SCF:")) {
2389 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
2390 compose_set_save_to(compose, &queueheader_buf[4]);
2392 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "RRCPT:")) {
2393 gint active = atoi(&queueheader_buf[strlen("RRCPT:")]);
2395 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
2400 if (compose_parse_header(compose, msginfo) < 0) {
2401 compose->updating = FALSE;
2402 compose_destroy(compose);
2405 compose_reedit_set_entry(compose, msginfo);
2407 textview = GTK_TEXT_VIEW(compose->text);
2408 textbuf = gtk_text_view_get_buffer(textview);
2409 compose_create_tags(textview, compose);
2411 mark = gtk_text_buffer_get_insert(textbuf);
2412 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
2414 g_signal_handlers_block_by_func(G_OBJECT(textbuf),
2415 G_CALLBACK(compose_changed_cb),
2418 if (MSG_IS_ENCRYPTED(msginfo->flags)) {
2419 fp = procmime_get_first_encrypted_text_content(msginfo);
2421 compose_force_encryption(compose, account, TRUE, NULL);
2424 fp = procmime_get_first_text_content(msginfo);
2427 g_warning("Can't get text part");
2431 gboolean prev_autowrap;
2432 GtkTextBuffer *buffer;
2434 while (fgets(buf, sizeof(buf), fp) != NULL) {
2436 gtk_text_buffer_insert(textbuf, &iter, buf, -1);
2442 compose_attach_parts(compose, msginfo);
2444 compose_colorize_signature(compose);
2446 g_signal_handlers_unblock_by_func(G_OBJECT(textbuf),
2447 G_CALLBACK(compose_changed_cb),
2450 if (manual_headers != NULL) {
2451 if (compose_parse_manual_headers(compose, msginfo, manual_headers) < 0) {
2452 procheader_entries_free(manual_headers);
2453 compose->updating = FALSE;
2454 compose_destroy(compose);
2457 procheader_entries_free(manual_headers);
2460 gtk_widget_grab_focus(compose->text);
2462 if (prefs_common.auto_exteditor) {
2463 compose_exec_ext_editor(compose);
2465 compose->modified = FALSE;
2466 compose_set_title(compose);
2468 compose->updating = FALSE;
2469 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2470 SCROLL_TO_CURSOR(compose);
2472 if (compose->deferred_destroy) {
2473 compose_destroy(compose);
2477 compose->sig_str = account_get_signature_str(compose->account);
2479 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2484 Compose *compose_redirect(PrefsAccount *account, MsgInfo *msginfo,
2491 cm_return_val_if_fail(msginfo != NULL, NULL);
2494 account = account_get_reply_account(msginfo,
2495 prefs_common.reply_account_autosel);
2496 cm_return_val_if_fail(account != NULL, NULL);
2498 compose = compose_create(account, msginfo->folder, COMPOSE_REDIRECT, batch);
2500 compose->updating = TRUE;
2502 compose_create_tags(GTK_TEXT_VIEW(compose->text), compose);
2503 compose->replyinfo = NULL;
2504 compose->fwdinfo = NULL;
2506 compose_show_first_last_header(compose, TRUE);
2508 gtk_widget_grab_focus(compose->header_last->entry);
2510 filename = procmsg_get_message_file(msginfo);
2512 if (filename == NULL) {
2513 compose->updating = FALSE;
2514 compose_destroy(compose);
2519 compose->redirect_filename = filename;
2521 /* Set save folder */
2522 item = msginfo->folder;
2523 if (item && item->prefs && item->prefs->save_copy_to_folder) {
2524 gchar *folderidentifier;
2526 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
2527 folderidentifier = folder_item_get_identifier(item);
2528 compose_set_save_to(compose, folderidentifier);
2529 g_free(folderidentifier);
2532 compose_attach_parts(compose, msginfo);
2534 if (msginfo->subject)
2535 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2537 gtk_editable_set_editable(GTK_EDITABLE(compose->subject_entry), FALSE);
2539 compose_quote_fmt(compose, msginfo, "%M", NULL, NULL, FALSE, FALSE,
2540 _("The body of the \"Redirect\" template has an error at line %d."));
2541 quote_fmt_reset_vartable();
2542 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), FALSE);
2544 compose_colorize_signature(compose);
2547 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Add", FALSE);
2548 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", FALSE);
2549 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", FALSE);
2551 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/Save", FALSE);
2552 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertFile", FALSE);
2553 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/AttachFile", FALSE);
2554 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertSig", FALSE);
2555 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/ReplaceSig", FALSE);
2556 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", FALSE);
2557 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", FALSE);
2558 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/ShowRuler", FALSE);
2559 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Actions", FALSE);
2561 if (compose->toolbar->draft_btn)
2562 gtk_widget_set_sensitive(compose->toolbar->draft_btn, FALSE);
2563 if (compose->toolbar->insert_btn)
2564 gtk_widget_set_sensitive(compose->toolbar->insert_btn, FALSE);
2565 if (compose->toolbar->attach_btn)
2566 gtk_widget_set_sensitive(compose->toolbar->attach_btn, FALSE);
2567 if (compose->toolbar->sig_btn)
2568 gtk_widget_set_sensitive(compose->toolbar->sig_btn, FALSE);
2569 if (compose->toolbar->exteditor_btn)
2570 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, FALSE);
2571 if (compose->toolbar->linewrap_current_btn)
2572 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, FALSE);
2573 if (compose->toolbar->linewrap_all_btn)
2574 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, FALSE);
2576 compose->modified = FALSE;
2577 compose_set_title(compose);
2578 compose->updating = FALSE;
2579 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2580 SCROLL_TO_CURSOR(compose);
2582 if (compose->deferred_destroy) {
2583 compose_destroy(compose);
2587 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2592 const GList *compose_get_compose_list(void)
2594 return compose_list;
2597 void compose_entry_append(Compose *compose, const gchar *address,
2598 ComposeEntryType type, ComposePrefType pref_type)
2600 const gchar *header;
2602 gboolean in_quote = FALSE;
2603 if (!address || *address == '\0') return;
2610 header = N_("Bcc:");
2612 case COMPOSE_REPLYTO:
2613 header = N_("Reply-To:");
2615 case COMPOSE_NEWSGROUPS:
2616 header = N_("Newsgroups:");
2618 case COMPOSE_FOLLOWUPTO:
2619 header = N_( "Followup-To:");
2621 case COMPOSE_INREPLYTO:
2622 header = N_( "In-Reply-To:");
2629 header = prefs_common_translated_header_name(header);
2631 cur = begin = (gchar *)address;
2633 /* we separate the line by commas, but not if we're inside a quoted
2635 while (*cur != '\0') {
2637 in_quote = !in_quote;
2638 if (*cur == ',' && !in_quote) {
2639 gchar *tmp = g_strdup(begin);
2641 tmp[cur-begin]='\0';
2644 while (*tmp == ' ' || *tmp == '\t')
2646 compose_add_header_entry(compose, header, tmp, pref_type);
2653 gchar *tmp = g_strdup(begin);
2655 tmp[cur-begin]='\0';
2656 while (*tmp == ' ' || *tmp == '\t')
2658 compose_add_header_entry(compose, header, tmp, pref_type);
2663 static void compose_entry_mark_default_to(Compose *compose, const gchar *mailto)
2665 #if !GTK_CHECK_VERSION(3, 0, 0)
2666 static GdkColor yellow;
2667 static GdkColor black;
2668 static gboolean yellow_initialised = FALSE;
2670 static GdkColor yellow = { (guint32)0, (guint16)0xf5, (guint16)0xf6, (guint16)0xbe };
2671 static GdkColor black = { (guint32)0, (guint16)0x0, (guint16)0x0, (guint16)0x0 };
2676 #if !GTK_CHECK_VERSION(3, 0, 0)
2677 if (!yellow_initialised) {
2678 gdk_color_parse("#f5f6be", &yellow);
2679 gdk_color_parse("#000000", &black);
2680 yellow_initialised = gdk_colormap_alloc_color(
2681 gdk_colormap_get_system(), &yellow, FALSE, TRUE);
2682 yellow_initialised &= gdk_colormap_alloc_color(
2683 gdk_colormap_get_system(), &black, FALSE, TRUE);
2687 for (h_list = compose->header_list; h_list != NULL; h_list = h_list->next) {
2688 entry = GTK_ENTRY(((ComposeHeaderEntry *)h_list->data)->entry);
2689 if (gtk_entry_get_text(entry) &&
2690 !g_utf8_collate(gtk_entry_get_text(entry), mailto)) {
2691 #if !GTK_CHECK_VERSION(3, 0, 0)
2692 if (yellow_initialised) {
2694 gtk_widget_modify_base(
2695 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2696 GTK_STATE_NORMAL, &yellow);
2697 gtk_widget_modify_text(
2698 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2699 GTK_STATE_NORMAL, &black);
2700 #if !GTK_CHECK_VERSION(3, 0, 0)
2707 void compose_toolbar_cb(gint action, gpointer data)
2709 ToolbarItem *toolbar_item = (ToolbarItem*)data;
2710 Compose *compose = (Compose*)toolbar_item->parent;
2712 cm_return_if_fail(compose != NULL);
2716 compose_send_cb(NULL, compose);
2719 compose_send_later_cb(NULL, compose);
2722 compose_draft(compose, COMPOSE_QUIT_EDITING);
2725 compose_insert_file_cb(NULL, compose);
2728 compose_attach_cb(NULL, compose);
2731 compose_insert_sig(compose, FALSE);
2734 compose_insert_sig(compose, TRUE);
2737 compose_ext_editor_cb(NULL, compose);
2739 case A_LINEWRAP_CURRENT:
2740 compose_beautify_paragraph(compose, NULL, TRUE);
2742 case A_LINEWRAP_ALL:
2743 compose_wrap_all_full(compose, TRUE);
2746 compose_address_cb(NULL, compose);
2749 case A_CHECK_SPELLING:
2750 compose_check_all(NULL, compose);
2758 static MailField compose_entries_set(Compose *compose, const gchar *mailto, ComposeEntryType to_type)
2763 gchar *subject = NULL;
2767 gchar **attach = NULL;
2768 gchar *inreplyto = NULL;
2769 MailField mfield = NO_FIELD_PRESENT;
2771 /* get mailto parts but skip from */
2772 scan_mailto_url(mailto, NULL, &to, &cc, &bcc, &subject, &body, &attach, &inreplyto);
2775 compose_entry_append(compose, to, to_type, PREF_MAILTO);
2776 mfield = TO_FIELD_PRESENT;
2779 compose_entry_append(compose, cc, COMPOSE_CC, PREF_MAILTO);
2781 compose_entry_append(compose, bcc, COMPOSE_BCC, PREF_MAILTO);
2783 if (!g_utf8_validate (subject, -1, NULL)) {
2784 temp = g_locale_to_utf8 (subject, -1, NULL, &len, NULL);
2785 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), temp);
2788 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), subject);
2790 mfield = SUBJECT_FIELD_PRESENT;
2793 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2794 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2797 gboolean prev_autowrap = compose->autowrap;
2799 compose->autowrap = FALSE;
2801 mark = gtk_text_buffer_get_insert(buffer);
2802 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2804 if (!g_utf8_validate (body, -1, NULL)) {
2805 temp = g_locale_to_utf8 (body, -1, NULL, &len, NULL);
2806 gtk_text_buffer_insert(buffer, &iter, temp, -1);
2809 gtk_text_buffer_insert(buffer, &iter, body, -1);
2811 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
2813 compose->autowrap = prev_autowrap;
2814 if (compose->autowrap)
2815 compose_wrap_all(compose);
2816 mfield = BODY_FIELD_PRESENT;
2820 gint i = 0, att = 0;
2821 gchar *warn_files = NULL;
2822 while (attach[i] != NULL) {
2823 gchar *utf8_filename = conv_filename_to_utf8(attach[i]);
2824 if (utf8_filename) {
2825 if (compose_attach_append(compose, attach[i], utf8_filename, NULL, NULL)) {
2826 gchar *tmp = g_strdup_printf("%s%s\n",
2827 warn_files?warn_files:"",
2833 g_free(utf8_filename);
2835 alertpanel_error(_("Couldn't attach a file (charset conversion failed)."));
2840 alertpanel_notice(ngettext(
2841 "The following file has been attached: \n%s",
2842 "The following files have been attached: \n%s", att), warn_files);
2847 compose_entry_append(compose, inreplyto, COMPOSE_INREPLYTO, PREF_MAILTO);
2860 static gint compose_parse_header(Compose *compose, MsgInfo *msginfo)
2862 static HeaderEntry hentry[] = {{"Reply-To:", NULL, TRUE},
2863 {"Cc:", NULL, TRUE},
2864 {"References:", NULL, FALSE},
2865 {"Bcc:", NULL, TRUE},
2866 {"Newsgroups:", NULL, TRUE},
2867 {"Followup-To:", NULL, TRUE},
2868 {"List-Post:", NULL, FALSE},
2869 {"X-Priority:", NULL, FALSE},
2870 {NULL, NULL, FALSE}};
2886 cm_return_val_if_fail(msginfo != NULL, -1);
2888 if ((fp = procmsg_open_message(msginfo)) == NULL) return -1;
2889 procheader_get_header_fields(fp, hentry);
2892 if (hentry[H_REPLY_TO].body != NULL) {
2893 if (hentry[H_REPLY_TO].body[0] != '\0') {
2895 conv_unmime_header(hentry[H_REPLY_TO].body,
2898 g_free(hentry[H_REPLY_TO].body);
2899 hentry[H_REPLY_TO].body = NULL;
2901 if (hentry[H_CC].body != NULL) {
2902 compose->cc = conv_unmime_header(hentry[H_CC].body, NULL, TRUE);
2903 g_free(hentry[H_CC].body);
2904 hentry[H_CC].body = NULL;
2906 if (hentry[H_REFERENCES].body != NULL) {
2907 if (compose->mode == COMPOSE_REEDIT)
2908 compose->references = hentry[H_REFERENCES].body;
2910 compose->references = compose_parse_references
2911 (hentry[H_REFERENCES].body, msginfo->msgid);
2912 g_free(hentry[H_REFERENCES].body);
2914 hentry[H_REFERENCES].body = NULL;
2916 if (hentry[H_BCC].body != NULL) {
2917 if (compose->mode == COMPOSE_REEDIT)
2919 conv_unmime_header(hentry[H_BCC].body, NULL, TRUE);
2920 g_free(hentry[H_BCC].body);
2921 hentry[H_BCC].body = NULL;
2923 if (hentry[H_NEWSGROUPS].body != NULL) {
2924 compose->newsgroups = hentry[H_NEWSGROUPS].body;
2925 hentry[H_NEWSGROUPS].body = NULL;
2927 if (hentry[H_FOLLOWUP_TO].body != NULL) {
2928 if (hentry[H_FOLLOWUP_TO].body[0] != '\0') {
2929 compose->followup_to =
2930 conv_unmime_header(hentry[H_FOLLOWUP_TO].body,
2933 g_free(hentry[H_FOLLOWUP_TO].body);
2934 hentry[H_FOLLOWUP_TO].body = NULL;
2936 if (hentry[H_LIST_POST].body != NULL) {
2937 gchar *to = NULL, *start = NULL;
2939 extract_address(hentry[H_LIST_POST].body);
2940 if (hentry[H_LIST_POST].body[0] != '\0') {
2941 start = strstr(hentry[H_LIST_POST].body, "mailto:");
2943 scan_mailto_url(start ? start : hentry[H_LIST_POST].body,
2944 NULL, &to, NULL, NULL, NULL, NULL, NULL, NULL);
2947 g_free(compose->ml_post);
2948 compose->ml_post = to;
2951 g_free(hentry[H_LIST_POST].body);
2952 hentry[H_LIST_POST].body = NULL;
2955 /* CLAWS - X-Priority */
2956 if (compose->mode == COMPOSE_REEDIT)
2957 if (hentry[H_X_PRIORITY].body != NULL) {
2960 priority = atoi(hentry[H_X_PRIORITY].body);
2961 g_free(hentry[H_X_PRIORITY].body);
2963 hentry[H_X_PRIORITY].body = NULL;
2965 if (priority < PRIORITY_HIGHEST ||
2966 priority > PRIORITY_LOWEST)
2967 priority = PRIORITY_NORMAL;
2969 compose->priority = priority;
2972 if (compose->mode == COMPOSE_REEDIT) {
2973 if (msginfo->inreplyto && *msginfo->inreplyto)
2974 compose->inreplyto = g_strdup(msginfo->inreplyto);
2978 if (msginfo->msgid && *msginfo->msgid)
2979 compose->inreplyto = g_strdup(msginfo->msgid);
2981 if (!compose->references) {
2982 if (msginfo->msgid && *msginfo->msgid) {
2983 if (msginfo->inreplyto && *msginfo->inreplyto)
2984 compose->references =
2985 g_strdup_printf("<%s>\n\t<%s>",
2989 compose->references =
2990 g_strconcat("<", msginfo->msgid, ">",
2992 } else if (msginfo->inreplyto && *msginfo->inreplyto) {
2993 compose->references =
2994 g_strconcat("<", msginfo->inreplyto, ">",
3002 static gint compose_parse_manual_headers(Compose *compose, MsgInfo *msginfo, HeaderEntry *entries)
3007 cm_return_val_if_fail(msginfo != NULL, -1);
3009 if ((fp = procmsg_open_message(msginfo)) == NULL) return -1;
3010 procheader_get_header_fields(fp, entries);
3014 while (he != NULL && he->name != NULL) {
3016 GtkListStore *model = NULL;
3018 debug_print("Adding manual header: %s with value %s\n", he->name, he->body);
3019 model = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(compose->header_last->combo)));
3020 COMBOBOX_ADD(model, he->name, COMPOSE_TO);
3021 gtk_combo_box_set_active_iter(GTK_COMBO_BOX(compose->header_last->combo), &iter);
3022 gtk_entry_set_text(GTK_ENTRY(compose->header_last->entry), he->body);
3029 static gchar *compose_parse_references(const gchar *ref, const gchar *msgid)
3031 GSList *ref_id_list, *cur;
3035 ref_id_list = references_list_append(NULL, ref);
3036 if (!ref_id_list) return NULL;
3037 if (msgid && *msgid)
3038 ref_id_list = g_slist_append(ref_id_list, g_strdup(msgid));
3043 for (cur = ref_id_list; cur != NULL; cur = cur->next)
3044 /* "<" + Message-ID + ">" + CR+LF+TAB */
3045 len += strlen((gchar *)cur->data) + 5;
3047 if (len > MAX_REFERENCES_LEN) {
3048 /* remove second message-ID */
3049 if (ref_id_list && ref_id_list->next &&
3050 ref_id_list->next->next) {
3051 g_free(ref_id_list->next->data);
3052 ref_id_list = g_slist_remove
3053 (ref_id_list, ref_id_list->next->data);
3055 slist_free_strings_full(ref_id_list);
3062 new_ref = g_string_new("");
3063 for (cur = ref_id_list; cur != NULL; cur = cur->next) {
3064 if (new_ref->len > 0)
3065 g_string_append(new_ref, "\n\t");
3066 g_string_append_printf(new_ref, "<%s>", (gchar *)cur->data);
3069 slist_free_strings_full(ref_id_list);
3071 new_ref_str = new_ref->str;
3072 g_string_free(new_ref, FALSE);
3077 static gchar *compose_quote_fmt(Compose *compose, MsgInfo *msginfo,
3078 const gchar *fmt, const gchar *qmark,
3079 const gchar *body, gboolean rewrap,
3080 gboolean need_unescape,
3081 const gchar *err_msg)
3083 MsgInfo* dummyinfo = NULL;
3084 gchar *quote_str = NULL;
3086 gboolean prev_autowrap;
3087 const gchar *trimmed_body = body;
3088 gint cursor_pos = -1;
3089 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3090 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3095 SIGNAL_BLOCK(buffer);
3098 dummyinfo = compose_msginfo_new_from_compose(compose);
3099 msginfo = dummyinfo;
3102 if (qmark != NULL) {
3104 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
3105 compose->gtkaspell);
3107 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
3109 quote_fmt_scan_string(qmark);
3112 buf = quote_fmt_get_buffer();
3114 alertpanel_error(_("The \"Quotation mark\" of the template is invalid."));
3116 Xstrdup_a(quote_str, buf, goto error)
3119 if (fmt && *fmt != '\0') {
3122 while (*trimmed_body == '\n')
3126 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE,
3127 compose->gtkaspell);
3129 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE);
3131 if (need_unescape) {
3134 /* decode \-escape sequences in the internal representation of the quote format */
3135 tmp = g_malloc(strlen(fmt)+1);
3136 pref_get_unescaped_pref(tmp, fmt);
3137 quote_fmt_scan_string(tmp);
3141 quote_fmt_scan_string(fmt);
3145 buf = quote_fmt_get_buffer();
3147 gint line = quote_fmt_get_line();
3148 alertpanel_error(err_msg, line);
3154 prev_autowrap = compose->autowrap;
3155 compose->autowrap = FALSE;
3157 mark = gtk_text_buffer_get_insert(buffer);
3158 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3159 if (g_utf8_validate(buf, -1, NULL)) {
3160 gtk_text_buffer_insert(buffer, &iter, buf, -1);
3162 gchar *tmpout = NULL;
3163 tmpout = conv_codeset_strdup
3164 (buf, conv_get_locale_charset_str_no_utf8(),
3166 if (!tmpout || !g_utf8_validate(tmpout, -1, NULL)) {
3168 tmpout = g_malloc(strlen(buf)*2+1);
3169 conv_localetodisp(tmpout, strlen(buf)*2+1, buf);
3171 gtk_text_buffer_insert(buffer, &iter, tmpout, -1);
3175 cursor_pos = quote_fmt_get_cursor_pos();
3176 if (cursor_pos == -1)
3177 cursor_pos = gtk_text_iter_get_offset(&iter);
3178 compose->set_cursor_pos = cursor_pos;
3180 gtk_text_buffer_get_start_iter(buffer, &iter);
3181 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
3182 gtk_text_buffer_place_cursor(buffer, &iter);
3184 compose->autowrap = prev_autowrap;
3185 if (compose->autowrap && rewrap)
3186 compose_wrap_all(compose);
3193 SIGNAL_UNBLOCK(buffer);
3195 procmsg_msginfo_free( &dummyinfo );
3200 /* if ml_post is of type addr@host and from is of type
3201 * addr-anything@host, return TRUE
3203 static gboolean is_subscription(const gchar *ml_post, const gchar *from)
3205 gchar *left_ml = NULL;
3206 gchar *right_ml = NULL;
3207 gchar *left_from = NULL;
3208 gchar *right_from = NULL;
3209 gboolean result = FALSE;
3211 if (!ml_post || !from)
3214 left_ml = g_strdup(ml_post);
3215 if (strstr(left_ml, "@")) {
3216 right_ml = strstr(left_ml, "@")+1;
3217 *(strstr(left_ml, "@")) = '\0';
3220 left_from = g_strdup(from);
3221 if (strstr(left_from, "@")) {
3222 right_from = strstr(left_from, "@")+1;
3223 *(strstr(left_from, "@")) = '\0';
3226 if (right_ml && right_from
3227 && !strncmp(left_from, left_ml, strlen(left_ml))
3228 && !strcmp(right_from, right_ml)) {
3237 static void compose_set_folder_prefs(Compose *compose, FolderItem *folder,
3238 gboolean respect_default_to)
3242 if (!folder || !folder->prefs)
3245 if (respect_default_to && folder->prefs->enable_default_to) {
3246 compose_entry_append(compose, folder->prefs->default_to,
3247 COMPOSE_TO, PREF_FOLDER);
3248 compose_entry_mark_default_to(compose, folder->prefs->default_to);
3250 if (folder->prefs->enable_default_cc)
3251 compose_entry_append(compose, folder->prefs->default_cc,
3252 COMPOSE_CC, PREF_FOLDER);
3253 if (folder->prefs->enable_default_bcc)
3254 compose_entry_append(compose, folder->prefs->default_bcc,
3255 COMPOSE_BCC, PREF_FOLDER);
3256 if (folder->prefs->enable_default_replyto)
3257 compose_entry_append(compose, folder->prefs->default_replyto,
3258 COMPOSE_REPLYTO, PREF_FOLDER);
3261 static void compose_reply_set_subject(Compose *compose, MsgInfo *msginfo)
3266 if (!compose || !msginfo)
3269 if (msginfo->subject && *msginfo->subject) {
3270 buf = p = g_strdup(msginfo->subject);
3271 p += subject_get_prefix_length(p);
3272 memmove(buf, p, strlen(p) + 1);
3274 buf2 = g_strdup_printf("Re: %s", buf);
3275 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
3280 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), "Re: ");
3283 static void compose_reply_set_entry(Compose *compose, MsgInfo *msginfo,
3284 gboolean to_all, gboolean to_ml,
3286 gboolean followup_and_reply_to)
3288 GSList *cc_list = NULL;
3291 gchar *replyto = NULL;
3292 gchar *ac_email = NULL;
3294 gboolean reply_to_ml = FALSE;
3295 gboolean default_reply_to = FALSE;
3297 cm_return_if_fail(compose->account != NULL);
3298 cm_return_if_fail(msginfo != NULL);
3300 reply_to_ml = to_ml && compose->ml_post;
3302 default_reply_to = msginfo->folder &&
3303 msginfo->folder->prefs->enable_default_reply_to;
3305 if (compose->account->protocol != A_NNTP) {
3306 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
3308 if (reply_to_ml && !default_reply_to) {
3310 gboolean is_subscr = is_subscription(compose->ml_post,
3313 /* normal answer to ml post with a reply-to */
3314 compose_entry_append(compose,
3316 COMPOSE_TO, PREF_ML);
3317 if (compose->replyto)
3318 compose_entry_append(compose,
3320 COMPOSE_CC, PREF_ML);
3322 /* answer to subscription confirmation */
3323 if (compose->replyto)
3324 compose_entry_append(compose,
3326 COMPOSE_TO, PREF_ML);
3327 else if (msginfo->from)
3328 compose_entry_append(compose,
3330 COMPOSE_TO, PREF_ML);
3333 else if (!(to_all || to_sender) && default_reply_to) {
3334 compose_entry_append(compose,
3335 msginfo->folder->prefs->default_reply_to,
3336 COMPOSE_TO, PREF_FOLDER);
3337 compose_entry_mark_default_to(compose,
3338 msginfo->folder->prefs->default_reply_to);
3344 compose_entry_append(compose, msginfo->from,
3345 COMPOSE_TO, PREF_NONE);
3347 Xstrdup_a(tmp1, msginfo->from, return);
3348 extract_address(tmp1);
3349 compose_entry_append(compose,
3350 (!account_find_from_address(tmp1, FALSE))
3353 COMPOSE_TO, 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);
3401 compose_entry_append
3403 compose->followup_to ? compose->followup_to :
3404 compose->newsgroups ? compose->newsgroups : "",
3405 COMPOSE_NEWSGROUPS, PREF_NONE);
3407 compose_reply_set_subject(compose, msginfo);
3409 if (to_ml && compose->ml_post) return;
3410 if (!to_all || compose->account->protocol == A_NNTP) return;
3412 if (compose->replyto) {
3413 Xstrdup_a(replyto, compose->replyto, return);
3414 extract_address(replyto);
3416 if (msginfo->from) {
3417 Xstrdup_a(from, msginfo->from, return);
3418 extract_address(from);
3421 if (replyto && from)
3422 cc_list = address_list_append_with_comments(cc_list, from);
3423 if (to_all && msginfo->folder &&
3424 msginfo->folder->prefs->enable_default_reply_to)
3425 cc_list = address_list_append_with_comments(cc_list,
3426 msginfo->folder->prefs->default_reply_to);
3427 cc_list = address_list_append_with_comments(cc_list, msginfo->to);
3428 cc_list = address_list_append_with_comments(cc_list, compose->cc);
3430 ac_email = g_utf8_strdown(compose->account->address, -1);
3433 for (cur = cc_list; cur != NULL; cur = cur->next) {
3434 gchar *addr = g_utf8_strdown(cur->data, -1);
3435 extract_address(addr);
3437 if (strcmp(ac_email, addr))
3438 compose_entry_append(compose, (gchar *)cur->data,
3439 COMPOSE_CC, PREF_NONE);
3441 debug_print("Cc address same as compose account's, ignoring\n");
3446 slist_free_strings_full(cc_list);
3452 #define SET_ENTRY(entry, str) \
3455 gtk_entry_set_text(GTK_ENTRY(compose->entry), str); \
3458 #define SET_ADDRESS(type, str) \
3461 compose_entry_append(compose, str, type, PREF_NONE); \
3464 static void compose_reedit_set_entry(Compose *compose, MsgInfo *msginfo)
3466 cm_return_if_fail(msginfo != NULL);
3468 SET_ENTRY(subject_entry, msginfo->subject);
3469 SET_ENTRY(from_name, msginfo->from);
3470 SET_ADDRESS(COMPOSE_TO, msginfo->to);
3471 SET_ADDRESS(COMPOSE_CC, compose->cc);
3472 SET_ADDRESS(COMPOSE_BCC, compose->bcc);
3473 SET_ADDRESS(COMPOSE_REPLYTO, compose->replyto);
3474 SET_ADDRESS(COMPOSE_NEWSGROUPS, compose->newsgroups);
3475 SET_ADDRESS(COMPOSE_FOLLOWUPTO, compose->followup_to);
3477 compose_update_priority_menu_item(compose);
3478 compose_update_privacy_system_menu_item(compose, FALSE);
3479 compose_show_first_last_header(compose, TRUE);
3485 static void compose_insert_sig(Compose *compose, gboolean replace)
3487 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3488 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3490 GtkTextIter iter, iter_end;
3491 gint cur_pos, ins_pos;
3492 gboolean prev_autowrap;
3493 gboolean found = FALSE;
3494 gboolean exists = FALSE;
3496 cm_return_if_fail(compose->account != NULL);
3500 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3501 G_CALLBACK(compose_changed_cb),
3504 mark = gtk_text_buffer_get_insert(buffer);
3505 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3506 cur_pos = gtk_text_iter_get_offset (&iter);
3509 gtk_text_buffer_get_end_iter(buffer, &iter);
3511 exists = (compose->sig_str != NULL);
3514 GtkTextIter first_iter, start_iter, end_iter;
3516 gtk_text_buffer_get_start_iter(buffer, &first_iter);
3518 if (!exists || compose->sig_str[0] == '\0')
3521 found = gtk_text_iter_forward_to_tag_toggle(&first_iter,
3522 compose->signature_tag);
3525 /* include previous \n\n */
3526 gtk_text_iter_backward_chars(&first_iter, 1);
3527 start_iter = first_iter;
3528 end_iter = first_iter;
3530 found = gtk_text_iter_forward_to_tag_toggle(&end_iter,
3531 compose->signature_tag);
3532 found &= gtk_text_iter_forward_to_tag_toggle(&end_iter,
3533 compose->signature_tag);
3535 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
3541 g_free(compose->sig_str);
3542 compose->sig_str = account_get_signature_str(compose->account);
3544 cur_pos = gtk_text_iter_get_offset(&iter);
3546 if (!compose->sig_str || (replace && !compose->account->auto_sig)) {
3547 g_free(compose->sig_str);
3548 compose->sig_str = NULL;
3550 if (compose->sig_inserted == FALSE)
3551 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
3552 compose->sig_inserted = TRUE;
3554 cur_pos = gtk_text_iter_get_offset(&iter);
3555 gtk_text_buffer_insert(buffer, &iter, compose->sig_str, -1);
3557 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cur_pos);
3558 gtk_text_iter_forward_chars(&iter, 1);
3559 gtk_text_buffer_get_end_iter(buffer, &iter_end);
3560 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &iter_end);
3562 if (cur_pos > gtk_text_buffer_get_char_count (buffer))
3563 cur_pos = gtk_text_buffer_get_char_count (buffer);
3566 /* put the cursor where it should be
3567 * either where the quote_fmt says, either where it was */
3568 if (compose->set_cursor_pos < 0)
3569 gtk_text_buffer_get_iter_at_offset(buffer, &iter, ins_pos);
3571 gtk_text_buffer_get_iter_at_offset(buffer, &iter,
3572 compose->set_cursor_pos);
3574 compose->set_cursor_pos = -1;
3575 gtk_text_buffer_place_cursor(buffer, &iter);
3576 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3577 G_CALLBACK(compose_changed_cb),
3583 static ComposeInsertResult compose_insert_file(Compose *compose, const gchar *file)
3586 GtkTextBuffer *buffer;
3589 const gchar *cur_encoding;
3590 gchar buf[BUFFSIZE];
3593 gboolean prev_autowrap;
3596 GString *file_contents = NULL;
3597 ComposeInsertResult result = COMPOSE_INSERT_SUCCESS;
3599 cm_return_val_if_fail(file != NULL, COMPOSE_INSERT_NO_FILE);
3601 /* get the size of the file we are about to insert */
3602 ret = g_stat(file, &file_stat);
3604 gchar *shortfile = g_path_get_basename(file);
3605 alertpanel_error(_("Could not get size of file '%s'."), shortfile);
3607 return COMPOSE_INSERT_NO_FILE;
3608 } else if (prefs_common.warn_large_insert == TRUE) {
3610 /* ask user for confirmation if the file is large */
3611 if (prefs_common.warn_large_insert_size < 0 ||
3612 file_stat.st_size > (prefs_common.warn_large_insert_size * 1024)) {
3616 msg = g_strdup_printf(_("You are about to insert a file of %s "
3617 "in the message body. Are you sure you want to do that?"),
3618 to_human_readable(file_stat.st_size));
3619 aval = alertpanel_full(_("Are you sure?"), msg, GTK_STOCK_CANCEL,
3620 g_strconcat("+", _("_Insert"), NULL), NULL, TRUE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
3623 /* do we ask for confirmation next time? */
3624 if (aval & G_ALERTDISABLE) {
3625 /* no confirmation next time, disable feature in preferences */
3626 aval &= ~G_ALERTDISABLE;
3627 prefs_common.warn_large_insert = FALSE;
3630 /* abort file insertion if user canceled action */
3631 if (aval != G_ALERTALTERNATE) {
3632 return COMPOSE_INSERT_NO_FILE;
3638 if ((fp = g_fopen(file, "rb")) == NULL) {
3639 FILE_OP_ERROR(file, "fopen");
3640 return COMPOSE_INSERT_READ_ERROR;
3643 prev_autowrap = compose->autowrap;
3644 compose->autowrap = FALSE;
3646 text = GTK_TEXT_VIEW(compose->text);
3647 buffer = gtk_text_view_get_buffer(text);
3648 mark = gtk_text_buffer_get_insert(buffer);
3649 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3651 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3652 G_CALLBACK(text_inserted),
3655 cur_encoding = conv_get_locale_charset_str_no_utf8();
3657 file_contents = g_string_new("");
3658 while (fgets(buf, sizeof(buf), fp) != NULL) {
3661 if (g_utf8_validate(buf, -1, NULL) == TRUE)
3662 str = g_strdup(buf);
3664 codeconv_set_strict(TRUE);
3665 str = conv_codeset_strdup
3666 (buf, cur_encoding, CS_INTERNAL);
3667 codeconv_set_strict(FALSE);
3670 result = COMPOSE_INSERT_INVALID_CHARACTER;
3676 /* strip <CR> if DOS/Windows file,
3677 replace <CR> with <LF> if Macintosh file. */
3680 if (len > 0 && str[len - 1] != '\n') {
3682 if (str[len] == '\r') str[len] = '\n';
3685 file_contents = g_string_append(file_contents, str);
3689 if (result == COMPOSE_INSERT_SUCCESS) {
3690 gtk_text_buffer_insert(buffer, &iter, file_contents->str, -1);
3692 compose_changed_cb(NULL, compose);
3693 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3694 G_CALLBACK(text_inserted),
3696 compose->autowrap = prev_autowrap;
3697 if (compose->autowrap)
3698 compose_wrap_all(compose);
3701 g_string_free(file_contents, TRUE);
3707 static gboolean compose_attach_append(Compose *compose, const gchar *file,
3708 const gchar *filename,
3709 const gchar *content_type,
3710 const gchar *charset)
3718 GtkListStore *store;
3720 gboolean has_binary = FALSE;
3722 if (!is_file_exist(file)) {
3723 gchar *file_from_uri = g_filename_from_uri(file, NULL, NULL);
3724 gboolean result = FALSE;
3725 if (file_from_uri && is_file_exist(file_from_uri)) {
3726 result = compose_attach_append(
3727 compose, file_from_uri,
3728 filename, content_type,
3731 g_free(file_from_uri);
3734 alertpanel_error("File %s doesn't exist\n", filename);
3737 if ((size = get_file_size(file)) < 0) {
3738 alertpanel_error("Can't get file size of %s\n", filename);
3742 /* In batch mode, we allow 0-length files to be attached no questions asked */
3743 if (size == 0 && !compose->batch) {
3744 gchar * msg = g_strdup_printf(_("File %s is empty."), filename);
3745 AlertValue aval = alertpanel_full(_("Empty file"), msg,
3746 GTK_STOCK_CANCEL, g_strconcat("+", _("_Attach anyway"), NULL), NULL, FALSE,
3747 NULL, ALERT_WARNING, G_ALERTDEFAULT);
3750 if (aval != G_ALERTALTERNATE) {
3754 if ((fp = g_fopen(file, "rb")) == NULL) {
3755 alertpanel_error(_("Can't read %s."), filename);
3760 ainfo = g_new0(AttachInfo, 1);
3761 auto_ainfo = g_auto_pointer_new_with_free
3762 (ainfo, (GFreeFunc) compose_attach_info_free);
3763 ainfo->file = g_strdup(file);
3766 ainfo->content_type = g_strdup(content_type);
3767 if (!g_ascii_strcasecmp(content_type, "message/rfc822")) {
3769 MsgFlags flags = {0, 0};
3771 if (procmime_get_encoding_for_text_file(file, &has_binary) == ENC_7BIT)
3772 ainfo->encoding = ENC_7BIT;
3774 ainfo->encoding = ENC_8BIT;
3776 msginfo = procheader_parse_file(file, flags, FALSE, FALSE);
3777 if (msginfo && msginfo->subject)
3778 name = g_strdup(msginfo->subject);
3780 name = g_path_get_basename(filename ? filename : file);
3782 ainfo->name = g_strdup_printf(_("Message: %s"), name);
3784 procmsg_msginfo_free(&msginfo);
3786 if (!g_ascii_strncasecmp(content_type, "text/", 5)) {
3787 ainfo->charset = g_strdup(charset);
3788 ainfo->encoding = procmime_get_encoding_for_text_file(file, &has_binary);
3790 ainfo->encoding = ENC_BASE64;
3792 name = g_path_get_basename(filename ? filename : file);
3793 ainfo->name = g_strdup(name);
3797 ainfo->content_type = procmime_get_mime_type(file);
3798 if (!ainfo->content_type) {
3799 ainfo->content_type =
3800 g_strdup("application/octet-stream");
3801 ainfo->encoding = ENC_BASE64;
3802 } else if (!g_ascii_strncasecmp(ainfo->content_type, "text/", 5))
3804 procmime_get_encoding_for_text_file(file, &has_binary);
3806 ainfo->encoding = ENC_BASE64;
3807 name = g_path_get_basename(filename ? filename : file);
3808 ainfo->name = g_strdup(name);
3812 if (ainfo->name != NULL
3813 && !strcmp(ainfo->name, ".")) {
3814 g_free(ainfo->name);
3818 if (!strcmp(ainfo->content_type, "unknown") || has_binary) {
3819 g_free(ainfo->content_type);
3820 ainfo->content_type = g_strdup("application/octet-stream");
3821 g_free(ainfo->charset);
3822 ainfo->charset = NULL;
3825 ainfo->size = (goffset)size;
3826 size_text = to_human_readable((goffset)size);
3828 store = GTK_LIST_STORE(gtk_tree_view_get_model
3829 (GTK_TREE_VIEW(compose->attach_clist)));
3831 gtk_list_store_append(store, &iter);
3832 gtk_list_store_set(store, &iter,
3833 COL_MIMETYPE, ainfo->content_type,
3834 COL_SIZE, size_text,
3835 COL_NAME, ainfo->name,
3836 COL_CHARSET, ainfo->charset,
3838 COL_AUTODATA, auto_ainfo,
3841 g_auto_pointer_free(auto_ainfo);
3842 compose_attach_update_label(compose);
3846 static void compose_use_signing(Compose *compose, gboolean use_signing)
3848 compose->use_signing = use_signing;
3849 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", use_signing);
3852 static void compose_use_encryption(Compose *compose, gboolean use_encryption)
3854 compose->use_encryption = use_encryption;
3855 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", use_encryption);
3858 #define NEXT_PART_NOT_CHILD(info) \
3860 node = info->node; \
3861 while (node->children) \
3862 node = g_node_last_child(node); \
3863 info = procmime_mimeinfo_next((MimeInfo *)node->data); \
3866 static void compose_attach_parts(Compose *compose, MsgInfo *msginfo)
3870 MimeInfo *firsttext = NULL;
3871 MimeInfo *encrypted = NULL;
3874 const gchar *partname = NULL;
3876 mimeinfo = procmime_scan_message(msginfo);
3877 if (!mimeinfo) return;
3879 if (mimeinfo->node->children == NULL) {
3880 procmime_mimeinfo_free_all(&mimeinfo);
3884 /* find first content part */
3885 child = (MimeInfo *) mimeinfo->node->children->data;
3886 while (child && child->node->children && (child->type == MIMETYPE_MULTIPART))
3887 child = (MimeInfo *)child->node->children->data;
3890 if (child->type == MIMETYPE_TEXT) {
3892 debug_print("First text part found\n");
3893 } else if (compose->mode == COMPOSE_REEDIT &&
3894 child->type == MIMETYPE_APPLICATION &&
3895 !g_ascii_strcasecmp(child->subtype, "pgp-encrypted")) {
3896 encrypted = (MimeInfo *)child->node->parent->data;
3899 child = (MimeInfo *) mimeinfo->node->children->data;
3900 while (child != NULL) {
3903 if (child == encrypted) {
3904 /* skip this part of tree */
3905 NEXT_PART_NOT_CHILD(child);
3909 if (child->type == MIMETYPE_MULTIPART) {
3910 /* get the actual content */
3911 child = procmime_mimeinfo_next(child);
3915 if (child == firsttext) {
3916 child = procmime_mimeinfo_next(child);
3920 outfile = procmime_get_tmp_file_name(child);
3921 if ((err = procmime_get_part(outfile, child)) < 0)
3922 g_warning("Can't get the part of multipart message. (%s)", g_strerror(-err));
3924 gchar *content_type;
3926 content_type = procmime_get_content_type_str(child->type, child->subtype);
3928 /* if we meet a pgp signature, we don't attach it, but
3929 * we force signing. */
3930 if ((strcmp(content_type, "application/pgp-signature") &&
3931 strcmp(content_type, "application/pkcs7-signature") &&
3932 strcmp(content_type, "application/x-pkcs7-signature"))
3933 || compose->mode == COMPOSE_REDIRECT) {
3934 partname = procmime_mimeinfo_get_parameter(child, "filename");
3935 if (partname == NULL)
3936 partname = procmime_mimeinfo_get_parameter(child, "name");
3937 if (partname == NULL)
3939 compose_attach_append(compose, outfile,
3940 partname, content_type,
3941 procmime_mimeinfo_get_parameter(child, "charset"));
3943 compose_force_signing(compose, compose->account, NULL);
3945 g_free(content_type);
3948 NEXT_PART_NOT_CHILD(child);
3950 procmime_mimeinfo_free_all(&mimeinfo);
3953 #undef NEXT_PART_NOT_CHILD
3958 WAIT_FOR_INDENT_CHAR,
3959 WAIT_FOR_INDENT_CHAR_OR_SPACE,
3962 /* return indent length, we allow:
3963 indent characters followed by indent characters or spaces/tabs,
3964 alphabets and numbers immediately followed by indent characters,
3965 and the repeating sequences of the above
3966 If quote ends with multiple spaces, only the first one is included. */
3967 static gchar *compose_get_quote_str(GtkTextBuffer *buffer,
3968 const GtkTextIter *start, gint *len)
3970 GtkTextIter iter = *start;
3974 IndentState state = WAIT_FOR_INDENT_CHAR;
3977 gint alnum_count = 0;
3978 gint space_count = 0;
3981 if (prefs_common.quote_chars == NULL) {
3985 while (!gtk_text_iter_ends_line(&iter)) {
3986 wc = gtk_text_iter_get_char(&iter);
3987 if (g_unichar_iswide(wc))
3989 clen = g_unichar_to_utf8(wc, ch);
3993 is_indent = strchr(prefs_common.quote_chars, ch[0]) ? TRUE : FALSE;
3994 is_space = g_unichar_isspace(wc);
3996 if (state == WAIT_FOR_INDENT_CHAR) {
3997 if (!is_indent && !g_unichar_isalnum(wc))
4000 quote_len += alnum_count + space_count + 1;
4001 alnum_count = space_count = 0;
4002 state = WAIT_FOR_INDENT_CHAR_OR_SPACE;
4005 } else if (state == WAIT_FOR_INDENT_CHAR_OR_SPACE) {
4006 if (!is_indent && !is_space && !g_unichar_isalnum(wc))
4010 else if (is_indent) {
4011 quote_len += alnum_count + space_count + 1;
4012 alnum_count = space_count = 0;
4015 state = WAIT_FOR_INDENT_CHAR;
4019 gtk_text_iter_forward_char(&iter);
4022 if (quote_len > 0 && space_count > 0)
4028 if (quote_len > 0) {
4030 gtk_text_iter_forward_chars(&iter, quote_len);
4031 return gtk_text_buffer_get_text(buffer, start, &iter, FALSE);
4037 /* return >0 if the line is itemized */
4038 static int compose_itemized_length(GtkTextBuffer *buffer,
4039 const GtkTextIter *start)
4041 GtkTextIter iter = *start;
4046 if (gtk_text_iter_ends_line(&iter))
4051 wc = gtk_text_iter_get_char(&iter);
4052 if (!g_unichar_isspace(wc))
4054 gtk_text_iter_forward_char(&iter);
4055 if (gtk_text_iter_ends_line(&iter))
4059 clen = g_unichar_to_utf8(wc, ch);
4063 if (!strchr("*-+", ch[0]))
4066 gtk_text_iter_forward_char(&iter);
4067 if (gtk_text_iter_ends_line(&iter))
4069 wc = gtk_text_iter_get_char(&iter);
4070 if (g_unichar_isspace(wc)) {
4076 /* return the string at the start of the itemization */
4077 static gchar * compose_get_itemized_chars(GtkTextBuffer *buffer,
4078 const GtkTextIter *start)
4080 GtkTextIter iter = *start;
4083 GString *item_chars = g_string_new("");
4086 if (gtk_text_iter_ends_line(&iter))
4091 wc = gtk_text_iter_get_char(&iter);
4092 if (!g_unichar_isspace(wc))
4094 gtk_text_iter_forward_char(&iter);
4095 if (gtk_text_iter_ends_line(&iter))
4097 g_string_append_unichar(item_chars, wc);
4100 str = item_chars->str;
4101 g_string_free(item_chars, FALSE);
4105 /* return the number of spaces at a line's start */
4106 static int compose_left_offset_length(GtkTextBuffer *buffer,
4107 const GtkTextIter *start)
4109 GtkTextIter iter = *start;
4112 if (gtk_text_iter_ends_line(&iter))
4116 wc = gtk_text_iter_get_char(&iter);
4117 if (!g_unichar_isspace(wc))
4120 gtk_text_iter_forward_char(&iter);
4121 if (gtk_text_iter_ends_line(&iter))
4125 gtk_text_iter_forward_char(&iter);
4126 if (gtk_text_iter_ends_line(&iter))
4131 static gboolean compose_get_line_break_pos(GtkTextBuffer *buffer,
4132 const GtkTextIter *start,
4133 GtkTextIter *break_pos,
4137 GtkTextIter iter = *start, line_end = *start;
4138 PangoLogAttr *attrs;
4145 gboolean can_break = FALSE;
4146 gboolean do_break = FALSE;
4147 gboolean was_white = FALSE;
4148 gboolean prev_dont_break = FALSE;
4150 gtk_text_iter_forward_to_line_end(&line_end);
4151 str = gtk_text_buffer_get_text(buffer, &iter, &line_end, FALSE);
4152 len = g_utf8_strlen(str, -1);
4156 g_warning("compose_get_line_break_pos: len = 0!");
4160 /* g_print("breaking line: %d: %s (len = %d)\n",
4161 gtk_text_iter_get_line(&iter), str, len); */
4163 attrs = g_new(PangoLogAttr, len + 1);
4165 pango_default_break(str, -1, NULL, attrs, len + 1);
4169 /* skip quote and leading spaces */
4170 for (i = 0; *p != '\0' && i < len; i++) {
4173 wc = g_utf8_get_char(p);
4174 if (i >= quote_len && !g_unichar_isspace(wc))
4176 if (g_unichar_iswide(wc))
4178 else if (*p == '\t')
4182 p = g_utf8_next_char(p);
4185 for (; *p != '\0' && i < len; i++) {
4186 PangoLogAttr *attr = attrs + i;
4190 if (attr->is_line_break && can_break && was_white && !prev_dont_break)
4193 was_white = attr->is_white;
4195 /* don't wrap URI */
4196 if ((uri_len = get_uri_len(p)) > 0) {
4198 if (pos > 0 && col > max_col) {
4208 wc = g_utf8_get_char(p);
4209 if (g_unichar_iswide(wc)) {
4211 if (prev_dont_break && can_break && attr->is_line_break)
4213 } else if (*p == '\t')
4217 if (pos > 0 && col > max_col) {
4222 if (*p == '-' || *p == '/')
4223 prev_dont_break = TRUE;
4225 prev_dont_break = FALSE;
4227 p = g_utf8_next_char(p);
4231 // debug_print("compose_get_line_break_pos(): do_break = %d, pos = %d, col = %d\n", do_break, pos, col);
4236 *break_pos = *start;
4237 gtk_text_iter_set_line_offset(break_pos, pos);
4242 static gboolean compose_join_next_line(Compose *compose,
4243 GtkTextBuffer *buffer,
4245 const gchar *quote_str)
4247 GtkTextIter iter_ = *iter, cur, prev, next, end;
4248 PangoLogAttr attrs[3];
4250 gchar *next_quote_str;
4253 gboolean keep_cursor = FALSE;
4255 if (!gtk_text_iter_forward_line(&iter_) ||
4256 gtk_text_iter_ends_line(&iter_)) {
4259 next_quote_str = compose_get_quote_str(buffer, &iter_, "e_len);
4261 if ((quote_str || next_quote_str) &&
4262 strcmp2(quote_str, next_quote_str) != 0) {
4263 g_free(next_quote_str);
4266 g_free(next_quote_str);
4269 if (quote_len > 0) {
4270 gtk_text_iter_forward_chars(&end, quote_len);
4271 if (gtk_text_iter_ends_line(&end)) {
4276 /* don't join itemized lines */
4277 if (compose_itemized_length(buffer, &end) > 0) {
4281 /* don't join signature separator */
4282 if (compose_is_sig_separator(compose, buffer, &iter_)) {
4285 /* delete quote str */
4287 gtk_text_buffer_delete(buffer, &iter_, &end);
4289 /* don't join line breaks put by the user */
4291 gtk_text_iter_backward_char(&cur);
4292 if (gtk_text_iter_has_tag(&cur, compose->no_join_tag)) {
4293 gtk_text_iter_forward_char(&cur);
4297 gtk_text_iter_forward_char(&cur);
4298 /* delete linebreak and extra spaces */
4299 while (gtk_text_iter_backward_char(&cur)) {
4300 wc1 = gtk_text_iter_get_char(&cur);
4301 if (!g_unichar_isspace(wc1))
4306 while (!gtk_text_iter_ends_line(&cur)) {
4307 wc1 = gtk_text_iter_get_char(&cur);
4308 if (!g_unichar_isspace(wc1))
4310 gtk_text_iter_forward_char(&cur);
4313 if (!gtk_text_iter_equal(&prev, &next)) {
4316 mark = gtk_text_buffer_get_insert(buffer);
4317 gtk_text_buffer_get_iter_at_mark(buffer, &cur, mark);
4318 if (gtk_text_iter_equal(&prev, &cur))
4320 gtk_text_buffer_delete(buffer, &prev, &next);
4324 /* insert space if required */
4325 gtk_text_iter_backward_char(&prev);
4326 wc1 = gtk_text_iter_get_char(&prev);
4327 wc2 = gtk_text_iter_get_char(&next);
4328 gtk_text_iter_forward_char(&next);
4329 str = gtk_text_buffer_get_text(buffer, &prev, &next, FALSE);
4330 pango_default_break(str, -1, NULL, attrs, 3);
4331 if (!attrs[1].is_line_break ||
4332 (!g_unichar_iswide(wc1) || !g_unichar_iswide(wc2))) {
4333 gtk_text_buffer_insert(buffer, &iter_, " ", 1);
4335 gtk_text_iter_backward_char(&iter_);
4336 gtk_text_buffer_place_cursor(buffer, &iter_);
4345 #define ADD_TXT_POS(bp_, ep_, pti_) \
4346 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
4347 last = last->next; \
4348 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
4349 last->next = NULL; \
4351 g_warning("alloc error scanning URIs"); \
4354 static gboolean compose_beautify_paragraph(Compose *compose, GtkTextIter *par_iter, gboolean force)
4356 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4357 GtkTextBuffer *buffer;
4358 GtkTextIter iter, break_pos, end_of_line;
4359 gchar *quote_str = NULL;
4361 gboolean wrap_quote = force || prefs_common.linewrap_quote;
4362 gboolean prev_autowrap = compose->autowrap;
4363 gint startq_offset = -1, noq_offset = -1;
4364 gint uri_start = -1, uri_stop = -1;
4365 gint nouri_start = -1, nouri_stop = -1;
4366 gint num_blocks = 0;
4367 gint quotelevel = -1;
4368 gboolean modified = force;
4369 gboolean removed = FALSE;
4370 gboolean modified_before_remove = FALSE;
4372 gboolean start = TRUE;
4373 gint itemized_len = 0, rem_item_len = 0;
4374 gchar *itemized_chars = NULL;
4375 gboolean item_continuation = FALSE;
4380 if (compose->draft_timeout_tag == COMPOSE_DRAFT_TIMEOUT_FORBIDDEN) {
4384 compose->autowrap = FALSE;
4386 buffer = gtk_text_view_get_buffer(text);
4387 undo_wrapping(compose->undostruct, TRUE);
4392 mark = gtk_text_buffer_get_insert(buffer);
4393 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
4397 if (compose->draft_timeout_tag == COMPOSE_DRAFT_TIMEOUT_FORBIDDEN) {
4398 if (gtk_text_iter_ends_line(&iter)) {
4399 while (gtk_text_iter_ends_line(&iter) &&
4400 gtk_text_iter_forward_line(&iter))
4403 while (gtk_text_iter_backward_line(&iter)) {
4404 if (gtk_text_iter_ends_line(&iter)) {
4405 gtk_text_iter_forward_line(&iter);
4411 /* move to line start */
4412 gtk_text_iter_set_line_offset(&iter, 0);
4415 itemized_len = compose_itemized_length(buffer, &iter);
4417 if (!itemized_len) {
4418 itemized_len = compose_left_offset_length(buffer, &iter);
4419 item_continuation = TRUE;
4423 itemized_chars = compose_get_itemized_chars(buffer, &iter);
4425 /* go until paragraph end (empty line) */
4426 while (start || !gtk_text_iter_ends_line(&iter)) {
4427 gchar *scanpos = NULL;
4428 /* parse table - in order of priority */
4430 const gchar *needle; /* token */
4432 /* token search function */
4433 gchar *(*search) (const gchar *haystack,
4434 const gchar *needle);
4435 /* part parsing function */
4436 gboolean (*parse) (const gchar *start,
4437 const gchar *scanpos,
4441 /* part to URI function */
4442 gchar *(*build_uri) (const gchar *bp,
4446 static struct table parser[] = {
4447 {"http://", strcasestr, get_uri_part, make_uri_string},
4448 {"https://", strcasestr, get_uri_part, make_uri_string},
4449 {"ftp://", strcasestr, get_uri_part, make_uri_string},
4450 {"sftp://", strcasestr, get_uri_part, make_uri_string},
4451 {"gopher://",strcasestr, get_uri_part, make_uri_string},
4452 {"www.", strcasestr, get_uri_part, make_http_string},
4453 {"mailto:", strcasestr, get_uri_part, make_uri_string},
4454 {"@", strcasestr, get_email_part, make_email_string}
4456 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
4457 gint last_index = PARSE_ELEMS;
4459 gchar *o_walk = NULL, *walk = NULL, *bp = NULL, *ep = NULL;
4463 if (!prev_autowrap && num_blocks == 0) {
4465 g_signal_handlers_block_by_func(G_OBJECT(buffer),
4466 G_CALLBACK(text_inserted),
4469 if (gtk_text_iter_has_tag(&iter, compose->no_wrap_tag) && !force)
4472 uri_start = uri_stop = -1;
4474 quote_str = compose_get_quote_str(buffer, &iter, "e_len);
4477 // debug_print("compose_beautify_paragraph(): quote_str = '%s'\n", quote_str);
4478 if (startq_offset == -1)
4479 startq_offset = gtk_text_iter_get_offset(&iter);
4480 quotelevel = get_quote_level(quote_str, prefs_common.quote_chars);
4481 if (quotelevel > 2) {
4482 /* recycle colors */
4483 if (prefs_common.recycle_quote_colors)
4492 if (startq_offset == -1)
4493 noq_offset = gtk_text_iter_get_offset(&iter);
4497 if (prev_autowrap == FALSE && !force && !wrap_quote) {
4500 if (gtk_text_iter_ends_line(&iter)) {
4502 } else if (compose_get_line_break_pos(buffer, &iter, &break_pos,
4503 prefs_common.linewrap_len,
4505 GtkTextIter prev, next, cur;
4506 if (prev_autowrap != FALSE || force) {
4507 compose->automatic_break = TRUE;
4509 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4510 compose->automatic_break = FALSE;
4511 if (itemized_len && compose->autoindent) {
4512 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4513 if (!item_continuation)
4514 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4516 } else if (quote_str && wrap_quote) {
4517 compose->automatic_break = TRUE;
4519 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4520 compose->automatic_break = FALSE;
4521 if (itemized_len && compose->autoindent) {
4522 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4523 if (!item_continuation)
4524 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4528 /* remove trailing spaces */
4530 rem_item_len = itemized_len;
4531 while (compose->autoindent && rem_item_len-- > 0)
4532 gtk_text_iter_backward_char(&cur);
4533 gtk_text_iter_backward_char(&cur);
4536 while (!gtk_text_iter_starts_line(&cur)) {
4539 gtk_text_iter_backward_char(&cur);
4540 wc = gtk_text_iter_get_char(&cur);
4541 if (!g_unichar_isspace(wc))
4545 if (!gtk_text_iter_equal(&prev, &next)) {
4546 gtk_text_buffer_delete(buffer, &prev, &next);
4548 gtk_text_iter_forward_char(&break_pos);
4552 gtk_text_buffer_insert(buffer, &break_pos,
4556 modified |= compose_join_next_line(compose, buffer, &iter, quote_str);
4558 /* move iter to current line start */
4559 gtk_text_iter_set_line_offset(&iter, 0);
4566 /* move iter to next line start */
4572 if (!prev_autowrap && num_blocks > 0) {
4574 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
4575 G_CALLBACK(text_inserted),
4579 while (!gtk_text_iter_ends_line(&end_of_line)) {
4580 gtk_text_iter_forward_char(&end_of_line);
4582 o_walk = walk = gtk_text_buffer_get_text(buffer, &iter, &end_of_line, FALSE);
4584 nouri_start = gtk_text_iter_get_offset(&iter);
4585 nouri_stop = gtk_text_iter_get_offset(&end_of_line);
4587 walk_pos = gtk_text_iter_get_offset(&iter);
4588 /* FIXME: this looks phony. scanning for anything in the parse table */
4589 for (n = 0; n < PARSE_ELEMS; n++) {
4592 tmp = parser[n].search(walk, parser[n].needle);
4594 if (scanpos == NULL || tmp < scanpos) {
4603 /* check if URI can be parsed */
4604 if (parser[last_index].parse(walk, scanpos, (const gchar **)&bp,
4605 (const gchar **)&ep, FALSE)
4606 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
4610 strlen(parser[last_index].needle);
4613 uri_start = walk_pos + (bp - o_walk);
4614 uri_stop = walk_pos + (ep - o_walk);
4618 gtk_text_iter_forward_line(&iter);
4621 if (startq_offset != -1) {
4622 GtkTextIter startquote, endquote;
4623 gtk_text_buffer_get_iter_at_offset(
4624 buffer, &startquote, startq_offset);
4627 switch (quotelevel) {
4629 if (!gtk_text_iter_has_tag(&startquote, compose->quote0_tag) ||
4630 !gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) {
4631 gtk_text_buffer_apply_tag_by_name(
4632 buffer, "quote0", &startquote, &endquote);
4633 gtk_text_buffer_remove_tag_by_name(
4634 buffer, "quote1", &startquote, &endquote);
4635 gtk_text_buffer_remove_tag_by_name(
4636 buffer, "quote2", &startquote, &endquote);
4641 if (!gtk_text_iter_has_tag(&startquote, compose->quote1_tag) ||
4642 !gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) {
4643 gtk_text_buffer_apply_tag_by_name(
4644 buffer, "quote1", &startquote, &endquote);
4645 gtk_text_buffer_remove_tag_by_name(
4646 buffer, "quote0", &startquote, &endquote);
4647 gtk_text_buffer_remove_tag_by_name(
4648 buffer, "quote2", &startquote, &endquote);
4653 if (!gtk_text_iter_has_tag(&startquote, compose->quote2_tag) ||
4654 !gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag)) {
4655 gtk_text_buffer_apply_tag_by_name(
4656 buffer, "quote2", &startquote, &endquote);
4657 gtk_text_buffer_remove_tag_by_name(
4658 buffer, "quote0", &startquote, &endquote);
4659 gtk_text_buffer_remove_tag_by_name(
4660 buffer, "quote1", &startquote, &endquote);
4666 } else if (noq_offset != -1) {
4667 GtkTextIter startnoquote, endnoquote;
4668 gtk_text_buffer_get_iter_at_offset(
4669 buffer, &startnoquote, noq_offset);
4672 if ((gtk_text_iter_has_tag(&startnoquote, compose->quote0_tag)
4673 && gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) ||
4674 (gtk_text_iter_has_tag(&startnoquote, compose->quote1_tag)
4675 && gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) ||
4676 (gtk_text_iter_has_tag(&startnoquote, compose->quote2_tag)
4677 && gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag))) {
4678 gtk_text_buffer_remove_tag_by_name(
4679 buffer, "quote0", &startnoquote, &endnoquote);
4680 gtk_text_buffer_remove_tag_by_name(
4681 buffer, "quote1", &startnoquote, &endnoquote);
4682 gtk_text_buffer_remove_tag_by_name(
4683 buffer, "quote2", &startnoquote, &endnoquote);
4689 if (uri_start != nouri_start && uri_stop != nouri_stop) {
4690 GtkTextIter nouri_start_iter, nouri_end_iter;
4691 gtk_text_buffer_get_iter_at_offset(
4692 buffer, &nouri_start_iter, nouri_start);
4693 gtk_text_buffer_get_iter_at_offset(
4694 buffer, &nouri_end_iter, nouri_stop);
4695 if (gtk_text_iter_has_tag(&nouri_start_iter, compose->uri_tag) &&
4696 gtk_text_iter_has_tag(&nouri_end_iter, compose->uri_tag)) {
4697 gtk_text_buffer_remove_tag_by_name(
4698 buffer, "link", &nouri_start_iter, &nouri_end_iter);
4699 modified_before_remove = modified;
4704 if (uri_start >= 0 && uri_stop > 0) {
4705 GtkTextIter uri_start_iter, uri_end_iter, back;
4706 gtk_text_buffer_get_iter_at_offset(
4707 buffer, &uri_start_iter, uri_start);
4708 gtk_text_buffer_get_iter_at_offset(
4709 buffer, &uri_end_iter, uri_stop);
4710 back = uri_end_iter;
4711 gtk_text_iter_backward_char(&back);
4712 if (!gtk_text_iter_has_tag(&uri_start_iter, compose->uri_tag) ||
4713 !gtk_text_iter_has_tag(&back, compose->uri_tag)) {
4714 gtk_text_buffer_apply_tag_by_name(
4715 buffer, "link", &uri_start_iter, &uri_end_iter);
4717 if (removed && !modified_before_remove) {
4723 // debug_print("not modified, out after %d lines\n", lines);
4727 // debug_print("modified, out after %d lines\n", lines);
4729 g_free(itemized_chars);
4732 undo_wrapping(compose->undostruct, FALSE);
4733 compose->autowrap = prev_autowrap;
4738 void compose_action_cb(void *data)
4740 Compose *compose = (Compose *)data;
4741 compose_wrap_all(compose);
4744 static void compose_wrap_all(Compose *compose)
4746 compose_wrap_all_full(compose, FALSE);
4749 static void compose_wrap_all_full(Compose *compose, gboolean force)
4751 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4752 GtkTextBuffer *buffer;
4754 gboolean modified = TRUE;
4756 buffer = gtk_text_view_get_buffer(text);
4758 gtk_text_buffer_get_start_iter(buffer, &iter);
4760 undo_wrapping(compose->undostruct, TRUE);
4762 while (!gtk_text_iter_is_end(&iter) && modified)
4763 modified = compose_beautify_paragraph(compose, &iter, force);
4765 undo_wrapping(compose->undostruct, FALSE);
4769 static void compose_set_title(Compose *compose)
4775 edited = compose->modified ? _(" [Edited]") : "";
4777 subject = gtk_editable_get_chars(
4778 GTK_EDITABLE(compose->subject_entry), 0, -1);
4780 #ifndef GENERIC_UMPC
4781 if (subject && strlen(subject))
4782 str = g_strdup_printf(_("%s - Compose message%s"),
4785 str = g_strdup_printf(_("[no subject] - Compose message%s"), edited);
4787 str = g_strdup(_("Compose message"));
4790 gtk_window_set_title(GTK_WINDOW(compose->window), str);
4796 * compose_current_mail_account:
4798 * Find a current mail account (the currently selected account, or the
4799 * default account, if a news account is currently selected). If a
4800 * mail account cannot be found, display an error message.
4802 * Return value: Mail account, or NULL if not found.
4804 static PrefsAccount *
4805 compose_current_mail_account(void)
4809 if (cur_account && cur_account->protocol != A_NNTP)
4812 ac = account_get_default();
4813 if (!ac || ac->protocol == A_NNTP) {
4814 alertpanel_error(_("Account for sending mail is not specified.\n"
4815 "Please select a mail account before sending."));
4822 #define QUOTE_IF_REQUIRED(out, str) \
4824 if (*str != '"' && strpbrk(str, ",.:;[]<>()@\\\"")) { \
4828 len = strlen(str) + 3; \
4829 if ((__tmp = alloca(len)) == NULL) { \
4830 g_warning("can't allocate memory"); \
4831 g_string_free(header, TRUE); \
4834 g_snprintf(__tmp, len, "\"%s\"", str); \
4839 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4840 g_warning("can't allocate memory"); \
4841 g_string_free(header, TRUE); \
4844 strcpy(__tmp, str); \
4850 #define QUOTE_IF_REQUIRED_NORMAL(out, str, errret) \
4852 if (*str != '"' && strpbrk(str, ",.:;[]<>()@\\\"")) { \
4856 len = strlen(str) + 3; \
4857 if ((__tmp = alloca(len)) == NULL) { \
4858 g_warning("can't allocate memory"); \
4861 g_snprintf(__tmp, len, "\"%s\"", str); \
4866 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4867 g_warning("can't allocate memory"); \
4870 strcpy(__tmp, str); \
4876 static void compose_select_account(Compose *compose, PrefsAccount *account,
4879 gchar *from = NULL, *header = NULL;
4880 ComposeHeaderEntry *header_entry;
4881 #if GTK_CHECK_VERSION(2, 24, 0)
4885 cm_return_if_fail(account != NULL);
4887 compose->account = account;
4888 if (account->name && *account->name) {
4890 QUOTE_IF_REQUIRED_NORMAL(buf, account->name, return);
4891 qbuf = escape_internal_quotes(buf, '"');
4892 from = g_strdup_printf("%s <%s>",
4893 qbuf, account->address);
4896 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4898 from = g_strdup_printf("<%s>",
4900 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4905 compose_set_title(compose);
4907 if (account->default_sign && compose->mode != COMPOSE_REDIRECT)
4908 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", TRUE);
4910 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", FALSE);
4911 if (account->default_encrypt && compose->mode != COMPOSE_REDIRECT)
4912 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", TRUE);
4914 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", FALSE);
4916 activate_privacy_system(compose, account, FALSE);
4918 if (!init && compose->mode != COMPOSE_REDIRECT) {
4919 undo_block(compose->undostruct);
4920 compose_insert_sig(compose, TRUE);
4921 undo_unblock(compose->undostruct);
4924 header_entry = (ComposeHeaderEntry *) compose->header_list->data;
4925 #if !GTK_CHECK_VERSION(2, 24, 0)
4926 header = gtk_combo_box_get_active_text(GTK_COMBO_BOX(header_entry->combo));
4928 if (gtk_combo_box_get_active_iter(GTK_COMBO_BOX(header_entry->combo), &iter))
4929 gtk_tree_model_get(gtk_combo_box_get_model(GTK_COMBO_BOX(
4930 header_entry->combo)), &iter, COMBOBOX_TEXT, &header, -1);
4933 if (header && !strlen(gtk_entry_get_text(GTK_ENTRY(header_entry->entry)))) {
4934 if (account->protocol == A_NNTP) {
4935 if (!strcmp(header, _("To:")))
4936 combobox_select_by_text(
4937 GTK_COMBO_BOX(header_entry->combo),
4940 if (!strcmp(header, _("Newsgroups:")))
4941 combobox_select_by_text(
4942 GTK_COMBO_BOX(header_entry->combo),
4950 /* use account's dict info if set */
4951 if (compose->gtkaspell) {
4952 if (account->enable_default_dictionary)
4953 gtkaspell_change_dict(compose->gtkaspell,
4954 account->default_dictionary, FALSE);
4955 if (account->enable_default_alt_dictionary)
4956 gtkaspell_change_alt_dict(compose->gtkaspell,
4957 account->default_alt_dictionary);
4958 if (account->enable_default_dictionary
4959 || account->enable_default_alt_dictionary)
4960 compose_spell_menu_changed(compose);
4965 gboolean compose_check_for_valid_recipient(Compose *compose) {
4966 gchar *recipient_headers_mail[] = {"To:", "Cc:", "Bcc:", NULL};
4967 gchar *recipient_headers_news[] = {"Newsgroups:", NULL};
4968 gboolean recipient_found = FALSE;
4972 /* free to and newsgroup list */
4973 slist_free_strings_full(compose->to_list);
4974 compose->to_list = NULL;
4976 slist_free_strings_full(compose->newsgroup_list);
4977 compose->newsgroup_list = NULL;
4979 /* search header entries for to and newsgroup entries */
4980 for (list = compose->header_list; list; list = list->next) {
4983 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
4984 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4987 if (entry[0] != '\0') {
4988 for (strptr = recipient_headers_mail; *strptr != NULL; strptr++) {
4989 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
4990 compose->to_list = address_list_append(compose->to_list, entry);
4991 recipient_found = TRUE;
4994 for (strptr = recipient_headers_news; *strptr != NULL; strptr++) {
4995 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
4996 compose->newsgroup_list = newsgroup_list_append(compose->newsgroup_list, entry);
4997 recipient_found = TRUE;
5004 return recipient_found;
5007 static gboolean compose_check_for_set_recipients(Compose *compose)
5009 if (compose->account->set_autocc && compose->account->auto_cc) {
5010 gboolean found_other = FALSE;
5012 /* search header entries for to and newsgroup entries */
5013 for (list = compose->header_list; list; list = list->next) {
5016 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5017 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5020 if (strcmp(entry, compose->account->auto_cc)
5021 || strcmp(header, prefs_common_translated_header_name("Cc:"))) {
5031 if (compose->batch) {
5032 gtk_widget_show_all(compose->window);
5034 aval = alertpanel(_("Send"),
5035 _("The only recipient is the default CC address. Send anyway?"),
5036 GTK_STOCK_CANCEL, g_strconcat("+", _("_Send"), NULL), NULL);
5037 if (aval != G_ALERTALTERNATE)
5041 if (compose->account->set_autobcc && compose->account->auto_bcc) {
5042 gboolean found_other = FALSE;
5044 /* search header entries for to and newsgroup entries */
5045 for (list = compose->header_list; list; list = list->next) {
5048 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5049 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5052 if (strcmp(entry, compose->account->auto_bcc)
5053 || strcmp(header, prefs_common_translated_header_name("Bcc:"))) {
5063 if (compose->batch) {
5064 gtk_widget_show_all(compose->window);
5066 aval = alertpanel(_("Send"),
5067 _("The only recipient is the default BCC address. Send anyway?"),
5068 GTK_STOCK_CANCEL, g_strconcat("+", _("_Send"), NULL), NULL);
5069 if (aval != G_ALERTALTERNATE)
5076 static gboolean compose_check_entries(Compose *compose, gboolean check_everything)
5080 if (compose_check_for_valid_recipient(compose) == FALSE) {
5081 if (compose->batch) {
5082 gtk_widget_show_all(compose->window);
5084 alertpanel_error(_("Recipient is not specified."));
5088 if (compose_check_for_set_recipients(compose) == FALSE) {
5092 if (!compose->batch && prefs_common.warn_empty_subj == TRUE) {
5093 str = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5094 if (*str == '\0' && check_everything == TRUE &&
5095 compose->mode != COMPOSE_REDIRECT) {
5097 gchar *button_label;
5100 if (compose->sending)
5101 button_label = g_strconcat("+", _("_Send"), NULL);
5103 button_label = g_strconcat("+", _("_Queue"), NULL);
5104 message = g_strdup_printf(_("Subject is empty. %s"),
5105 compose->sending?_("Send it anyway?"):
5106 _("Queue it anyway?"));
5108 aval = alertpanel_full(compose->sending?_("Send"):_("Send later"), message,
5109 GTK_STOCK_CANCEL, button_label, NULL, TRUE, NULL,
5110 ALERT_QUESTION, G_ALERTDEFAULT);
5112 if (aval & G_ALERTDISABLE) {
5113 aval &= ~G_ALERTDISABLE;
5114 prefs_common.warn_empty_subj = FALSE;
5116 if (aval != G_ALERTALTERNATE)
5121 if (check_everything && hooks_invoke(COMPOSE_CHECK_BEFORE_SEND_HOOKLIST, compose))
5127 gint compose_send(Compose *compose)
5130 FolderItem *folder = NULL;
5132 gchar *msgpath = NULL;
5133 gboolean discard_window = FALSE;
5134 gchar *errstr = NULL;
5135 gchar *tmsgid = NULL;
5136 MainWindow *mainwin = mainwindow_get_mainwindow();
5137 gboolean queued_removed = FALSE;
5139 if (prefs_common.send_dialog_invisible
5140 || compose->batch == TRUE)
5141 discard_window = TRUE;
5143 compose_allow_user_actions (compose, FALSE);
5144 compose->sending = TRUE;
5146 if (compose_check_entries(compose, TRUE) == FALSE) {
5147 if (compose->batch) {
5148 gtk_widget_show_all(compose->window);
5154 val = compose_queue(compose, &msgnum, &folder, &msgpath, TRUE);
5157 if (compose->batch) {
5158 gtk_widget_show_all(compose->window);
5161 alertpanel_error(_("Could not queue message for sending:\n\n"
5162 "Charset conversion failed."));
5163 } else if (val == -5) {
5164 alertpanel_error(_("Could not queue message for sending:\n\n"
5165 "Couldn't get recipient encryption key."));
5166 } else if (val == -6) {
5168 } else if (val == -3) {
5169 if (privacy_peek_error())
5170 alertpanel_error(_("Could not queue message for sending:\n\n"
5171 "Signature failed: %s"), privacy_get_error());
5172 } else if (val == -2 && errno != 0) {
5173 alertpanel_error(_("Could not queue message for sending:\n\n%s."), g_strerror(errno));
5175 alertpanel_error(_("Could not queue message for sending."));
5180 tmsgid = compose->msgid ? g_strdup(compose->msgid) : NULL;
5181 if (discard_window) {
5182 compose->sending = FALSE;
5183 compose_close(compose);
5184 /* No more compose access in the normal codepath
5185 * after this point! */
5190 alertpanel_error(_("The message was queued but could not be "
5191 "sent.\nUse \"Send queued messages\" from "
5192 "the main window to retry."));
5193 if (!discard_window) {
5200 if (msgpath == NULL) {
5201 msgpath = folder_item_fetch_msg(folder, msgnum);
5202 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5205 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5206 claws_unlink(msgpath);
5209 if (!discard_window) {
5211 if (!queued_removed)
5212 folder_item_remove_msg(folder, msgnum);
5213 folder_item_scan(folder);
5215 /* make sure we delete that */
5216 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5218 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5219 folder_item_remove_msg(folder, tmp->msgnum);
5220 procmsg_msginfo_free(&tmp);
5227 if (!queued_removed)
5228 folder_item_remove_msg(folder, msgnum);
5229 folder_item_scan(folder);
5231 /* make sure we delete that */
5232 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5234 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5235 folder_item_remove_msg(folder, tmp->msgnum);
5236 procmsg_msginfo_free(&tmp);
5239 if (!discard_window) {
5240 compose->sending = FALSE;
5241 compose_allow_user_actions (compose, TRUE);
5242 compose_close(compose);
5246 alertpanel_error_log(_("%s\nUse \"Send queued messages\" from "
5247 "the main window to retry."), errstr);
5250 alertpanel_error_log(_("The message was queued but could not be "
5251 "sent.\nUse \"Send queued messages\" from "
5252 "the main window to retry."));
5254 if (!discard_window) {
5263 toolbar_main_set_sensitive(mainwin);
5264 main_window_set_menu_sensitive(mainwin);
5270 compose_allow_user_actions (compose, TRUE);
5271 compose->sending = FALSE;
5272 compose->modified = TRUE;
5273 toolbar_main_set_sensitive(mainwin);
5274 main_window_set_menu_sensitive(mainwin);
5279 static gboolean compose_use_attach(Compose *compose)
5281 GtkTreeModel *model = gtk_tree_view_get_model
5282 (GTK_TREE_VIEW(compose->attach_clist));
5283 return gtk_tree_model_iter_n_children(model, NULL) > 0;
5286 static gint compose_redirect_write_headers_from_headerlist(Compose *compose,
5289 gchar buf[BUFFSIZE];
5291 gboolean first_to_address;
5292 gboolean first_cc_address;
5294 ComposeHeaderEntry *headerentry;
5295 const gchar *headerentryname;
5296 const gchar *cc_hdr;
5297 const gchar *to_hdr;
5298 gboolean err = FALSE;
5300 debug_print("Writing redirect header\n");
5302 cc_hdr = prefs_common_translated_header_name("Cc:");
5303 to_hdr = prefs_common_translated_header_name("To:");
5305 first_to_address = TRUE;
5306 for (list = compose->header_list; list; list = list->next) {
5307 headerentry = ((ComposeHeaderEntry *)list->data);
5308 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5310 if (g_utf8_collate(headerentryname, to_hdr) == 0) {
5311 const gchar *entstr = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5312 Xstrdup_a(str, entstr, return -1);
5314 if (str[0] != '\0') {
5315 compose_convert_header
5316 (compose, buf, sizeof(buf), str,
5317 strlen("Resent-To") + 2, TRUE);
5319 if (first_to_address) {
5320 err |= (fprintf(fp, "Resent-To: ") < 0);
5321 first_to_address = FALSE;
5323 err |= (fprintf(fp, ",") < 0);
5325 err |= (fprintf(fp, "%s", buf) < 0);
5329 if (!first_to_address) {
5330 err |= (fprintf(fp, "\n") < 0);
5333 first_cc_address = TRUE;
5334 for (list = compose->header_list; list; list = list->next) {
5335 headerentry = ((ComposeHeaderEntry *)list->data);
5336 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5338 if (g_utf8_collate(headerentryname, cc_hdr) == 0) {
5339 const gchar *strg = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5340 Xstrdup_a(str, strg, return -1);
5342 if (str[0] != '\0') {
5343 compose_convert_header
5344 (compose, buf, sizeof(buf), str,
5345 strlen("Resent-Cc") + 2, TRUE);
5347 if (first_cc_address) {
5348 err |= (fprintf(fp, "Resent-Cc: ") < 0);
5349 first_cc_address = FALSE;
5351 err |= (fprintf(fp, ",") < 0);
5353 err |= (fprintf(fp, "%s", buf) < 0);
5357 if (!first_cc_address) {
5358 err |= (fprintf(fp, "\n") < 0);
5361 return (err ? -1:0);
5364 static gint compose_redirect_write_headers(Compose *compose, FILE *fp)
5366 gchar buf[BUFFSIZE];
5368 const gchar *entstr;
5369 /* struct utsname utsbuf; */
5370 gboolean err = FALSE;
5372 cm_return_val_if_fail(fp != NULL, -1);
5373 cm_return_val_if_fail(compose->account != NULL, -1);
5374 cm_return_val_if_fail(compose->account->address != NULL, -1);
5377 get_rfc822_date(buf, sizeof(buf));
5378 err |= (fprintf(fp, "Resent-Date: %s\n", buf) < 0);
5381 if (compose->account->name && *compose->account->name) {
5382 compose_convert_header
5383 (compose, buf, sizeof(buf), compose->account->name,
5384 strlen("From: "), TRUE);
5385 err |= (fprintf(fp, "Resent-From: %s <%s>\n",
5386 buf, compose->account->address) < 0);
5388 err |= (fprintf(fp, "Resent-From: %s\n", compose->account->address) < 0);
5391 entstr = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5392 if (*entstr != '\0') {
5393 Xstrdup_a(str, entstr, return -1);
5396 compose_convert_header(compose, buf, sizeof(buf), str,
5397 strlen("Subject: "), FALSE);
5398 err |= (fprintf(fp, "Subject: %s\n", buf) < 0);
5402 /* Resent-Message-ID */
5403 if (compose->account->set_domain && compose->account->domain) {
5404 g_snprintf(buf, sizeof(buf), "%s", compose->account->domain);
5405 } else if (!strncmp(get_domain_name(), "localhost", strlen("localhost"))) {
5406 g_snprintf(buf, sizeof(buf), "%s",
5407 strchr(compose->account->address, '@') ?
5408 strchr(compose->account->address, '@')+1 :
5409 compose->account->address);
5411 g_snprintf(buf, sizeof(buf), "%s", "");
5414 if (compose->account->gen_msgid) {
5416 if (compose->account->msgid_with_addr) {
5417 addr = compose->account->address;
5419 generate_msgid(buf, sizeof(buf), addr);
5420 err |= (fprintf(fp, "Resent-Message-ID: <%s>\n", buf) < 0);
5422 g_free(compose->msgid);
5423 compose->msgid = g_strdup(buf);
5425 compose->msgid = NULL;
5428 if (compose_redirect_write_headers_from_headerlist(compose, fp))
5431 /* separator between header and body */
5432 err |= (fputs("\n", fp) == EOF);
5434 return (err ? -1:0);
5437 static gint compose_redirect_write_to_file(Compose *compose, FILE *fdest)
5441 gchar buf[BUFFSIZE];
5443 gboolean skip = FALSE;
5444 gboolean err = FALSE;
5445 gchar *not_included[]={
5446 "Return-Path:", "Delivered-To:", "Received:",
5447 "Subject:", "X-UIDL:", "AF:",
5448 "NF:", "PS:", "SRH:",
5449 "SFN:", "DSR:", "MID:",
5450 "CFG:", "PT:", "S:",
5451 "RQ:", "SSV:", "NSV:",
5452 "SSH:", "R:", "MAID:",
5453 "NAID:", "RMID:", "FMID:",
5454 "SCF:", "RRCPT:", "NG:",
5455 "X-Claws-Privacy", "X-Claws-Sign:", "X-Claws-Encrypt",
5456 "X-Claws-End-Special-Headers:", "X-Claws-Account-Id:",
5457 "X-Sylpheed-Privacy", "X-Sylpheed-Sign:", "X-Sylpheed-Encrypt",
5458 "X-Sylpheed-End-Special-Headers:", "X-Sylpheed-Account-Id:",
5459 "X-Claws-Auto-Wrapping:", "X-Claws-Auto-Indent:",
5462 if ((fp = g_fopen(compose->redirect_filename, "rb")) == NULL) {
5463 FILE_OP_ERROR(compose->redirect_filename, "fopen");
5467 while (procheader_get_one_field_asis(buf, sizeof(buf), fp) != -1) {
5469 for (i = 0; not_included[i] != NULL; i++) {
5470 if (g_ascii_strncasecmp(buf, not_included[i],
5471 strlen(not_included[i])) == 0) {
5478 if (fputs(buf, fdest) == -1)
5481 if (!prefs_common.redirect_keep_from) {
5482 if (g_ascii_strncasecmp(buf, "From:",
5483 strlen("From:")) == 0) {
5484 err |= (fputs(" (by way of ", fdest) == EOF);
5485 if (compose->account->name
5486 && *compose->account->name) {
5487 compose_convert_header
5488 (compose, buf, sizeof(buf),
5489 compose->account->name,
5492 err |= (fprintf(fdest, "%s <%s>",
5494 compose->account->address) < 0);
5496 err |= (fprintf(fdest, "%s",
5497 compose->account->address) < 0);
5498 err |= (fputs(")", fdest) == EOF);
5502 if (fputs("\n", fdest) == -1)
5509 if (compose_redirect_write_headers(compose, fdest))
5512 while ((len = fread(buf, sizeof(gchar), sizeof(buf), fp)) > 0) {
5513 if (fwrite(buf, sizeof(gchar), len, fdest) != len)
5526 static gint compose_write_to_file(Compose *compose, FILE *fp, gint action, gboolean attach_parts)
5528 GtkTextBuffer *buffer;
5529 GtkTextIter start, end;
5530 gchar *chars, *tmp_enc_file, *content;
5532 const gchar *out_codeset;
5533 EncodingType encoding = ENC_UNKNOWN;
5534 MimeInfo *mimemsg, *mimetext;
5536 const gchar *src_codeset = CS_INTERNAL;
5537 gchar *from_addr = NULL;
5538 gchar *from_name = NULL;
5541 if (action == COMPOSE_WRITE_FOR_SEND)
5542 attach_parts = TRUE;
5544 /* create message MimeInfo */
5545 mimemsg = procmime_mimeinfo_new();
5546 mimemsg->type = MIMETYPE_MESSAGE;
5547 mimemsg->subtype = g_strdup("rfc822");
5548 mimemsg->content = MIMECONTENT_MEM;
5549 mimemsg->tmp = TRUE; /* must free content later */
5550 mimemsg->data.mem = compose_get_header(compose);
5552 /* Create text part MimeInfo */
5553 /* get all composed text */
5554 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5555 gtk_text_buffer_get_start_iter(buffer, &start);
5556 gtk_text_buffer_get_end_iter(buffer, &end);
5557 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5559 out_codeset = conv_get_charset_str(compose->out_encoding);
5561 if (!out_codeset && is_ascii_str(chars)) {
5562 out_codeset = CS_US_ASCII;
5563 } else if (prefs_common.outgoing_fallback_to_ascii &&
5564 is_ascii_str(chars)) {
5565 out_codeset = CS_US_ASCII;
5566 encoding = ENC_7BIT;
5570 gchar *test_conv_global_out = NULL;
5571 gchar *test_conv_reply = NULL;
5573 /* automatic mode. be automatic. */
5574 codeconv_set_strict(TRUE);
5576 out_codeset = conv_get_outgoing_charset_str();
5578 debug_print("trying to convert to %s\n", out_codeset);
5579 test_conv_global_out = conv_codeset_strdup(chars, src_codeset, out_codeset);
5582 if (!test_conv_global_out && compose->orig_charset
5583 && strcmp(compose->orig_charset, CS_US_ASCII)) {
5584 out_codeset = compose->orig_charset;
5585 debug_print("failure; trying to convert to %s\n", out_codeset);
5586 test_conv_reply = conv_codeset_strdup(chars, src_codeset, out_codeset);
5589 if (!test_conv_global_out && !test_conv_reply) {
5591 out_codeset = CS_INTERNAL;
5592 debug_print("failure; finally using %s\n", out_codeset);
5594 g_free(test_conv_global_out);
5595 g_free(test_conv_reply);
5596 codeconv_set_strict(FALSE);
5599 if (encoding == ENC_UNKNOWN) {
5600 if (prefs_common.encoding_method == CTE_BASE64)
5601 encoding = ENC_BASE64;
5602 else if (prefs_common.encoding_method == CTE_QUOTED_PRINTABLE)
5603 encoding = ENC_QUOTED_PRINTABLE;
5604 else if (prefs_common.encoding_method == CTE_8BIT)
5605 encoding = ENC_8BIT;
5607 encoding = procmime_get_encoding_for_charset(out_codeset);
5610 debug_print("src encoding = %s, out encoding = %s, transfer encoding = %s\n",
5611 src_codeset, out_codeset, procmime_get_encoding_str(encoding));
5613 if (action == COMPOSE_WRITE_FOR_SEND) {
5614 codeconv_set_strict(TRUE);
5615 buf = conv_codeset_strdup(chars, src_codeset, out_codeset);
5616 codeconv_set_strict(FALSE);
5621 msg = g_strdup_printf(_("Can't convert the character encoding of the message \n"
5622 "to the specified %s charset.\n"
5623 "Send it as %s?"), out_codeset, src_codeset);
5624 aval = alertpanel_full(_("Error"), msg, GTK_STOCK_CANCEL,
5625 g_strconcat("+", _("_Send"), NULL), NULL, FALSE,
5626 NULL, ALERT_ERROR, G_ALERTDEFAULT);
5629 if (aval != G_ALERTALTERNATE) {
5634 out_codeset = src_codeset;
5640 out_codeset = src_codeset;
5645 if (encoding == ENC_8BIT || encoding == ENC_7BIT) {
5646 if (!strncmp(buf, "From ", sizeof("From ")-1) ||
5647 strstr(buf, "\nFrom ") != NULL) {
5648 encoding = ENC_QUOTED_PRINTABLE;
5652 mimetext = procmime_mimeinfo_new();
5653 mimetext->content = MIMECONTENT_MEM;
5654 mimetext->tmp = TRUE; /* must free content later */
5655 /* dup'ed because procmime_encode_content can turn it into a tmpfile
5656 * and free the data, which we need later. */
5657 mimetext->data.mem = g_strdup(buf);
5658 mimetext->type = MIMETYPE_TEXT;
5659 mimetext->subtype = g_strdup("plain");
5660 g_hash_table_insert(mimetext->typeparameters, g_strdup("charset"),
5661 g_strdup(out_codeset));
5663 /* protect trailing spaces when signing message */
5664 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5665 privacy_system_can_sign(compose->privacy_system)) {
5666 encoding = ENC_QUOTED_PRINTABLE;
5669 debug_print("main text: %zd bytes encoded as %s in %d\n",
5670 strlen(buf), out_codeset, encoding);
5672 /* check for line length limit */
5673 if (action == COMPOSE_WRITE_FOR_SEND &&
5674 encoding != ENC_QUOTED_PRINTABLE && encoding != ENC_BASE64 &&
5675 check_line_length(buf, 1000, &line) < 0) {
5678 msg = g_strdup_printf
5679 (_("Line %d exceeds the line length limit (998 bytes).\n"
5680 "The contents of the message might be broken on the way to the delivery.\n"
5682 "Send it anyway?"), line + 1);
5683 aval = alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, GTK_STOCK_OK, NULL);
5685 if (aval != G_ALERTALTERNATE) {
5691 if (encoding != ENC_UNKNOWN)
5692 procmime_encode_content(mimetext, encoding);
5694 /* append attachment parts */
5695 if (compose_use_attach(compose) && attach_parts) {
5696 MimeInfo *mimempart;
5697 gchar *boundary = NULL;
5698 mimempart = procmime_mimeinfo_new();
5699 mimempart->content = MIMECONTENT_EMPTY;
5700 mimempart->type = MIMETYPE_MULTIPART;
5701 mimempart->subtype = g_strdup("mixed");
5705 boundary = generate_mime_boundary(NULL);
5706 } while (strstr(buf, boundary) != NULL);
5708 g_hash_table_insert(mimempart->typeparameters, g_strdup("boundary"),
5711 mimetext->disposition = DISPOSITIONTYPE_INLINE;
5713 g_node_append(mimempart->node, mimetext->node);
5714 g_node_append(mimemsg->node, mimempart->node);
5716 if (compose_add_attachments(compose, mimempart) < 0)
5719 g_node_append(mimemsg->node, mimetext->node);
5723 if (strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) != 0) {
5724 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
5725 /* extract name and address */
5726 if (strstr(spec, " <") && strstr(spec, ">")) {
5727 from_addr = g_strdup(strrchr(spec, '<')+1);
5728 *(strrchr(from_addr, '>')) = '\0';
5729 from_name = g_strdup(spec);
5730 *(strrchr(from_name, '<')) = '\0';
5737 /* sign message if sending */
5738 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5739 privacy_system_can_sign(compose->privacy_system))
5740 if (!privacy_sign(compose->privacy_system, mimemsg,
5741 compose->account, from_addr)) {
5749 if (compose->use_encryption) {
5750 if (compose->encdata != NULL &&
5751 strcmp(compose->encdata, "_DONT_ENCRYPT_")) {
5753 /* First, write an unencrypted copy and save it to outbox, if
5754 * user wants that. */
5755 if (compose->account->save_encrypted_as_clear_text) {
5756 debug_print("saving sent message unencrypted...\n");
5757 FILE *tmpfp = get_tmpfile_in_dir(get_mime_tmp_dir(), &tmp_enc_file);
5761 /* fp now points to a file with headers written,
5762 * let's make a copy. */
5764 content = file_read_stream_to_str(fp);
5766 str_write_to_file(content, tmp_enc_file);
5769 /* Now write the unencrypted body. */
5770 if ((tmpfp = g_fopen(tmp_enc_file, "a")) != NULL) {
5771 procmime_write_mimeinfo(mimemsg, tmpfp);
5774 outbox = folder_find_item_from_identifier(compose_get_save_to(compose));
5776 outbox = folder_get_default_outbox();
5778 procmsg_save_to_outbox(outbox, tmp_enc_file, TRUE);
5779 claws_unlink(tmp_enc_file);
5781 g_warning("Can't open file '%s'", tmp_enc_file);
5784 g_warning("couldn't get tempfile");
5787 if (!privacy_encrypt(compose->privacy_system, mimemsg, compose->encdata)) {
5788 debug_print("Couldn't encrypt mime structure: %s.\n",
5789 privacy_get_error());
5790 alertpanel_error(_("Couldn't encrypt the email: %s"),
5791 privacy_get_error());
5796 procmime_write_mimeinfo(mimemsg, fp);
5798 procmime_mimeinfo_free_all(&mimemsg);
5803 static gint compose_write_body_to_file(Compose *compose, const gchar *file)
5805 GtkTextBuffer *buffer;
5806 GtkTextIter start, end;
5811 if ((fp = g_fopen(file, "wb")) == NULL) {
5812 FILE_OP_ERROR(file, "fopen");
5816 /* chmod for security */
5817 if (change_file_mode_rw(fp, file) < 0) {
5818 FILE_OP_ERROR(file, "chmod");
5819 g_warning("can't change file mode");
5822 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5823 gtk_text_buffer_get_start_iter(buffer, &start);
5824 gtk_text_buffer_get_end_iter(buffer, &end);
5825 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5827 chars = conv_codeset_strdup
5828 (tmp, CS_INTERNAL, conv_get_locale_charset_str());
5837 len = strlen(chars);
5838 if (fwrite(chars, sizeof(gchar), len, fp) != len) {
5839 FILE_OP_ERROR(file, "fwrite");
5848 if (fclose(fp) == EOF) {
5849 FILE_OP_ERROR(file, "fclose");
5856 static gint compose_remove_reedit_target(Compose *compose, gboolean force)
5859 MsgInfo *msginfo = compose->targetinfo;
5861 cm_return_val_if_fail(compose->mode == COMPOSE_REEDIT, -1);
5862 if (!msginfo) return -1;
5864 if (!force && MSG_IS_LOCKED(msginfo->flags))
5867 item = msginfo->folder;
5868 cm_return_val_if_fail(item != NULL, -1);
5870 if (procmsg_msg_exist(msginfo) &&
5871 (folder_has_parent_of_type(item, F_QUEUE) ||
5872 folder_has_parent_of_type(item, F_DRAFT)
5873 || msginfo == compose->autosaved_draft)) {
5874 if (folder_item_remove_msg(item, msginfo->msgnum) < 0) {
5875 g_warning("can't remove the old message");
5878 debug_print("removed reedit target %d\n", msginfo->msgnum);
5885 static void compose_remove_draft(Compose *compose)
5888 MsgInfo *msginfo = compose->targetinfo;
5889 drafts = account_get_special_folder(compose->account, F_DRAFT);
5891 if (procmsg_msg_exist(msginfo)) {
5892 folder_item_remove_msg(drafts, msginfo->msgnum);
5897 gint compose_queue(Compose *compose, gint *msgnum, FolderItem **item, gchar **msgpath,
5898 gboolean remove_reedit_target)
5900 return compose_queue_sub (compose, msgnum, item, msgpath, FALSE, remove_reedit_target);
5903 static gboolean compose_warn_encryption(Compose *compose)
5905 const gchar *warning = privacy_get_encrypt_warning(compose->privacy_system);
5906 AlertValue val = G_ALERTALTERNATE;
5908 if (warning == NULL)
5911 val = alertpanel_full(_("Encryption warning"), warning,
5912 GTK_STOCK_CANCEL, g_strconcat("+", _("C_ontinue"), NULL), NULL,
5913 TRUE, NULL, ALERT_WARNING, G_ALERTALTERNATE);
5914 if (val & G_ALERTDISABLE) {
5915 val &= ~G_ALERTDISABLE;
5916 if (val == G_ALERTALTERNATE)
5917 privacy_inhibit_encrypt_warning(compose->privacy_system,
5921 if (val == G_ALERTALTERNATE) {
5928 static gint compose_queue_sub(Compose *compose, gint *msgnum, FolderItem **item,
5929 gchar **msgpath, gboolean check_subject,
5930 gboolean remove_reedit_target)
5937 PrefsAccount *mailac = NULL, *newsac = NULL;
5938 gboolean err = FALSE;
5940 debug_print("queueing message...\n");
5941 cm_return_val_if_fail(compose->account != NULL, -1);
5943 if (compose_check_entries(compose, check_subject) == FALSE) {
5944 if (compose->batch) {
5945 gtk_widget_show_all(compose->window);
5950 if (!compose->to_list && !compose->newsgroup_list) {
5951 g_warning("can't get recipient list.");
5955 if (compose->to_list) {
5956 if (compose->account->protocol != A_NNTP)
5957 mailac = compose->account;
5958 else if (cur_account && cur_account->protocol != A_NNTP)
5959 mailac = cur_account;
5960 else if (!(mailac = compose_current_mail_account())) {
5961 alertpanel_error(_("No account for sending mails available!"));
5966 if (compose->newsgroup_list) {
5967 if (compose->account->protocol == A_NNTP)
5968 newsac = compose->account;
5970 alertpanel_error(_("Selected account isn't NNTP: Posting is impossible."));
5975 /* write queue header */
5976 tmp = g_strdup_printf("%s%cqueue.%p%08x", get_tmp_dir(),
5977 G_DIR_SEPARATOR, compose, (guint) rand());
5978 debug_print("queuing to %s\n", tmp);
5979 if ((fp = g_fopen(tmp, "w+b")) == NULL) {
5980 FILE_OP_ERROR(tmp, "fopen");
5985 if (change_file_mode_rw(fp, tmp) < 0) {
5986 FILE_OP_ERROR(tmp, "chmod");
5987 g_warning("can't change file mode");
5990 /* queueing variables */
5991 err |= (fprintf(fp, "AF:\n") < 0);
5992 err |= (fprintf(fp, "NF:0\n") < 0);
5993 err |= (fprintf(fp, "PS:10\n") < 0);
5994 err |= (fprintf(fp, "SRH:1\n") < 0);
5995 err |= (fprintf(fp, "SFN:\n") < 0);
5996 err |= (fprintf(fp, "DSR:\n") < 0);
5998 err |= (fprintf(fp, "MID:<%s>\n", compose->msgid) < 0);
6000 err |= (fprintf(fp, "MID:\n") < 0);
6001 err |= (fprintf(fp, "CFG:\n") < 0);
6002 err |= (fprintf(fp, "PT:0\n") < 0);
6003 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
6004 err |= (fprintf(fp, "RQ:\n") < 0);
6006 err |= (fprintf(fp, "SSV:%s\n", mailac->smtp_server) < 0);
6008 err |= (fprintf(fp, "SSV:\n") < 0);
6010 err |= (fprintf(fp, "NSV:%s\n", newsac->nntp_server) < 0);
6012 err |= (fprintf(fp, "NSV:\n") < 0);
6013 err |= (fprintf(fp, "SSH:\n") < 0);
6014 /* write recepient list */
6015 if (compose->to_list) {
6016 err |= (fprintf(fp, "R:<%s>", (gchar *)compose->to_list->data) < 0);
6017 for (cur = compose->to_list->next; cur != NULL;
6019 err |= (fprintf(fp, ",<%s>", (gchar *)cur->data) < 0);
6020 err |= (fprintf(fp, "\n") < 0);
6022 /* write newsgroup list */
6023 if (compose->newsgroup_list) {
6024 err |= (fprintf(fp, "NG:") < 0);
6025 err |= (fprintf(fp, "%s", (gchar *)compose->newsgroup_list->data) < 0);
6026 for (cur = compose->newsgroup_list->next; cur != NULL; cur = cur->next)
6027 err |= (fprintf(fp, ",%s", (gchar *)cur->data) < 0);
6028 err |= (fprintf(fp, "\n") < 0);
6030 /* Sylpheed account IDs */
6032 err |= (fprintf(fp, "MAID:%d\n", mailac->account_id) < 0);
6034 err |= (fprintf(fp, "NAID:%d\n", newsac->account_id) < 0);
6037 if (compose->privacy_system != NULL) {
6038 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
6039 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
6040 if (compose->use_encryption) {
6041 if (!compose_warn_encryption(compose)) {
6047 if (mailac && mailac->encrypt_to_self) {
6048 GSList *tmp_list = g_slist_copy(compose->to_list);
6049 tmp_list = g_slist_append(tmp_list, compose->account->address);
6050 compose->encdata = privacy_get_encrypt_data(compose->privacy_system, tmp_list);
6051 g_slist_free(tmp_list);
6053 compose->encdata = privacy_get_encrypt_data(compose->privacy_system, compose->to_list);
6055 if (compose->encdata != NULL) {
6056 if (strcmp(compose->encdata, "_DONT_ENCRYPT_")) {
6057 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
6058 err |= (fprintf(fp, "X-Claws-Encrypt-Data:%s\n",
6059 compose->encdata) < 0);
6060 } /* else we finally dont want to encrypt */
6062 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
6063 /* and if encdata was null, it means there's been a problem in
6066 g_warning("failed to write queue message");
6075 /* Save copy folder */
6076 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
6077 gchar *savefolderid;
6079 savefolderid = compose_get_save_to(compose);
6080 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
6081 g_free(savefolderid);
6083 /* Save copy folder */
6084 if (compose->return_receipt) {
6085 err |= (fprintf(fp, "RRCPT:1\n") < 0);
6087 /* Message-ID of message replying to */
6088 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
6089 gchar *folderid = NULL;
6091 if (compose->replyinfo->folder)
6092 folderid = folder_item_get_identifier(compose->replyinfo->folder);
6093 if (folderid == NULL)
6094 folderid = g_strdup("NULL");
6096 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
6099 /* Message-ID of message forwarding to */
6100 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
6101 gchar *folderid = NULL;
6103 if (compose->fwdinfo->folder)
6104 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
6105 if (folderid == NULL)
6106 folderid = g_strdup("NULL");
6108 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
6112 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
6113 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
6115 /* end of headers */
6116 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
6118 if (compose->redirect_filename != NULL) {
6119 if (compose_redirect_write_to_file(compose, fp) < 0) {
6127 if ((result = compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_SEND, TRUE)) < 0) {
6131 return result - 1; /* -2 for a generic error, -3 for signing error, -4 for encoding */
6135 g_warning("failed to write queue message");
6141 if (fclose(fp) == EOF) {
6142 FILE_OP_ERROR(tmp, "fclose");
6148 if (item && *item) {
6151 queue = account_get_special_folder(compose->account, F_QUEUE);
6154 g_warning("can't find queue folder");
6159 folder_item_scan(queue);
6160 if ((num = folder_item_add_msg(queue, tmp, NULL, FALSE)) < 0) {
6161 g_warning("can't queue the message");
6167 if (msgpath == NULL) {
6173 if (compose->mode == COMPOSE_REEDIT && remove_reedit_target) {
6174 compose_remove_reedit_target(compose, FALSE);
6177 if ((msgnum != NULL) && (item != NULL)) {
6185 static int compose_add_attachments(Compose *compose, MimeInfo *parent)
6188 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
6191 gchar *type, *subtype;
6192 GtkTreeModel *model;
6195 model = gtk_tree_view_get_model(tree_view);
6197 if (!gtk_tree_model_get_iter_first(model, &iter))
6200 gtk_tree_model_get(model, &iter,
6204 if (!is_file_exist(ainfo->file)) {
6205 gchar *msg = g_strdup_printf(_("Attachment %s doesn't exist anymore. Ignore?"), ainfo->file);
6206 AlertValue val = alertpanel_full(_("Warning"), msg, _("Cancel sending"), _("Ignore attachment"),
6207 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
6209 if (val == G_ALERTDEFAULT) {
6214 if (g_stat(ainfo->file, &statbuf) < 0)
6217 mimepart = procmime_mimeinfo_new();
6218 mimepart->content = MIMECONTENT_FILE;
6219 mimepart->data.filename = g_strdup(ainfo->file);
6220 mimepart->tmp = FALSE; /* or we destroy our attachment */
6221 mimepart->offset = 0;
6222 mimepart->length = statbuf.st_size;
6224 type = g_strdup(ainfo->content_type);
6226 if (!strchr(type, '/')) {
6228 type = g_strdup("application/octet-stream");
6231 subtype = strchr(type, '/') + 1;
6232 *(subtype - 1) = '\0';
6233 mimepart->type = procmime_get_media_type(type);
6234 mimepart->subtype = g_strdup(subtype);
6237 if (mimepart->type == MIMETYPE_MESSAGE &&
6238 !g_ascii_strcasecmp(mimepart->subtype, "rfc822")) {
6239 mimepart->disposition = DISPOSITIONTYPE_INLINE;
6240 } else if (mimepart->type == MIMETYPE_TEXT) {
6241 if (!ainfo->name && g_ascii_strcasecmp(mimepart->subtype, "plain")) {
6242 /* Text parts with no name come from multipart/alternative
6243 * forwards. Make sure the recipient won't look at the
6244 * original HTML part by mistake. */
6245 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6246 ainfo->name = g_strdup_printf(_("Original %s part"),
6250 g_hash_table_insert(mimepart->typeparameters,
6251 g_strdup("charset"), g_strdup(ainfo->charset));
6253 if (ainfo->name && mimepart->type != MIMETYPE_MESSAGE) {
6254 if (mimepart->type == MIMETYPE_APPLICATION &&
6255 !strcmp2(mimepart->subtype, "octet-stream"))
6256 g_hash_table_insert(mimepart->typeparameters,
6257 g_strdup("name"), g_strdup(ainfo->name));
6258 g_hash_table_insert(mimepart->dispositionparameters,
6259 g_strdup("filename"), g_strdup(ainfo->name));
6260 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6263 if (mimepart->type == MIMETYPE_MESSAGE
6264 || mimepart->type == MIMETYPE_MULTIPART)
6265 ainfo->encoding = ENC_BINARY;
6266 else if (compose->use_signing) {
6267 if (ainfo->encoding == ENC_7BIT)
6268 ainfo->encoding = ENC_QUOTED_PRINTABLE;
6269 else if (ainfo->encoding == ENC_8BIT)
6270 ainfo->encoding = ENC_BASE64;
6275 procmime_encode_content(mimepart, ainfo->encoding);
6277 g_node_append(parent->node, mimepart->node);
6278 } while (gtk_tree_model_iter_next(model, &iter));
6283 static gchar *compose_quote_list_of_addresses(gchar *str)
6285 GSList *list = NULL, *item = NULL;
6286 gchar *qname = NULL, *faddr = NULL, *result = NULL;
6288 list = address_list_append_with_comments(list, str);
6289 for (item = list; item != NULL; item = item->next) {
6290 gchar *spec = item->data;
6291 gchar *endofname = strstr(spec, " <");
6292 if (endofname != NULL) {
6295 QUOTE_IF_REQUIRED_NORMAL(qname, spec, return NULL);
6296 qqname = escape_internal_quotes(qname, '"');
6298 if (*qname != *spec || qqname != qname) { /* has been quoted, compute new */
6299 gchar *addr = g_strdup(endofname);
6300 gchar *name = (qqname != qname)? qqname: g_strdup(qname);
6301 faddr = g_strconcat(name, addr, NULL);
6304 debug_print("new auto-quoted address: '%s'", faddr);
6308 result = g_strdup((faddr != NULL)? faddr: spec);
6310 result = g_strconcat(result,
6312 (faddr != NULL)? faddr: spec,
6315 if (faddr != NULL) {
6320 slist_free_strings_full(list);
6325 #define IS_IN_CUSTOM_HEADER(header) \
6326 (compose->account->add_customhdr && \
6327 custom_header_find(compose->account->customhdr_list, header) != NULL)
6329 static void compose_add_headerfield_from_headerlist(Compose *compose,
6331 const gchar *fieldname,
6332 const gchar *seperator)
6334 gchar *str, *fieldname_w_colon;
6335 gboolean add_field = FALSE;
6337 ComposeHeaderEntry *headerentry;
6338 const gchar *headerentryname;
6339 const gchar *trans_fieldname;
6342 if (IS_IN_CUSTOM_HEADER(fieldname))
6345 debug_print("Adding %s-fields\n", fieldname);
6347 fieldstr = g_string_sized_new(64);
6349 fieldname_w_colon = g_strconcat(fieldname, ":", NULL);
6350 trans_fieldname = prefs_common_translated_header_name(fieldname_w_colon);
6352 for (list = compose->header_list; list; list = list->next) {
6353 headerentry = ((ComposeHeaderEntry *)list->data);
6354 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
6356 if (!g_utf8_collate(trans_fieldname, headerentryname)) {
6357 gchar * ustr = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
6359 str = compose_quote_list_of_addresses(ustr);
6361 if (str != NULL && str[0] != '\0') {
6363 g_string_append(fieldstr, seperator);
6364 g_string_append(fieldstr, str);
6373 buf = g_new0(gchar, fieldstr->len * 4 + 256);
6374 compose_convert_header
6375 (compose, buf, fieldstr->len * 4 + 256, fieldstr->str,
6376 strlen(fieldname) + 2, TRUE);
6377 g_string_append_printf(header, "%s: %s\n", fieldname, buf);
6381 g_free(fieldname_w_colon);
6382 g_string_free(fieldstr, TRUE);
6387 static gchar *compose_get_manual_headers_info(Compose *compose)
6389 GString *sh_header = g_string_new(" ");
6391 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6393 for (list = compose->header_list; list; list = list->next) {
6394 ComposeHeaderEntry *headerentry;
6397 gchar *headername_wcolon;
6398 const gchar *headername_trans;
6400 gboolean standard_header = FALSE;
6402 headerentry = ((ComposeHeaderEntry *)list->data);
6404 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6406 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6411 if (!strstr(tmp, ":")) {
6412 headername_wcolon = g_strconcat(tmp, ":", NULL);
6413 headername = g_strdup(tmp);
6415 headername_wcolon = g_strdup(tmp);
6416 headername = g_strdup(strtok(tmp, ":"));
6420 string = std_headers;
6421 while (*string != NULL) {
6422 headername_trans = prefs_common_translated_header_name(*string);
6423 if (!strcmp(headername_trans, headername_wcolon))
6424 standard_header = TRUE;
6427 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
6428 g_string_append_printf(sh_header, "%s ", headername);
6430 g_free(headername_wcolon);
6432 g_string_truncate(sh_header, strlen(sh_header->str) - 1); /* remove last space */
6433 return g_string_free(sh_header, FALSE);
6436 static gchar *compose_get_header(Compose *compose)
6438 gchar buf[BUFFSIZE];
6439 const gchar *entry_str;
6443 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6445 gchar *from_name = NULL, *from_address = NULL;
6448 cm_return_val_if_fail(compose->account != NULL, NULL);
6449 cm_return_val_if_fail(compose->account->address != NULL, NULL);
6451 header = g_string_sized_new(64);
6454 get_rfc822_date(buf, sizeof(buf));
6455 g_string_append_printf(header, "Date: %s\n", buf);
6459 if (compose->account->name && *compose->account->name) {
6461 QUOTE_IF_REQUIRED(buf, compose->account->name);
6462 tmp = g_strdup_printf("%s <%s>",
6463 buf, compose->account->address);
6465 tmp = g_strdup_printf("%s",
6466 compose->account->address);
6468 if (!strcmp(gtk_entry_get_text(GTK_ENTRY(compose->from_name)), tmp)
6469 || strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) == 0) {
6471 from_name = compose->account->name ? g_strdup(compose->account->name):NULL;
6472 from_address = g_strdup(compose->account->address);
6474 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
6475 /* extract name and address */
6476 if (strstr(spec, " <") && strstr(spec, ">")) {
6477 from_address = g_strdup(strrchr(spec, '<')+1);
6478 *(strrchr(from_address, '>')) = '\0';
6479 from_name = g_strdup(spec);
6480 *(strrchr(from_name, '<')) = '\0';
6483 from_address = g_strdup(spec);
6490 if (from_name && *from_name) {
6492 compose_convert_header
6493 (compose, buf, sizeof(buf), from_name,
6494 strlen("From: "), TRUE);
6495 QUOTE_IF_REQUIRED(name, buf);
6496 qname = escape_internal_quotes(name, '"');
6498 g_string_append_printf(header, "From: %s <%s>\n",
6499 qname, from_address);
6500 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To") &&
6501 compose->return_receipt) {
6502 compose_convert_header(compose, buf, sizeof(buf), from_name,
6503 strlen("Disposition-Notification-To: "),
6505 g_string_append_printf(header, "Disposition-Notification-To: %s <%s>\n", buf, from_address);
6510 g_string_append_printf(header, "From: %s\n", from_address);
6511 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To") &&
6512 compose->return_receipt)
6513 g_string_append_printf(header, "Disposition-Notification-To: %s\n", from_address);
6517 g_free(from_address);
6520 compose_add_headerfield_from_headerlist(compose, header, "To", ", ");
6523 compose_add_headerfield_from_headerlist(compose, header, "Newsgroups", ",");
6526 compose_add_headerfield_from_headerlist(compose, header, "Cc", ", ");
6530 * If this account is a NNTP account remove Bcc header from
6531 * message body since it otherwise will be publicly shown
6533 if (compose->account->protocol != A_NNTP)
6534 compose_add_headerfield_from_headerlist(compose, header, "Bcc", ", ");
6537 str = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
6539 if (*str != '\0' && !IS_IN_CUSTOM_HEADER("Subject")) {
6542 compose_convert_header(compose, buf, sizeof(buf), str,
6543 strlen("Subject: "), FALSE);
6544 g_string_append_printf(header, "Subject: %s\n", buf);
6550 if (compose->account->set_domain && compose->account->domain) {
6551 g_snprintf(buf, sizeof(buf), "%s", compose->account->domain);
6552 } else if (!strncmp(get_domain_name(), "localhost", strlen("localhost"))) {
6553 g_snprintf(buf, sizeof(buf), "%s",
6554 strchr(compose->account->address, '@') ?
6555 strchr(compose->account->address, '@')+1 :
6556 compose->account->address);
6558 g_snprintf(buf, sizeof(buf), "%s", "");
6561 if (compose->account->gen_msgid) {
6563 if (compose->account->msgid_with_addr) {
6564 addr = compose->account->address;
6566 generate_msgid(buf, sizeof(buf), addr);
6567 g_string_append_printf(header, "Message-ID: <%s>\n", buf);
6569 g_free(compose->msgid);
6570 compose->msgid = g_strdup(buf);
6572 compose->msgid = NULL;
6575 if (compose->remove_references == FALSE) {
6577 if (compose->inreplyto && compose->to_list)
6578 g_string_append_printf(header, "In-Reply-To: <%s>\n", compose->inreplyto);
6581 if (compose->references)
6582 g_string_append_printf(header, "References: %s\n", compose->references);
6586 compose_add_headerfield_from_headerlist(compose, header, "Followup-To", ",");
6589 compose_add_headerfield_from_headerlist(compose, header, "Reply-To", ", ");
6592 if (compose->account->organization &&
6593 strlen(compose->account->organization) &&
6594 !IS_IN_CUSTOM_HEADER("Organization")) {
6595 compose_convert_header(compose, buf, sizeof(buf),
6596 compose->account->organization,
6597 strlen("Organization: "), FALSE);
6598 g_string_append_printf(header, "Organization: %s\n", buf);
6601 /* Program version and system info */
6602 if (compose->account->gen_xmailer &&
6603 g_slist_length(compose->to_list) && !IS_IN_CUSTOM_HEADER("X-Mailer") &&
6604 !compose->newsgroup_list) {
6605 g_string_append_printf(header, "X-Mailer: %s (GTK+ %d.%d.%d; %s)\n",
6607 gtk_major_version, gtk_minor_version, gtk_micro_version,
6610 if (compose->account->gen_xmailer &&
6611 g_slist_length(compose->newsgroup_list) && !IS_IN_CUSTOM_HEADER("X-Newsreader")) {
6612 g_string_append_printf(header, "X-Newsreader: %s (GTK+ %d.%d.%d; %s)\n",
6614 gtk_major_version, gtk_minor_version, gtk_micro_version,
6618 /* custom headers */
6619 if (compose->account->add_customhdr) {
6622 for (cur = compose->account->customhdr_list; cur != NULL;
6624 CustomHeader *chdr = (CustomHeader *)cur->data;
6626 if (custom_header_is_allowed(chdr->name)
6627 && chdr->value != NULL
6628 && *(chdr->value) != '\0') {
6629 compose_convert_header
6630 (compose, buf, sizeof(buf),
6632 strlen(chdr->name) + 2, FALSE);
6633 g_string_append_printf(header, "%s: %s\n", chdr->name, buf);
6638 /* Automatic Faces and X-Faces */
6639 if (get_account_xface (buf, sizeof(buf), compose->account->account_name) == 0) {
6640 g_string_append_printf(header, "X-Face: %s\n", buf);
6642 else if (get_default_xface (buf, sizeof(buf)) == 0) {
6643 g_string_append_printf(header, "X-Face: %s\n", buf);
6645 if (get_account_face (buf, sizeof(buf), compose->account->account_name) == 0) {
6646 g_string_append_printf(header, "Face: %s\n", buf);
6648 else if (get_default_face (buf, sizeof(buf)) == 0) {
6649 g_string_append_printf(header, "Face: %s\n", buf);
6653 switch (compose->priority) {
6654 case PRIORITY_HIGHEST: g_string_append_printf(header, "Importance: high\n"
6655 "X-Priority: 1 (Highest)\n");
6657 case PRIORITY_HIGH: g_string_append_printf(header, "Importance: high\n"
6658 "X-Priority: 2 (High)\n");
6660 case PRIORITY_NORMAL: break;
6661 case PRIORITY_LOW: g_string_append_printf(header, "Importance: low\n"
6662 "X-Priority: 4 (Low)\n");
6664 case PRIORITY_LOWEST: g_string_append_printf(header, "Importance: low\n"
6665 "X-Priority: 5 (Lowest)\n");
6667 default: debug_print("compose: priority unknown : %d\n",
6671 /* get special headers */
6672 for (list = compose->header_list; list; list = list->next) {
6673 ComposeHeaderEntry *headerentry;
6676 gchar *headername_wcolon;
6677 const gchar *headername_trans;
6680 gboolean standard_header = FALSE;
6682 headerentry = ((ComposeHeaderEntry *)list->data);
6684 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6686 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6691 if (!strstr(tmp, ":")) {
6692 headername_wcolon = g_strconcat(tmp, ":", NULL);
6693 headername = g_strdup(tmp);
6695 headername_wcolon = g_strdup(tmp);
6696 headername = g_strdup(strtok(tmp, ":"));
6700 entry_str = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
6701 Xstrdup_a(headervalue, entry_str, return NULL);
6702 subst_char(headervalue, '\r', ' ');
6703 subst_char(headervalue, '\n', ' ');
6704 string = std_headers;
6705 while (*string != NULL) {
6706 headername_trans = prefs_common_translated_header_name(*string);
6707 if (!strcmp(headername_trans, headername_wcolon))
6708 standard_header = TRUE;
6711 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
6712 g_string_append_printf(header, "%s %s\n", headername_wcolon, headervalue);
6715 g_free(headername_wcolon);
6719 g_string_free(header, FALSE);
6724 #undef IS_IN_CUSTOM_HEADER
6726 static void compose_convert_header(Compose *compose, gchar *dest, gint len, gchar *src,
6727 gint header_len, gboolean addr_field)
6729 gchar *tmpstr = NULL;
6730 const gchar *out_codeset = NULL;
6732 cm_return_if_fail(src != NULL);
6733 cm_return_if_fail(dest != NULL);
6735 if (len < 1) return;
6737 tmpstr = g_strdup(src);
6739 subst_char(tmpstr, '\n', ' ');
6740 subst_char(tmpstr, '\r', ' ');
6743 if (!g_utf8_validate(tmpstr, -1, NULL)) {
6744 gchar *mybuf = g_malloc(strlen(tmpstr)*2 +1);
6745 conv_localetodisp(mybuf, strlen(tmpstr)*2 +1, tmpstr);
6750 codeconv_set_strict(TRUE);
6751 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6752 conv_get_charset_str(compose->out_encoding));
6753 codeconv_set_strict(FALSE);
6755 if (!dest || *dest == '\0') {
6756 gchar *test_conv_global_out = NULL;
6757 gchar *test_conv_reply = NULL;
6759 /* automatic mode. be automatic. */
6760 codeconv_set_strict(TRUE);
6762 out_codeset = conv_get_outgoing_charset_str();
6764 debug_print("trying to convert to %s\n", out_codeset);
6765 test_conv_global_out = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6768 if (!test_conv_global_out && compose->orig_charset
6769 && strcmp(compose->orig_charset, CS_US_ASCII)) {
6770 out_codeset = compose->orig_charset;
6771 debug_print("failure; trying to convert to %s\n", out_codeset);
6772 test_conv_reply = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6775 if (!test_conv_global_out && !test_conv_reply) {
6777 out_codeset = CS_INTERNAL;
6778 debug_print("finally using %s\n", out_codeset);
6780 g_free(test_conv_global_out);
6781 g_free(test_conv_reply);
6782 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6784 codeconv_set_strict(FALSE);
6789 static void compose_add_to_addressbook_cb(GtkMenuItem *menuitem, gpointer user_data)
6793 cm_return_if_fail(user_data != NULL);
6795 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(user_data)));
6796 g_strstrip(address);
6797 if (*address != '\0') {
6798 gchar *name = procheader_get_fromname(address);
6799 extract_address(address);
6800 #ifndef USE_ALT_ADDRBOOK
6801 addressbook_add_contact(name, address, NULL, NULL);
6803 debug_print("%s: %s\n", name, address);
6804 if (addressadd_selection(name, address, NULL, NULL)) {
6805 debug_print( "addressbook_add_contact - added\n" );
6812 static void compose_entry_popup_extend(GtkEntry *entry, GtkMenu *menu, gpointer user_data)
6814 GtkWidget *menuitem;
6817 cm_return_if_fail(menu != NULL);
6818 cm_return_if_fail(GTK_IS_MENU_SHELL(menu));
6820 menuitem = gtk_separator_menu_item_new();
6821 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6822 gtk_widget_show(menuitem);
6824 menuitem = gtk_menu_item_new_with_mnemonic(_("Add to address _book"));
6825 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6827 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
6828 g_strstrip(address);
6829 if (*address == '\0') {
6830 gtk_widget_set_sensitive(GTK_WIDGET(menuitem), FALSE);
6833 g_signal_connect(G_OBJECT(menuitem), "activate",
6834 G_CALLBACK(compose_add_to_addressbook_cb), entry);
6835 gtk_widget_show(menuitem);
6838 void compose_add_extra_header(gchar *header, GtkListStore *model)
6841 if (strcmp(header, "")) {
6842 COMBOBOX_ADD(model, header, COMPOSE_TO);
6846 void compose_add_extra_header_entries(GtkListStore *model)
6850 gchar buf[BUFFSIZE];
6853 if (extra_headers == NULL) {
6854 exhrc = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, "extraheaderrc", NULL);
6855 if ((exh = g_fopen(exhrc, "rb")) == NULL) {
6856 debug_print("extra headers file not found\n");
6857 goto extra_headers_done;
6859 while (fgets(buf, BUFFSIZE, exh) != NULL) {
6860 lastc = strlen(buf) - 1; /* remove trailing control chars */
6861 while (lastc >= 0 && buf[lastc] != ':')
6862 buf[lastc--] = '\0';
6863 if (lastc > 0 && buf[0] != '#' && buf[lastc] == ':') {
6864 buf[lastc] = '\0'; /* remove trailing : for comparison */
6865 if (custom_header_is_allowed(buf)) {
6867 extra_headers = g_slist_prepend(extra_headers, g_strdup(buf));
6870 g_message("disallowed extra header line: %s\n", buf);
6874 g_message("invalid extra header line: %s\n", buf);
6880 extra_headers = g_slist_prepend(extra_headers, g_strdup("")); /* end of list */
6881 extra_headers = g_slist_reverse(extra_headers);
6883 g_slist_foreach(extra_headers, (GFunc)compose_add_extra_header, (gpointer)model);
6886 static void compose_create_header_entry(Compose *compose)
6888 gchar *headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6895 const gchar *header = NULL;
6896 ComposeHeaderEntry *headerentry;
6897 gboolean standard_header = FALSE;
6898 GtkListStore *model;
6901 headerentry = g_new0(ComposeHeaderEntry, 1);
6903 /* Combo box model */
6904 model = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_BOOLEAN);
6905 #if !GTK_CHECK_VERSION(2, 24, 0)
6906 combo = gtk_combo_box_entry_new_with_model(GTK_TREE_MODEL(model), 0);
6908 COMBOBOX_ADD(model, prefs_common_translated_header_name("To:"),
6910 COMBOBOX_ADD(model, prefs_common_translated_header_name("Cc:"),
6912 COMBOBOX_ADD(model, prefs_common_translated_header_name("Bcc:"),
6914 COMBOBOX_ADD(model, prefs_common_translated_header_name("Newsgroups:"),
6915 COMPOSE_NEWSGROUPS);
6916 COMBOBOX_ADD(model, prefs_common_translated_header_name("Reply-To:"),
6918 COMBOBOX_ADD(model, prefs_common_translated_header_name("Followup-To:"),
6919 COMPOSE_FOLLOWUPTO);
6920 compose_add_extra_header_entries(model);
6923 #if GTK_CHECK_VERSION(2, 24, 0)
6924 combo = gtk_combo_box_new_with_model_and_entry(GTK_TREE_MODEL(model));
6925 GtkCellRenderer *cell = gtk_cell_renderer_text_new();
6926 gtk_cell_renderer_set_alignment(cell, 0.0, 0.5);
6927 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), cell, TRUE);
6928 gtk_combo_box_set_entry_text_column(GTK_COMBO_BOX(combo), 0);
6930 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
6931 g_signal_connect(G_OBJECT(gtk_bin_get_child(GTK_BIN(combo))), "grab_focus",
6932 G_CALLBACK(compose_grab_focus_cb), compose);
6933 gtk_widget_show(combo);
6935 /* Putting only the combobox child into focus chain of its parent causes
6936 * the parent to be skipped when changing focus via Tab or Shift+Tab.
6937 * This eliminates need to pres Tab twice in order to really get from the
6938 * combobox to next widget. */
6940 l = g_list_prepend(l, gtk_bin_get_child(GTK_BIN(combo)));
6941 gtk_container_set_focus_chain(GTK_CONTAINER(combo), l);
6944 gtk_table_attach(GTK_TABLE(compose->header_table), combo, 0, 1,
6945 compose->header_nextrow, compose->header_nextrow+1,
6946 GTK_SHRINK, GTK_FILL, 0, 0);
6947 if (compose->header_last && (compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN)) {
6948 const gchar *last_header_entry = gtk_entry_get_text(
6949 GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
6951 while (*string != NULL) {
6952 if (!strcmp(prefs_common_translated_header_name(*string), last_header_entry))
6953 standard_header = TRUE;
6956 if (standard_header)
6957 header = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
6959 if (!compose->header_last || !standard_header) {
6960 switch(compose->account->protocol) {
6962 header = prefs_common_translated_header_name("Newsgroups:");
6965 header = prefs_common_translated_header_name("To:");
6970 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((combo)))), header);
6972 gtk_editable_set_editable(
6973 GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((combo)))),
6974 prefs_common.type_any_header);
6976 g_signal_connect_after(G_OBJECT(gtk_bin_get_child(GTK_BIN((combo)))), "grab_focus",
6977 G_CALLBACK(compose_grab_focus_cb), compose);
6979 /* Entry field with cleanup button */
6980 button = gtk_button_new();
6981 gtk_button_set_image(GTK_BUTTON(button),
6982 gtk_image_new_from_stock(GTK_STOCK_CLEAR, GTK_ICON_SIZE_MENU));
6983 gtk_widget_show(button);
6984 CLAWS_SET_TIP(button,
6985 _("Delete entry contents"));
6986 entry = gtk_entry_new();
6987 gtk_widget_show(entry);
6988 CLAWS_SET_TIP(entry,
6989 _("Use <tab> to autocomplete from addressbook"));
6990 hbox = gtk_hbox_new (FALSE, 0);
6991 gtk_widget_show(hbox);
6992 gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, TRUE, 0);
6993 gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
6994 gtk_table_attach(GTK_TABLE(compose->header_table), hbox, 1, 2,
6995 compose->header_nextrow, compose->header_nextrow+1,
6996 GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
6998 g_signal_connect(G_OBJECT(entry), "key-press-event",
6999 G_CALLBACK(compose_headerentry_key_press_event_cb),
7001 g_signal_connect(G_OBJECT(entry), "changed",
7002 G_CALLBACK(compose_headerentry_changed_cb),
7004 g_signal_connect_after(G_OBJECT(entry), "grab_focus",
7005 G_CALLBACK(compose_grab_focus_cb), compose);
7007 g_signal_connect(G_OBJECT(button), "clicked",
7008 G_CALLBACK(compose_headerentry_button_clicked_cb),
7012 gtk_drag_dest_set(entry, GTK_DEST_DEFAULT_ALL, compose_mime_types,
7013 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7014 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7015 g_signal_connect(G_OBJECT(entry), "drag_data_received",
7016 G_CALLBACK(compose_header_drag_received_cb),
7018 g_signal_connect(G_OBJECT(entry), "drag-drop",
7019 G_CALLBACK(compose_drag_drop),
7021 g_signal_connect(G_OBJECT(entry), "populate-popup",
7022 G_CALLBACK(compose_entry_popup_extend),
7025 address_completion_register_entry(GTK_ENTRY(entry), TRUE);
7027 headerentry->compose = compose;
7028 headerentry->combo = combo;
7029 headerentry->entry = entry;
7030 headerentry->button = button;
7031 headerentry->hbox = hbox;
7032 headerentry->headernum = compose->header_nextrow;
7033 headerentry->type = PREF_NONE;
7035 compose->header_nextrow++;
7036 compose->header_last = headerentry;
7037 compose->header_list =
7038 g_slist_append(compose->header_list,
7042 static void compose_add_header_entry(Compose *compose, const gchar *header,
7043 gchar *text, ComposePrefType pref_type)
7045 ComposeHeaderEntry *last_header = compose->header_last;
7046 gchar *tmp = g_strdup(text), *email;
7047 gboolean replyto_hdr;
7049 replyto_hdr = (!strcasecmp(header,
7050 prefs_common_translated_header_name("Reply-To:")) ||
7052 prefs_common_translated_header_name("Followup-To:")) ||
7054 prefs_common_translated_header_name("In-Reply-To:")));
7056 extract_address(tmp);
7057 email = g_utf8_strdown(tmp, -1);
7059 if (replyto_hdr == FALSE &&
7060 g_hash_table_lookup(compose->email_hashtable, email) != NULL)
7062 debug_print("Ignoring duplicate address - %s %s, pref_type: %d\n",
7063 header, text, (gint) pref_type);
7069 if (!strcasecmp(header, prefs_common_translated_header_name("In-Reply-To:")))
7070 gtk_entry_set_text(GTK_ENTRY(
7071 gtk_bin_get_child(GTK_BIN(last_header->combo))), header);
7073 combobox_select_by_text(GTK_COMBO_BOX(last_header->combo), header);
7074 gtk_entry_set_text(GTK_ENTRY(last_header->entry), text);
7075 last_header->type = pref_type;
7077 if (replyto_hdr == FALSE)
7078 g_hash_table_insert(compose->email_hashtable, email,
7079 GUINT_TO_POINTER(1));
7086 static void compose_destroy_headerentry(Compose *compose,
7087 ComposeHeaderEntry *headerentry)
7089 gchar *text = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
7092 extract_address(text);
7093 email = g_utf8_strdown(text, -1);
7094 g_hash_table_remove(compose->email_hashtable, email);
7098 gtk_widget_destroy(headerentry->combo);
7099 gtk_widget_destroy(headerentry->entry);
7100 gtk_widget_destroy(headerentry->button);
7101 gtk_widget_destroy(headerentry->hbox);
7102 g_free(headerentry);
7105 static void compose_remove_header_entries(Compose *compose)
7108 for (list = compose->header_list; list; list = list->next)
7109 compose_destroy_headerentry(compose, (ComposeHeaderEntry *)list->data);
7111 compose->header_last = NULL;
7112 g_slist_free(compose->header_list);
7113 compose->header_list = NULL;
7114 compose->header_nextrow = 1;
7115 compose_create_header_entry(compose);
7118 static GtkWidget *compose_create_header(Compose *compose)
7120 GtkWidget *from_optmenu_hbox;
7121 GtkWidget *header_table_main;
7122 GtkWidget *header_scrolledwin;
7123 GtkWidget *header_table;
7125 /* parent with account selection and from header */
7126 header_table_main = gtk_table_new(2, 2, FALSE);
7127 gtk_widget_show(header_table_main);
7128 gtk_container_set_border_width(GTK_CONTAINER(header_table_main), BORDER_WIDTH);
7130 from_optmenu_hbox = compose_account_option_menu_create(compose);
7131 gtk_table_attach(GTK_TABLE(header_table_main), from_optmenu_hbox,
7132 0, 2, 0, 1, GTK_EXPAND | GTK_FILL, GTK_SHRINK, 0, 0);
7134 /* child with header labels and entries */
7135 header_scrolledwin = gtk_scrolled_window_new(NULL, NULL);
7136 gtk_widget_show(header_scrolledwin);
7137 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
7139 header_table = gtk_table_new(2, 2, FALSE);
7140 gtk_widget_show(header_table);
7141 gtk_container_set_border_width(GTK_CONTAINER(header_table), 0);
7142 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin), header_table);
7143 gtk_container_set_focus_vadjustment(GTK_CONTAINER(header_table),
7144 gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(header_scrolledwin)));
7145 gtk_viewport_set_shadow_type(GTK_VIEWPORT(gtk_bin_get_child(GTK_BIN(header_scrolledwin))), GTK_SHADOW_NONE);
7147 gtk_table_attach(GTK_TABLE(header_table_main), header_scrolledwin,
7148 0, 2, 1, 2, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 2);
7150 compose->header_table = header_table;
7151 compose->header_list = NULL;
7152 compose->header_nextrow = 0;
7154 compose_create_header_entry(compose);
7156 compose->table = NULL;
7158 return header_table_main;
7161 static gboolean popup_attach_button_pressed(GtkWidget *widget, gpointer data)
7163 Compose *compose = (Compose *)data;
7164 GdkEventButton event;
7167 event.time = gtk_get_current_event_time();
7169 return attach_button_pressed(compose->attach_clist, &event, compose);
7172 static GtkWidget *compose_create_attach(Compose *compose)
7174 GtkWidget *attach_scrwin;
7175 GtkWidget *attach_clist;
7177 GtkListStore *store;
7178 GtkCellRenderer *renderer;
7179 GtkTreeViewColumn *column;
7180 GtkTreeSelection *selection;
7182 /* attachment list */
7183 attach_scrwin = gtk_scrolled_window_new(NULL, NULL);
7184 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(attach_scrwin),
7185 GTK_POLICY_AUTOMATIC,
7186 GTK_POLICY_AUTOMATIC);
7187 gtk_widget_set_size_request(attach_scrwin, -1, 80);
7189 store = gtk_list_store_new(N_ATTACH_COLS,
7195 G_TYPE_AUTO_POINTER,
7197 attach_clist = GTK_WIDGET(gtk_tree_view_new_with_model
7198 (GTK_TREE_MODEL(store)));
7199 gtk_container_add(GTK_CONTAINER(attach_scrwin), attach_clist);
7200 g_object_unref(store);
7202 renderer = gtk_cell_renderer_text_new();
7203 column = gtk_tree_view_column_new_with_attributes
7204 (_("Mime type"), renderer, "text",
7205 COL_MIMETYPE, NULL);
7206 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7208 renderer = gtk_cell_renderer_text_new();
7209 column = gtk_tree_view_column_new_with_attributes
7210 (_("Size"), renderer, "text",
7212 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7214 renderer = gtk_cell_renderer_text_new();
7215 column = gtk_tree_view_column_new_with_attributes
7216 (_("Name"), renderer, "text",
7218 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7220 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(attach_clist),
7221 prefs_common.use_stripes_everywhere);
7222 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(attach_clist));
7223 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
7225 g_signal_connect(G_OBJECT(attach_clist), "row_activated",
7226 G_CALLBACK(attach_selected), compose);
7227 g_signal_connect(G_OBJECT(attach_clist), "button_press_event",
7228 G_CALLBACK(attach_button_pressed), compose);
7229 g_signal_connect(G_OBJECT(attach_clist), "popup-menu",
7230 G_CALLBACK(popup_attach_button_pressed), compose);
7231 g_signal_connect(G_OBJECT(attach_clist), "key_press_event",
7232 G_CALLBACK(attach_key_pressed), compose);
7235 gtk_drag_dest_set(attach_clist,
7236 GTK_DEST_DEFAULT_ALL, compose_mime_types,
7237 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7238 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7239 g_signal_connect(G_OBJECT(attach_clist), "drag_data_received",
7240 G_CALLBACK(compose_attach_drag_received_cb),
7242 g_signal_connect(G_OBJECT(attach_clist), "drag-drop",
7243 G_CALLBACK(compose_drag_drop),
7246 compose->attach_scrwin = attach_scrwin;
7247 compose->attach_clist = attach_clist;
7249 return attach_scrwin;
7252 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose);
7253 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose);
7255 static GtkWidget *compose_create_others(Compose *compose)
7258 GtkWidget *savemsg_checkbtn;
7259 GtkWidget *savemsg_combo;
7260 GtkWidget *savemsg_select;
7263 gchar *folderidentifier;
7265 /* Table for settings */
7266 table = gtk_table_new(3, 1, FALSE);
7267 gtk_container_set_border_width(GTK_CONTAINER(table), BORDER_WIDTH);
7268 gtk_widget_show(table);
7269 gtk_table_set_row_spacings(GTK_TABLE(table), VSPACING_NARROW);
7272 /* Save Message to folder */
7273 savemsg_checkbtn = gtk_check_button_new_with_label(_("Save Message to "));
7274 gtk_widget_show(savemsg_checkbtn);
7275 gtk_table_attach(GTK_TABLE(table), savemsg_checkbtn, 0, 1, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7276 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7277 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), prefs_common.savemsg);
7279 g_signal_connect(G_OBJECT(savemsg_checkbtn), "toggled",
7280 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
7282 #if !GTK_CHECK_VERSION(2, 24, 0)
7283 savemsg_combo = gtk_combo_box_entry_new_text();
7285 savemsg_combo = gtk_combo_box_text_new_with_entry();
7287 compose->savemsg_checkbtn = savemsg_checkbtn;
7288 compose->savemsg_combo = savemsg_combo;
7289 gtk_widget_show(savemsg_combo);
7291 if (prefs_common.compose_save_to_history)
7292 #if !GTK_CHECK_VERSION(2, 24, 0)
7293 combobox_set_popdown_strings(GTK_COMBO_BOX(savemsg_combo),
7294 prefs_common.compose_save_to_history);
7296 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(savemsg_combo),
7297 prefs_common.compose_save_to_history);
7299 gtk_table_attach(GTK_TABLE(table), savemsg_combo, 1, 2, rowcount, rowcount + 1, GTK_FILL|GTK_EXPAND, GTK_SHRINK, 0, 0);
7300 gtk_widget_set_sensitive(GTK_WIDGET(savemsg_combo), prefs_common.savemsg);
7301 g_signal_connect_after(G_OBJECT(savemsg_combo), "grab_focus",
7302 G_CALLBACK(compose_grab_focus_cb), compose);
7303 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7304 folderidentifier = folder_item_get_identifier(account_get_special_folder
7305 (compose->account, F_OUTBOX));
7306 compose_set_save_to(compose, folderidentifier);
7307 g_free(folderidentifier);
7310 savemsg_select = gtkut_get_browse_file_btn(_("_Browse"));
7311 gtk_widget_show(savemsg_select);
7312 gtk_table_attach(GTK_TABLE(table), savemsg_select, 2, 3, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7313 g_signal_connect(G_OBJECT(savemsg_select), "clicked",
7314 G_CALLBACK(compose_savemsg_select_cb),
7320 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose)
7322 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo),
7323 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn)));
7326 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose)
7331 dest = foldersel_folder_sel(NULL, FOLDER_SEL_COPY, NULL, FALSE);
7334 path = folder_item_get_identifier(dest);
7336 compose_set_save_to(compose, path);
7340 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry, gboolean wrap,
7341 GdkAtom clip, GtkTextIter *insert_place);
7344 static gboolean text_clicked(GtkWidget *text, GdkEventButton *event,
7348 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
7350 if (event->button == 3) {
7352 GtkTextIter sel_start, sel_end;
7353 gboolean stuff_selected;
7355 /* move the cursor to allow GtkAspell to check the word
7356 * under the mouse */
7357 if (event->x && event->y) {
7358 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7359 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7361 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7364 GtkTextMark *mark = gtk_text_buffer_get_insert(buffer);
7365 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7368 stuff_selected = gtk_text_buffer_get_selection_bounds(
7370 &sel_start, &sel_end);
7372 gtk_text_buffer_place_cursor (buffer, &iter);
7373 /* reselect stuff */
7375 && gtk_text_iter_in_range(&iter, &sel_start, &sel_end)) {
7376 gtk_text_buffer_select_range(buffer,
7377 &sel_start, &sel_end);
7379 return FALSE; /* pass the event so that the right-click goes through */
7382 if (event->button == 2) {
7387 /* get the middle-click position to paste at the correct place */
7388 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7389 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7391 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7394 entry_paste_clipboard(compose, text,
7395 prefs_common.linewrap_pastes,
7396 GDK_SELECTION_PRIMARY, &iter);
7404 static void compose_spell_menu_changed(void *data)
7406 Compose *compose = (Compose *)data;
7408 GtkWidget *menuitem;
7409 GtkWidget *parent_item;
7410 GtkMenu *menu = GTK_MENU(gtk_menu_new());
7413 if (compose->gtkaspell == NULL)
7416 parent_item = gtk_ui_manager_get_widget(compose->ui_manager,
7417 "/Menu/Spelling/Options");
7419 /* setting the submenu removes /Spelling/Options from the factory
7420 * so we need to save it */
7422 if (parent_item == NULL) {
7423 parent_item = compose->aspell_options_menu;
7424 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), NULL);
7426 compose->aspell_options_menu = parent_item;
7428 spell_menu = gtkaspell_make_config_menu(compose->gtkaspell);
7430 spell_menu = g_slist_reverse(spell_menu);
7431 for (items = spell_menu;
7432 items; items = items->next) {
7433 menuitem = GTK_WIDGET(GTK_MENU_ITEM(items->data));
7434 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), GTK_WIDGET(menuitem));
7435 gtk_widget_show(GTK_WIDGET(menuitem));
7437 g_slist_free(spell_menu);
7439 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), GTK_WIDGET(menu));
7440 gtk_widget_show(parent_item);
7443 static void compose_dict_changed(void *data)
7445 Compose *compose = (Compose *) data;
7447 if(!compose->gtkaspell)
7449 if(compose->gtkaspell->recheck_when_changing_dict == FALSE)
7452 gtkaspell_highlight_all(compose->gtkaspell);
7453 claws_spell_entry_recheck_all(CLAWS_SPELL_ENTRY(compose->subject_entry));
7457 static gboolean compose_popup_menu(GtkWidget *widget, gpointer data)
7459 Compose *compose = (Compose *)data;
7460 GdkEventButton event;
7463 event.time = gtk_get_current_event_time();
7467 return text_clicked(compose->text, &event, compose);
7470 static gboolean compose_force_window_origin = TRUE;
7471 static Compose *compose_create(PrefsAccount *account,
7480 GtkWidget *handlebox;
7482 GtkWidget *notebook;
7484 GtkWidget *attach_hbox;
7485 GtkWidget *attach_lab1;
7486 GtkWidget *attach_lab2;
7491 GtkWidget *subject_hbox;
7492 GtkWidget *subject_frame;
7493 GtkWidget *subject_entry;
7497 GtkWidget *edit_vbox;
7498 GtkWidget *ruler_hbox;
7500 GtkWidget *scrolledwin;
7502 GtkTextBuffer *buffer;
7503 GtkClipboard *clipboard;
7505 UndoMain *undostruct;
7507 GtkWidget *popupmenu;
7508 GtkWidget *tmpl_menu;
7509 GtkActionGroup *action_group = NULL;
7512 GtkAspell * gtkaspell = NULL;
7515 static GdkGeometry geometry;
7517 cm_return_val_if_fail(account != NULL, NULL);
7519 debug_print("Creating compose window...\n");
7520 compose = g_new0(Compose, 1);
7522 compose->batch = batch;
7523 compose->account = account;
7524 compose->folder = folder;
7526 compose->mutex = cm_mutex_new();
7527 compose->set_cursor_pos = -1;
7529 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose");
7531 gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
7532 gtk_widget_set_size_request(window, prefs_common.compose_width,
7533 prefs_common.compose_height);
7535 if (!geometry.max_width) {
7536 geometry.max_width = gdk_screen_width();
7537 geometry.max_height = gdk_screen_height();
7540 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7541 &geometry, GDK_HINT_MAX_SIZE);
7542 if (!geometry.min_width) {
7543 geometry.min_width = 600;
7544 geometry.min_height = 440;
7546 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7547 &geometry, GDK_HINT_MIN_SIZE);
7549 #ifndef GENERIC_UMPC
7550 if (compose_force_window_origin)
7551 gtk_window_move(GTK_WINDOW(window), prefs_common.compose_x,
7552 prefs_common.compose_y);
7554 g_signal_connect(G_OBJECT(window), "delete_event",
7555 G_CALLBACK(compose_delete_cb), compose);
7556 MANAGE_WINDOW_SIGNALS_CONNECT(window);
7557 gtk_widget_realize(window);
7559 gtkut_widget_set_composer_icon(window);
7561 vbox = gtk_vbox_new(FALSE, 0);
7562 gtk_container_add(GTK_CONTAINER(window), vbox);
7564 compose->ui_manager = gtk_ui_manager_new();
7565 action_group = cm_menu_create_action_group_full(compose->ui_manager,"Menu", compose_entries,
7566 G_N_ELEMENTS(compose_entries), (gpointer)compose);
7567 gtk_action_group_add_toggle_actions(action_group, compose_toggle_entries,
7568 G_N_ELEMENTS(compose_toggle_entries), (gpointer)compose);
7569 gtk_action_group_add_radio_actions(action_group, compose_radio_rm_entries,
7570 G_N_ELEMENTS(compose_radio_rm_entries), COMPOSE_REPLY, G_CALLBACK(compose_reply_change_mode_cb), (gpointer)compose);
7571 gtk_action_group_add_radio_actions(action_group, compose_radio_prio_entries,
7572 G_N_ELEMENTS(compose_radio_prio_entries), PRIORITY_NORMAL, G_CALLBACK(compose_set_priority_cb), (gpointer)compose);
7573 gtk_action_group_add_radio_actions(action_group, compose_radio_enc_entries,
7574 G_N_ELEMENTS(compose_radio_enc_entries), C_AUTO, G_CALLBACK(compose_set_encoding_cb), (gpointer)compose);
7576 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Menu", NULL, GTK_UI_MANAGER_MENUBAR)
7578 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Message", "Message", GTK_UI_MANAGER_MENU)
7579 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Edit", "Edit", GTK_UI_MANAGER_MENU)
7581 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Spelling", "Spelling", GTK_UI_MANAGER_MENU)
7583 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Options", "Options", GTK_UI_MANAGER_MENU)
7584 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Tools", "Tools", GTK_UI_MANAGER_MENU)
7585 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Help", "Help", GTK_UI_MANAGER_MENU)
7588 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Send", "Message/Send", GTK_UI_MANAGER_MENUITEM)
7589 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "SendLater", "Message/SendLater", GTK_UI_MANAGER_MENUITEM)
7590 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator1", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7591 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "AttachFile", "Message/AttachFile", GTK_UI_MANAGER_MENUITEM)
7592 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertFile", "Message/InsertFile", GTK_UI_MANAGER_MENUITEM)
7593 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertSig", "Message/InsertSig", GTK_UI_MANAGER_MENUITEM)
7594 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "ReplaceSig", "Message/ReplaceSig", GTK_UI_MANAGER_MENUITEM)
7595 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator2", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7596 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Save", "Message/Save", GTK_UI_MANAGER_MENUITEM)
7597 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator3", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7598 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Print", "Message/Print", GTK_UI_MANAGER_MENUITEM)
7599 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator4", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7600 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Close", "Message/Close", GTK_UI_MANAGER_MENUITEM)
7603 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Undo", "Edit/Undo", GTK_UI_MANAGER_MENUITEM)
7604 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Redo", "Edit/Redo", GTK_UI_MANAGER_MENUITEM)
7605 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator1", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7607 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Cut", "Edit/Cut", GTK_UI_MANAGER_MENUITEM)
7608 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Copy", "Edit/Copy", GTK_UI_MANAGER_MENUITEM)
7609 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Paste", "Edit/Paste", GTK_UI_MANAGER_MENUITEM)
7611 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SpecialPaste", "Edit/SpecialPaste", GTK_UI_MANAGER_MENU)
7612 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "AsQuotation", "Edit/SpecialPaste/AsQuotation", GTK_UI_MANAGER_MENUITEM)
7613 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Wrapped", "Edit/SpecialPaste/Wrapped", GTK_UI_MANAGER_MENUITEM)
7614 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Unwrapped", "Edit/SpecialPaste/Unwrapped", GTK_UI_MANAGER_MENUITEM)
7616 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SelectAll", "Edit/SelectAll", GTK_UI_MANAGER_MENUITEM)
7618 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Advanced", "Edit/Advanced", GTK_UI_MANAGER_MENU)
7619 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackChar", "Edit/Advanced/BackChar", GTK_UI_MANAGER_MENUITEM)
7620 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwChar", "Edit/Advanced/ForwChar", GTK_UI_MANAGER_MENUITEM)
7621 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackWord", "Edit/Advanced/BackWord", GTK_UI_MANAGER_MENUITEM)
7622 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwWord", "Edit/Advanced/ForwWord", GTK_UI_MANAGER_MENUITEM)
7623 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BegLine", "Edit/Advanced/BegLine", GTK_UI_MANAGER_MENUITEM)
7624 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "EndLine", "Edit/Advanced/EndLine", GTK_UI_MANAGER_MENUITEM)
7625 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "PrevLine", "Edit/Advanced/PrevLine", GTK_UI_MANAGER_MENUITEM)
7626 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "NextLine", "Edit/Advanced/NextLine", GTK_UI_MANAGER_MENUITEM)
7627 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackChar", "Edit/Advanced/DelBackChar", GTK_UI_MANAGER_MENUITEM)
7628 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwChar", "Edit/Advanced/DelForwChar", GTK_UI_MANAGER_MENUITEM)
7629 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackWord", "Edit/Advanced/DelBackWord", GTK_UI_MANAGER_MENUITEM)
7630 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwWord", "Edit/Advanced/DelForwWord", GTK_UI_MANAGER_MENUITEM)
7631 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelLine", "Edit/Advanced/DelLine", GTK_UI_MANAGER_MENUITEM)
7632 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelEndLine", "Edit/Advanced/DelEndLine", GTK_UI_MANAGER_MENUITEM)
7634 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator2", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7636 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Find", "Edit/Find", GTK_UI_MANAGER_MENUITEM)
7637 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapPara", "Edit/WrapPara", GTK_UI_MANAGER_MENUITEM)
7638 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapAllLines", "Edit/WrapAllLines", GTK_UI_MANAGER_MENUITEM)
7639 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoWrap", "Edit/AutoWrap", GTK_UI_MANAGER_MENUITEM)
7640 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoIndent", "Edit/AutoIndent", GTK_UI_MANAGER_MENUITEM)
7642 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator3", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7644 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "ExtEditor", "Edit/ExtEditor", GTK_UI_MANAGER_MENUITEM)
7648 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckAllSel", "Spelling/CheckAllSel", GTK_UI_MANAGER_MENUITEM)
7649 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "HighlightAll", "Spelling/HighlightAll", GTK_UI_MANAGER_MENUITEM)
7650 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckBackwards", "Spelling/CheckBackwards", GTK_UI_MANAGER_MENUITEM)
7651 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "ForwardNext", "Spelling/ForwardNext", GTK_UI_MANAGER_MENUITEM)
7652 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Separator1", "Spelling/---", GTK_UI_MANAGER_SEPARATOR)
7653 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Options", "Spelling/Options", GTK_UI_MANAGER_MENU)
7657 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "ReplyMode", "Options/ReplyMode", GTK_UI_MANAGER_MENU)
7658 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Normal", "Options/ReplyMode/Normal", GTK_UI_MANAGER_MENUITEM)
7659 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "All", "Options/ReplyMode/All", GTK_UI_MANAGER_MENUITEM)
7660 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Sender", "Options/ReplyMode/Sender", GTK_UI_MANAGER_MENUITEM)
7661 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "List", "Options/ReplyMode/List", GTK_UI_MANAGER_MENUITEM)
7663 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator1", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7664 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "PrivacySystem", "Options/PrivacySystem", GTK_UI_MANAGER_MENU)
7665 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/PrivacySystem", "PlaceHolder", "Options/PrivacySystem/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7666 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Sign", "Options/Sign", GTK_UI_MANAGER_MENUITEM)
7667 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encrypt", "Options/Encrypt", GTK_UI_MANAGER_MENUITEM)
7670 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator2", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7671 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Priority", "Options/Priority", GTK_UI_MANAGER_MENU)
7672 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Highest", "Options/Priority/Highest", GTK_UI_MANAGER_MENUITEM)
7673 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "High", "Options/Priority/High", GTK_UI_MANAGER_MENUITEM)
7674 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Normal", "Options/Priority/Normal", GTK_UI_MANAGER_MENUITEM)
7675 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Low", "Options/Priority/Low", GTK_UI_MANAGER_MENUITEM)
7676 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Lowest", "Options/Priority/Lowest", GTK_UI_MANAGER_MENUITEM)
7678 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator3", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7679 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RequestRetRcpt", "Options/RequestRetRcpt", GTK_UI_MANAGER_MENUITEM)
7680 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator4", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7681 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RemoveReferences", "Options/RemoveReferences", GTK_UI_MANAGER_MENUITEM)
7682 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator5", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7684 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encoding", "Options/Encoding", GTK_UI_MANAGER_MENU)
7686 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_AUTO, "Options/Encoding/"CS_AUTO, GTK_UI_MANAGER_MENUITEM)
7687 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator1", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7688 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_US_ASCII, "Options/Encoding/"CS_US_ASCII, GTK_UI_MANAGER_MENUITEM)
7689 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_UTF_8, "Options/Encoding/"CS_UTF_8, GTK_UI_MANAGER_MENUITEM)
7690 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator2", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7692 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Western", "Options/Encoding/Western", GTK_UI_MANAGER_MENU)
7693 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)
7694 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)
7695 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Western", CS_WINDOWS_1252, "Options/Encoding/Western/"CS_WINDOWS_1252, GTK_UI_MANAGER_MENUITEM)
7697 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_2, "Options/Encoding/"CS_ISO_8859_2, GTK_UI_MANAGER_MENUITEM)
7699 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Baltic", "Options/Encoding/Baltic", GTK_UI_MANAGER_MENU)
7700 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)
7701 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)
7703 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_7, "Options/Encoding/"CS_ISO_8859_7, GTK_UI_MANAGER_MENUITEM)
7705 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Hebrew", "Options/Encoding/Hebrew", GTK_UI_MANAGER_MENU)
7706 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)
7707 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Hebrew", CS_WINDOWS_1255, "Options/Encoding/Hebrew/"CS_WINDOWS_1255, GTK_UI_MANAGER_MENUITEM)
7709 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Arabic", "Options/Encoding/Arabic", GTK_UI_MANAGER_MENU)
7710 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)
7711 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Arabic", CS_WINDOWS_1256, "Options/Encoding/Arabic/"CS_WINDOWS_1256, GTK_UI_MANAGER_MENUITEM)
7713 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_9, "Options/Encoding/"CS_ISO_8859_9, GTK_UI_MANAGER_MENUITEM)
7715 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Cyrillic", "Options/Encoding/Cyrillic", GTK_UI_MANAGER_MENU)
7716 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)
7717 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_R, "Options/Encoding/Cyrillic/"CS_KOI8_R, GTK_UI_MANAGER_MENUITEM)
7718 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_MACCYR, "Options/Encoding/Cyrillic/"CS_MACCYR, GTK_UI_MANAGER_MENUITEM)
7719 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_U, "Options/Encoding/Cyrillic/"CS_KOI8_U, GTK_UI_MANAGER_MENUITEM)
7720 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_WINDOWS_1251, "Options/Encoding/Cyrillic/"CS_WINDOWS_1251, GTK_UI_MANAGER_MENUITEM)
7722 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Japanese", "Options/Encoding/Japanese", GTK_UI_MANAGER_MENU)
7723 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)
7724 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)
7725 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_EUC_JP, "Options/Encoding/Japanese/"CS_EUC_JP, GTK_UI_MANAGER_MENUITEM)
7726 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_SHIFT_JIS, "Options/Encoding/Japanese/"CS_SHIFT_JIS, GTK_UI_MANAGER_MENUITEM)
7728 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Chinese", "Options/Encoding/Chinese", GTK_UI_MANAGER_MENU)
7729 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB18030, "Options/Encoding/Chinese/"CS_GB18030, GTK_UI_MANAGER_MENUITEM)
7730 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB2312, "Options/Encoding/Chinese/"CS_GB2312, GTK_UI_MANAGER_MENUITEM)
7731 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GBK, "Options/Encoding/Chinese/"CS_GBK, GTK_UI_MANAGER_MENUITEM)
7732 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_BIG5, "Options/Encoding/Chinese/"CS_BIG5, GTK_UI_MANAGER_MENUITEM)
7733 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_EUC_TW, "Options/Encoding/Chinese/"CS_EUC_TW, GTK_UI_MANAGER_MENUITEM)
7735 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Korean", "Options/Encoding/Korean", GTK_UI_MANAGER_MENU)
7736 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Korean", CS_EUC_KR, "Options/Encoding/Korean/"CS_EUC_KR, GTK_UI_MANAGER_MENUITEM)
7737 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)
7739 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Thai", "Options/Encoding/Thai", GTK_UI_MANAGER_MENU)
7740 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_TIS_620, "Options/Encoding/Thai/"CS_TIS_620, GTK_UI_MANAGER_MENUITEM)
7741 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_WINDOWS_874, "Options/Encoding/Thai/"CS_WINDOWS_874, GTK_UI_MANAGER_MENUITEM)
7745 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "ShowRuler", "Tools/ShowRuler", GTK_UI_MANAGER_MENUITEM)
7746 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "AddressBook", "Tools/AddressBook", GTK_UI_MANAGER_MENUITEM)
7747 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Template", "Tools/Template", GTK_UI_MANAGER_MENU)
7748 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Template", "PlaceHolder", "Tools/Template/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7749 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Actions", "Tools/Actions", GTK_UI_MANAGER_MENU)
7750 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Actions", "PlaceHolder", "Tools/Actions/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7753 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Help", "About", "Help/About", GTK_UI_MANAGER_MENUITEM)
7755 menubar = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu");
7756 gtk_widget_show_all(menubar);
7758 gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(compose->ui_manager));
7759 gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, TRUE, 0);
7761 if (prefs_common.toolbar_detachable) {
7762 handlebox = gtk_handle_box_new();
7764 handlebox = gtk_hbox_new(FALSE, 0);
7766 gtk_box_pack_start(GTK_BOX(vbox), handlebox, FALSE, FALSE, 0);
7768 gtk_widget_realize(handlebox);
7769 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, handlebox,
7772 vbox2 = gtk_vbox_new(FALSE, 2);
7773 gtk_box_pack_start(GTK_BOX(vbox), vbox2, TRUE, TRUE, 0);
7774 gtk_container_set_border_width(GTK_CONTAINER(vbox2), 0);
7777 notebook = gtk_notebook_new();
7778 gtk_widget_show(notebook);
7780 /* header labels and entries */
7781 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7782 compose_create_header(compose),
7783 gtk_label_new_with_mnemonic(_("Hea_der")));
7784 /* attachment list */
7785 attach_hbox = gtk_hbox_new(FALSE, 0);
7786 gtk_widget_show(attach_hbox);
7788 attach_lab1 = gtk_label_new_with_mnemonic(_("_Attachments"));
7789 gtk_widget_show(attach_lab1);
7790 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab1, TRUE, TRUE, 0);
7792 attach_lab2 = gtk_label_new("");
7793 gtk_widget_show(attach_lab2);
7794 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab2, FALSE, FALSE, 0);
7796 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7797 compose_create_attach(compose),
7800 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7801 compose_create_others(compose),
7802 gtk_label_new_with_mnemonic(_("Othe_rs")));
7805 subject_hbox = gtk_hbox_new(FALSE, 0);
7806 gtk_widget_show(subject_hbox);
7808 subject_frame = gtk_frame_new(NULL);
7809 gtk_frame_set_shadow_type(GTK_FRAME(subject_frame), GTK_SHADOW_NONE);
7810 gtk_box_pack_start(GTK_BOX(subject_hbox), subject_frame, TRUE, TRUE, 0);
7811 gtk_widget_show(subject_frame);
7813 subject = gtk_hbox_new(FALSE, HSPACING_NARROW);
7814 gtk_container_set_border_width(GTK_CONTAINER(subject), 0);
7815 gtk_widget_show(subject);
7817 label = gtk_label_new_with_mnemonic(_("S_ubject:"));
7818 gtk_box_pack_start(GTK_BOX(subject), label, FALSE, FALSE, 0);
7819 gtk_widget_show(label);
7822 subject_entry = claws_spell_entry_new();
7824 subject_entry = gtk_entry_new();
7826 gtk_box_pack_start(GTK_BOX(subject), subject_entry, TRUE, TRUE, 0);
7827 g_signal_connect_after(G_OBJECT(subject_entry), "grab_focus",
7828 G_CALLBACK(compose_grab_focus_cb), compose);
7829 gtk_label_set_mnemonic_widget(GTK_LABEL(label), subject_entry);
7830 gtk_widget_show(subject_entry);
7831 compose->subject_entry = subject_entry;
7832 gtk_container_add(GTK_CONTAINER(subject_frame), subject);
7834 edit_vbox = gtk_vbox_new(FALSE, 0);
7836 gtk_box_pack_start(GTK_BOX(edit_vbox), subject_hbox, FALSE, FALSE, 0);
7839 ruler_hbox = gtk_hbox_new(FALSE, 0);
7840 gtk_box_pack_start(GTK_BOX(edit_vbox), ruler_hbox, FALSE, FALSE, 0);
7842 ruler = gtk_shruler_new(GTK_ORIENTATION_HORIZONTAL);
7843 gtk_shruler_set_range(GTK_SHRULER(ruler), 0.0, 100.0, 1.0);
7844 gtk_box_pack_start(GTK_BOX(ruler_hbox), ruler, TRUE, TRUE,
7848 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
7849 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
7850 GTK_POLICY_AUTOMATIC,
7851 GTK_POLICY_AUTOMATIC);
7852 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
7854 gtk_box_pack_start(GTK_BOX(edit_vbox), scrolledwin, TRUE, TRUE, 0);
7856 text = gtk_text_view_new();
7857 if (prefs_common.show_compose_margin) {
7858 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text), 6);
7859 gtk_text_view_set_right_margin(GTK_TEXT_VIEW(text), 6);
7861 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
7862 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
7863 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), TRUE);
7864 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
7865 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
7867 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
7868 g_signal_connect_after(G_OBJECT(text), "size_allocate",
7869 G_CALLBACK(compose_edit_size_alloc),
7871 g_signal_connect(G_OBJECT(buffer), "changed",
7872 G_CALLBACK(compose_changed_cb), compose);
7873 g_signal_connect(G_OBJECT(text), "grab_focus",
7874 G_CALLBACK(compose_grab_focus_cb), compose);
7875 g_signal_connect(G_OBJECT(buffer), "insert_text",
7876 G_CALLBACK(text_inserted), compose);
7877 g_signal_connect(G_OBJECT(text), "button_press_event",
7878 G_CALLBACK(text_clicked), compose);
7879 g_signal_connect(G_OBJECT(text), "popup-menu",
7880 G_CALLBACK(compose_popup_menu), compose);
7881 g_signal_connect(G_OBJECT(subject_entry), "changed",
7882 G_CALLBACK(compose_changed_cb), compose);
7883 g_signal_connect(G_OBJECT(subject_entry), "activate",
7884 G_CALLBACK(compose_subject_entry_activated), compose);
7887 gtk_drag_dest_set(text, GTK_DEST_DEFAULT_ALL, compose_mime_types,
7888 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7889 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7890 g_signal_connect(G_OBJECT(text), "drag_data_received",
7891 G_CALLBACK(compose_insert_drag_received_cb),
7893 g_signal_connect(G_OBJECT(text), "drag-drop",
7894 G_CALLBACK(compose_drag_drop),
7896 g_signal_connect(G_OBJECT(text), "key-press-event",
7897 G_CALLBACK(completion_set_focus_to_subject),
7899 gtk_widget_show_all(vbox);
7901 /* pane between attach clist and text */
7902 paned = gtk_vpaned_new();
7903 gtk_container_add(GTK_CONTAINER(vbox2), paned);
7904 gtk_paned_pack1(GTK_PANED(paned), notebook, FALSE, FALSE);
7905 gtk_paned_pack2(GTK_PANED(paned), edit_vbox, TRUE, FALSE);
7906 gtk_paned_set_position(GTK_PANED(paned), prefs_common.compose_notebook_height);
7907 g_signal_connect(G_OBJECT(notebook), "size_allocate",
7908 G_CALLBACK(compose_notebook_size_alloc), paned);
7910 gtk_widget_show_all(paned);
7913 if (prefs_common.textfont) {
7914 PangoFontDescription *font_desc;
7916 font_desc = pango_font_description_from_string
7917 (prefs_common.textfont);
7919 gtk_widget_modify_font(text, font_desc);
7920 pango_font_description_free(font_desc);
7924 gtk_action_group_add_actions(action_group, compose_popup_entries,
7925 G_N_ELEMENTS(compose_popup_entries), (gpointer)compose);
7926 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Popup", NULL, GTK_UI_MANAGER_MENUBAR)
7927 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup", "Compose", "Compose", GTK_UI_MANAGER_MENU)
7928 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Add", "Compose/Add", GTK_UI_MANAGER_MENUITEM)
7929 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Remove", "Compose/Remove", GTK_UI_MANAGER_MENUITEM)
7930 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Separator1", "Compose/---", GTK_UI_MANAGER_SEPARATOR)
7931 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Properties", "Compose/Properties", GTK_UI_MANAGER_MENUITEM)
7933 popupmenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(gtk_ui_manager_get_widget(compose->ui_manager, "/Popup/Compose")));
7935 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
7936 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
7937 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
7939 tmpl_menu = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu/Tools/Template");
7941 undostruct = undo_init(text);
7942 undo_set_change_state_func(undostruct, &compose_undo_state_changed,
7945 address_completion_start(window);
7947 compose->window = window;
7948 compose->vbox = vbox;
7949 compose->menubar = menubar;
7950 compose->handlebox = handlebox;
7952 compose->vbox2 = vbox2;
7954 compose->paned = paned;
7956 compose->attach_label = attach_lab2;
7958 compose->notebook = notebook;
7959 compose->edit_vbox = edit_vbox;
7960 compose->ruler_hbox = ruler_hbox;
7961 compose->ruler = ruler;
7962 compose->scrolledwin = scrolledwin;
7963 compose->text = text;
7965 compose->focused_editable = NULL;
7967 compose->popupmenu = popupmenu;
7969 compose->tmpl_menu = tmpl_menu;
7971 compose->mode = mode;
7972 compose->rmode = mode;
7974 compose->targetinfo = NULL;
7975 compose->replyinfo = NULL;
7976 compose->fwdinfo = NULL;
7978 compose->email_hashtable = g_hash_table_new_full(g_str_hash,
7979 g_str_equal, (GDestroyNotify) g_free, NULL);
7981 compose->replyto = NULL;
7983 compose->bcc = NULL;
7984 compose->followup_to = NULL;
7986 compose->ml_post = NULL;
7988 compose->inreplyto = NULL;
7989 compose->references = NULL;
7990 compose->msgid = NULL;
7991 compose->boundary = NULL;
7993 compose->autowrap = prefs_common.autowrap;
7994 compose->autoindent = prefs_common.auto_indent;
7995 compose->use_signing = FALSE;
7996 compose->use_encryption = FALSE;
7997 compose->privacy_system = NULL;
7998 compose->encdata = NULL;
8000 compose->modified = FALSE;
8002 compose->return_receipt = FALSE;
8004 compose->to_list = NULL;
8005 compose->newsgroup_list = NULL;
8007 compose->undostruct = undostruct;
8009 compose->sig_str = NULL;
8011 compose->exteditor_file = NULL;
8012 compose->exteditor_pid = -1;
8013 compose->exteditor_tag = -1;
8014 compose->exteditor_socket = NULL;
8015 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN; /* inhibit auto-drafting while loading */
8017 compose->folder_update_callback_id =
8018 hooks_register_hook(FOLDER_UPDATE_HOOKLIST,
8019 compose_update_folder_hook,
8020 (gpointer) compose);
8023 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
8024 if (mode != COMPOSE_REDIRECT) {
8025 if (prefs_common.enable_aspell && prefs_common.dictionary &&
8026 strcmp(prefs_common.dictionary, "")) {
8027 gtkaspell = gtkaspell_new(prefs_common.dictionary,
8028 prefs_common.alt_dictionary,
8029 conv_get_locale_charset_str(),
8030 prefs_common.misspelled_col,
8031 prefs_common.check_while_typing,
8032 prefs_common.recheck_when_changing_dict,
8033 prefs_common.use_alternate,
8034 prefs_common.use_both_dicts,
8035 GTK_TEXT_VIEW(text),
8036 GTK_WINDOW(compose->window),
8037 compose_dict_changed,
8038 compose_spell_menu_changed,
8041 alertpanel_error(_("Spell checker could not "
8043 gtkaspell_checkers_strerror());
8044 gtkaspell_checkers_reset_error();
8046 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", TRUE);
8050 compose->gtkaspell = gtkaspell;
8051 compose_spell_menu_changed(compose);
8052 claws_spell_entry_set_gtkaspell(CLAWS_SPELL_ENTRY(subject_entry), gtkaspell);
8055 compose_select_account(compose, account, TRUE);
8057 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", prefs_common.autowrap);
8058 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", prefs_common.auto_indent);
8060 if (account->set_autocc && account->auto_cc && mode != COMPOSE_REEDIT)
8061 compose_entry_append(compose, account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8063 if (account->set_autobcc && account->auto_bcc && mode != COMPOSE_REEDIT)
8064 compose_entry_append(compose, account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8066 if (account->set_autoreplyto && account->auto_replyto && mode != COMPOSE_REEDIT)
8067 compose_entry_append(compose, account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8069 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", compose->mode == COMPOSE_REPLY);
8070 if (account->protocol != A_NNTP)
8071 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
8072 prefs_common_translated_header_name("To:"));
8074 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
8075 prefs_common_translated_header_name("Newsgroups:"));
8077 #ifndef USE_ALT_ADDRBOOK
8078 addressbook_set_target_compose(compose);
8080 if (mode != COMPOSE_REDIRECT)
8081 compose_set_template_menu(compose);
8083 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Template", FALSE);
8086 compose_list = g_list_append(compose_list, compose);
8088 if (!prefs_common.show_ruler)
8089 gtk_widget_hide(ruler_hbox);
8091 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Tools/ShowRuler", prefs_common.show_ruler);
8094 compose->priority = PRIORITY_NORMAL;
8095 compose_update_priority_menu_item(compose);
8097 compose_set_out_encoding(compose);
8100 compose_update_actions_menu(compose);
8102 /* Privacy Systems menu */
8103 compose_update_privacy_systems_menu(compose);
8105 activate_privacy_system(compose, account, TRUE);
8106 toolbar_set_style(compose->toolbar->toolbar, compose->handlebox, prefs_common.toolbar_style);
8108 gtk_widget_realize(window);
8110 gtk_widget_show(window);
8116 static GtkWidget *compose_account_option_menu_create(Compose *compose)
8121 GtkWidget *optmenubox;
8122 GtkWidget *fromlabel;
8125 GtkWidget *from_name = NULL;
8127 gint num = 0, def_menu = 0;
8129 accounts = account_get_list();
8130 cm_return_val_if_fail(accounts != NULL, NULL);
8132 optmenubox = gtk_event_box_new();
8133 optmenu = gtkut_sc_combobox_create(optmenubox, FALSE);
8134 menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
8136 hbox = gtk_hbox_new(FALSE, 4);
8137 from_name = gtk_entry_new();
8139 g_signal_connect_after(G_OBJECT(from_name), "grab_focus",
8140 G_CALLBACK(compose_grab_focus_cb), compose);
8141 g_signal_connect_after(G_OBJECT(from_name), "activate",
8142 G_CALLBACK(from_name_activate_cb), optmenu);
8144 for (; accounts != NULL; accounts = accounts->next, num++) {
8145 PrefsAccount *ac = (PrefsAccount *)accounts->data;
8146 gchar *name, *from = NULL;
8148 if (ac == compose->account) def_menu = num;
8150 name = g_markup_printf_escaped("<i>%s</i>",
8153 if (ac == compose->account) {
8154 if (ac->name && *ac->name) {
8156 QUOTE_IF_REQUIRED_NORMAL(buf, ac->name, return NULL);
8157 from = g_strdup_printf("%s <%s>",
8159 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8161 from = g_strdup_printf("%s",
8163 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8166 COMBOBOX_ADD(menu, name, ac->account_id);
8171 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), def_menu);
8173 g_signal_connect(G_OBJECT(optmenu), "changed",
8174 G_CALLBACK(account_activated),
8176 g_signal_connect(G_OBJECT(from_name), "populate-popup",
8177 G_CALLBACK(compose_entry_popup_extend),
8180 fromlabel = gtk_label_new_with_mnemonic(_("_From:"));
8181 gtk_label_set_mnemonic_widget(GTK_LABEL(fromlabel), from_name);
8183 gtk_box_pack_start(GTK_BOX(hbox), fromlabel, FALSE, FALSE, 4);
8184 gtk_box_pack_start(GTK_BOX(hbox), optmenubox, FALSE, FALSE, 0);
8185 gtk_box_pack_start(GTK_BOX(hbox), from_name, TRUE, TRUE, 0);
8187 /* Putting only the GtkEntry into focus chain of parent hbox causes
8188 * the account selector combobox next to it to be unreachable when
8189 * navigating widgets in GtkTable with up/down arrow keys.
8190 * Note: gtk_widget_set_can_focus() was not enough. */
8192 l = g_list_prepend(l, from_name);
8193 gtk_container_set_focus_chain(GTK_CONTAINER(hbox), l);
8196 CLAWS_SET_TIP(optmenubox,
8197 _("Account to use for this email"));
8198 CLAWS_SET_TIP(from_name,
8199 _("Sender address to be used"));
8201 compose->account_combo = optmenu;
8202 compose->from_name = from_name;
8207 static void compose_set_priority_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8209 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8210 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8211 Compose *compose = (Compose *) data;
8213 compose->priority = value;
8217 static void compose_reply_change_mode(Compose *compose,
8220 gboolean was_modified = compose->modified;
8222 gboolean all = FALSE, ml = FALSE, sender = FALSE, followup = FALSE;
8224 cm_return_if_fail(compose->replyinfo != NULL);
8226 if (action == COMPOSE_REPLY && prefs_common.default_reply_list)
8228 if (action == COMPOSE_REPLY && compose->rmode == COMPOSE_FOLLOWUP_AND_REPLY_TO)
8230 if (action == COMPOSE_REPLY_TO_ALL)
8232 if (action == COMPOSE_REPLY_TO_SENDER)
8234 if (action == COMPOSE_REPLY_TO_LIST)
8237 compose_remove_header_entries(compose);
8238 compose_reply_set_entry(compose, compose->replyinfo, all, ml, sender, followup);
8239 if (compose->account->set_autocc && compose->account->auto_cc)
8240 compose_entry_append(compose, compose->account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8242 if (compose->account->set_autobcc && compose->account->auto_bcc)
8243 compose_entry_append(compose, compose->account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8245 if (compose->account->set_autoreplyto && compose->account->auto_replyto)
8246 compose_entry_append(compose, compose->account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8247 compose_show_first_last_header(compose, TRUE);
8248 compose->modified = was_modified;
8249 compose_set_title(compose);
8252 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8254 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8255 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8256 Compose *compose = (Compose *) data;
8259 compose_reply_change_mode(compose, value);
8262 static void compose_update_priority_menu_item(Compose * compose)
8264 GtkWidget *menuitem = NULL;
8265 switch (compose->priority) {
8266 case PRIORITY_HIGHEST:
8267 menuitem = gtk_ui_manager_get_widget
8268 (compose->ui_manager, "/Menu/Options/Priority/Highest");
8271 menuitem = gtk_ui_manager_get_widget
8272 (compose->ui_manager, "/Menu/Options/Priority/High");
8274 case PRIORITY_NORMAL:
8275 menuitem = gtk_ui_manager_get_widget
8276 (compose->ui_manager, "/Menu/Options/Priority/Normal");
8279 menuitem = gtk_ui_manager_get_widget
8280 (compose->ui_manager, "/Menu/Options/Priority/Low");
8282 case PRIORITY_LOWEST:
8283 menuitem = gtk_ui_manager_get_widget
8284 (compose->ui_manager, "/Menu/Options/Priority/Lowest");
8287 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8290 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data)
8292 Compose *compose = (Compose *) data;
8294 gboolean can_sign = FALSE, can_encrypt = FALSE;
8296 cm_return_if_fail(GTK_IS_CHECK_MENU_ITEM(widget));
8298 if (!gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget)))
8301 systemid = g_object_get_data(G_OBJECT(widget), "privacy_system");
8302 g_free(compose->privacy_system);
8303 compose->privacy_system = NULL;
8304 g_free(compose->encdata);
8305 compose->encdata = NULL;
8306 if (systemid != NULL) {
8307 compose->privacy_system = g_strdup(systemid);
8309 can_sign = privacy_system_can_sign(systemid);
8310 can_encrypt = privacy_system_can_encrypt(systemid);
8313 debug_print("activated privacy system: %s\n", systemid != NULL ? systemid : "None");
8315 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8316 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8319 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn)
8321 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8322 GtkWidget *menuitem = NULL;
8323 GList *children, *amenu;
8324 gboolean can_sign = FALSE, can_encrypt = FALSE;
8325 gboolean found = FALSE;
8327 if (compose->privacy_system != NULL) {
8329 menuitem = gtk_menu_item_get_submenu(GTK_MENU_ITEM(
8330 gtk_ui_manager_get_widget(compose->ui_manager, branch_path)));
8331 cm_return_if_fail(menuitem != NULL);
8333 children = gtk_container_get_children(GTK_CONTAINER(GTK_MENU_SHELL(menuitem)));
8336 while (amenu != NULL) {
8337 systemid = g_object_get_data(G_OBJECT(amenu->data), "privacy_system");
8338 if (systemid != NULL) {
8339 if (strcmp(systemid, compose->privacy_system) == 0 &&
8340 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8341 menuitem = GTK_WIDGET(amenu->data);
8343 can_sign = privacy_system_can_sign(systemid);
8344 can_encrypt = privacy_system_can_encrypt(systemid);
8348 } else if (strlen(compose->privacy_system) == 0 &&
8349 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8350 menuitem = GTK_WIDGET(amenu->data);
8353 can_encrypt = FALSE;
8358 amenu = amenu->next;
8360 g_list_free(children);
8361 if (menuitem != NULL)
8362 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8364 if (warn && !found && strlen(compose->privacy_system)) {
8365 alertpanel_warning(_("The privacy system '%s' cannot be loaded. You "
8366 "will not be able to sign or encrypt this message."),
8367 compose->privacy_system);
8371 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8372 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8375 static void compose_set_out_encoding(Compose *compose)
8377 CharSet out_encoding;
8378 const gchar *branch = NULL;
8379 out_encoding = conv_get_charset_from_str(prefs_common.outgoing_charset);
8381 switch(out_encoding) {
8382 case C_AUTO: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8383 case C_US_ASCII: branch = "Menu/Options/Encoding/" CS_US_ASCII; break;
8384 case C_UTF_8: branch = "Menu/Options/Encoding/" CS_UTF_8; break;
8385 case C_ISO_8859_2: branch = "Menu/Options/Encoding/" CS_ISO_8859_2; break;
8386 case C_ISO_8859_7: branch = "Menu/Options/Encoding/" CS_ISO_8859_7; break;
8387 case C_ISO_8859_9: branch = "Menu/Options/Encoding/" CS_ISO_8859_9; break;
8388 case C_ISO_8859_1: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_1; break;
8389 case C_ISO_8859_15: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_15; break;
8390 case C_WINDOWS_1252: branch = "Menu/Options/Encoding/Western/" CS_WINDOWS_1252; break;
8391 case C_ISO_8859_13: branch = "Menu/Options/Encoding/Baltic/" CS_ISO_8859_13; break;
8392 case C_ISO_8859_4: branch = "Menu/Options/Encoding/Baltic" CS_ISO_8859_4; break;
8393 case C_ISO_8859_8: branch = "Menu/Options/Encoding/Hebrew/" CS_ISO_8859_8; break;
8394 case C_WINDOWS_1255: branch = "Menu/Options/Encoding/Hebrew/" CS_WINDOWS_1255; break;
8395 case C_ISO_8859_6: branch = "Menu/Options/Encoding/Arabic/" CS_ISO_8859_6; break;
8396 case C_WINDOWS_1256: branch = "Menu/Options/Encoding/Arabic/" CS_WINDOWS_1256; break;
8397 case C_ISO_8859_5: branch = "Menu/Options/Encoding/Cyrillic/" CS_ISO_8859_5; break;
8398 case C_KOI8_R: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_R; break;
8399 case C_MACCYR: branch = "Menu/Options/Encoding/Cyrillic/" CS_MACCYR; break;
8400 case C_KOI8_U: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_U; break;
8401 case C_WINDOWS_1251: branch = "Menu/Options/Encoding/Cyrillic/" CS_WINDOWS_1251; break;
8402 case C_ISO_2022_JP: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP; break;
8403 case C_ISO_2022_JP_2: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP_2; break;
8404 case C_EUC_JP: branch = "Menu/Options/Encoding/Japanese/" CS_EUC_JP; break;
8405 case C_SHIFT_JIS: branch = "Menu/Options/Encoding/Japanese/" CS_SHIFT_JIS; break;
8406 case C_GB18030: branch = "Menu/Options/Encoding/Chinese/" CS_GB18030; break;
8407 case C_GB2312: branch = "Menu/Options/Encoding/Chinese/" CS_GB2312; break;
8408 case C_GBK: branch = "Menu/Options/Encoding/Chinese/" CS_GBK; break;
8409 case C_BIG5: branch = "Menu/Options/Encoding/Chinese/" CS_BIG5; break;
8410 case C_EUC_TW: branch = "Menu/Options/Encoding/Chinese/" CS_EUC_TW; break;
8411 case C_EUC_KR: branch = "Menu/Options/Encoding/Korean/" CS_EUC_KR; break;
8412 case C_ISO_2022_KR: branch = "Menu/Options/Encoding/Korean/" CS_ISO_2022_KR; break;
8413 case C_TIS_620: branch = "Menu/Options/Encoding/Thai/" CS_TIS_620; break;
8414 case C_WINDOWS_874: branch = "Menu/Options/Encoding/Thai/" CS_WINDOWS_874; break;
8415 default: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8417 cm_toggle_menu_set_active_full(compose->ui_manager, (gchar *)branch, TRUE);
8420 static void compose_set_template_menu(Compose *compose)
8422 GSList *tmpl_list, *cur;
8426 tmpl_list = template_get_config();
8428 menu = gtk_menu_new();
8430 gtk_menu_set_accel_group (GTK_MENU (menu),
8431 gtk_ui_manager_get_accel_group(compose->ui_manager));
8432 for (cur = tmpl_list; cur != NULL; cur = cur->next) {
8433 Template *tmpl = (Template *)cur->data;
8434 gchar *accel_path = NULL;
8435 item = gtk_menu_item_new_with_label(tmpl->name);
8436 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
8437 g_signal_connect(G_OBJECT(item), "activate",
8438 G_CALLBACK(compose_template_activate_cb),
8440 g_object_set_data(G_OBJECT(item), "template", tmpl);
8441 gtk_widget_show(item);
8442 accel_path = g_strconcat("<ComposeTemplates>" , "/", tmpl->name, NULL);
8443 gtk_menu_item_set_accel_path(GTK_MENU_ITEM(item), accel_path);
8447 gtk_widget_show(menu);
8448 gtk_menu_item_set_submenu(GTK_MENU_ITEM(compose->tmpl_menu), menu);
8451 void compose_update_actions_menu(Compose *compose)
8453 action_update_compose_menu(compose->ui_manager, "/Menu/Tools/Actions", compose);
8456 static void compose_update_privacy_systems_menu(Compose *compose)
8458 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8459 GSList *systems, *cur;
8461 GtkWidget *system_none;
8463 GtkWidget *privacy_menuitem = gtk_ui_manager_get_widget(compose->ui_manager, branch_path);
8464 GtkWidget *privacy_menu = gtk_menu_new();
8466 system_none = gtk_radio_menu_item_new_with_mnemonic(NULL, _("_None"));
8467 g_object_set_data_full(G_OBJECT(system_none), "privacy_system", NULL, NULL);
8469 g_signal_connect(G_OBJECT(system_none), "activate",
8470 G_CALLBACK(compose_set_privacy_system_cb), compose);
8472 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), system_none);
8473 gtk_widget_show(system_none);
8475 systems = privacy_get_system_ids();
8476 for (cur = systems; cur != NULL; cur = g_slist_next(cur)) {
8477 gchar *systemid = cur->data;
8479 group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(system_none));
8480 widget = gtk_radio_menu_item_new_with_label(group,
8481 privacy_system_get_name(systemid));
8482 g_object_set_data_full(G_OBJECT(widget), "privacy_system",
8483 g_strdup(systemid), g_free);
8484 g_signal_connect(G_OBJECT(widget), "activate",
8485 G_CALLBACK(compose_set_privacy_system_cb), compose);
8487 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), widget);
8488 gtk_widget_show(widget);
8491 g_slist_free(systems);
8492 gtk_menu_item_set_submenu(GTK_MENU_ITEM(privacy_menuitem), privacy_menu);
8493 gtk_widget_show_all(privacy_menu);
8494 gtk_widget_show_all(privacy_menuitem);
8497 void compose_reflect_prefs_all(void)
8502 for (cur = compose_list; cur != NULL; cur = cur->next) {
8503 compose = (Compose *)cur->data;
8504 compose_set_template_menu(compose);
8508 void compose_reflect_prefs_pixmap_theme(void)
8513 for (cur = compose_list; cur != NULL; cur = cur->next) {
8514 compose = (Compose *)cur->data;
8515 toolbar_update(TOOLBAR_COMPOSE, compose);
8519 static const gchar *compose_quote_char_from_context(Compose *compose)
8521 const gchar *qmark = NULL;
8523 cm_return_val_if_fail(compose != NULL, NULL);
8525 switch (compose->mode) {
8526 /* use forward-specific quote char */
8527 case COMPOSE_FORWARD:
8528 case COMPOSE_FORWARD_AS_ATTACH:
8529 case COMPOSE_FORWARD_INLINE:
8530 if (compose->folder && compose->folder->prefs &&
8531 compose->folder->prefs->forward_with_format)
8532 qmark = compose->folder->prefs->forward_quotemark;
8533 else if (compose->account->forward_with_format)
8534 qmark = compose->account->forward_quotemark;
8536 qmark = prefs_common.fw_quotemark;
8539 /* use reply-specific quote char in all other modes */
8541 if (compose->folder && compose->folder->prefs &&
8542 compose->folder->prefs->reply_with_format)
8543 qmark = compose->folder->prefs->reply_quotemark;
8544 else if (compose->account->reply_with_format)
8545 qmark = compose->account->reply_quotemark;
8547 qmark = prefs_common.quotemark;
8551 if (qmark == NULL || *qmark == '\0')
8557 static void compose_template_apply(Compose *compose, Template *tmpl,
8561 GtkTextBuffer *buffer;
8565 gchar *parsed_str = NULL;
8566 gint cursor_pos = 0;
8567 const gchar *err_msg = _("The body of the template has an error at line %d.");
8570 /* process the body */
8572 text = GTK_TEXT_VIEW(compose->text);
8573 buffer = gtk_text_view_get_buffer(text);
8576 qmark = compose_quote_char_from_context(compose);
8578 if (compose->replyinfo != NULL) {
8581 gtk_text_buffer_set_text(buffer, "", -1);
8582 mark = gtk_text_buffer_get_insert(buffer);
8583 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8585 parsed_str = compose_quote_fmt(compose, compose->replyinfo,
8586 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8588 } else if (compose->fwdinfo != NULL) {
8591 gtk_text_buffer_set_text(buffer, "", -1);
8592 mark = gtk_text_buffer_get_insert(buffer);
8593 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8595 parsed_str = compose_quote_fmt(compose, compose->fwdinfo,
8596 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8599 MsgInfo* dummyinfo = compose_msginfo_new_from_compose(compose);
8601 GtkTextIter start, end;
8604 gtk_text_buffer_get_start_iter(buffer, &start);
8605 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
8606 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
8608 /* clear the buffer now */
8610 gtk_text_buffer_set_text(buffer, "", -1);
8612 parsed_str = compose_quote_fmt(compose, dummyinfo,
8613 tmpl->value, qmark, tmp, FALSE, FALSE, err_msg);
8614 procmsg_msginfo_free( &dummyinfo );
8620 gtk_text_buffer_set_text(buffer, "", -1);
8621 mark = gtk_text_buffer_get_insert(buffer);
8622 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8625 if (replace && parsed_str && compose->account->auto_sig)
8626 compose_insert_sig(compose, FALSE);
8628 if (replace && parsed_str) {
8629 gtk_text_buffer_get_start_iter(buffer, &iter);
8630 gtk_text_buffer_place_cursor(buffer, &iter);
8634 cursor_pos = quote_fmt_get_cursor_pos();
8635 compose->set_cursor_pos = cursor_pos;
8636 if (cursor_pos == -1)
8638 gtk_text_buffer_get_start_iter(buffer, &iter);
8639 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
8640 gtk_text_buffer_place_cursor(buffer, &iter);
8643 /* process the other fields */
8645 compose_template_apply_fields(compose, tmpl);
8646 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
8647 quote_fmt_reset_vartable();
8648 compose_changed_cb(NULL, compose);
8651 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
8652 gtkaspell_highlight_all(compose->gtkaspell);
8656 static void compose_template_apply_fields(Compose *compose, Template *tmpl)
8658 MsgInfo* dummyinfo = NULL;
8659 MsgInfo *msginfo = NULL;
8662 if (compose->replyinfo != NULL)
8663 msginfo = compose->replyinfo;
8664 else if (compose->fwdinfo != NULL)
8665 msginfo = compose->fwdinfo;
8667 dummyinfo = compose_msginfo_new_from_compose(compose);
8668 msginfo = dummyinfo;
8671 if (tmpl->from && *tmpl->from != '\0') {
8673 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8674 compose->gtkaspell);
8676 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8678 quote_fmt_scan_string(tmpl->from);
8681 buf = quote_fmt_get_buffer();
8683 alertpanel_error(_("Template From format error."));
8685 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
8689 if (tmpl->to && *tmpl->to != '\0') {
8691 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8692 compose->gtkaspell);
8694 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8696 quote_fmt_scan_string(tmpl->to);
8699 buf = quote_fmt_get_buffer();
8701 alertpanel_error(_("Template To format error."));
8703 compose_entry_append(compose, buf, COMPOSE_TO, PREF_TEMPLATE);
8707 if (tmpl->cc && *tmpl->cc != '\0') {
8709 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8710 compose->gtkaspell);
8712 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8714 quote_fmt_scan_string(tmpl->cc);
8717 buf = quote_fmt_get_buffer();
8719 alertpanel_error(_("Template Cc format error."));
8721 compose_entry_append(compose, buf, COMPOSE_CC, PREF_TEMPLATE);
8725 if (tmpl->bcc && *tmpl->bcc != '\0') {
8727 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8728 compose->gtkaspell);
8730 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8732 quote_fmt_scan_string(tmpl->bcc);
8735 buf = quote_fmt_get_buffer();
8737 alertpanel_error(_("Template Bcc format error."));
8739 compose_entry_append(compose, buf, COMPOSE_BCC, PREF_TEMPLATE);
8743 if (tmpl->replyto && *tmpl->replyto != '\0') {
8745 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8746 compose->gtkaspell);
8748 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8750 quote_fmt_scan_string(tmpl->replyto);
8753 buf = quote_fmt_get_buffer();
8755 alertpanel_error(_("Template Reply-To format error."));
8757 compose_entry_append(compose, buf, COMPOSE_REPLYTO, PREF_TEMPLATE);
8761 /* process the subject */
8762 if (tmpl->subject && *tmpl->subject != '\0') {
8764 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8765 compose->gtkaspell);
8767 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8769 quote_fmt_scan_string(tmpl->subject);
8772 buf = quote_fmt_get_buffer();
8774 alertpanel_error(_("Template subject format error."));
8776 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
8780 procmsg_msginfo_free( &dummyinfo );
8783 static void compose_destroy(Compose *compose)
8785 GtkAllocation allocation;
8786 GtkTextBuffer *buffer;
8787 GtkClipboard *clipboard;
8789 compose_list = g_list_remove(compose_list, compose);
8791 if (compose->updating) {
8792 debug_print("danger, not destroying anything now\n");
8793 compose->deferred_destroy = TRUE;
8797 /* NOTE: address_completion_end() does nothing with the window
8798 * however this may change. */
8799 address_completion_end(compose->window);
8801 slist_free_strings_full(compose->to_list);
8802 slist_free_strings_full(compose->newsgroup_list);
8803 slist_free_strings_full(compose->header_list);
8805 slist_free_strings_full(extra_headers);
8806 extra_headers = NULL;
8808 compose->header_list = compose->newsgroup_list = compose->to_list = NULL;
8810 g_hash_table_destroy(compose->email_hashtable);
8812 hooks_unregister_hook(FOLDER_UPDATE_HOOKLIST,
8813 compose->folder_update_callback_id);
8815 procmsg_msginfo_free(&(compose->targetinfo));
8816 procmsg_msginfo_free(&(compose->replyinfo));
8817 procmsg_msginfo_free(&(compose->fwdinfo));
8819 g_free(compose->replyto);
8820 g_free(compose->cc);
8821 g_free(compose->bcc);
8822 g_free(compose->newsgroups);
8823 g_free(compose->followup_to);
8825 g_free(compose->ml_post);
8827 g_free(compose->inreplyto);
8828 g_free(compose->references);
8829 g_free(compose->msgid);
8830 g_free(compose->boundary);
8832 g_free(compose->redirect_filename);
8833 if (compose->undostruct)
8834 undo_destroy(compose->undostruct);
8836 g_free(compose->sig_str);
8838 g_free(compose->exteditor_file);
8840 g_free(compose->orig_charset);
8842 g_free(compose->privacy_system);
8843 g_free(compose->encdata);
8845 #ifndef USE_ALT_ADDRBOOK
8846 if (addressbook_get_target_compose() == compose)
8847 addressbook_set_target_compose(NULL);
8850 if (compose->gtkaspell) {
8851 gtkaspell_delete(compose->gtkaspell);
8852 compose->gtkaspell = NULL;
8856 if (!compose->batch) {
8857 gtk_widget_get_allocation(compose->window, &allocation);
8858 prefs_common.compose_width = allocation.width;
8859 prefs_common.compose_height = allocation.height;
8862 if (!gtk_widget_get_parent(compose->paned))
8863 gtk_widget_destroy(compose->paned);
8864 gtk_widget_destroy(compose->popupmenu);
8866 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
8867 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
8868 gtk_text_buffer_remove_selection_clipboard(buffer, clipboard);
8870 gtk_widget_destroy(compose->window);
8871 toolbar_destroy(compose->toolbar);
8872 g_free(compose->toolbar);
8873 cm_mutex_free(compose->mutex);
8877 static void compose_attach_info_free(AttachInfo *ainfo)
8879 g_free(ainfo->file);
8880 g_free(ainfo->content_type);
8881 g_free(ainfo->name);
8882 g_free(ainfo->charset);
8886 static void compose_attach_update_label(Compose *compose)
8891 GtkTreeModel *model;
8896 model = gtk_tree_view_get_model(GTK_TREE_VIEW(compose->attach_clist));
8897 if(!gtk_tree_model_get_iter_first(model, &iter)) {
8898 gtk_label_set_text(GTK_LABEL(compose->attach_label), "");
8902 while(gtk_tree_model_iter_next(model, &iter))
8905 text = g_strdup_printf("(%d)", i);
8906 gtk_label_set_text(GTK_LABEL(compose->attach_label), text);
8910 static void compose_attach_remove_selected(GtkAction *action, gpointer data)
8912 Compose *compose = (Compose *)data;
8913 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
8914 GtkTreeSelection *selection;
8916 GtkTreeModel *model;
8918 selection = gtk_tree_view_get_selection(tree_view);
8919 sel = gtk_tree_selection_get_selected_rows(selection, &model);
8924 for (cur = sel; cur != NULL; cur = cur->next) {
8925 GtkTreePath *path = cur->data;
8926 GtkTreeRowReference *ref = gtk_tree_row_reference_new
8929 gtk_tree_path_free(path);
8932 for (cur = sel; cur != NULL; cur = cur->next) {
8933 GtkTreeRowReference *ref = cur->data;
8934 GtkTreePath *path = gtk_tree_row_reference_get_path(ref);
8937 if (gtk_tree_model_get_iter(model, &iter, path))
8938 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
8940 gtk_tree_path_free(path);
8941 gtk_tree_row_reference_free(ref);
8945 compose_attach_update_label(compose);
8948 static struct _AttachProperty
8951 GtkWidget *mimetype_entry;
8952 GtkWidget *encoding_optmenu;
8953 GtkWidget *path_entry;
8954 GtkWidget *filename_entry;
8956 GtkWidget *cancel_btn;
8959 static void gtk_tree_path_free_(gpointer ptr, gpointer data)
8961 gtk_tree_path_free((GtkTreePath *)ptr);
8964 static void compose_attach_property(GtkAction *action, gpointer data)
8966 Compose *compose = (Compose *)data;
8967 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
8969 GtkComboBox *optmenu;
8970 GtkTreeSelection *selection;
8972 GtkTreeModel *model;
8975 static gboolean cancelled;
8977 /* only if one selected */
8978 selection = gtk_tree_view_get_selection(tree_view);
8979 if (gtk_tree_selection_count_selected_rows(selection) != 1)
8982 sel = gtk_tree_selection_get_selected_rows(selection, &model);
8986 path = (GtkTreePath *) sel->data;
8987 gtk_tree_model_get_iter(model, &iter, path);
8988 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
8991 g_list_foreach(sel, gtk_tree_path_free_, NULL);
8997 if (!attach_prop.window)
8998 compose_attach_property_create(&cancelled);
8999 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), TRUE);
9000 gtk_widget_grab_focus(attach_prop.ok_btn);
9001 gtk_widget_show(attach_prop.window);
9002 gtk_window_set_transient_for(GTK_WINDOW(attach_prop.window),
9003 GTK_WINDOW(compose->window));
9005 optmenu = GTK_COMBO_BOX(attach_prop.encoding_optmenu);
9006 if (ainfo->encoding == ENC_UNKNOWN)
9007 combobox_select_by_data(optmenu, ENC_BASE64);
9009 combobox_select_by_data(optmenu, ainfo->encoding);
9011 gtk_entry_set_text(GTK_ENTRY(attach_prop.mimetype_entry),
9012 ainfo->content_type ? ainfo->content_type : "");
9013 gtk_entry_set_text(GTK_ENTRY(attach_prop.path_entry),
9014 ainfo->file ? ainfo->file : "");
9015 gtk_entry_set_text(GTK_ENTRY(attach_prop.filename_entry),
9016 ainfo->name ? ainfo->name : "");
9019 const gchar *entry_text;
9021 gchar *cnttype = NULL;
9028 gtk_widget_hide(attach_prop.window);
9029 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), FALSE);
9034 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.mimetype_entry));
9035 if (*entry_text != '\0') {
9038 text = g_strstrip(g_strdup(entry_text));
9039 if ((p = strchr(text, '/')) && !strchr(p + 1, '/')) {
9040 cnttype = g_strdup(text);
9043 alertpanel_error(_("Invalid MIME type."));
9049 ainfo->encoding = combobox_get_active_data(optmenu);
9051 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.path_entry));
9052 if (*entry_text != '\0') {
9053 if (is_file_exist(entry_text) &&
9054 (size = get_file_size(entry_text)) > 0)
9055 file = g_strdup(entry_text);
9058 (_("File doesn't exist or is empty."));
9064 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.filename_entry));
9065 if (*entry_text != '\0') {
9066 g_free(ainfo->name);
9067 ainfo->name = g_strdup(entry_text);
9071 g_free(ainfo->content_type);
9072 ainfo->content_type = cnttype;
9075 g_free(ainfo->file);
9079 ainfo->size = (goffset)size;
9081 /* update tree store */
9082 text = to_human_readable(ainfo->size);
9083 gtk_tree_model_get_iter(model, &iter, path);
9084 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
9085 COL_MIMETYPE, ainfo->content_type,
9087 COL_NAME, ainfo->name,
9088 COL_CHARSET, ainfo->charset,
9094 gtk_tree_path_free(path);
9097 #define SET_LABEL_AND_ENTRY(str, entry, top) \
9099 label = gtk_label_new(str); \
9100 gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), \
9101 GTK_FILL, 0, 0, 0); \
9102 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); \
9104 entry = gtk_entry_new(); \
9105 gtk_table_attach(GTK_TABLE(table), entry, 1, 2, top, (top + 1), \
9106 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); \
9109 static void compose_attach_property_create(gboolean *cancelled)
9115 GtkWidget *mimetype_entry;
9118 GtkListStore *optmenu_menu;
9119 GtkWidget *path_entry;
9120 GtkWidget *filename_entry;
9123 GtkWidget *cancel_btn;
9124 GList *mime_type_list, *strlist;
9127 debug_print("Creating attach_property window...\n");
9129 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose_attach_property");
9130 gtk_widget_set_size_request(window, 480, -1);
9131 gtk_container_set_border_width(GTK_CONTAINER(window), 8);
9132 gtk_window_set_title(GTK_WINDOW(window), _("Properties"));
9133 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
9134 g_signal_connect(G_OBJECT(window), "delete_event",
9135 G_CALLBACK(attach_property_delete_event),
9137 g_signal_connect(G_OBJECT(window), "key_press_event",
9138 G_CALLBACK(attach_property_key_pressed),
9141 vbox = gtk_vbox_new(FALSE, 8);
9142 gtk_container_add(GTK_CONTAINER(window), vbox);
9144 table = gtk_table_new(4, 2, FALSE);
9145 gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
9146 gtk_table_set_row_spacings(GTK_TABLE(table), 8);
9147 gtk_table_set_col_spacings(GTK_TABLE(table), 8);
9149 label = gtk_label_new(_("MIME type"));
9150 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, (0 + 1),
9152 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
9153 #if !GTK_CHECK_VERSION(2, 24, 0)
9154 mimetype_entry = gtk_combo_box_entry_new_text();
9156 mimetype_entry = gtk_combo_box_text_new_with_entry();
9158 gtk_table_attach(GTK_TABLE(table), mimetype_entry, 1, 2, 0, (0 + 1),
9159 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
9161 /* stuff with list */
9162 mime_type_list = procmime_get_mime_type_list();
9164 for (; mime_type_list != NULL; mime_type_list = mime_type_list->next) {
9165 MimeType *type = (MimeType *) mime_type_list->data;
9168 tmp = g_strdup_printf("%s/%s", type->type, type->sub_type);
9170 if (g_list_find_custom(strlist, tmp, (GCompareFunc)strcmp2))
9173 strlist = g_list_insert_sorted(strlist, (gpointer)tmp,
9174 (GCompareFunc)strcmp2);
9177 for (mime_type_list = strlist; mime_type_list != NULL;
9178 mime_type_list = mime_type_list->next) {
9179 #if !GTK_CHECK_VERSION(2, 24, 0)
9180 gtk_combo_box_append_text(GTK_COMBO_BOX(mimetype_entry), mime_type_list->data);
9182 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(mimetype_entry), mime_type_list->data);
9184 g_free(mime_type_list->data);
9186 g_list_free(strlist);
9187 gtk_combo_box_set_active(GTK_COMBO_BOX(mimetype_entry), 0);
9188 mimetype_entry = gtk_bin_get_child(GTK_BIN((mimetype_entry)));
9190 label = gtk_label_new(_("Encoding"));
9191 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2,
9193 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
9195 hbox = gtk_hbox_new(FALSE, 0);
9196 gtk_table_attach(GTK_TABLE(table), hbox, 1, 2, 1, 2,
9197 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
9199 optmenu = gtkut_sc_combobox_create(NULL, TRUE);
9200 optmenu_menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
9202 COMBOBOX_ADD(optmenu_menu, "7bit", ENC_7BIT);
9203 COMBOBOX_ADD(optmenu_menu, "8bit", ENC_8BIT);
9204 COMBOBOX_ADD(optmenu_menu, "quoted-printable", ENC_QUOTED_PRINTABLE);
9205 COMBOBOX_ADD(optmenu_menu, "base64", ENC_BASE64);
9206 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), 0);
9208 gtk_box_pack_start(GTK_BOX(hbox), optmenu, TRUE, TRUE, 0);
9210 SET_LABEL_AND_ENTRY(_("Path"), path_entry, 2);
9211 SET_LABEL_AND_ENTRY(_("File name"), filename_entry, 3);
9213 gtkut_stock_button_set_create(&hbbox, &cancel_btn, GTK_STOCK_CANCEL,
9214 &ok_btn, GTK_STOCK_OK,
9216 gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
9217 gtk_widget_grab_default(ok_btn);
9219 g_signal_connect(G_OBJECT(ok_btn), "clicked",
9220 G_CALLBACK(attach_property_ok),
9222 g_signal_connect(G_OBJECT(cancel_btn), "clicked",
9223 G_CALLBACK(attach_property_cancel),
9226 gtk_widget_show_all(vbox);
9228 attach_prop.window = window;
9229 attach_prop.mimetype_entry = mimetype_entry;
9230 attach_prop.encoding_optmenu = optmenu;
9231 attach_prop.path_entry = path_entry;
9232 attach_prop.filename_entry = filename_entry;
9233 attach_prop.ok_btn = ok_btn;
9234 attach_prop.cancel_btn = cancel_btn;
9237 #undef SET_LABEL_AND_ENTRY
9239 static void attach_property_ok(GtkWidget *widget, gboolean *cancelled)
9245 static void attach_property_cancel(GtkWidget *widget, gboolean *cancelled)
9251 static gint attach_property_delete_event(GtkWidget *widget, GdkEventAny *event,
9252 gboolean *cancelled)
9260 static gboolean attach_property_key_pressed(GtkWidget *widget,
9262 gboolean *cancelled)
9264 if (event && event->keyval == GDK_KEY_Escape) {
9268 if (event && event->keyval == GDK_KEY_Return) {
9276 static void compose_exec_ext_editor(Compose *compose)
9281 GdkNativeWindow socket_wid = 0;
9285 tmp = g_strdup_printf("%s%ctmpmsg.%p", get_tmp_dir(),
9286 G_DIR_SEPARATOR, compose);
9288 if (compose_get_ext_editor_uses_socket()) {
9289 /* Only allow one socket */
9290 if (compose->exteditor_socket != NULL) {
9291 if (gtk_widget_is_focus(compose->exteditor_socket)) {
9292 /* Move the focus off of the socket */
9293 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9298 /* Create the receiving GtkSocket */
9299 socket = gtk_socket_new ();
9300 g_signal_connect (GTK_OBJECT(socket), "plug-removed",
9301 G_CALLBACK(compose_ext_editor_plug_removed_cb),
9303 gtk_box_pack_start(GTK_BOX(compose->edit_vbox), socket, TRUE, TRUE, 0);
9304 gtk_widget_set_size_request(socket, prefs_common.compose_width, -1);
9305 /* Realize the socket so that we can use its ID */
9306 gtk_widget_realize(socket);
9307 socket_wid = gtk_socket_get_id(GTK_SOCKET (socket));
9308 compose->exteditor_socket = socket;
9311 if (pipe(pipe_fds) < 0) {
9317 if ((pid = fork()) < 0) {
9324 /* close the write side of the pipe */
9327 compose->exteditor_file = g_strdup(tmp);
9328 compose->exteditor_pid = pid;
9330 compose_set_ext_editor_sensitive(compose, FALSE);
9333 compose->exteditor_ch = g_io_channel_unix_new(pipe_fds[0]);
9335 compose->exteditor_ch = g_io_channel_win32_new_fd(pipe_fds[0]);
9337 compose->exteditor_tag = g_io_add_watch(compose->exteditor_ch,
9341 } else { /* process-monitoring process */
9347 /* close the read side of the pipe */
9350 if (compose_write_body_to_file(compose, tmp) < 0) {
9351 fd_write_all(pipe_fds[1], "2\n", 2);
9355 pid_ed = compose_exec_ext_editor_real(tmp, socket_wid);
9357 fd_write_all(pipe_fds[1], "1\n", 2);
9361 /* wait until editor is terminated */
9362 waitpid(pid_ed, NULL, 0);
9364 fd_write_all(pipe_fds[1], "0\n", 2);
9371 #endif /* G_OS_UNIX */
9375 static gboolean compose_get_ext_editor_cmd_valid()
9377 gboolean has_s = FALSE;
9378 gboolean has_w = FALSE;
9379 const gchar *p = prefs_common_get_ext_editor_cmd();
9382 while ((p = strchr(p, '%'))) {
9388 } else if (*p == 'w') {
9399 static gint compose_exec_ext_editor_real(const gchar *file, GdkNativeWindow socket_wid)
9406 cm_return_val_if_fail(file != NULL, -1);
9408 if ((pid = fork()) < 0) {
9413 if (pid != 0) return pid;
9415 /* grandchild process */
9417 if (setpgid(0, getppid()))
9420 if (compose_get_ext_editor_cmd_valid()) {
9421 if (compose_get_ext_editor_uses_socket()) {
9422 p = g_strdup(prefs_common_get_ext_editor_cmd());
9423 s = strstr(p, "%w");
9425 if (strstr(p, "%s") < s)
9426 g_snprintf(buf, sizeof(buf), p, file, socket_wid);
9428 g_snprintf(buf, sizeof(buf), p, socket_wid, file);
9431 g_snprintf(buf, sizeof(buf),
9432 prefs_common_get_ext_editor_cmd(), file);
9435 if (prefs_common_get_ext_editor_cmd())
9436 g_warning("External editor command-line is invalid: '%s'",
9437 prefs_common_get_ext_editor_cmd());
9438 g_snprintf(buf, sizeof(buf), DEFAULT_EDITOR_CMD, file);
9441 cmdline = strsplit_with_quote(buf, " ", 1024);
9442 execvp(cmdline[0], cmdline);
9445 g_strfreev(cmdline);
9450 static gboolean compose_ext_editor_kill(Compose *compose)
9452 pid_t pgid = compose->exteditor_pid * -1;
9455 ret = kill(pgid, 0);
9457 if (ret == 0 || (ret == -1 && EPERM == errno)) {
9461 msg = g_strdup_printf
9462 (_("The external editor is still working.\n"
9463 "Force terminating the process?\n"
9464 "process group id: %d"), -pgid);
9465 val = alertpanel_full(_("Notice"), msg, GTK_STOCK_NO, GTK_STOCK_YES,
9466 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
9470 if (val == G_ALERTALTERNATE) {
9471 g_source_remove(compose->exteditor_tag);
9472 g_io_channel_shutdown(compose->exteditor_ch,
9474 g_io_channel_unref(compose->exteditor_ch);
9476 if (kill(pgid, SIGTERM) < 0) perror("kill");
9477 waitpid(compose->exteditor_pid, NULL, 0);
9479 g_warning("Terminated process group id: %d. "
9480 "Temporary file: %s", -pgid, compose->exteditor_file);
9482 compose_set_ext_editor_sensitive(compose, TRUE);
9484 g_free(compose->exteditor_file);
9485 compose->exteditor_file = NULL;
9486 compose->exteditor_pid = -1;
9487 compose->exteditor_ch = NULL;
9488 compose->exteditor_tag = -1;
9496 static gboolean compose_input_cb(GIOChannel *source, GIOCondition condition,
9500 Compose *compose = (Compose *)data;
9503 debug_print("Compose: input from monitoring process\n");
9505 if (g_io_channel_read_chars(source, buf, sizeof(buf), &bytes_read, NULL) != G_IO_STATUS_NORMAL) {
9510 g_io_channel_shutdown(source, FALSE, NULL);
9511 g_io_channel_unref(source);
9513 waitpid(compose->exteditor_pid, NULL, 0);
9515 if (buf[0] == '0') { /* success */
9516 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9517 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
9518 GtkTextIter start, end;
9521 gtk_text_buffer_set_text(buffer, "", -1);
9522 compose_insert_file(compose, compose->exteditor_file);
9523 compose_changed_cb(NULL, compose);
9524 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
9526 if (claws_unlink(compose->exteditor_file) < 0)
9527 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9529 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
9530 gtk_text_buffer_get_start_iter(buffer, &start);
9531 gtk_text_buffer_get_end_iter(buffer, &end);
9532 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
9533 if (chars && strlen(chars) > 0)
9534 compose->modified = TRUE;
9536 } else if (buf[0] == '1') { /* failed */
9537 g_warning("Couldn't exec external editor");
9538 if (claws_unlink(compose->exteditor_file) < 0)
9539 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9540 } else if (buf[0] == '2') {
9541 g_warning("Couldn't write to file");
9542 } else if (buf[0] == '3') {
9543 g_warning("Pipe read failed");
9546 compose_set_ext_editor_sensitive(compose, TRUE);
9548 g_free(compose->exteditor_file);
9549 compose->exteditor_file = NULL;
9550 compose->exteditor_pid = -1;
9551 compose->exteditor_ch = NULL;
9552 compose->exteditor_tag = -1;
9553 if (compose->exteditor_socket) {
9554 gtk_widget_destroy(compose->exteditor_socket);
9555 compose->exteditor_socket = NULL;
9562 static char *ext_editor_menu_entries[] = {
9563 "Menu/Message/Send",
9564 "Menu/Message/SendLater",
9565 "Menu/Message/InsertFile",
9566 "Menu/Message/InsertSig",
9567 "Menu/Message/ReplaceSig",
9568 "Menu/Message/Save",
9569 "Menu/Message/Print",
9574 "Menu/Tools/ShowRuler",
9575 "Menu/Tools/Actions",
9580 static void compose_set_ext_editor_sensitive(Compose *compose,
9585 for (i = 0; ext_editor_menu_entries[i]; ++i) {
9586 cm_menu_set_sensitive_full(compose->ui_manager,
9587 ext_editor_menu_entries[i], sensitive);
9590 if (compose_get_ext_editor_uses_socket()) {
9592 if (compose->exteditor_socket)
9593 gtk_widget_hide(compose->exteditor_socket);
9594 gtk_widget_show(compose->scrolledwin);
9595 if (prefs_common.show_ruler)
9596 gtk_widget_show(compose->ruler_hbox);
9597 /* Fix the focus, as it doesn't go anywhere when the
9598 * socket is hidden or destroyed */
9599 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9601 g_assert (compose->exteditor_socket != NULL);
9602 /* Fix the focus, as it doesn't go anywhere when the
9603 * edit box is hidden */
9604 if (gtk_widget_is_focus(compose->text))
9605 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9606 gtk_widget_hide(compose->scrolledwin);
9607 gtk_widget_hide(compose->ruler_hbox);
9608 gtk_widget_show(compose->exteditor_socket);
9611 gtk_widget_set_sensitive(compose->text, sensitive);
9613 if (compose->toolbar->send_btn)
9614 gtk_widget_set_sensitive(compose->toolbar->send_btn, sensitive);
9615 if (compose->toolbar->sendl_btn)
9616 gtk_widget_set_sensitive(compose->toolbar->sendl_btn, sensitive);
9617 if (compose->toolbar->draft_btn)
9618 gtk_widget_set_sensitive(compose->toolbar->draft_btn, sensitive);
9619 if (compose->toolbar->insert_btn)
9620 gtk_widget_set_sensitive(compose->toolbar->insert_btn, sensitive);
9621 if (compose->toolbar->sig_btn)
9622 gtk_widget_set_sensitive(compose->toolbar->sig_btn, sensitive);
9623 if (compose->toolbar->exteditor_btn)
9624 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, sensitive);
9625 if (compose->toolbar->linewrap_current_btn)
9626 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, sensitive);
9627 if (compose->toolbar->linewrap_all_btn)
9628 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, sensitive);
9631 static gboolean compose_get_ext_editor_uses_socket()
9633 return (prefs_common_get_ext_editor_cmd() &&
9634 strstr(prefs_common_get_ext_editor_cmd(), "%w"));
9637 static gboolean compose_ext_editor_plug_removed_cb(GtkSocket *socket, Compose *compose)
9639 compose->exteditor_socket = NULL;
9640 /* returning FALSE allows destruction of the socket */
9643 #endif /* G_OS_UNIX */
9646 * compose_undo_state_changed:
9648 * Change the sensivity of the menuentries undo and redo
9650 static void compose_undo_state_changed(UndoMain *undostruct, gint undo_state,
9651 gint redo_state, gpointer data)
9653 Compose *compose = (Compose *)data;
9655 switch (undo_state) {
9656 case UNDO_STATE_TRUE:
9657 if (!undostruct->undo_state) {
9658 undostruct->undo_state = TRUE;
9659 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", TRUE);
9662 case UNDO_STATE_FALSE:
9663 if (undostruct->undo_state) {
9664 undostruct->undo_state = FALSE;
9665 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
9668 case UNDO_STATE_UNCHANGED:
9670 case UNDO_STATE_REFRESH:
9671 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", undostruct->undo_state);
9674 g_warning("Undo state not recognized");
9678 switch (redo_state) {
9679 case UNDO_STATE_TRUE:
9680 if (!undostruct->redo_state) {
9681 undostruct->redo_state = TRUE;
9682 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", TRUE);
9685 case UNDO_STATE_FALSE:
9686 if (undostruct->redo_state) {
9687 undostruct->redo_state = FALSE;
9688 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
9691 case UNDO_STATE_UNCHANGED:
9693 case UNDO_STATE_REFRESH:
9694 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", undostruct->redo_state);
9697 g_warning("Redo state not recognized");
9702 /* callback functions */
9704 static void compose_notebook_size_alloc(GtkNotebook *notebook,
9705 GtkAllocation *allocation,
9708 prefs_common.compose_notebook_height = gtk_paned_get_position(paned);
9711 /* compose_edit_size_alloc() - called when resized. don't know whether Gtk
9712 * includes "non-client" (windows-izm) in calculation, so this calculation
9713 * may not be accurate.
9715 static gboolean compose_edit_size_alloc(GtkEditable *widget,
9716 GtkAllocation *allocation,
9717 GtkSHRuler *shruler)
9719 if (prefs_common.show_ruler) {
9720 gint char_width = 0, char_height = 0;
9721 gint line_width_in_chars;
9723 gtkut_get_font_size(GTK_WIDGET(widget),
9724 &char_width, &char_height);
9725 line_width_in_chars =
9726 (allocation->width - allocation->x) / char_width;
9728 /* got the maximum */
9729 gtk_shruler_set_range(GTK_SHRULER(shruler),
9730 0.0, line_width_in_chars, 0);
9739 ComposePrefType type;
9740 gboolean entry_marked;
9743 static void account_activated(GtkComboBox *optmenu, gpointer data)
9745 Compose *compose = (Compose *)data;
9748 gchar *folderidentifier;
9749 gint account_id = 0;
9752 GSList *list, *saved_list = NULL;
9753 HeaderEntryState *state;
9754 GtkRcStyle *style = NULL;
9755 #if !GTK_CHECK_VERSION(3, 0, 0)
9756 static GdkColor yellow;
9757 static gboolean color_set = FALSE;
9759 static GdkColor yellow = { (guint32)0, (guint32)0xf5, (guint32)0xf6, (guint32)0xbe };
9762 /* Get ID of active account in the combo box */
9763 menu = gtk_combo_box_get_model(optmenu);
9764 cm_return_if_fail(gtk_combo_box_get_active_iter(optmenu, &iter));
9765 gtk_tree_model_get(menu, &iter, 1, &account_id, -1);
9767 ac = account_find_from_id(account_id);
9768 cm_return_if_fail(ac != NULL);
9770 if (ac != compose->account) {
9771 compose_select_account(compose, ac, FALSE);
9773 for (list = compose->header_list; list; list = list->next) {
9774 ComposeHeaderEntry *hentry=(ComposeHeaderEntry *)list->data;
9776 if (hentry->type == PREF_ACCOUNT || !list->next) {
9777 compose_destroy_headerentry(compose, hentry);
9781 state = g_malloc0(sizeof(HeaderEntryState));
9782 state->header = gtk_editable_get_chars(GTK_EDITABLE(
9783 gtk_bin_get_child(GTK_BIN(hentry->combo))), 0, -1);
9784 state->entry = gtk_editable_get_chars(
9785 GTK_EDITABLE(hentry->entry), 0, -1);
9786 state->type = hentry->type;
9788 #if !GTK_CHECK_VERSION(3, 0, 0)
9790 gdk_color_parse("#f5f6be", &yellow);
9791 color_set = gdk_colormap_alloc_color(
9792 gdk_colormap_get_system(),
9793 &yellow, FALSE, TRUE);
9797 style = gtk_widget_get_modifier_style(hentry->entry);
9798 state->entry_marked = gdk_color_equal(&yellow,
9799 &style->base[GTK_STATE_NORMAL]);
9801 saved_list = g_slist_append(saved_list, state);
9802 compose_destroy_headerentry(compose, hentry);
9805 compose->header_last = NULL;
9806 g_slist_free(compose->header_list);
9807 compose->header_list = NULL;
9808 compose->header_nextrow = 1;
9809 compose_create_header_entry(compose);
9811 if (ac->set_autocc && ac->auto_cc)
9812 compose_entry_append(compose, ac->auto_cc,
9813 COMPOSE_CC, PREF_ACCOUNT);
9815 if (ac->set_autobcc && ac->auto_bcc)
9816 compose_entry_append(compose, ac->auto_bcc,
9817 COMPOSE_BCC, PREF_ACCOUNT);
9819 if (ac->set_autoreplyto && ac->auto_replyto)
9820 compose_entry_append(compose, ac->auto_replyto,
9821 COMPOSE_REPLYTO, PREF_ACCOUNT);
9823 for (list = saved_list; list; list = list->next) {
9824 state = (HeaderEntryState *) list->data;
9826 compose_add_header_entry(compose, state->header,
9827 state->entry, state->type);
9828 if (state->entry_marked)
9829 compose_entry_mark_default_to(compose, state->entry);
9831 g_free(state->header);
9832 g_free(state->entry);
9835 g_slist_free(saved_list);
9837 combobox_select_by_data(GTK_COMBO_BOX(compose->header_last->combo),
9838 (ac->protocol == A_NNTP) ?
9839 COMPOSE_NEWSGROUPS : COMPOSE_TO);
9842 /* Set message save folder */
9843 if (account_get_special_folder(compose->account, F_OUTBOX)) {
9844 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
9846 g_signal_connect(G_OBJECT(compose->savemsg_checkbtn), "toggled",
9847 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
9849 compose_set_save_to(compose, NULL);
9850 if (account_get_special_folder(compose->account, F_OUTBOX)) {
9851 folderidentifier = folder_item_get_identifier(account_get_special_folder
9852 (compose->account, F_OUTBOX));
9853 compose_set_save_to(compose, folderidentifier);
9854 g_free(folderidentifier);
9858 static void attach_selected(GtkTreeView *tree_view, GtkTreePath *tree_path,
9859 GtkTreeViewColumn *column, Compose *compose)
9861 compose_attach_property(NULL, compose);
9864 static gboolean attach_button_pressed(GtkWidget *widget, GdkEventButton *event,
9867 Compose *compose = (Compose *)data;
9868 GtkTreeSelection *attach_selection;
9869 gint attach_nr_selected;
9872 if (!event) return FALSE;
9874 if (event->button == 3) {
9875 attach_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
9876 attach_nr_selected = gtk_tree_selection_count_selected_rows(attach_selection);
9878 /* If no rows, or just one row is selected, right-click should
9879 * open menu relevant to the row being right-clicked on. We
9880 * achieve that by selecting the clicked row first. If more
9881 * than one row is selected, we shouldn't modify the selection,
9882 * as user may want to remove selected rows (attachments). */
9883 if (attach_nr_selected < 2) {
9884 gtk_tree_selection_unselect_all(attach_selection);
9885 attach_nr_selected = 0;
9886 gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget),
9887 event->x, event->y, &path, NULL, NULL, NULL);
9889 gtk_tree_selection_select_path(attach_selection, path);
9890 gtk_tree_path_free(path);
9891 attach_nr_selected++;
9895 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", (attach_nr_selected > 0));
9896 /* Properties menu item makes no sense with more than one row
9897 * selected, the properties dialog can only edit one attachment. */
9898 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", (attach_nr_selected == 1));
9900 gtk_menu_popup(GTK_MENU(compose->popupmenu), NULL, NULL,
9901 NULL, NULL, event->button, event->time);
9908 static gboolean attach_key_pressed(GtkWidget *widget, GdkEventKey *event,
9911 Compose *compose = (Compose *)data;
9913 if (!event) return FALSE;
9915 switch (event->keyval) {
9916 case GDK_KEY_Delete:
9917 compose_attach_remove_selected(NULL, compose);
9923 static void compose_allow_user_actions (Compose *compose, gboolean allow)
9925 toolbar_comp_set_sensitive(compose, allow);
9926 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message", allow);
9927 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", allow);
9929 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", allow);
9931 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", allow);
9932 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools", allow);
9933 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Help", allow);
9935 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), allow);
9939 static void compose_send_cb(GtkAction *action, gpointer data)
9941 Compose *compose = (Compose *)data;
9944 if (compose->exteditor_tag != -1) {
9945 debug_print("ignoring send: external editor still open\n");
9949 if (prefs_common.work_offline &&
9950 !inc_offline_should_override(TRUE,
9951 _("Claws Mail needs network access in order "
9952 "to send this email.")))
9955 if (compose->draft_timeout_tag >= 0) { /* CLAWS: disable draft timeout */
9956 g_source_remove(compose->draft_timeout_tag);
9957 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
9960 compose_send(compose);
9963 static void compose_send_later_cb(GtkAction *action, gpointer data)
9965 Compose *compose = (Compose *)data;
9969 compose_allow_user_actions(compose, FALSE);
9970 val = compose_queue_sub(compose, NULL, NULL, NULL, TRUE, TRUE);
9971 compose_allow_user_actions(compose, TRUE);
9975 compose_close(compose);
9976 } else if (val == -1) {
9977 alertpanel_error(_("Could not queue message."));
9978 } else if (val == -2) {
9979 alertpanel_error(_("Could not queue message:\n\n%s."), g_strerror(errno));
9980 } else if (val == -3) {
9981 if (privacy_peek_error())
9982 alertpanel_error(_("Could not queue message for sending:\n\n"
9983 "Signature failed: %s"), privacy_get_error());
9984 } else if (val == -4) {
9985 alertpanel_error(_("Could not queue message for sending:\n\n"
9986 "Charset conversion failed."));
9987 } else if (val == -5) {
9988 alertpanel_error(_("Could not queue message for sending:\n\n"
9989 "Couldn't get recipient encryption key."));
9990 } else if (val == -6) {
9993 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
9996 #define DRAFTED_AT_EXIT "drafted_at_exit"
9997 static void compose_register_draft(MsgInfo *info)
9999 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10000 DRAFTED_AT_EXIT, NULL);
10001 FILE *fp = g_fopen(filepath, "ab");
10004 fprintf(fp, "%s\t%d\n", folder_item_get_identifier(info->folder),
10012 gboolean compose_draft (gpointer data, guint action)
10014 Compose *compose = (Compose *)data;
10019 MsgFlags flag = {0, 0};
10020 static gboolean lock = FALSE;
10021 MsgInfo *newmsginfo;
10023 gboolean target_locked = FALSE;
10024 gboolean err = FALSE;
10026 if (lock) return FALSE;
10028 if (compose->sending)
10031 draft = account_get_special_folder(compose->account, F_DRAFT);
10032 cm_return_val_if_fail(draft != NULL, FALSE);
10034 if (!g_mutex_trylock(compose->mutex)) {
10035 /* we don't want to lock the mutex once it's available,
10036 * because as the only other part of compose.c locking
10037 * it is compose_close - which means once unlocked,
10038 * the compose struct will be freed */
10039 debug_print("couldn't lock mutex, probably sending\n");
10045 tmp = g_strdup_printf("%s%cdraft.%p", get_tmp_dir(),
10046 G_DIR_SEPARATOR, compose);
10047 if ((fp = g_fopen(tmp, "wb")) == NULL) {
10048 FILE_OP_ERROR(tmp, "fopen");
10052 /* chmod for security */
10053 if (change_file_mode_rw(fp, tmp) < 0) {
10054 FILE_OP_ERROR(tmp, "chmod");
10055 g_warning("can't change file mode");
10058 /* Save draft infos */
10059 err |= (fprintf(fp, "X-Claws-Account-Id:%d\n", compose->account->account_id) < 0);
10060 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
10062 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
10063 gchar *savefolderid;
10065 savefolderid = compose_get_save_to(compose);
10066 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
10067 g_free(savefolderid);
10069 if (compose->return_receipt) {
10070 err |= (fprintf(fp, "RRCPT:1\n") < 0);
10072 if (compose->privacy_system) {
10073 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
10074 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
10075 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
10078 /* Message-ID of message replying to */
10079 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
10080 gchar *folderid = NULL;
10082 if (compose->replyinfo->folder)
10083 folderid = folder_item_get_identifier(compose->replyinfo->folder);
10084 if (folderid == NULL)
10085 folderid = g_strdup("NULL");
10087 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
10090 /* Message-ID of message forwarding to */
10091 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
10092 gchar *folderid = NULL;
10094 if (compose->fwdinfo->folder)
10095 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
10096 if (folderid == NULL)
10097 folderid = g_strdup("NULL");
10099 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
10103 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
10104 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
10106 sheaders = compose_get_manual_headers_info(compose);
10107 err |= (fprintf(fp, "X-Claws-Manual-Headers:%s\n", sheaders) < 0);
10110 /* end of headers */
10111 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
10118 if (compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_STORE, action != COMPOSE_AUTO_SAVE) < 0) {
10122 if (fclose(fp) == EOF) {
10126 flag.perm_flags = MSG_NEW|MSG_UNREAD;
10127 if (compose->targetinfo) {
10128 target_locked = MSG_IS_LOCKED(compose->targetinfo->flags);
10130 flag.perm_flags |= MSG_LOCKED;
10132 flag.tmp_flags = MSG_DRAFT;
10134 folder_item_scan(draft);
10135 if ((msgnum = folder_item_add_msg(draft, tmp, &flag, TRUE)) < 0) {
10136 MsgInfo *tmpinfo = NULL;
10137 debug_print("didn't get msgnum after adding draft [%s]\n", compose->msgid?compose->msgid:"no msgid");
10138 if (compose->msgid) {
10139 tmpinfo = folder_item_get_msginfo_by_msgid(draft, compose->msgid);
10142 msgnum = tmpinfo->msgnum;
10143 procmsg_msginfo_free(&tmpinfo);
10144 debug_print("got draft msgnum %d from scanning\n", msgnum);
10146 debug_print("didn't get draft msgnum after scanning\n");
10149 debug_print("got draft msgnum %d from adding\n", msgnum);
10155 if (action != COMPOSE_AUTO_SAVE) {
10156 if (action != COMPOSE_DRAFT_FOR_EXIT)
10157 alertpanel_error(_("Could not save draft."));
10160 gtkut_window_popup(compose->window);
10161 val = alertpanel_full(_("Could not save draft"),
10162 _("Could not save draft.\n"
10163 "Do you want to cancel exit or discard this email?"),
10164 _("_Cancel exit"), _("_Discard email"), NULL,
10165 FALSE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
10166 if (val == G_ALERTALTERNATE) {
10168 g_mutex_unlock(compose->mutex); /* must be done before closing */
10169 compose_close(compose);
10173 g_mutex_unlock(compose->mutex); /* must be done before closing */
10182 if (compose->mode == COMPOSE_REEDIT) {
10183 compose_remove_reedit_target(compose, TRUE);
10186 newmsginfo = folder_item_get_msginfo(draft, msgnum);
10189 procmsg_msginfo_unset_flags(newmsginfo, ~0, ~0);
10191 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD|MSG_LOCKED, MSG_DRAFT);
10193 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD, MSG_DRAFT);
10194 if (compose_use_attach(compose) && action != COMPOSE_AUTO_SAVE)
10195 procmsg_msginfo_set_flags(newmsginfo, 0,
10196 MSG_HAS_ATTACHMENT);
10198 if (action == COMPOSE_DRAFT_FOR_EXIT) {
10199 compose_register_draft(newmsginfo);
10201 procmsg_msginfo_free(&newmsginfo);
10204 folder_item_scan(draft);
10206 if (action == COMPOSE_QUIT_EDITING || action == COMPOSE_DRAFT_FOR_EXIT) {
10208 g_mutex_unlock(compose->mutex); /* must be done before closing */
10209 compose_close(compose);
10215 path = folder_item_fetch_msg(draft, msgnum);
10216 if (path == NULL) {
10217 debug_print("can't fetch %s:%d\n", draft->path, msgnum);
10220 if (g_stat(path, &s) < 0) {
10221 FILE_OP_ERROR(path, "stat");
10227 procmsg_msginfo_free(&(compose->targetinfo));
10228 compose->targetinfo = procmsg_msginfo_new();
10229 compose->targetinfo->msgnum = msgnum;
10230 compose->targetinfo->size = (goffset)s.st_size;
10231 compose->targetinfo->mtime = s.st_mtime;
10232 compose->targetinfo->folder = draft;
10234 procmsg_msginfo_set_flags(compose->targetinfo, MSG_LOCKED, 0);
10235 compose->mode = COMPOSE_REEDIT;
10237 if (action == COMPOSE_AUTO_SAVE) {
10238 compose->autosaved_draft = compose->targetinfo;
10240 compose->modified = FALSE;
10241 compose_set_title(compose);
10245 g_mutex_unlock(compose->mutex);
10249 void compose_clear_exit_drafts(void)
10251 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10252 DRAFTED_AT_EXIT, NULL);
10253 if (is_file_exist(filepath))
10254 claws_unlink(filepath);
10259 void compose_reopen_exit_drafts(void)
10261 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10262 DRAFTED_AT_EXIT, NULL);
10263 FILE *fp = g_fopen(filepath, "rb");
10267 while (fgets(buf, sizeof(buf), fp)) {
10268 gchar **parts = g_strsplit(buf, "\t", 2);
10269 const gchar *folder = parts[0];
10270 int msgnum = parts[1] ? atoi(parts[1]):-1;
10272 if (folder && *folder && msgnum > -1) {
10273 FolderItem *item = folder_find_item_from_identifier(folder);
10274 MsgInfo *info = folder_item_get_msginfo(item, msgnum);
10276 compose_reedit(info, FALSE);
10283 compose_clear_exit_drafts();
10286 static void compose_save_cb(GtkAction *action, gpointer data)
10288 Compose *compose = (Compose *)data;
10289 compose_draft(compose, COMPOSE_KEEP_EDITING);
10290 compose->rmode = COMPOSE_REEDIT;
10293 void compose_attach_from_list(Compose *compose, GList *file_list, gboolean free_data)
10295 if (compose && file_list) {
10298 for ( tmp = file_list; tmp; tmp = tmp->next) {
10299 gchar *file = (gchar *) tmp->data;
10300 gchar *utf8_filename = conv_filename_to_utf8(file);
10301 compose_attach_append(compose, file, utf8_filename, NULL, NULL);
10302 compose_changed_cb(NULL, compose);
10307 g_free(utf8_filename);
10312 static void compose_attach_cb(GtkAction *action, gpointer data)
10314 Compose *compose = (Compose *)data;
10317 if (compose->redirect_filename != NULL)
10320 /* Set focus_window properly, in case we were called via popup menu,
10321 * which unsets it (via focus_out_event callback on compose window). */
10322 manage_window_focus_in(compose->window, NULL, NULL);
10324 file_list = filesel_select_multiple_files_open(_("Select file"));
10327 compose_attach_from_list(compose, file_list, TRUE);
10328 g_list_free(file_list);
10332 static void compose_insert_file_cb(GtkAction *action, gpointer data)
10334 Compose *compose = (Compose *)data;
10336 gint files_inserted = 0;
10338 file_list = filesel_select_multiple_files_open(_("Select file"));
10343 for ( tmp = file_list; tmp; tmp = tmp->next) {
10344 gchar *file = (gchar *) tmp->data;
10345 gchar *filedup = g_strdup(file);
10346 gchar *shortfile = g_path_get_basename(filedup);
10347 ComposeInsertResult res;
10348 /* insert the file if the file is short or if the user confirmed that
10349 he/she wants to insert the large file */
10350 res = compose_insert_file(compose, file);
10351 if (res == COMPOSE_INSERT_READ_ERROR) {
10352 alertpanel_error(_("File '%s' could not be read."), shortfile);
10353 } else if (res == COMPOSE_INSERT_INVALID_CHARACTER) {
10354 alertpanel_error(_("File '%s' contained invalid characters\n"
10355 "for the current encoding, insertion may be incorrect."),
10357 } else if (res == COMPOSE_INSERT_SUCCESS)
10364 g_list_free(file_list);
10368 if (files_inserted > 0 && compose->gtkaspell &&
10369 compose->gtkaspell->check_while_typing)
10370 gtkaspell_highlight_all(compose->gtkaspell);
10374 static void compose_insert_sig_cb(GtkAction *action, gpointer data)
10376 Compose *compose = (Compose *)data;
10378 compose_insert_sig(compose, FALSE);
10381 static void compose_replace_sig_cb(GtkAction *action, gpointer data)
10383 Compose *compose = (Compose *)data;
10385 compose_insert_sig(compose, TRUE);
10388 static gint compose_delete_cb(GtkWidget *widget, GdkEventAny *event,
10392 Compose *compose = (Compose *)data;
10394 gtkut_widget_get_uposition(widget, &x, &y);
10395 if (!compose->batch) {
10396 prefs_common.compose_x = x;
10397 prefs_common.compose_y = y;
10399 if (compose->sending || compose->updating)
10401 compose_close_cb(NULL, compose);
10405 void compose_close_toolbar(Compose *compose)
10407 compose_close_cb(NULL, compose);
10410 static gboolean compose_can_autosave(Compose *compose)
10412 if (compose->privacy_system && compose->use_encryption)
10413 return prefs_common.autosave && prefs_common.autosave_encrypted;
10415 return prefs_common.autosave;
10418 static void compose_close_cb(GtkAction *action, gpointer data)
10420 Compose *compose = (Compose *)data;
10424 if (compose->exteditor_tag != -1) {
10425 if (!compose_ext_editor_kill(compose))
10430 if (compose->modified) {
10431 gboolean reedit = (compose->rmode == COMPOSE_REEDIT);
10432 if (!g_mutex_trylock(compose->mutex)) {
10433 /* we don't want to lock the mutex once it's available,
10434 * because as the only other part of compose.c locking
10435 * it is compose_close - which means once unlocked,
10436 * the compose struct will be freed */
10437 debug_print("couldn't lock mutex, probably sending\n");
10441 val = alertpanel(_("Discard message"),
10442 _("This message has been modified. Discard it?"),
10443 _("_Discard"), _("_Save to Drafts"), GTK_STOCK_CANCEL);
10445 val = alertpanel(_("Save changes"),
10446 _("This message has been modified. Save the latest changes?"),
10447 _("_Don't save"), g_strconcat("+", _("_Save to Drafts"), NULL),
10450 g_mutex_unlock(compose->mutex);
10452 case G_ALERTDEFAULT:
10453 if (compose_can_autosave(compose) && !reedit)
10454 compose_remove_draft(compose);
10456 case G_ALERTALTERNATE:
10457 compose_draft(data, COMPOSE_QUIT_EDITING);
10464 compose_close(compose);
10467 static void compose_print_cb(GtkAction *action, gpointer data)
10469 Compose *compose = (Compose *) data;
10471 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
10472 if (compose->targetinfo)
10473 messageview_print(compose->targetinfo, FALSE, -1, -1, 0);
10476 static void compose_set_encoding_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
10478 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
10479 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
10480 Compose *compose = (Compose *) data;
10483 compose->out_encoding = (CharSet)value;
10486 static void compose_address_cb(GtkAction *action, gpointer data)
10488 Compose *compose = (Compose *)data;
10490 #ifndef USE_ALT_ADDRBOOK
10491 addressbook_open(compose);
10493 GError* error = NULL;
10494 addressbook_connect_signals(compose);
10495 addressbook_dbus_open(TRUE, &error);
10497 g_warning("%s", error->message);
10498 g_error_free(error);
10503 static void about_show_cb(GtkAction *action, gpointer data)
10508 static void compose_template_activate_cb(GtkWidget *widget, gpointer data)
10510 Compose *compose = (Compose *)data;
10515 tmpl = g_object_get_data(G_OBJECT(widget), "template");
10516 cm_return_if_fail(tmpl != NULL);
10518 msg = g_strdup_printf(_("Do you want to apply the template '%s'?"),
10520 val = alertpanel(_("Apply template"), msg,
10521 _("_Replace"), _("_Insert"), GTK_STOCK_CANCEL);
10524 if (val == G_ALERTDEFAULT)
10525 compose_template_apply(compose, tmpl, TRUE);
10526 else if (val == G_ALERTALTERNATE)
10527 compose_template_apply(compose, tmpl, FALSE);
10530 static void compose_ext_editor_cb(GtkAction *action, gpointer data)
10532 Compose *compose = (Compose *)data;
10534 compose_exec_ext_editor(compose);
10537 static void compose_undo_cb(GtkAction *action, gpointer data)
10539 Compose *compose = (Compose *)data;
10540 gboolean prev_autowrap = compose->autowrap;
10542 compose->autowrap = FALSE;
10543 undo_undo(compose->undostruct);
10544 compose->autowrap = prev_autowrap;
10547 static void compose_redo_cb(GtkAction *action, gpointer data)
10549 Compose *compose = (Compose *)data;
10550 gboolean prev_autowrap = compose->autowrap;
10552 compose->autowrap = FALSE;
10553 undo_redo(compose->undostruct);
10554 compose->autowrap = prev_autowrap;
10557 static void entry_cut_clipboard(GtkWidget *entry)
10559 if (GTK_IS_EDITABLE(entry))
10560 gtk_editable_cut_clipboard (GTK_EDITABLE(entry));
10561 else if (GTK_IS_TEXT_VIEW(entry))
10562 gtk_text_buffer_cut_clipboard(
10563 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10564 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
10568 static void entry_copy_clipboard(GtkWidget *entry)
10570 if (GTK_IS_EDITABLE(entry))
10571 gtk_editable_copy_clipboard (GTK_EDITABLE(entry));
10572 else if (GTK_IS_TEXT_VIEW(entry))
10573 gtk_text_buffer_copy_clipboard(
10574 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10575 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
10578 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry,
10579 gboolean wrap, GdkAtom clip, GtkTextIter *insert_place)
10581 if (GTK_IS_TEXT_VIEW(entry)) {
10582 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10583 GtkTextMark *mark_start = gtk_text_buffer_get_insert(buffer);
10584 GtkTextIter start_iter, end_iter;
10586 gchar *contents = gtk_clipboard_wait_for_text(gtk_clipboard_get(clip));
10588 if (contents == NULL)
10591 /* we shouldn't delete the selection when middle-click-pasting, or we
10592 * can't mid-click-paste our own selection */
10593 if (clip != GDK_SELECTION_PRIMARY) {
10594 undo_paste_clipboard(GTK_TEXT_VIEW(compose->text), compose->undostruct);
10595 gtk_text_buffer_delete_selection(buffer, FALSE, TRUE);
10598 if (insert_place == NULL) {
10599 /* if insert_place isn't specified, insert at the cursor.
10600 * used for Ctrl-V pasting */
10601 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10602 start = gtk_text_iter_get_offset(&start_iter);
10603 gtk_text_buffer_insert(buffer, &start_iter, contents, strlen(contents));
10605 /* if insert_place is specified, paste here.
10606 * used for mid-click-pasting */
10607 start = gtk_text_iter_get_offset(insert_place);
10608 gtk_text_buffer_insert(buffer, insert_place, contents, strlen(contents));
10609 if (prefs_common.primary_paste_unselects)
10610 gtk_text_buffer_select_range(buffer, insert_place, insert_place);
10614 /* paste unwrapped: mark the paste so it's not wrapped later */
10615 end = start + strlen(contents);
10616 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, start);
10617 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, end);
10618 gtk_text_buffer_apply_tag_by_name(buffer, "no_wrap", &start_iter, &end_iter);
10619 } else if (wrap && clip == GDK_SELECTION_PRIMARY) {
10620 /* rewrap paragraph now (after a mid-click-paste) */
10621 mark_start = gtk_text_buffer_get_insert(buffer);
10622 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10623 gtk_text_iter_backward_char(&start_iter);
10624 compose_beautify_paragraph(compose, &start_iter, TRUE);
10626 } else if (GTK_IS_EDITABLE(entry))
10627 gtk_editable_paste_clipboard (GTK_EDITABLE(entry));
10629 compose->modified = TRUE;
10632 static void entry_allsel(GtkWidget *entry)
10634 if (GTK_IS_EDITABLE(entry))
10635 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
10636 else if (GTK_IS_TEXT_VIEW(entry)) {
10637 GtkTextIter startiter, enditer;
10638 GtkTextBuffer *textbuf;
10640 textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10641 gtk_text_buffer_get_start_iter(textbuf, &startiter);
10642 gtk_text_buffer_get_end_iter(textbuf, &enditer);
10644 gtk_text_buffer_move_mark_by_name(textbuf,
10645 "selection_bound", &startiter);
10646 gtk_text_buffer_move_mark_by_name(textbuf,
10647 "insert", &enditer);
10651 static void compose_cut_cb(GtkAction *action, gpointer data)
10653 Compose *compose = (Compose *)data;
10654 if (compose->focused_editable
10655 #ifndef GENERIC_UMPC
10656 && gtk_widget_has_focus(compose->focused_editable)
10659 entry_cut_clipboard(compose->focused_editable);
10662 static void compose_copy_cb(GtkAction *action, gpointer data)
10664 Compose *compose = (Compose *)data;
10665 if (compose->focused_editable
10666 #ifndef GENERIC_UMPC
10667 && gtk_widget_has_focus(compose->focused_editable)
10670 entry_copy_clipboard(compose->focused_editable);
10673 static void compose_paste_cb(GtkAction *action, gpointer data)
10675 Compose *compose = (Compose *)data;
10676 gint prev_autowrap;
10677 GtkTextBuffer *buffer;
10679 if (compose->focused_editable &&
10680 #ifndef GENERIC_UMPC
10681 gtk_widget_has_focus(compose->focused_editable)
10684 entry_paste_clipboard(compose, compose->focused_editable,
10685 prefs_common.linewrap_pastes,
10686 GDK_SELECTION_CLIPBOARD, NULL);
10691 #ifndef GENERIC_UMPC
10692 gtk_widget_has_focus(compose->text) &&
10694 compose->gtkaspell &&
10695 compose->gtkaspell->check_while_typing)
10696 gtkaspell_highlight_all(compose->gtkaspell);
10700 static void compose_paste_as_quote_cb(GtkAction *action, gpointer data)
10702 Compose *compose = (Compose *)data;
10703 gint wrap_quote = prefs_common.linewrap_quote;
10704 if (compose->focused_editable
10705 #ifndef GENERIC_UMPC
10706 && gtk_widget_has_focus(compose->focused_editable)
10709 /* let text_insert() (called directly or at a later time
10710 * after the gtk_editable_paste_clipboard) know that
10711 * text is to be inserted as a quotation. implemented
10712 * by using a simple refcount... */
10713 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data(
10714 G_OBJECT(compose->focused_editable),
10715 "paste_as_quotation"));
10716 g_object_set_data(G_OBJECT(compose->focused_editable),
10717 "paste_as_quotation",
10718 GINT_TO_POINTER(paste_as_quotation + 1));
10719 prefs_common.linewrap_quote = prefs_common.linewrap_pastes;
10720 entry_paste_clipboard(compose, compose->focused_editable,
10721 prefs_common.linewrap_pastes,
10722 GDK_SELECTION_CLIPBOARD, NULL);
10723 prefs_common.linewrap_quote = wrap_quote;
10727 static void compose_paste_no_wrap_cb(GtkAction *action, gpointer data)
10729 Compose *compose = (Compose *)data;
10730 gint prev_autowrap;
10731 GtkTextBuffer *buffer;
10733 if (compose->focused_editable
10734 #ifndef GENERIC_UMPC
10735 && gtk_widget_has_focus(compose->focused_editable)
10738 entry_paste_clipboard(compose, compose->focused_editable, FALSE,
10739 GDK_SELECTION_CLIPBOARD, NULL);
10744 #ifndef GENERIC_UMPC
10745 gtk_widget_has_focus(compose->text) &&
10747 compose->gtkaspell &&
10748 compose->gtkaspell->check_while_typing)
10749 gtkaspell_highlight_all(compose->gtkaspell);
10753 static void compose_paste_wrap_cb(GtkAction *action, gpointer data)
10755 Compose *compose = (Compose *)data;
10756 gint prev_autowrap;
10757 GtkTextBuffer *buffer;
10759 if (compose->focused_editable
10760 #ifndef GENERIC_UMPC
10761 && gtk_widget_has_focus(compose->focused_editable)
10764 entry_paste_clipboard(compose, compose->focused_editable, TRUE,
10765 GDK_SELECTION_CLIPBOARD, NULL);
10770 #ifndef GENERIC_UMPC
10771 gtk_widget_has_focus(compose->text) &&
10773 compose->gtkaspell &&
10774 compose->gtkaspell->check_while_typing)
10775 gtkaspell_highlight_all(compose->gtkaspell);
10779 static void compose_allsel_cb(GtkAction *action, gpointer data)
10781 Compose *compose = (Compose *)data;
10782 if (compose->focused_editable
10783 #ifndef GENERIC_UMPC
10784 && gtk_widget_has_focus(compose->focused_editable)
10787 entry_allsel(compose->focused_editable);
10790 static void textview_move_beginning_of_line (GtkTextView *text)
10792 GtkTextBuffer *buffer;
10796 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10798 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10799 mark = gtk_text_buffer_get_insert(buffer);
10800 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10801 gtk_text_iter_set_line_offset(&ins, 0);
10802 gtk_text_buffer_place_cursor(buffer, &ins);
10805 static void textview_move_forward_character (GtkTextView *text)
10807 GtkTextBuffer *buffer;
10811 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10813 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10814 mark = gtk_text_buffer_get_insert(buffer);
10815 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10816 if (gtk_text_iter_forward_cursor_position(&ins))
10817 gtk_text_buffer_place_cursor(buffer, &ins);
10820 static void textview_move_backward_character (GtkTextView *text)
10822 GtkTextBuffer *buffer;
10826 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10828 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10829 mark = gtk_text_buffer_get_insert(buffer);
10830 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10831 if (gtk_text_iter_backward_cursor_position(&ins))
10832 gtk_text_buffer_place_cursor(buffer, &ins);
10835 static void textview_move_forward_word (GtkTextView *text)
10837 GtkTextBuffer *buffer;
10842 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10844 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10845 mark = gtk_text_buffer_get_insert(buffer);
10846 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10847 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
10848 if (gtk_text_iter_forward_word_ends(&ins, count)) {
10849 gtk_text_iter_backward_word_start(&ins);
10850 gtk_text_buffer_place_cursor(buffer, &ins);
10854 static void textview_move_backward_word (GtkTextView *text)
10856 GtkTextBuffer *buffer;
10860 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10862 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10863 mark = gtk_text_buffer_get_insert(buffer);
10864 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10865 if (gtk_text_iter_backward_word_starts(&ins, 1))
10866 gtk_text_buffer_place_cursor(buffer, &ins);
10869 static void textview_move_end_of_line (GtkTextView *text)
10871 GtkTextBuffer *buffer;
10875 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10877 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10878 mark = gtk_text_buffer_get_insert(buffer);
10879 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10880 if (gtk_text_iter_forward_to_line_end(&ins))
10881 gtk_text_buffer_place_cursor(buffer, &ins);
10884 static void textview_move_next_line (GtkTextView *text)
10886 GtkTextBuffer *buffer;
10891 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10893 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10894 mark = gtk_text_buffer_get_insert(buffer);
10895 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10896 offset = gtk_text_iter_get_line_offset(&ins);
10897 if (gtk_text_iter_forward_line(&ins)) {
10898 gtk_text_iter_set_line_offset(&ins, offset);
10899 gtk_text_buffer_place_cursor(buffer, &ins);
10903 static void textview_move_previous_line (GtkTextView *text)
10905 GtkTextBuffer *buffer;
10910 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10912 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10913 mark = gtk_text_buffer_get_insert(buffer);
10914 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10915 offset = gtk_text_iter_get_line_offset(&ins);
10916 if (gtk_text_iter_backward_line(&ins)) {
10917 gtk_text_iter_set_line_offset(&ins, offset);
10918 gtk_text_buffer_place_cursor(buffer, &ins);
10922 static void textview_delete_forward_character (GtkTextView *text)
10924 GtkTextBuffer *buffer;
10926 GtkTextIter ins, end_iter;
10928 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10930 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10931 mark = gtk_text_buffer_get_insert(buffer);
10932 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10934 if (gtk_text_iter_forward_char(&end_iter)) {
10935 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10939 static void textview_delete_backward_character (GtkTextView *text)
10941 GtkTextBuffer *buffer;
10943 GtkTextIter ins, end_iter;
10945 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10947 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10948 mark = gtk_text_buffer_get_insert(buffer);
10949 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10951 if (gtk_text_iter_backward_char(&end_iter)) {
10952 gtk_text_buffer_delete(buffer, &end_iter, &ins);
10956 static void textview_delete_forward_word (GtkTextView *text)
10958 GtkTextBuffer *buffer;
10960 GtkTextIter ins, end_iter;
10962 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10964 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10965 mark = gtk_text_buffer_get_insert(buffer);
10966 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10968 if (gtk_text_iter_forward_word_end(&end_iter)) {
10969 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10973 static void textview_delete_backward_word (GtkTextView *text)
10975 GtkTextBuffer *buffer;
10977 GtkTextIter ins, end_iter;
10979 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10981 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10982 mark = gtk_text_buffer_get_insert(buffer);
10983 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10985 if (gtk_text_iter_backward_word_start(&end_iter)) {
10986 gtk_text_buffer_delete(buffer, &end_iter, &ins);
10990 static void textview_delete_line (GtkTextView *text)
10992 GtkTextBuffer *buffer;
10994 GtkTextIter ins, start_iter, end_iter;
10996 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10998 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10999 mark = gtk_text_buffer_get_insert(buffer);
11000 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11003 gtk_text_iter_set_line_offset(&start_iter, 0);
11006 if (gtk_text_iter_ends_line(&end_iter)){
11007 if (!gtk_text_iter_forward_char(&end_iter))
11008 gtk_text_iter_backward_char(&start_iter);
11011 gtk_text_iter_forward_to_line_end(&end_iter);
11012 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
11015 static void textview_delete_to_line_end (GtkTextView *text)
11017 GtkTextBuffer *buffer;
11019 GtkTextIter ins, end_iter;
11021 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11023 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11024 mark = gtk_text_buffer_get_insert(buffer);
11025 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11027 if (gtk_text_iter_ends_line(&end_iter))
11028 gtk_text_iter_forward_char(&end_iter);
11030 gtk_text_iter_forward_to_line_end(&end_iter);
11031 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11034 #define DO_ACTION(name, act) { \
11035 if(!strcmp(name, a_name)) { \
11039 static ComposeCallAdvancedAction compose_call_advanced_action_from_path(GtkAction *action)
11041 const gchar *a_name = gtk_action_get_name(action);
11042 DO_ACTION("Edit/Advanced/BackChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER);
11043 DO_ACTION("Edit/Advanced/ForwChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER);
11044 DO_ACTION("Edit/Advanced/BackWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD);
11045 DO_ACTION("Edit/Advanced/ForwWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD);
11046 DO_ACTION("Edit/Advanced/BegLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE);
11047 DO_ACTION("Edit/Advanced/EndLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE);
11048 DO_ACTION("Edit/Advanced/PrevLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE);
11049 DO_ACTION("Edit/Advanced/NextLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE);
11050 DO_ACTION("Edit/Advanced/DelBackChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER);
11051 DO_ACTION("Edit/Advanced/DelForwChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER);
11052 DO_ACTION("Edit/Advanced/DelBackWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD);
11053 DO_ACTION("Edit/Advanced/DelForwWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD);
11054 DO_ACTION("Edit/Advanced/DelLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE);
11055 DO_ACTION("Edit/Advanced/DelEndLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END);
11059 static void compose_advanced_action_cb(GtkAction *gaction, gpointer data)
11061 Compose *compose = (Compose *)data;
11062 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11063 ComposeCallAdvancedAction action = -1;
11065 action = compose_call_advanced_action_from_path(gaction);
11068 void (*do_action) (GtkTextView *text);
11069 } action_table[] = {
11070 {textview_move_beginning_of_line},
11071 {textview_move_forward_character},
11072 {textview_move_backward_character},
11073 {textview_move_forward_word},
11074 {textview_move_backward_word},
11075 {textview_move_end_of_line},
11076 {textview_move_next_line},
11077 {textview_move_previous_line},
11078 {textview_delete_forward_character},
11079 {textview_delete_backward_character},
11080 {textview_delete_forward_word},
11081 {textview_delete_backward_word},
11082 {textview_delete_line},
11083 {textview_delete_to_line_end}
11086 if (!gtk_widget_has_focus(GTK_WIDGET(text))) return;
11088 if (action >= COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE &&
11089 action <= COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END) {
11090 if (action_table[action].do_action)
11091 action_table[action].do_action(text);
11093 g_warning("Not implemented yet.");
11097 static void compose_grab_focus_cb(GtkWidget *widget, Compose *compose)
11099 GtkAllocation allocation;
11103 if (GTK_IS_EDITABLE(widget)) {
11104 str = gtk_editable_get_chars(GTK_EDITABLE(widget), 0, -1);
11105 gtk_editable_set_position(GTK_EDITABLE(widget),
11108 if ((parent = gtk_widget_get_parent(widget))
11109 && (parent = gtk_widget_get_parent(parent))
11110 && (parent = gtk_widget_get_parent(parent))) {
11111 if (GTK_IS_SCROLLED_WINDOW(parent)) {
11112 gtk_widget_get_allocation(widget, &allocation);
11113 gint y = allocation.y;
11114 gint height = allocation.height;
11115 GtkAdjustment *shown = gtk_scrolled_window_get_vadjustment
11116 (GTK_SCROLLED_WINDOW(parent));
11118 gfloat value = gtk_adjustment_get_value(shown);
11119 gfloat upper = gtk_adjustment_get_upper(shown);
11120 gfloat page_size = gtk_adjustment_get_page_size(shown);
11121 if (y < (int)value) {
11122 gtk_adjustment_set_value(shown, y - 1);
11124 if ((y + height) > ((int)value + (int)page_size)) {
11125 if ((y - height - 1) < ((int)upper - (int)page_size)) {
11126 gtk_adjustment_set_value(shown,
11127 y + height - (int)page_size - 1);
11129 gtk_adjustment_set_value(shown,
11130 (int)upper - (int)page_size - 1);
11137 if (GTK_IS_EDITABLE(widget) || GTK_IS_TEXT_VIEW(widget))
11138 compose->focused_editable = widget;
11140 #ifdef GENERIC_UMPC
11141 if (GTK_IS_TEXT_VIEW(widget)
11142 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->edit_vbox) {
11143 g_object_ref(compose->notebook);
11144 g_object_ref(compose->edit_vbox);
11145 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
11146 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
11147 gtk_paned_add1(GTK_PANED(compose->paned), compose->edit_vbox);
11148 gtk_paned_add2(GTK_PANED(compose->paned), compose->notebook);
11149 g_object_unref(compose->notebook);
11150 g_object_unref(compose->edit_vbox);
11151 g_signal_handlers_block_by_func(G_OBJECT(widget),
11152 G_CALLBACK(compose_grab_focus_cb),
11154 gtk_widget_grab_focus(widget);
11155 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
11156 G_CALLBACK(compose_grab_focus_cb),
11158 } else if (!GTK_IS_TEXT_VIEW(widget)
11159 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->notebook) {
11160 g_object_ref(compose->notebook);
11161 g_object_ref(compose->edit_vbox);
11162 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
11163 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
11164 gtk_paned_add1(GTK_PANED(compose->paned), compose->notebook);
11165 gtk_paned_add2(GTK_PANED(compose->paned), compose->edit_vbox);
11166 g_object_unref(compose->notebook);
11167 g_object_unref(compose->edit_vbox);
11168 g_signal_handlers_block_by_func(G_OBJECT(widget),
11169 G_CALLBACK(compose_grab_focus_cb),
11171 gtk_widget_grab_focus(widget);
11172 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
11173 G_CALLBACK(compose_grab_focus_cb),
11179 static void compose_changed_cb(GtkTextBuffer *textbuf, Compose *compose)
11181 compose->modified = TRUE;
11182 // compose_beautify_paragraph(compose, NULL, TRUE);
11183 #ifndef GENERIC_UMPC
11184 compose_set_title(compose);
11188 static void compose_wrap_cb(GtkAction *action, gpointer data)
11190 Compose *compose = (Compose *)data;
11191 compose_beautify_paragraph(compose, NULL, TRUE);
11194 static void compose_wrap_all_cb(GtkAction *action, gpointer data)
11196 Compose *compose = (Compose *)data;
11197 compose_wrap_all_full(compose, TRUE);
11200 static void compose_find_cb(GtkAction *action, gpointer data)
11202 Compose *compose = (Compose *)data;
11204 message_search_compose(compose);
11207 static void compose_toggle_autowrap_cb(GtkToggleAction *action,
11210 Compose *compose = (Compose *)data;
11211 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11212 if (compose->autowrap)
11213 compose_wrap_all_full(compose, TRUE);
11214 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11217 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
11220 Compose *compose = (Compose *)data;
11221 compose->autoindent = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11224 static void compose_toggle_sign_cb(GtkToggleAction *action, gpointer data)
11226 Compose *compose = (Compose *)data;
11228 compose->use_signing = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11231 static void compose_toggle_encrypt_cb(GtkToggleAction *action, gpointer data)
11233 Compose *compose = (Compose *)data;
11235 compose->use_encryption = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11238 static void activate_privacy_system(Compose *compose, PrefsAccount *account, gboolean warn)
11240 g_free(compose->privacy_system);
11241 g_free(compose->encdata);
11243 compose->privacy_system = g_strdup(account->default_privacy_system);
11244 compose_update_privacy_system_menu_item(compose, warn);
11247 static void compose_toggle_ruler_cb(GtkToggleAction *action, gpointer data)
11249 Compose *compose = (Compose *)data;
11251 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action))) {
11252 gtk_widget_show(compose->ruler_hbox);
11253 prefs_common.show_ruler = TRUE;
11255 gtk_widget_hide(compose->ruler_hbox);
11256 gtk_widget_queue_resize(compose->edit_vbox);
11257 prefs_common.show_ruler = FALSE;
11261 static void compose_attach_drag_received_cb (GtkWidget *widget,
11262 GdkDragContext *context,
11265 GtkSelectionData *data,
11268 gpointer user_data)
11270 Compose *compose = (Compose *)user_data;
11274 type = gtk_selection_data_get_data_type(data);
11275 if ((gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list"))
11276 && gtk_drag_get_source_widget(context) !=
11277 summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
11278 list = uri_list_extract_filenames(
11279 (const gchar *)gtk_selection_data_get_data(data));
11280 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11281 gchar *utf8_filename = conv_filename_to_utf8((const gchar *)tmp->data);
11282 compose_attach_append
11283 (compose, (const gchar *)tmp->data,
11284 utf8_filename, NULL, NULL);
11285 g_free(utf8_filename);
11287 if (list) compose_changed_cb(NULL, compose);
11288 list_free_strings(list);
11290 } else if (gtk_drag_get_source_widget(context)
11291 == summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
11292 /* comes from our summaryview */
11293 SummaryView * summaryview = NULL;
11294 GSList * list = NULL, *cur = NULL;
11296 if (mainwindow_get_mainwindow())
11297 summaryview = mainwindow_get_mainwindow()->summaryview;
11300 list = summary_get_selected_msg_list(summaryview);
11302 for (cur = list; cur; cur = cur->next) {
11303 MsgInfo *msginfo = (MsgInfo *)cur->data;
11304 gchar *file = NULL;
11306 file = procmsg_get_message_file_full(msginfo,
11309 compose_attach_append(compose, (const gchar *)file,
11310 (const gchar *)file, "message/rfc822", NULL);
11314 g_slist_free(list);
11318 static gboolean compose_drag_drop(GtkWidget *widget,
11319 GdkDragContext *drag_context,
11321 guint time, gpointer user_data)
11323 /* not handling this signal makes compose_insert_drag_received_cb
11328 static gboolean completion_set_focus_to_subject
11329 (GtkWidget *widget,
11330 GdkEventKey *event,
11333 cm_return_val_if_fail(compose != NULL, FALSE);
11335 /* make backtab move to subject field */
11336 if(event->keyval == GDK_KEY_ISO_Left_Tab) {
11337 gtk_widget_grab_focus(compose->subject_entry);
11343 static void compose_insert_drag_received_cb (GtkWidget *widget,
11344 GdkDragContext *drag_context,
11347 GtkSelectionData *data,
11350 gpointer user_data)
11352 Compose *compose = (Compose *)user_data;
11356 /* strangely, testing data->type == gdk_atom_intern("text/uri-list", TRUE)
11358 type = gtk_selection_data_get_data_type(data);
11359 if (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list")) {
11360 AlertValue val = G_ALERTDEFAULT;
11361 const gchar* ddata = (const gchar *)gtk_selection_data_get_data(data);
11363 list = uri_list_extract_filenames(ddata);
11364 if (list == NULL && strstr(ddata, "://")) {
11365 /* Assume a list of no files, and data has ://, is a remote link */
11366 gchar *tmpdata = g_strstrip(g_strdup(ddata));
11367 gchar *tmpfile = get_tmp_file();
11368 str_write_to_file(tmpdata, tmpfile);
11370 compose_insert_file(compose, tmpfile);
11371 claws_unlink(tmpfile);
11373 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11374 compose_beautify_paragraph(compose, NULL, TRUE);
11377 switch (prefs_common.compose_dnd_mode) {
11378 case COMPOSE_DND_ASK:
11379 val = alertpanel_full(_("Insert or attach?"),
11380 _("Do you want to insert the contents of the file(s) "
11381 "into the message body, or attach it to the email?"),
11382 GTK_STOCK_CANCEL, g_strconcat("+", _("_Insert"), NULL), _("_Attach"),
11383 TRUE, NULL, ALERT_QUESTION, G_ALERTALTERNATE);
11385 case COMPOSE_DND_INSERT:
11386 val = G_ALERTALTERNATE;
11388 case COMPOSE_DND_ATTACH:
11389 val = G_ALERTOTHER;
11392 /* unexpected case */
11393 g_warning("error: unexpected compose_dnd_mode option value in compose_insert_drag_received_cb()");
11396 if (val & G_ALERTDISABLE) {
11397 val &= ~G_ALERTDISABLE;
11398 /* remember what action to perform by default, only if we don't click Cancel */
11399 if (val == G_ALERTALTERNATE)
11400 prefs_common.compose_dnd_mode = COMPOSE_DND_INSERT;
11401 else if (val == G_ALERTOTHER)
11402 prefs_common.compose_dnd_mode = COMPOSE_DND_ATTACH;
11405 if (val == G_ALERTDEFAULT || val == G_ALERTCANCEL) {
11406 gtk_drag_finish(drag_context, FALSE, FALSE, time);
11407 list_free_strings(list);
11410 } else if (val == G_ALERTOTHER) {
11411 compose_attach_drag_received_cb(widget, drag_context, x, y, data, info, time, user_data);
11412 list_free_strings(list);
11417 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11418 compose_insert_file(compose, (const gchar *)tmp->data);
11420 list_free_strings(list);
11422 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11427 static void compose_header_drag_received_cb (GtkWidget *widget,
11428 GdkDragContext *drag_context,
11431 GtkSelectionData *data,
11434 gpointer user_data)
11436 GtkEditable *entry = (GtkEditable *)user_data;
11437 const gchar *email = (const gchar *)gtk_selection_data_get_data(data);
11439 /* strangely, testing data->type == gdk_atom_intern("text/plain", TRUE)
11442 if (!strncmp(email, "mailto:", strlen("mailto:"))) {
11443 gchar *decoded=g_new(gchar, strlen(email));
11446 decode_uri(decoded, email + strlen("mailto:")); /* will fit */
11447 gtk_editable_delete_text(entry, 0, -1);
11448 gtk_editable_insert_text(entry, decoded, strlen(decoded), &start);
11449 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11453 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11456 static void compose_toggle_return_receipt_cb(GtkToggleAction *action, gpointer data)
11458 Compose *compose = (Compose *)data;
11460 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11461 compose->return_receipt = TRUE;
11463 compose->return_receipt = FALSE;
11466 static void compose_toggle_remove_refs_cb(GtkToggleAction *action, gpointer data)
11468 Compose *compose = (Compose *)data;
11470 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11471 compose->remove_references = TRUE;
11473 compose->remove_references = FALSE;
11476 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
11477 ComposeHeaderEntry *headerentry)
11479 gtk_entry_set_text(GTK_ENTRY(headerentry->entry), "");
11483 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
11484 GdkEventKey *event,
11485 ComposeHeaderEntry *headerentry)
11487 if ((g_slist_length(headerentry->compose->header_list) > 0) &&
11488 ((headerentry->headernum + 1) != headerentry->compose->header_nextrow) &&
11489 !(event->state & GDK_MODIFIER_MASK) &&
11490 (event->keyval == GDK_KEY_BackSpace) &&
11491 (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) == 0)) {
11492 gtk_container_remove
11493 (GTK_CONTAINER(headerentry->compose->header_table),
11494 headerentry->combo);
11495 gtk_container_remove
11496 (GTK_CONTAINER(headerentry->compose->header_table),
11497 headerentry->entry);
11498 headerentry->compose->header_list =
11499 g_slist_remove(headerentry->compose->header_list,
11501 g_free(headerentry);
11502 } else if (event->keyval == GDK_KEY_Tab) {
11503 if (headerentry->compose->header_last == headerentry) {
11504 /* Override default next focus, and give it to subject_entry
11505 * instead of notebook tabs
11507 g_signal_stop_emission_by_name(G_OBJECT(entry), "key-press-event");
11508 gtk_widget_grab_focus(headerentry->compose->subject_entry);
11515 static gboolean scroll_postpone(gpointer data)
11517 Compose *compose = (Compose *)data;
11519 if (compose->batch)
11522 GTK_EVENTS_FLUSH();
11523 compose_show_first_last_header(compose, FALSE);
11527 static void compose_headerentry_changed_cb(GtkWidget *entry,
11528 ComposeHeaderEntry *headerentry)
11530 if (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) != 0) {
11531 compose_create_header_entry(headerentry->compose);
11532 g_signal_handlers_disconnect_matched
11533 (G_OBJECT(entry), G_SIGNAL_MATCH_DATA,
11534 0, 0, NULL, NULL, headerentry);
11536 if (!headerentry->compose->batch)
11537 g_timeout_add(0, scroll_postpone, headerentry->compose);
11541 static gboolean compose_defer_auto_save_draft(Compose *compose)
11543 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
11544 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
11548 static void compose_show_first_last_header(Compose *compose, gboolean show_first)
11550 GtkAdjustment *vadj;
11552 cm_return_if_fail(compose);
11557 cm_return_if_fail(GTK_IS_WIDGET(compose->header_table));
11558 cm_return_if_fail(GTK_IS_VIEWPORT(gtk_widget_get_parent(compose->header_table)));
11559 vadj = gtk_viewport_get_vadjustment(GTK_VIEWPORT(
11560 gtk_widget_get_parent(compose->header_table)));
11561 gtk_adjustment_set_value(vadj, (show_first ?
11562 gtk_adjustment_get_lower(vadj) :
11563 (gtk_adjustment_get_upper(vadj) -
11564 gtk_adjustment_get_page_size(vadj))));
11565 gtk_adjustment_changed(vadj);
11568 static void text_inserted(GtkTextBuffer *buffer, GtkTextIter *iter,
11569 const gchar *text, gint len, Compose *compose)
11571 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data
11572 (G_OBJECT(compose->text), "paste_as_quotation"));
11575 cm_return_if_fail(text != NULL);
11577 g_signal_handlers_block_by_func(G_OBJECT(buffer),
11578 G_CALLBACK(text_inserted),
11580 if (paste_as_quotation) {
11582 const gchar *qmark;
11584 GtkTextIter start_iter;
11587 len = strlen(text);
11589 new_text = g_strndup(text, len);
11591 qmark = compose_quote_char_from_context(compose);
11593 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11594 gtk_text_buffer_place_cursor(buffer, iter);
11596 pos = gtk_text_iter_get_offset(iter);
11598 compose_quote_fmt(compose, NULL, "%Q", qmark, new_text, TRUE, FALSE,
11599 _("Quote format error at line %d."));
11600 quote_fmt_reset_vartable();
11602 g_object_set_data(G_OBJECT(compose->text), "paste_as_quotation",
11603 GINT_TO_POINTER(paste_as_quotation - 1));
11605 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11606 gtk_text_buffer_place_cursor(buffer, iter);
11607 gtk_text_buffer_delete_mark(buffer, mark);
11609 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, pos);
11610 mark = gtk_text_buffer_create_mark(buffer, NULL, &start_iter, FALSE);
11611 compose_beautify_paragraph(compose, &start_iter, FALSE);
11612 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark);
11613 gtk_text_buffer_delete_mark(buffer, mark);
11615 if (strcmp(text, "\n") || compose->automatic_break
11616 || gtk_text_iter_starts_line(iter)) {
11617 GtkTextIter before_ins;
11618 gtk_text_buffer_insert(buffer, iter, text, len);
11619 if (!strstr(text, "\n") && gtk_text_iter_has_tag(iter, compose->no_join_tag)) {
11620 before_ins = *iter;
11621 gtk_text_iter_backward_chars(&before_ins, len);
11622 gtk_text_buffer_remove_tag_by_name(buffer, "no_join", &before_ins, iter);
11625 /* check if the preceding is just whitespace or quote */
11626 GtkTextIter start_line;
11627 gchar *tmp = NULL, *quote = NULL;
11628 gint quote_len = 0, is_normal = 0;
11629 start_line = *iter;
11630 gtk_text_iter_set_line_offset(&start_line, 0);
11631 tmp = gtk_text_buffer_get_text(buffer, &start_line, iter, FALSE);
11634 if (*tmp == '\0') {
11637 quote = compose_get_quote_str(buffer, &start_line, "e_len);
11645 gtk_text_buffer_insert(buffer, iter, text, len);
11647 gtk_text_buffer_insert_with_tags_by_name(buffer,
11648 iter, text, len, "no_join", NULL);
11653 if (!paste_as_quotation) {
11654 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11655 compose_beautify_paragraph(compose, iter, FALSE);
11656 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11657 gtk_text_buffer_delete_mark(buffer, mark);
11660 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
11661 G_CALLBACK(text_inserted),
11663 g_signal_stop_emission_by_name(G_OBJECT(buffer), "insert-text");
11665 if (compose_can_autosave(compose) &&
11666 gtk_text_buffer_get_char_count(buffer) % prefs_common.autosave_length == 0 &&
11667 compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN /* disabled while loading */)
11668 compose->draft_timeout_tag = g_timeout_add
11669 (500, (GSourceFunc) compose_defer_auto_save_draft, compose);
11673 static void compose_check_all(GtkAction *action, gpointer data)
11675 Compose *compose = (Compose *)data;
11676 if (!compose->gtkaspell)
11679 if (gtk_widget_has_focus(compose->subject_entry))
11680 claws_spell_entry_check_all(
11681 CLAWS_SPELL_ENTRY(compose->subject_entry));
11683 gtkaspell_check_all(compose->gtkaspell);
11686 static void compose_highlight_all(GtkAction *action, gpointer data)
11688 Compose *compose = (Compose *)data;
11689 if (compose->gtkaspell) {
11690 claws_spell_entry_recheck_all(
11691 CLAWS_SPELL_ENTRY(compose->subject_entry));
11692 gtkaspell_highlight_all(compose->gtkaspell);
11696 static void compose_check_backwards(GtkAction *action, gpointer data)
11698 Compose *compose = (Compose *)data;
11699 if (!compose->gtkaspell) {
11700 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
11704 if (gtk_widget_has_focus(compose->subject_entry))
11705 claws_spell_entry_check_backwards(
11706 CLAWS_SPELL_ENTRY(compose->subject_entry));
11708 gtkaspell_check_backwards(compose->gtkaspell);
11711 static void compose_check_forwards_go(GtkAction *action, gpointer data)
11713 Compose *compose = (Compose *)data;
11714 if (!compose->gtkaspell) {
11715 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
11719 if (gtk_widget_has_focus(compose->subject_entry))
11720 claws_spell_entry_check_forwards_go(
11721 CLAWS_SPELL_ENTRY(compose->subject_entry));
11723 gtkaspell_check_forwards_go(compose->gtkaspell);
11728 *\brief Guess originating forward account from MsgInfo and several
11729 * "common preference" settings. Return NULL if no guess.
11731 static PrefsAccount *compose_guess_forward_account_from_msginfo(MsgInfo *msginfo)
11733 PrefsAccount *account = NULL;
11735 cm_return_val_if_fail(msginfo, NULL);
11736 cm_return_val_if_fail(msginfo->folder, NULL);
11737 cm_return_val_if_fail(msginfo->folder->prefs, NULL);
11739 if (msginfo->folder->prefs->enable_default_account)
11740 account = account_find_from_id(msginfo->folder->prefs->default_account);
11743 account = msginfo->folder->folder->account;
11745 if (!account && msginfo->to && prefs_common.forward_account_autosel) {
11747 Xstrdup_a(to, msginfo->to, return NULL);
11748 extract_address(to);
11749 account = account_find_from_address(to, FALSE);
11752 if (!account && prefs_common.forward_account_autosel) {
11753 gchar cc[BUFFSIZE];
11754 if (!procheader_get_header_from_msginfo
11755 (msginfo, cc,sizeof cc , "Cc:")) {
11756 gchar *buf = cc + strlen("Cc:");
11757 extract_address(buf);
11758 account = account_find_from_address(buf, FALSE);
11762 if (!account && prefs_common.forward_account_autosel) {
11763 gchar deliveredto[BUFFSIZE];
11764 if (!procheader_get_header_from_msginfo
11765 (msginfo, deliveredto,sizeof deliveredto , "Delivered-To:")) {
11766 gchar *buf = deliveredto + strlen("Delivered-To:");
11767 extract_address(buf);
11768 account = account_find_from_address(buf, FALSE);
11775 gboolean compose_close(Compose *compose)
11779 cm_return_val_if_fail(compose, FALSE);
11781 if (!g_mutex_trylock(compose->mutex)) {
11782 /* we have to wait for the (possibly deferred by auto-save)
11783 * drafting to be done, before destroying the compose under
11785 debug_print("waiting for drafting to finish...\n");
11786 compose_allow_user_actions(compose, FALSE);
11787 if (compose->close_timeout_tag == 0) {
11788 compose->close_timeout_tag =
11789 g_timeout_add (500, (GSourceFunc) compose_close,
11795 if (compose->draft_timeout_tag >= 0) {
11796 g_source_remove(compose->draft_timeout_tag);
11797 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN;
11800 gtkut_widget_get_uposition(compose->window, &x, &y);
11801 if (!compose->batch) {
11802 prefs_common.compose_x = x;
11803 prefs_common.compose_y = y;
11805 g_mutex_unlock(compose->mutex);
11806 compose_destroy(compose);
11811 * Add entry field for each address in list.
11812 * \param compose E-Mail composition object.
11813 * \param listAddress List of (formatted) E-Mail addresses.
11815 static void compose_add_field_list( Compose *compose, GList *listAddress ) {
11818 node = listAddress;
11820 addr = ( gchar * ) node->data;
11821 compose_entry_append( compose, addr, COMPOSE_TO, PREF_NONE );
11822 node = g_list_next( node );
11826 static void compose_reply_from_messageview_real(MessageView *msgview, GSList *msginfo_list,
11827 guint action, gboolean opening_multiple)
11829 gchar *body = NULL;
11830 GSList *new_msglist = NULL;
11831 MsgInfo *tmp_msginfo = NULL;
11832 gboolean originally_enc = FALSE;
11833 gboolean originally_sig = FALSE;
11834 Compose *compose = NULL;
11835 gchar *s_system = NULL;
11837 cm_return_if_fail(msgview != NULL);
11839 cm_return_if_fail(msginfo_list != NULL);
11841 if (g_slist_length(msginfo_list) == 1 && !opening_multiple) {
11842 MimeInfo *mimeinfo = messageview_get_selected_mime_part(msgview);
11843 MsgInfo *orig_msginfo = (MsgInfo *)msginfo_list->data;
11845 if (mimeinfo != NULL && mimeinfo->type == MIMETYPE_MESSAGE &&
11846 !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
11847 tmp_msginfo = procmsg_msginfo_new_from_mimeinfo(
11848 orig_msginfo, mimeinfo);
11849 if (tmp_msginfo != NULL) {
11850 new_msglist = g_slist_append(NULL, tmp_msginfo);
11852 originally_enc = MSG_IS_ENCRYPTED(orig_msginfo->flags);
11853 privacy_msginfo_get_signed_state(orig_msginfo, &s_system);
11854 originally_sig = MSG_IS_SIGNED(orig_msginfo->flags);
11856 tmp_msginfo->folder = orig_msginfo->folder;
11857 tmp_msginfo->msgnum = orig_msginfo->msgnum;
11858 if (orig_msginfo->tags) {
11859 tmp_msginfo->tags = g_slist_copy(orig_msginfo->tags);
11860 tmp_msginfo->folder->tags_dirty = TRUE;
11866 if (!opening_multiple)
11867 body = messageview_get_selection(msgview);
11870 compose = compose_reply_mode((ComposeMode)action, new_msglist, body);
11871 procmsg_msginfo_free(&tmp_msginfo);
11872 g_slist_free(new_msglist);
11874 compose = compose_reply_mode((ComposeMode)action, msginfo_list, body);
11876 if (compose && originally_enc) {
11877 compose_force_encryption(compose, compose->account, FALSE, s_system);
11880 if (compose && originally_sig && compose->account->default_sign_reply) {
11881 compose_force_signing(compose, compose->account, s_system);
11885 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
11888 void compose_reply_from_messageview(MessageView *msgview, GSList *msginfo_list,
11891 if ((!prefs_common.forward_as_attachment || action != COMPOSE_FORWARD)
11892 && action != COMPOSE_FORWARD_AS_ATTACH && g_slist_length(msginfo_list) > 1) {
11893 GSList *cur = msginfo_list;
11894 gchar *msg = g_strdup_printf(_("You are about to reply to %d "
11895 "messages. Opening the windows "
11896 "could take some time. Do you "
11897 "want to continue?"),
11898 g_slist_length(msginfo_list));
11899 if (g_slist_length(msginfo_list) > 9
11900 && alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, "+" GTK_STOCK_YES, NULL)
11901 != G_ALERTALTERNATE) {
11906 /* We'll open multiple compose windows */
11907 /* let the WM place the next windows */
11908 compose_force_window_origin = FALSE;
11909 for (; cur; cur = cur->next) {
11911 tmplist.data = cur->data;
11912 tmplist.next = NULL;
11913 compose_reply_from_messageview_real(msgview, &tmplist, action, TRUE);
11915 compose_force_window_origin = TRUE;
11917 /* forwarding multiple mails as attachments is done via a
11918 * single compose window */
11919 compose_reply_from_messageview_real(msgview, msginfo_list, action, FALSE);
11923 void compose_check_for_email_account(Compose *compose)
11925 PrefsAccount *ac = NULL, *curr = NULL;
11931 if (compose->account && compose->account->protocol == A_NNTP) {
11932 ac = account_get_cur_account();
11933 if (ac->protocol == A_NNTP) {
11934 list = account_get_list();
11936 for( ; list != NULL ; list = g_list_next(list)) {
11937 curr = (PrefsAccount *) list->data;
11938 if (curr->protocol != A_NNTP) {
11944 combobox_select_by_data(GTK_COMBO_BOX(compose->account_combo),
11949 void compose_reply_to_address(MessageView *msgview, MsgInfo *msginfo,
11950 const gchar *address)
11952 GSList *msginfo_list = NULL;
11953 gchar *body = messageview_get_selection(msgview);
11956 msginfo_list = g_slist_prepend(msginfo_list, msginfo);
11958 compose = compose_reply_mode(COMPOSE_REPLY_TO_ADDRESS, msginfo_list, body);
11959 compose_check_for_email_account(compose);
11960 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
11961 compose_entry_append(compose, address, COMPOSE_TO, PREF_NONE);
11962 compose_reply_set_subject(compose, msginfo);
11965 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
11968 void compose_set_position(Compose *compose, gint pos)
11970 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11972 gtkut_text_view_set_position(text, pos);
11975 gboolean compose_search_string(Compose *compose,
11976 const gchar *str, gboolean case_sens)
11978 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11980 return gtkut_text_view_search_string(text, str, case_sens);
11983 gboolean compose_search_string_backward(Compose *compose,
11984 const gchar *str, gboolean case_sens)
11986 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11988 return gtkut_text_view_search_string_backward(text, str, case_sens);
11991 /* allocate a msginfo structure and populate its data from a compose data structure */
11992 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose)
11994 MsgInfo *newmsginfo;
11996 gchar buf[BUFFSIZE];
11998 cm_return_val_if_fail( compose != NULL, NULL );
12000 newmsginfo = procmsg_msginfo_new();
12003 get_rfc822_date(buf, sizeof(buf));
12004 newmsginfo->date = g_strdup(buf);
12007 if (compose->from_name) {
12008 newmsginfo->from = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
12009 newmsginfo->fromname = procheader_get_fromname(newmsginfo->from);
12013 if (compose->subject_entry)
12014 newmsginfo->subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
12016 /* to, cc, reply-to, newsgroups */
12017 for (list = compose->header_list; list; list = list->next) {
12018 gchar *header = gtk_editable_get_chars(
12020 gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
12021 gchar *entry = gtk_editable_get_chars(
12022 GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
12024 if ( strcasecmp(header, prefs_common_translated_header_name("To:")) == 0 ) {
12025 if ( newmsginfo->to == NULL ) {
12026 newmsginfo->to = g_strdup(entry);
12027 } else if (entry && *entry) {
12028 gchar *tmp = g_strconcat(newmsginfo->to, ", ", entry, NULL);
12029 g_free(newmsginfo->to);
12030 newmsginfo->to = tmp;
12033 if ( strcasecmp(header, prefs_common_translated_header_name("Cc:")) == 0 ) {
12034 if ( newmsginfo->cc == NULL ) {
12035 newmsginfo->cc = g_strdup(entry);
12036 } else if (entry && *entry) {
12037 gchar *tmp = g_strconcat(newmsginfo->cc, ", ", entry, NULL);
12038 g_free(newmsginfo->cc);
12039 newmsginfo->cc = tmp;
12042 if ( strcasecmp(header,
12043 prefs_common_translated_header_name("Newsgroups:")) == 0 ) {
12044 if ( newmsginfo->newsgroups == NULL ) {
12045 newmsginfo->newsgroups = g_strdup(entry);
12046 } else if (entry && *entry) {
12047 gchar *tmp = g_strconcat(newmsginfo->newsgroups, ", ", entry, NULL);
12048 g_free(newmsginfo->newsgroups);
12049 newmsginfo->newsgroups = tmp;
12057 /* other data is unset */
12063 /* update compose's dictionaries from folder dict settings */
12064 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
12065 FolderItem *folder_item)
12067 cm_return_if_fail(compose != NULL);
12069 if (compose->gtkaspell && folder_item && folder_item->prefs) {
12070 FolderItemPrefs *prefs = folder_item->prefs;
12072 if (prefs->enable_default_dictionary)
12073 gtkaspell_change_dict(compose->gtkaspell,
12074 prefs->default_dictionary, FALSE);
12075 if (folder_item->prefs->enable_default_alt_dictionary)
12076 gtkaspell_change_alt_dict(compose->gtkaspell,
12077 prefs->default_alt_dictionary);
12078 if (prefs->enable_default_dictionary
12079 || prefs->enable_default_alt_dictionary)
12080 compose_spell_menu_changed(compose);
12085 static void compose_subject_entry_activated(GtkWidget *widget, gpointer data)
12087 Compose *compose = (Compose *)data;
12089 cm_return_if_fail(compose != NULL);
12091 gtk_widget_grab_focus(compose->text);
12094 static void from_name_activate_cb(GtkWidget *widget, gpointer data)
12096 gtk_combo_box_popup(GTK_COMBO_BOX(data));