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);
3354 if (compose->replyto)
3355 compose_entry_append(compose,
3357 COMPOSE_CC, PREF_NONE);
3359 if (!folder_has_parent_of_type(msginfo->folder, F_QUEUE) &&
3360 !folder_has_parent_of_type(msginfo->folder, F_OUTBOX) &&
3361 !folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
3362 if (compose->replyto) {
3363 compose_entry_append(compose,
3365 COMPOSE_TO, PREF_NONE);
3367 compose_entry_append(compose,
3368 msginfo->from ? msginfo->from : "",
3369 COMPOSE_TO, PREF_NONE);
3372 /* replying to own mail, use original recp */
3373 compose_entry_append(compose,
3374 msginfo->to ? msginfo->to : "",
3375 COMPOSE_TO, PREF_NONE);
3376 compose_entry_append(compose,
3377 msginfo->cc ? msginfo->cc : "",
3378 COMPOSE_CC, PREF_NONE);
3383 if (to_sender || (compose->followup_to &&
3384 !strncmp(compose->followup_to, "poster", 6)))
3385 compose_entry_append
3387 (compose->replyto ? compose->replyto :
3388 msginfo->from ? msginfo->from : ""),
3389 COMPOSE_TO, PREF_NONE);
3391 else if (followup_and_reply_to || to_all) {
3392 compose_entry_append
3394 (compose->replyto ? compose->replyto :
3395 msginfo->from ? msginfo->from : ""),
3396 COMPOSE_TO, PREF_NONE);
3398 compose_entry_append
3400 compose->followup_to ? compose->followup_to :
3401 compose->newsgroups ? compose->newsgroups : "",
3402 COMPOSE_NEWSGROUPS, PREF_NONE);
3405 compose_entry_append
3407 compose->followup_to ? compose->followup_to :
3408 compose->newsgroups ? compose->newsgroups : "",
3409 COMPOSE_NEWSGROUPS, PREF_NONE);
3411 compose_reply_set_subject(compose, msginfo);
3413 if (to_ml && compose->ml_post) return;
3414 if (!to_all || compose->account->protocol == A_NNTP) return;
3416 if (compose->replyto) {
3417 Xstrdup_a(replyto, compose->replyto, return);
3418 extract_address(replyto);
3420 if (msginfo->from) {
3421 Xstrdup_a(from, msginfo->from, return);
3422 extract_address(from);
3425 if (replyto && from)
3426 cc_list = address_list_append_with_comments(cc_list, from);
3427 if (to_all && msginfo->folder &&
3428 msginfo->folder->prefs->enable_default_reply_to)
3429 cc_list = address_list_append_with_comments(cc_list,
3430 msginfo->folder->prefs->default_reply_to);
3431 cc_list = address_list_append_with_comments(cc_list, msginfo->to);
3432 cc_list = address_list_append_with_comments(cc_list, compose->cc);
3434 ac_email = g_utf8_strdown(compose->account->address, -1);
3437 for (cur = cc_list; cur != NULL; cur = cur->next) {
3438 gchar *addr = g_utf8_strdown(cur->data, -1);
3439 extract_address(addr);
3441 if (strcmp(ac_email, addr))
3442 compose_entry_append(compose, (gchar *)cur->data,
3443 COMPOSE_CC, PREF_NONE);
3445 debug_print("Cc address same as compose account's, ignoring\n");
3450 slist_free_strings_full(cc_list);
3456 #define SET_ENTRY(entry, str) \
3459 gtk_entry_set_text(GTK_ENTRY(compose->entry), str); \
3462 #define SET_ADDRESS(type, str) \
3465 compose_entry_append(compose, str, type, PREF_NONE); \
3468 static void compose_reedit_set_entry(Compose *compose, MsgInfo *msginfo)
3470 cm_return_if_fail(msginfo != NULL);
3472 SET_ENTRY(subject_entry, msginfo->subject);
3473 SET_ENTRY(from_name, msginfo->from);
3474 SET_ADDRESS(COMPOSE_TO, msginfo->to);
3475 SET_ADDRESS(COMPOSE_CC, compose->cc);
3476 SET_ADDRESS(COMPOSE_BCC, compose->bcc);
3477 SET_ADDRESS(COMPOSE_REPLYTO, compose->replyto);
3478 SET_ADDRESS(COMPOSE_NEWSGROUPS, compose->newsgroups);
3479 SET_ADDRESS(COMPOSE_FOLLOWUPTO, compose->followup_to);
3481 compose_update_priority_menu_item(compose);
3482 compose_update_privacy_system_menu_item(compose, FALSE);
3483 compose_show_first_last_header(compose, TRUE);
3489 static void compose_insert_sig(Compose *compose, gboolean replace)
3491 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3492 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3494 GtkTextIter iter, iter_end;
3495 gint cur_pos, ins_pos;
3496 gboolean prev_autowrap;
3497 gboolean found = FALSE;
3498 gboolean exists = FALSE;
3500 cm_return_if_fail(compose->account != NULL);
3504 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3505 G_CALLBACK(compose_changed_cb),
3508 mark = gtk_text_buffer_get_insert(buffer);
3509 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3510 cur_pos = gtk_text_iter_get_offset (&iter);
3513 gtk_text_buffer_get_end_iter(buffer, &iter);
3515 exists = (compose->sig_str != NULL);
3518 GtkTextIter first_iter, start_iter, end_iter;
3520 gtk_text_buffer_get_start_iter(buffer, &first_iter);
3522 if (!exists || compose->sig_str[0] == '\0')
3525 found = gtk_text_iter_forward_to_tag_toggle(&first_iter,
3526 compose->signature_tag);
3529 /* include previous \n\n */
3530 gtk_text_iter_backward_chars(&first_iter, 1);
3531 start_iter = first_iter;
3532 end_iter = first_iter;
3534 found = gtk_text_iter_forward_to_tag_toggle(&end_iter,
3535 compose->signature_tag);
3536 found &= gtk_text_iter_forward_to_tag_toggle(&end_iter,
3537 compose->signature_tag);
3539 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
3545 g_free(compose->sig_str);
3546 compose->sig_str = account_get_signature_str(compose->account);
3548 cur_pos = gtk_text_iter_get_offset(&iter);
3550 if (!compose->sig_str || (replace && !compose->account->auto_sig)) {
3551 g_free(compose->sig_str);
3552 compose->sig_str = NULL;
3554 if (compose->sig_inserted == FALSE)
3555 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
3556 compose->sig_inserted = TRUE;
3558 cur_pos = gtk_text_iter_get_offset(&iter);
3559 gtk_text_buffer_insert(buffer, &iter, compose->sig_str, -1);
3561 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cur_pos);
3562 gtk_text_iter_forward_chars(&iter, 1);
3563 gtk_text_buffer_get_end_iter(buffer, &iter_end);
3564 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &iter_end);
3566 if (cur_pos > gtk_text_buffer_get_char_count (buffer))
3567 cur_pos = gtk_text_buffer_get_char_count (buffer);
3570 /* put the cursor where it should be
3571 * either where the quote_fmt says, either where it was */
3572 if (compose->set_cursor_pos < 0)
3573 gtk_text_buffer_get_iter_at_offset(buffer, &iter, ins_pos);
3575 gtk_text_buffer_get_iter_at_offset(buffer, &iter,
3576 compose->set_cursor_pos);
3578 compose->set_cursor_pos = -1;
3579 gtk_text_buffer_place_cursor(buffer, &iter);
3580 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3581 G_CALLBACK(compose_changed_cb),
3587 static ComposeInsertResult compose_insert_file(Compose *compose, const gchar *file)
3590 GtkTextBuffer *buffer;
3593 const gchar *cur_encoding;
3594 gchar buf[BUFFSIZE];
3597 gboolean prev_autowrap;
3600 GString *file_contents = NULL;
3601 ComposeInsertResult result = COMPOSE_INSERT_SUCCESS;
3603 cm_return_val_if_fail(file != NULL, COMPOSE_INSERT_NO_FILE);
3605 /* get the size of the file we are about to insert */
3606 ret = g_stat(file, &file_stat);
3608 gchar *shortfile = g_path_get_basename(file);
3609 alertpanel_error(_("Could not get size of file '%s'."), shortfile);
3611 return COMPOSE_INSERT_NO_FILE;
3612 } else if (prefs_common.warn_large_insert == TRUE) {
3614 /* ask user for confirmation if the file is large */
3615 if (prefs_common.warn_large_insert_size < 0 ||
3616 file_stat.st_size > (prefs_common.warn_large_insert_size * 1024)) {
3620 msg = g_strdup_printf(_("You are about to insert a file of %s "
3621 "in the message body. Are you sure you want to do that?"),
3622 to_human_readable(file_stat.st_size));
3623 aval = alertpanel_full(_("Are you sure?"), msg, GTK_STOCK_CANCEL,
3624 g_strconcat("+", _("_Insert"), NULL), NULL, TRUE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
3627 /* do we ask for confirmation next time? */
3628 if (aval & G_ALERTDISABLE) {
3629 /* no confirmation next time, disable feature in preferences */
3630 aval &= ~G_ALERTDISABLE;
3631 prefs_common.warn_large_insert = FALSE;
3634 /* abort file insertion if user canceled action */
3635 if (aval != G_ALERTALTERNATE) {
3636 return COMPOSE_INSERT_NO_FILE;
3642 if ((fp = g_fopen(file, "rb")) == NULL) {
3643 FILE_OP_ERROR(file, "fopen");
3644 return COMPOSE_INSERT_READ_ERROR;
3647 prev_autowrap = compose->autowrap;
3648 compose->autowrap = FALSE;
3650 text = GTK_TEXT_VIEW(compose->text);
3651 buffer = gtk_text_view_get_buffer(text);
3652 mark = gtk_text_buffer_get_insert(buffer);
3653 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3655 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3656 G_CALLBACK(text_inserted),
3659 cur_encoding = conv_get_locale_charset_str_no_utf8();
3661 file_contents = g_string_new("");
3662 while (fgets(buf, sizeof(buf), fp) != NULL) {
3665 if (g_utf8_validate(buf, -1, NULL) == TRUE)
3666 str = g_strdup(buf);
3668 codeconv_set_strict(TRUE);
3669 str = conv_codeset_strdup
3670 (buf, cur_encoding, CS_INTERNAL);
3671 codeconv_set_strict(FALSE);
3674 result = COMPOSE_INSERT_INVALID_CHARACTER;
3680 /* strip <CR> if DOS/Windows file,
3681 replace <CR> with <LF> if Macintosh file. */
3684 if (len > 0 && str[len - 1] != '\n') {
3686 if (str[len] == '\r') str[len] = '\n';
3689 file_contents = g_string_append(file_contents, str);
3693 if (result == COMPOSE_INSERT_SUCCESS) {
3694 gtk_text_buffer_insert(buffer, &iter, file_contents->str, -1);
3696 compose_changed_cb(NULL, compose);
3697 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3698 G_CALLBACK(text_inserted),
3700 compose->autowrap = prev_autowrap;
3701 if (compose->autowrap)
3702 compose_wrap_all(compose);
3705 g_string_free(file_contents, TRUE);
3711 static gboolean compose_attach_append(Compose *compose, const gchar *file,
3712 const gchar *filename,
3713 const gchar *content_type,
3714 const gchar *charset)
3722 GtkListStore *store;
3724 gboolean has_binary = FALSE;
3726 if (!is_file_exist(file)) {
3727 gchar *file_from_uri = g_filename_from_uri(file, NULL, NULL);
3728 gboolean result = FALSE;
3729 if (file_from_uri && is_file_exist(file_from_uri)) {
3730 result = compose_attach_append(
3731 compose, file_from_uri,
3732 filename, content_type,
3735 g_free(file_from_uri);
3738 alertpanel_error("File %s doesn't exist\n", filename);
3741 if ((size = get_file_size(file)) < 0) {
3742 alertpanel_error("Can't get file size of %s\n", filename);
3746 /* In batch mode, we allow 0-length files to be attached no questions asked */
3747 if (size == 0 && !compose->batch) {
3748 gchar * msg = g_strdup_printf(_("File %s is empty."), filename);
3749 AlertValue aval = alertpanel_full(_("Empty file"), msg,
3750 GTK_STOCK_CANCEL, g_strconcat("+", _("_Attach anyway"), NULL), NULL, FALSE,
3751 NULL, ALERT_WARNING, G_ALERTDEFAULT);
3754 if (aval != G_ALERTALTERNATE) {
3758 if ((fp = g_fopen(file, "rb")) == NULL) {
3759 alertpanel_error(_("Can't read %s."), filename);
3764 ainfo = g_new0(AttachInfo, 1);
3765 auto_ainfo = g_auto_pointer_new_with_free
3766 (ainfo, (GFreeFunc) compose_attach_info_free);
3767 ainfo->file = g_strdup(file);
3770 ainfo->content_type = g_strdup(content_type);
3771 if (!g_ascii_strcasecmp(content_type, "message/rfc822")) {
3773 MsgFlags flags = {0, 0};
3775 if (procmime_get_encoding_for_text_file(file, &has_binary) == ENC_7BIT)
3776 ainfo->encoding = ENC_7BIT;
3778 ainfo->encoding = ENC_8BIT;
3780 msginfo = procheader_parse_file(file, flags, FALSE, FALSE);
3781 if (msginfo && msginfo->subject)
3782 name = g_strdup(msginfo->subject);
3784 name = g_path_get_basename(filename ? filename : file);
3786 ainfo->name = g_strdup_printf(_("Message: %s"), name);
3788 procmsg_msginfo_free(&msginfo);
3790 if (!g_ascii_strncasecmp(content_type, "text/", 5)) {
3791 ainfo->charset = g_strdup(charset);
3792 ainfo->encoding = procmime_get_encoding_for_text_file(file, &has_binary);
3794 ainfo->encoding = ENC_BASE64;
3796 name = g_path_get_basename(filename ? filename : file);
3797 ainfo->name = g_strdup(name);
3801 ainfo->content_type = procmime_get_mime_type(file);
3802 if (!ainfo->content_type) {
3803 ainfo->content_type =
3804 g_strdup("application/octet-stream");
3805 ainfo->encoding = ENC_BASE64;
3806 } else if (!g_ascii_strncasecmp(ainfo->content_type, "text/", 5))
3808 procmime_get_encoding_for_text_file(file, &has_binary);
3810 ainfo->encoding = ENC_BASE64;
3811 name = g_path_get_basename(filename ? filename : file);
3812 ainfo->name = g_strdup(name);
3816 if (ainfo->name != NULL
3817 && !strcmp(ainfo->name, ".")) {
3818 g_free(ainfo->name);
3822 if (!strcmp(ainfo->content_type, "unknown") || has_binary) {
3823 g_free(ainfo->content_type);
3824 ainfo->content_type = g_strdup("application/octet-stream");
3825 g_free(ainfo->charset);
3826 ainfo->charset = NULL;
3829 ainfo->size = (goffset)size;
3830 size_text = to_human_readable((goffset)size);
3832 store = GTK_LIST_STORE(gtk_tree_view_get_model
3833 (GTK_TREE_VIEW(compose->attach_clist)));
3835 gtk_list_store_append(store, &iter);
3836 gtk_list_store_set(store, &iter,
3837 COL_MIMETYPE, ainfo->content_type,
3838 COL_SIZE, size_text,
3839 COL_NAME, ainfo->name,
3840 COL_CHARSET, ainfo->charset,
3842 COL_AUTODATA, auto_ainfo,
3845 g_auto_pointer_free(auto_ainfo);
3846 compose_attach_update_label(compose);
3850 static void compose_use_signing(Compose *compose, gboolean use_signing)
3852 compose->use_signing = use_signing;
3853 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", use_signing);
3856 static void compose_use_encryption(Compose *compose, gboolean use_encryption)
3858 compose->use_encryption = use_encryption;
3859 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", use_encryption);
3862 #define NEXT_PART_NOT_CHILD(info) \
3864 node = info->node; \
3865 while (node->children) \
3866 node = g_node_last_child(node); \
3867 info = procmime_mimeinfo_next((MimeInfo *)node->data); \
3870 static void compose_attach_parts(Compose *compose, MsgInfo *msginfo)
3874 MimeInfo *firsttext = NULL;
3875 MimeInfo *encrypted = NULL;
3878 const gchar *partname = NULL;
3880 mimeinfo = procmime_scan_message(msginfo);
3881 if (!mimeinfo) return;
3883 if (mimeinfo->node->children == NULL) {
3884 procmime_mimeinfo_free_all(&mimeinfo);
3888 /* find first content part */
3889 child = (MimeInfo *) mimeinfo->node->children->data;
3890 while (child && child->node->children && (child->type == MIMETYPE_MULTIPART))
3891 child = (MimeInfo *)child->node->children->data;
3894 if (child->type == MIMETYPE_TEXT) {
3896 debug_print("First text part found\n");
3897 } else if (compose->mode == COMPOSE_REEDIT &&
3898 child->type == MIMETYPE_APPLICATION &&
3899 !g_ascii_strcasecmp(child->subtype, "pgp-encrypted")) {
3900 encrypted = (MimeInfo *)child->node->parent->data;
3903 child = (MimeInfo *) mimeinfo->node->children->data;
3904 while (child != NULL) {
3907 if (child == encrypted) {
3908 /* skip this part of tree */
3909 NEXT_PART_NOT_CHILD(child);
3913 if (child->type == MIMETYPE_MULTIPART) {
3914 /* get the actual content */
3915 child = procmime_mimeinfo_next(child);
3919 if (child == firsttext) {
3920 child = procmime_mimeinfo_next(child);
3924 outfile = procmime_get_tmp_file_name(child);
3925 if ((err = procmime_get_part(outfile, child)) < 0)
3926 g_warning("Can't get the part of multipart message. (%s)", g_strerror(-err));
3928 gchar *content_type;
3930 content_type = procmime_get_content_type_str(child->type, child->subtype);
3932 /* if we meet a pgp signature, we don't attach it, but
3933 * we force signing. */
3934 if ((strcmp(content_type, "application/pgp-signature") &&
3935 strcmp(content_type, "application/pkcs7-signature") &&
3936 strcmp(content_type, "application/x-pkcs7-signature"))
3937 || compose->mode == COMPOSE_REDIRECT) {
3938 partname = procmime_mimeinfo_get_parameter(child, "filename");
3939 if (partname == NULL)
3940 partname = procmime_mimeinfo_get_parameter(child, "name");
3941 if (partname == NULL)
3943 compose_attach_append(compose, outfile,
3944 partname, content_type,
3945 procmime_mimeinfo_get_parameter(child, "charset"));
3947 compose_force_signing(compose, compose->account, NULL);
3949 g_free(content_type);
3952 NEXT_PART_NOT_CHILD(child);
3954 procmime_mimeinfo_free_all(&mimeinfo);
3957 #undef NEXT_PART_NOT_CHILD
3962 WAIT_FOR_INDENT_CHAR,
3963 WAIT_FOR_INDENT_CHAR_OR_SPACE,
3966 /* return indent length, we allow:
3967 indent characters followed by indent characters or spaces/tabs,
3968 alphabets and numbers immediately followed by indent characters,
3969 and the repeating sequences of the above
3970 If quote ends with multiple spaces, only the first one is included. */
3971 static gchar *compose_get_quote_str(GtkTextBuffer *buffer,
3972 const GtkTextIter *start, gint *len)
3974 GtkTextIter iter = *start;
3978 IndentState state = WAIT_FOR_INDENT_CHAR;
3981 gint alnum_count = 0;
3982 gint space_count = 0;
3985 if (prefs_common.quote_chars == NULL) {
3989 while (!gtk_text_iter_ends_line(&iter)) {
3990 wc = gtk_text_iter_get_char(&iter);
3991 if (g_unichar_iswide(wc))
3993 clen = g_unichar_to_utf8(wc, ch);
3997 is_indent = strchr(prefs_common.quote_chars, ch[0]) ? TRUE : FALSE;
3998 is_space = g_unichar_isspace(wc);
4000 if (state == WAIT_FOR_INDENT_CHAR) {
4001 if (!is_indent && !g_unichar_isalnum(wc))
4004 quote_len += alnum_count + space_count + 1;
4005 alnum_count = space_count = 0;
4006 state = WAIT_FOR_INDENT_CHAR_OR_SPACE;
4009 } else if (state == WAIT_FOR_INDENT_CHAR_OR_SPACE) {
4010 if (!is_indent && !is_space && !g_unichar_isalnum(wc))
4014 else if (is_indent) {
4015 quote_len += alnum_count + space_count + 1;
4016 alnum_count = space_count = 0;
4019 state = WAIT_FOR_INDENT_CHAR;
4023 gtk_text_iter_forward_char(&iter);
4026 if (quote_len > 0 && space_count > 0)
4032 if (quote_len > 0) {
4034 gtk_text_iter_forward_chars(&iter, quote_len);
4035 return gtk_text_buffer_get_text(buffer, start, &iter, FALSE);
4041 /* return >0 if the line is itemized */
4042 static int compose_itemized_length(GtkTextBuffer *buffer,
4043 const GtkTextIter *start)
4045 GtkTextIter iter = *start;
4050 if (gtk_text_iter_ends_line(&iter))
4055 wc = gtk_text_iter_get_char(&iter);
4056 if (!g_unichar_isspace(wc))
4058 gtk_text_iter_forward_char(&iter);
4059 if (gtk_text_iter_ends_line(&iter))
4063 clen = g_unichar_to_utf8(wc, ch);
4067 if (!strchr("*-+", ch[0]))
4070 gtk_text_iter_forward_char(&iter);
4071 if (gtk_text_iter_ends_line(&iter))
4073 wc = gtk_text_iter_get_char(&iter);
4074 if (g_unichar_isspace(wc)) {
4080 /* return the string at the start of the itemization */
4081 static gchar * compose_get_itemized_chars(GtkTextBuffer *buffer,
4082 const GtkTextIter *start)
4084 GtkTextIter iter = *start;
4087 GString *item_chars = g_string_new("");
4090 if (gtk_text_iter_ends_line(&iter))
4095 wc = gtk_text_iter_get_char(&iter);
4096 if (!g_unichar_isspace(wc))
4098 gtk_text_iter_forward_char(&iter);
4099 if (gtk_text_iter_ends_line(&iter))
4101 g_string_append_unichar(item_chars, wc);
4104 str = item_chars->str;
4105 g_string_free(item_chars, FALSE);
4109 /* return the number of spaces at a line's start */
4110 static int compose_left_offset_length(GtkTextBuffer *buffer,
4111 const GtkTextIter *start)
4113 GtkTextIter iter = *start;
4116 if (gtk_text_iter_ends_line(&iter))
4120 wc = gtk_text_iter_get_char(&iter);
4121 if (!g_unichar_isspace(wc))
4124 gtk_text_iter_forward_char(&iter);
4125 if (gtk_text_iter_ends_line(&iter))
4129 gtk_text_iter_forward_char(&iter);
4130 if (gtk_text_iter_ends_line(&iter))
4135 static gboolean compose_get_line_break_pos(GtkTextBuffer *buffer,
4136 const GtkTextIter *start,
4137 GtkTextIter *break_pos,
4141 GtkTextIter iter = *start, line_end = *start;
4142 PangoLogAttr *attrs;
4149 gboolean can_break = FALSE;
4150 gboolean do_break = FALSE;
4151 gboolean was_white = FALSE;
4152 gboolean prev_dont_break = FALSE;
4154 gtk_text_iter_forward_to_line_end(&line_end);
4155 str = gtk_text_buffer_get_text(buffer, &iter, &line_end, FALSE);
4156 len = g_utf8_strlen(str, -1);
4160 g_warning("compose_get_line_break_pos: len = 0!");
4164 /* g_print("breaking line: %d: %s (len = %d)\n",
4165 gtk_text_iter_get_line(&iter), str, len); */
4167 attrs = g_new(PangoLogAttr, len + 1);
4169 pango_default_break(str, -1, NULL, attrs, len + 1);
4173 /* skip quote and leading spaces */
4174 for (i = 0; *p != '\0' && i < len; i++) {
4177 wc = g_utf8_get_char(p);
4178 if (i >= quote_len && !g_unichar_isspace(wc))
4180 if (g_unichar_iswide(wc))
4182 else if (*p == '\t')
4186 p = g_utf8_next_char(p);
4189 for (; *p != '\0' && i < len; i++) {
4190 PangoLogAttr *attr = attrs + i;
4194 if (attr->is_line_break && can_break && was_white && !prev_dont_break)
4197 was_white = attr->is_white;
4199 /* don't wrap URI */
4200 if ((uri_len = get_uri_len(p)) > 0) {
4202 if (pos > 0 && col > max_col) {
4212 wc = g_utf8_get_char(p);
4213 if (g_unichar_iswide(wc)) {
4215 if (prev_dont_break && can_break && attr->is_line_break)
4217 } else if (*p == '\t')
4221 if (pos > 0 && col > max_col) {
4226 if (*p == '-' || *p == '/')
4227 prev_dont_break = TRUE;
4229 prev_dont_break = FALSE;
4231 p = g_utf8_next_char(p);
4235 // debug_print("compose_get_line_break_pos(): do_break = %d, pos = %d, col = %d\n", do_break, pos, col);
4240 *break_pos = *start;
4241 gtk_text_iter_set_line_offset(break_pos, pos);
4246 static gboolean compose_join_next_line(Compose *compose,
4247 GtkTextBuffer *buffer,
4249 const gchar *quote_str)
4251 GtkTextIter iter_ = *iter, cur, prev, next, end;
4252 PangoLogAttr attrs[3];
4254 gchar *next_quote_str;
4257 gboolean keep_cursor = FALSE;
4259 if (!gtk_text_iter_forward_line(&iter_) ||
4260 gtk_text_iter_ends_line(&iter_)) {
4263 next_quote_str = compose_get_quote_str(buffer, &iter_, "e_len);
4265 if ((quote_str || next_quote_str) &&
4266 strcmp2(quote_str, next_quote_str) != 0) {
4267 g_free(next_quote_str);
4270 g_free(next_quote_str);
4273 if (quote_len > 0) {
4274 gtk_text_iter_forward_chars(&end, quote_len);
4275 if (gtk_text_iter_ends_line(&end)) {
4280 /* don't join itemized lines */
4281 if (compose_itemized_length(buffer, &end) > 0) {
4285 /* don't join signature separator */
4286 if (compose_is_sig_separator(compose, buffer, &iter_)) {
4289 /* delete quote str */
4291 gtk_text_buffer_delete(buffer, &iter_, &end);
4293 /* don't join line breaks put by the user */
4295 gtk_text_iter_backward_char(&cur);
4296 if (gtk_text_iter_has_tag(&cur, compose->no_join_tag)) {
4297 gtk_text_iter_forward_char(&cur);
4301 gtk_text_iter_forward_char(&cur);
4302 /* delete linebreak and extra spaces */
4303 while (gtk_text_iter_backward_char(&cur)) {
4304 wc1 = gtk_text_iter_get_char(&cur);
4305 if (!g_unichar_isspace(wc1))
4310 while (!gtk_text_iter_ends_line(&cur)) {
4311 wc1 = gtk_text_iter_get_char(&cur);
4312 if (!g_unichar_isspace(wc1))
4314 gtk_text_iter_forward_char(&cur);
4317 if (!gtk_text_iter_equal(&prev, &next)) {
4320 mark = gtk_text_buffer_get_insert(buffer);
4321 gtk_text_buffer_get_iter_at_mark(buffer, &cur, mark);
4322 if (gtk_text_iter_equal(&prev, &cur))
4324 gtk_text_buffer_delete(buffer, &prev, &next);
4328 /* insert space if required */
4329 gtk_text_iter_backward_char(&prev);
4330 wc1 = gtk_text_iter_get_char(&prev);
4331 wc2 = gtk_text_iter_get_char(&next);
4332 gtk_text_iter_forward_char(&next);
4333 str = gtk_text_buffer_get_text(buffer, &prev, &next, FALSE);
4334 pango_default_break(str, -1, NULL, attrs, 3);
4335 if (!attrs[1].is_line_break ||
4336 (!g_unichar_iswide(wc1) || !g_unichar_iswide(wc2))) {
4337 gtk_text_buffer_insert(buffer, &iter_, " ", 1);
4339 gtk_text_iter_backward_char(&iter_);
4340 gtk_text_buffer_place_cursor(buffer, &iter_);
4349 #define ADD_TXT_POS(bp_, ep_, pti_) \
4350 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
4351 last = last->next; \
4352 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
4353 last->next = NULL; \
4355 g_warning("alloc error scanning URIs"); \
4358 static gboolean compose_beautify_paragraph(Compose *compose, GtkTextIter *par_iter, gboolean force)
4360 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4361 GtkTextBuffer *buffer;
4362 GtkTextIter iter, break_pos, end_of_line;
4363 gchar *quote_str = NULL;
4365 gboolean wrap_quote = force || prefs_common.linewrap_quote;
4366 gboolean prev_autowrap = compose->autowrap;
4367 gint startq_offset = -1, noq_offset = -1;
4368 gint uri_start = -1, uri_stop = -1;
4369 gint nouri_start = -1, nouri_stop = -1;
4370 gint num_blocks = 0;
4371 gint quotelevel = -1;
4372 gboolean modified = force;
4373 gboolean removed = FALSE;
4374 gboolean modified_before_remove = FALSE;
4376 gboolean start = TRUE;
4377 gint itemized_len = 0, rem_item_len = 0;
4378 gchar *itemized_chars = NULL;
4379 gboolean item_continuation = FALSE;
4384 if (compose->draft_timeout_tag == COMPOSE_DRAFT_TIMEOUT_FORBIDDEN) {
4388 compose->autowrap = FALSE;
4390 buffer = gtk_text_view_get_buffer(text);
4391 undo_wrapping(compose->undostruct, TRUE);
4396 mark = gtk_text_buffer_get_insert(buffer);
4397 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
4401 if (compose->draft_timeout_tag == COMPOSE_DRAFT_TIMEOUT_FORBIDDEN) {
4402 if (gtk_text_iter_ends_line(&iter)) {
4403 while (gtk_text_iter_ends_line(&iter) &&
4404 gtk_text_iter_forward_line(&iter))
4407 while (gtk_text_iter_backward_line(&iter)) {
4408 if (gtk_text_iter_ends_line(&iter)) {
4409 gtk_text_iter_forward_line(&iter);
4415 /* move to line start */
4416 gtk_text_iter_set_line_offset(&iter, 0);
4419 itemized_len = compose_itemized_length(buffer, &iter);
4421 if (!itemized_len) {
4422 itemized_len = compose_left_offset_length(buffer, &iter);
4423 item_continuation = TRUE;
4427 itemized_chars = compose_get_itemized_chars(buffer, &iter);
4429 /* go until paragraph end (empty line) */
4430 while (start || !gtk_text_iter_ends_line(&iter)) {
4431 gchar *scanpos = NULL;
4432 /* parse table - in order of priority */
4434 const gchar *needle; /* token */
4436 /* token search function */
4437 gchar *(*search) (const gchar *haystack,
4438 const gchar *needle);
4439 /* part parsing function */
4440 gboolean (*parse) (const gchar *start,
4441 const gchar *scanpos,
4445 /* part to URI function */
4446 gchar *(*build_uri) (const gchar *bp,
4450 static struct table parser[] = {
4451 {"http://", strcasestr, get_uri_part, make_uri_string},
4452 {"https://", strcasestr, get_uri_part, make_uri_string},
4453 {"ftp://", strcasestr, get_uri_part, make_uri_string},
4454 {"sftp://", strcasestr, get_uri_part, make_uri_string},
4455 {"gopher://",strcasestr, get_uri_part, make_uri_string},
4456 {"www.", strcasestr, get_uri_part, make_http_string},
4457 {"mailto:", strcasestr, get_uri_part, make_uri_string},
4458 {"@", strcasestr, get_email_part, make_email_string}
4460 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
4461 gint last_index = PARSE_ELEMS;
4463 gchar *o_walk = NULL, *walk = NULL, *bp = NULL, *ep = NULL;
4467 if (!prev_autowrap && num_blocks == 0) {
4469 g_signal_handlers_block_by_func(G_OBJECT(buffer),
4470 G_CALLBACK(text_inserted),
4473 if (gtk_text_iter_has_tag(&iter, compose->no_wrap_tag) && !force)
4476 uri_start = uri_stop = -1;
4478 quote_str = compose_get_quote_str(buffer, &iter, "e_len);
4481 // debug_print("compose_beautify_paragraph(): quote_str = '%s'\n", quote_str);
4482 if (startq_offset == -1)
4483 startq_offset = gtk_text_iter_get_offset(&iter);
4484 quotelevel = get_quote_level(quote_str, prefs_common.quote_chars);
4485 if (quotelevel > 2) {
4486 /* recycle colors */
4487 if (prefs_common.recycle_quote_colors)
4496 if (startq_offset == -1)
4497 noq_offset = gtk_text_iter_get_offset(&iter);
4501 if (prev_autowrap == FALSE && !force && !wrap_quote) {
4504 if (gtk_text_iter_ends_line(&iter)) {
4506 } else if (compose_get_line_break_pos(buffer, &iter, &break_pos,
4507 prefs_common.linewrap_len,
4509 GtkTextIter prev, next, cur;
4510 if (prev_autowrap != FALSE || force) {
4511 compose->automatic_break = TRUE;
4513 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4514 compose->automatic_break = FALSE;
4515 if (itemized_len && compose->autoindent) {
4516 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4517 if (!item_continuation)
4518 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4520 } else if (quote_str && wrap_quote) {
4521 compose->automatic_break = TRUE;
4523 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4524 compose->automatic_break = FALSE;
4525 if (itemized_len && compose->autoindent) {
4526 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4527 if (!item_continuation)
4528 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4532 /* remove trailing spaces */
4534 rem_item_len = itemized_len;
4535 while (compose->autoindent && rem_item_len-- > 0)
4536 gtk_text_iter_backward_char(&cur);
4537 gtk_text_iter_backward_char(&cur);
4540 while (!gtk_text_iter_starts_line(&cur)) {
4543 gtk_text_iter_backward_char(&cur);
4544 wc = gtk_text_iter_get_char(&cur);
4545 if (!g_unichar_isspace(wc))
4549 if (!gtk_text_iter_equal(&prev, &next)) {
4550 gtk_text_buffer_delete(buffer, &prev, &next);
4552 gtk_text_iter_forward_char(&break_pos);
4556 gtk_text_buffer_insert(buffer, &break_pos,
4560 modified |= compose_join_next_line(compose, buffer, &iter, quote_str);
4562 /* move iter to current line start */
4563 gtk_text_iter_set_line_offset(&iter, 0);
4570 /* move iter to next line start */
4576 if (!prev_autowrap && num_blocks > 0) {
4578 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
4579 G_CALLBACK(text_inserted),
4583 while (!gtk_text_iter_ends_line(&end_of_line)) {
4584 gtk_text_iter_forward_char(&end_of_line);
4586 o_walk = walk = gtk_text_buffer_get_text(buffer, &iter, &end_of_line, FALSE);
4588 nouri_start = gtk_text_iter_get_offset(&iter);
4589 nouri_stop = gtk_text_iter_get_offset(&end_of_line);
4591 walk_pos = gtk_text_iter_get_offset(&iter);
4592 /* FIXME: this looks phony. scanning for anything in the parse table */
4593 for (n = 0; n < PARSE_ELEMS; n++) {
4596 tmp = parser[n].search(walk, parser[n].needle);
4598 if (scanpos == NULL || tmp < scanpos) {
4607 /* check if URI can be parsed */
4608 if (parser[last_index].parse(walk, scanpos, (const gchar **)&bp,
4609 (const gchar **)&ep, FALSE)
4610 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
4614 strlen(parser[last_index].needle);
4617 uri_start = walk_pos + (bp - o_walk);
4618 uri_stop = walk_pos + (ep - o_walk);
4622 gtk_text_iter_forward_line(&iter);
4625 if (startq_offset != -1) {
4626 GtkTextIter startquote, endquote;
4627 gtk_text_buffer_get_iter_at_offset(
4628 buffer, &startquote, startq_offset);
4631 switch (quotelevel) {
4633 if (!gtk_text_iter_has_tag(&startquote, compose->quote0_tag) ||
4634 !gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) {
4635 gtk_text_buffer_apply_tag_by_name(
4636 buffer, "quote0", &startquote, &endquote);
4637 gtk_text_buffer_remove_tag_by_name(
4638 buffer, "quote1", &startquote, &endquote);
4639 gtk_text_buffer_remove_tag_by_name(
4640 buffer, "quote2", &startquote, &endquote);
4645 if (!gtk_text_iter_has_tag(&startquote, compose->quote1_tag) ||
4646 !gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) {
4647 gtk_text_buffer_apply_tag_by_name(
4648 buffer, "quote1", &startquote, &endquote);
4649 gtk_text_buffer_remove_tag_by_name(
4650 buffer, "quote0", &startquote, &endquote);
4651 gtk_text_buffer_remove_tag_by_name(
4652 buffer, "quote2", &startquote, &endquote);
4657 if (!gtk_text_iter_has_tag(&startquote, compose->quote2_tag) ||
4658 !gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag)) {
4659 gtk_text_buffer_apply_tag_by_name(
4660 buffer, "quote2", &startquote, &endquote);
4661 gtk_text_buffer_remove_tag_by_name(
4662 buffer, "quote0", &startquote, &endquote);
4663 gtk_text_buffer_remove_tag_by_name(
4664 buffer, "quote1", &startquote, &endquote);
4670 } else if (noq_offset != -1) {
4671 GtkTextIter startnoquote, endnoquote;
4672 gtk_text_buffer_get_iter_at_offset(
4673 buffer, &startnoquote, noq_offset);
4676 if ((gtk_text_iter_has_tag(&startnoquote, compose->quote0_tag)
4677 && gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) ||
4678 (gtk_text_iter_has_tag(&startnoquote, compose->quote1_tag)
4679 && gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) ||
4680 (gtk_text_iter_has_tag(&startnoquote, compose->quote2_tag)
4681 && gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag))) {
4682 gtk_text_buffer_remove_tag_by_name(
4683 buffer, "quote0", &startnoquote, &endnoquote);
4684 gtk_text_buffer_remove_tag_by_name(
4685 buffer, "quote1", &startnoquote, &endnoquote);
4686 gtk_text_buffer_remove_tag_by_name(
4687 buffer, "quote2", &startnoquote, &endnoquote);
4693 if (uri_start != nouri_start && uri_stop != nouri_stop) {
4694 GtkTextIter nouri_start_iter, nouri_end_iter;
4695 gtk_text_buffer_get_iter_at_offset(
4696 buffer, &nouri_start_iter, nouri_start);
4697 gtk_text_buffer_get_iter_at_offset(
4698 buffer, &nouri_end_iter, nouri_stop);
4699 if (gtk_text_iter_has_tag(&nouri_start_iter, compose->uri_tag) &&
4700 gtk_text_iter_has_tag(&nouri_end_iter, compose->uri_tag)) {
4701 gtk_text_buffer_remove_tag_by_name(
4702 buffer, "link", &nouri_start_iter, &nouri_end_iter);
4703 modified_before_remove = modified;
4708 if (uri_start >= 0 && uri_stop > 0) {
4709 GtkTextIter uri_start_iter, uri_end_iter, back;
4710 gtk_text_buffer_get_iter_at_offset(
4711 buffer, &uri_start_iter, uri_start);
4712 gtk_text_buffer_get_iter_at_offset(
4713 buffer, &uri_end_iter, uri_stop);
4714 back = uri_end_iter;
4715 gtk_text_iter_backward_char(&back);
4716 if (!gtk_text_iter_has_tag(&uri_start_iter, compose->uri_tag) ||
4717 !gtk_text_iter_has_tag(&back, compose->uri_tag)) {
4718 gtk_text_buffer_apply_tag_by_name(
4719 buffer, "link", &uri_start_iter, &uri_end_iter);
4721 if (removed && !modified_before_remove) {
4727 // debug_print("not modified, out after %d lines\n", lines);
4731 // debug_print("modified, out after %d lines\n", lines);
4733 g_free(itemized_chars);
4736 undo_wrapping(compose->undostruct, FALSE);
4737 compose->autowrap = prev_autowrap;
4742 void compose_action_cb(void *data)
4744 Compose *compose = (Compose *)data;
4745 compose_wrap_all(compose);
4748 static void compose_wrap_all(Compose *compose)
4750 compose_wrap_all_full(compose, FALSE);
4753 static void compose_wrap_all_full(Compose *compose, gboolean force)
4755 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4756 GtkTextBuffer *buffer;
4758 gboolean modified = TRUE;
4760 buffer = gtk_text_view_get_buffer(text);
4762 gtk_text_buffer_get_start_iter(buffer, &iter);
4764 undo_wrapping(compose->undostruct, TRUE);
4766 while (!gtk_text_iter_is_end(&iter) && modified)
4767 modified = compose_beautify_paragraph(compose, &iter, force);
4769 undo_wrapping(compose->undostruct, FALSE);
4773 static void compose_set_title(Compose *compose)
4779 edited = compose->modified ? _(" [Edited]") : "";
4781 subject = gtk_editable_get_chars(
4782 GTK_EDITABLE(compose->subject_entry), 0, -1);
4784 #ifndef GENERIC_UMPC
4785 if (subject && strlen(subject))
4786 str = g_strdup_printf(_("%s - Compose message%s"),
4789 str = g_strdup_printf(_("[no subject] - Compose message%s"), edited);
4791 str = g_strdup(_("Compose message"));
4794 gtk_window_set_title(GTK_WINDOW(compose->window), str);
4800 * compose_current_mail_account:
4802 * Find a current mail account (the currently selected account, or the
4803 * default account, if a news account is currently selected). If a
4804 * mail account cannot be found, display an error message.
4806 * Return value: Mail account, or NULL if not found.
4808 static PrefsAccount *
4809 compose_current_mail_account(void)
4813 if (cur_account && cur_account->protocol != A_NNTP)
4816 ac = account_get_default();
4817 if (!ac || ac->protocol == A_NNTP) {
4818 alertpanel_error(_("Account for sending mail is not specified.\n"
4819 "Please select a mail account before sending."));
4826 #define QUOTE_IF_REQUIRED(out, str) \
4828 if (*str != '"' && strpbrk(str, ",.:;[]<>()@\\\"")) { \
4832 len = strlen(str) + 3; \
4833 if ((__tmp = alloca(len)) == NULL) { \
4834 g_warning("can't allocate memory"); \
4835 g_string_free(header, TRUE); \
4838 g_snprintf(__tmp, len, "\"%s\"", str); \
4843 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4844 g_warning("can't allocate memory"); \
4845 g_string_free(header, TRUE); \
4848 strcpy(__tmp, str); \
4854 #define QUOTE_IF_REQUIRED_NORMAL(out, str, errret) \
4856 if (*str != '"' && strpbrk(str, ",.:;[]<>()@\\\"")) { \
4860 len = strlen(str) + 3; \
4861 if ((__tmp = alloca(len)) == NULL) { \
4862 g_warning("can't allocate memory"); \
4865 g_snprintf(__tmp, len, "\"%s\"", str); \
4870 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4871 g_warning("can't allocate memory"); \
4874 strcpy(__tmp, str); \
4880 static void compose_select_account(Compose *compose, PrefsAccount *account,
4883 gchar *from = NULL, *header = NULL;
4884 ComposeHeaderEntry *header_entry;
4885 #if GTK_CHECK_VERSION(2, 24, 0)
4889 cm_return_if_fail(account != NULL);
4891 compose->account = account;
4892 if (account->name && *account->name) {
4894 QUOTE_IF_REQUIRED_NORMAL(buf, account->name, return);
4895 qbuf = escape_internal_quotes(buf, '"');
4896 from = g_strdup_printf("%s <%s>",
4897 qbuf, account->address);
4900 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4902 from = g_strdup_printf("<%s>",
4904 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4909 compose_set_title(compose);
4911 if (account->default_sign && compose->mode != COMPOSE_REDIRECT)
4912 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", TRUE);
4914 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", FALSE);
4915 if (account->default_encrypt && compose->mode != COMPOSE_REDIRECT)
4916 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", TRUE);
4918 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", FALSE);
4920 activate_privacy_system(compose, account, FALSE);
4922 if (!init && compose->mode != COMPOSE_REDIRECT) {
4923 undo_block(compose->undostruct);
4924 compose_insert_sig(compose, TRUE);
4925 undo_unblock(compose->undostruct);
4928 header_entry = (ComposeHeaderEntry *) compose->header_list->data;
4929 #if !GTK_CHECK_VERSION(2, 24, 0)
4930 header = gtk_combo_box_get_active_text(GTK_COMBO_BOX(header_entry->combo));
4932 if (gtk_combo_box_get_active_iter(GTK_COMBO_BOX(header_entry->combo), &iter))
4933 gtk_tree_model_get(gtk_combo_box_get_model(GTK_COMBO_BOX(
4934 header_entry->combo)), &iter, COMBOBOX_TEXT, &header, -1);
4937 if (header && !strlen(gtk_entry_get_text(GTK_ENTRY(header_entry->entry)))) {
4938 if (account->protocol == A_NNTP) {
4939 if (!strcmp(header, _("To:")))
4940 combobox_select_by_text(
4941 GTK_COMBO_BOX(header_entry->combo),
4944 if (!strcmp(header, _("Newsgroups:")))
4945 combobox_select_by_text(
4946 GTK_COMBO_BOX(header_entry->combo),
4954 /* use account's dict info if set */
4955 if (compose->gtkaspell) {
4956 if (account->enable_default_dictionary)
4957 gtkaspell_change_dict(compose->gtkaspell,
4958 account->default_dictionary, FALSE);
4959 if (account->enable_default_alt_dictionary)
4960 gtkaspell_change_alt_dict(compose->gtkaspell,
4961 account->default_alt_dictionary);
4962 if (account->enable_default_dictionary
4963 || account->enable_default_alt_dictionary)
4964 compose_spell_menu_changed(compose);
4969 gboolean compose_check_for_valid_recipient(Compose *compose) {
4970 gchar *recipient_headers_mail[] = {"To:", "Cc:", "Bcc:", NULL};
4971 gchar *recipient_headers_news[] = {"Newsgroups:", NULL};
4972 gboolean recipient_found = FALSE;
4976 /* free to and newsgroup list */
4977 slist_free_strings_full(compose->to_list);
4978 compose->to_list = NULL;
4980 slist_free_strings_full(compose->newsgroup_list);
4981 compose->newsgroup_list = NULL;
4983 /* search header entries for to and newsgroup entries */
4984 for (list = compose->header_list; list; list = list->next) {
4987 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
4988 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4991 if (entry[0] != '\0') {
4992 for (strptr = recipient_headers_mail; *strptr != NULL; strptr++) {
4993 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
4994 compose->to_list = address_list_append(compose->to_list, entry);
4995 recipient_found = TRUE;
4998 for (strptr = recipient_headers_news; *strptr != NULL; strptr++) {
4999 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
5000 compose->newsgroup_list = newsgroup_list_append(compose->newsgroup_list, entry);
5001 recipient_found = TRUE;
5008 return recipient_found;
5011 static gboolean compose_check_for_set_recipients(Compose *compose)
5013 if (compose->account->set_autocc && compose->account->auto_cc) {
5014 gboolean found_other = FALSE;
5016 /* search header entries for to and newsgroup entries */
5017 for (list = compose->header_list; list; list = list->next) {
5020 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5021 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5024 if (strcmp(entry, compose->account->auto_cc)
5025 || strcmp(header, prefs_common_translated_header_name("Cc:"))) {
5035 if (compose->batch) {
5036 gtk_widget_show_all(compose->window);
5038 aval = alertpanel(_("Send"),
5039 _("The only recipient is the default CC address. Send anyway?"),
5040 GTK_STOCK_CANCEL, g_strconcat("+", _("_Send"), NULL), NULL);
5041 if (aval != G_ALERTALTERNATE)
5045 if (compose->account->set_autobcc && compose->account->auto_bcc) {
5046 gboolean found_other = FALSE;
5048 /* search header entries for to and newsgroup entries */
5049 for (list = compose->header_list; list; list = list->next) {
5052 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5053 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5056 if (strcmp(entry, compose->account->auto_bcc)
5057 || strcmp(header, prefs_common_translated_header_name("Bcc:"))) {
5067 if (compose->batch) {
5068 gtk_widget_show_all(compose->window);
5070 aval = alertpanel(_("Send"),
5071 _("The only recipient is the default BCC address. Send anyway?"),
5072 GTK_STOCK_CANCEL, g_strconcat("+", _("_Send"), NULL), NULL);
5073 if (aval != G_ALERTALTERNATE)
5080 static gboolean compose_check_entries(Compose *compose, gboolean check_everything)
5084 if (compose_check_for_valid_recipient(compose) == FALSE) {
5085 if (compose->batch) {
5086 gtk_widget_show_all(compose->window);
5088 alertpanel_error(_("Recipient is not specified."));
5092 if (compose_check_for_set_recipients(compose) == FALSE) {
5096 if (!compose->batch && prefs_common.warn_empty_subj == TRUE) {
5097 str = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5098 if (*str == '\0' && check_everything == TRUE &&
5099 compose->mode != COMPOSE_REDIRECT) {
5101 gchar *button_label;
5104 if (compose->sending)
5105 button_label = g_strconcat("+", _("_Send"), NULL);
5107 button_label = g_strconcat("+", _("_Queue"), NULL);
5108 message = g_strdup_printf(_("Subject is empty. %s"),
5109 compose->sending?_("Send it anyway?"):
5110 _("Queue it anyway?"));
5112 aval = alertpanel_full(compose->sending?_("Send"):_("Send later"), message,
5113 GTK_STOCK_CANCEL, button_label, NULL, TRUE, NULL,
5114 ALERT_QUESTION, G_ALERTDEFAULT);
5116 if (aval & G_ALERTDISABLE) {
5117 aval &= ~G_ALERTDISABLE;
5118 prefs_common.warn_empty_subj = FALSE;
5120 if (aval != G_ALERTALTERNATE)
5125 if (check_everything && hooks_invoke(COMPOSE_CHECK_BEFORE_SEND_HOOKLIST, compose))
5131 gint compose_send(Compose *compose)
5134 FolderItem *folder = NULL;
5136 gchar *msgpath = NULL;
5137 gboolean discard_window = FALSE;
5138 gchar *errstr = NULL;
5139 gchar *tmsgid = NULL;
5140 MainWindow *mainwin = mainwindow_get_mainwindow();
5141 gboolean queued_removed = FALSE;
5143 if (prefs_common.send_dialog_invisible
5144 || compose->batch == TRUE)
5145 discard_window = TRUE;
5147 compose_allow_user_actions (compose, FALSE);
5148 compose->sending = TRUE;
5150 if (compose_check_entries(compose, TRUE) == FALSE) {
5151 if (compose->batch) {
5152 gtk_widget_show_all(compose->window);
5158 val = compose_queue(compose, &msgnum, &folder, &msgpath, TRUE);
5161 if (compose->batch) {
5162 gtk_widget_show_all(compose->window);
5165 alertpanel_error(_("Could not queue message for sending:\n\n"
5166 "Charset conversion failed."));
5167 } else if (val == -5) {
5168 alertpanel_error(_("Could not queue message for sending:\n\n"
5169 "Couldn't get recipient encryption key."));
5170 } else if (val == -6) {
5172 } else if (val == -3) {
5173 if (privacy_peek_error())
5174 alertpanel_error(_("Could not queue message for sending:\n\n"
5175 "Signature failed: %s"), privacy_get_error());
5176 } else if (val == -2 && errno != 0) {
5177 alertpanel_error(_("Could not queue message for sending:\n\n%s."), g_strerror(errno));
5179 alertpanel_error(_("Could not queue message for sending."));
5184 tmsgid = compose->msgid ? g_strdup(compose->msgid) : NULL;
5185 if (discard_window) {
5186 compose->sending = FALSE;
5187 compose_close(compose);
5188 /* No more compose access in the normal codepath
5189 * after this point! */
5194 alertpanel_error(_("The message was queued but could not be "
5195 "sent.\nUse \"Send queued messages\" from "
5196 "the main window to retry."));
5197 if (!discard_window) {
5204 if (msgpath == NULL) {
5205 msgpath = folder_item_fetch_msg(folder, msgnum);
5206 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5209 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5210 claws_unlink(msgpath);
5213 if (!discard_window) {
5215 if (!queued_removed)
5216 folder_item_remove_msg(folder, msgnum);
5217 folder_item_scan(folder);
5219 /* make sure we delete that */
5220 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5222 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5223 folder_item_remove_msg(folder, tmp->msgnum);
5224 procmsg_msginfo_free(&tmp);
5231 if (!queued_removed)
5232 folder_item_remove_msg(folder, msgnum);
5233 folder_item_scan(folder);
5235 /* make sure we delete that */
5236 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5238 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5239 folder_item_remove_msg(folder, tmp->msgnum);
5240 procmsg_msginfo_free(&tmp);
5243 if (!discard_window) {
5244 compose->sending = FALSE;
5245 compose_allow_user_actions (compose, TRUE);
5246 compose_close(compose);
5250 alertpanel_error_log(_("%s\nUse \"Send queued messages\" from "
5251 "the main window to retry."), errstr);
5254 alertpanel_error_log(_("The message was queued but could not be "
5255 "sent.\nUse \"Send queued messages\" from "
5256 "the main window to retry."));
5258 if (!discard_window) {
5267 toolbar_main_set_sensitive(mainwin);
5268 main_window_set_menu_sensitive(mainwin);
5274 compose_allow_user_actions (compose, TRUE);
5275 compose->sending = FALSE;
5276 compose->modified = TRUE;
5277 toolbar_main_set_sensitive(mainwin);
5278 main_window_set_menu_sensitive(mainwin);
5283 static gboolean compose_use_attach(Compose *compose)
5285 GtkTreeModel *model = gtk_tree_view_get_model
5286 (GTK_TREE_VIEW(compose->attach_clist));
5287 return gtk_tree_model_iter_n_children(model, NULL) > 0;
5290 static gint compose_redirect_write_headers_from_headerlist(Compose *compose,
5293 gchar buf[BUFFSIZE];
5295 gboolean first_to_address;
5296 gboolean first_cc_address;
5298 ComposeHeaderEntry *headerentry;
5299 const gchar *headerentryname;
5300 const gchar *cc_hdr;
5301 const gchar *to_hdr;
5302 gboolean err = FALSE;
5304 debug_print("Writing redirect header\n");
5306 cc_hdr = prefs_common_translated_header_name("Cc:");
5307 to_hdr = prefs_common_translated_header_name("To:");
5309 first_to_address = TRUE;
5310 for (list = compose->header_list; list; list = list->next) {
5311 headerentry = ((ComposeHeaderEntry *)list->data);
5312 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5314 if (g_utf8_collate(headerentryname, to_hdr) == 0) {
5315 const gchar *entstr = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5316 Xstrdup_a(str, entstr, return -1);
5318 if (str[0] != '\0') {
5319 compose_convert_header
5320 (compose, buf, sizeof(buf), str,
5321 strlen("Resent-To") + 2, TRUE);
5323 if (first_to_address) {
5324 err |= (fprintf(fp, "Resent-To: ") < 0);
5325 first_to_address = FALSE;
5327 err |= (fprintf(fp, ",") < 0);
5329 err |= (fprintf(fp, "%s", buf) < 0);
5333 if (!first_to_address) {
5334 err |= (fprintf(fp, "\n") < 0);
5337 first_cc_address = TRUE;
5338 for (list = compose->header_list; list; list = list->next) {
5339 headerentry = ((ComposeHeaderEntry *)list->data);
5340 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5342 if (g_utf8_collate(headerentryname, cc_hdr) == 0) {
5343 const gchar *strg = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5344 Xstrdup_a(str, strg, return -1);
5346 if (str[0] != '\0') {
5347 compose_convert_header
5348 (compose, buf, sizeof(buf), str,
5349 strlen("Resent-Cc") + 2, TRUE);
5351 if (first_cc_address) {
5352 err |= (fprintf(fp, "Resent-Cc: ") < 0);
5353 first_cc_address = FALSE;
5355 err |= (fprintf(fp, ",") < 0);
5357 err |= (fprintf(fp, "%s", buf) < 0);
5361 if (!first_cc_address) {
5362 err |= (fprintf(fp, "\n") < 0);
5365 return (err ? -1:0);
5368 static gint compose_redirect_write_headers(Compose *compose, FILE *fp)
5370 gchar buf[BUFFSIZE];
5372 const gchar *entstr;
5373 /* struct utsname utsbuf; */
5374 gboolean err = FALSE;
5376 cm_return_val_if_fail(fp != NULL, -1);
5377 cm_return_val_if_fail(compose->account != NULL, -1);
5378 cm_return_val_if_fail(compose->account->address != NULL, -1);
5381 get_rfc822_date(buf, sizeof(buf));
5382 err |= (fprintf(fp, "Resent-Date: %s\n", buf) < 0);
5385 if (compose->account->name && *compose->account->name) {
5386 compose_convert_header
5387 (compose, buf, sizeof(buf), compose->account->name,
5388 strlen("From: "), TRUE);
5389 err |= (fprintf(fp, "Resent-From: %s <%s>\n",
5390 buf, compose->account->address) < 0);
5392 err |= (fprintf(fp, "Resent-From: %s\n", compose->account->address) < 0);
5395 entstr = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5396 if (*entstr != '\0') {
5397 Xstrdup_a(str, entstr, return -1);
5400 compose_convert_header(compose, buf, sizeof(buf), str,
5401 strlen("Subject: "), FALSE);
5402 err |= (fprintf(fp, "Subject: %s\n", buf) < 0);
5406 /* Resent-Message-ID */
5407 if (compose->account->set_domain && compose->account->domain) {
5408 g_snprintf(buf, sizeof(buf), "%s", compose->account->domain);
5409 } else if (!strncmp(get_domain_name(), "localhost", strlen("localhost"))) {
5410 g_snprintf(buf, sizeof(buf), "%s",
5411 strchr(compose->account->address, '@') ?
5412 strchr(compose->account->address, '@')+1 :
5413 compose->account->address);
5415 g_snprintf(buf, sizeof(buf), "%s", "");
5418 if (compose->account->gen_msgid) {
5420 if (compose->account->msgid_with_addr) {
5421 addr = compose->account->address;
5423 generate_msgid(buf, sizeof(buf), addr);
5424 err |= (fprintf(fp, "Resent-Message-ID: <%s>\n", buf) < 0);
5426 g_free(compose->msgid);
5427 compose->msgid = g_strdup(buf);
5429 compose->msgid = NULL;
5432 if (compose_redirect_write_headers_from_headerlist(compose, fp))
5435 /* separator between header and body */
5436 err |= (fputs("\n", fp) == EOF);
5438 return (err ? -1:0);
5441 static gint compose_redirect_write_to_file(Compose *compose, FILE *fdest)
5445 gchar buf[BUFFSIZE];
5447 gboolean skip = FALSE;
5448 gboolean err = FALSE;
5449 gchar *not_included[]={
5450 "Return-Path:", "Delivered-To:", "Received:",
5451 "Subject:", "X-UIDL:", "AF:",
5452 "NF:", "PS:", "SRH:",
5453 "SFN:", "DSR:", "MID:",
5454 "CFG:", "PT:", "S:",
5455 "RQ:", "SSV:", "NSV:",
5456 "SSH:", "R:", "MAID:",
5457 "NAID:", "RMID:", "FMID:",
5458 "SCF:", "RRCPT:", "NG:",
5459 "X-Claws-Privacy", "X-Claws-Sign:", "X-Claws-Encrypt",
5460 "X-Claws-End-Special-Headers:", "X-Claws-Account-Id:",
5461 "X-Sylpheed-Privacy", "X-Sylpheed-Sign:", "X-Sylpheed-Encrypt",
5462 "X-Sylpheed-End-Special-Headers:", "X-Sylpheed-Account-Id:",
5463 "X-Claws-Auto-Wrapping:", "X-Claws-Auto-Indent:",
5466 if ((fp = g_fopen(compose->redirect_filename, "rb")) == NULL) {
5467 FILE_OP_ERROR(compose->redirect_filename, "fopen");
5471 while (procheader_get_one_field_asis(buf, sizeof(buf), fp) != -1) {
5473 for (i = 0; not_included[i] != NULL; i++) {
5474 if (g_ascii_strncasecmp(buf, not_included[i],
5475 strlen(not_included[i])) == 0) {
5482 if (fputs(buf, fdest) == -1)
5485 if (!prefs_common.redirect_keep_from) {
5486 if (g_ascii_strncasecmp(buf, "From:",
5487 strlen("From:")) == 0) {
5488 err |= (fputs(" (by way of ", fdest) == EOF);
5489 if (compose->account->name
5490 && *compose->account->name) {
5491 compose_convert_header
5492 (compose, buf, sizeof(buf),
5493 compose->account->name,
5496 err |= (fprintf(fdest, "%s <%s>",
5498 compose->account->address) < 0);
5500 err |= (fprintf(fdest, "%s",
5501 compose->account->address) < 0);
5502 err |= (fputs(")", fdest) == EOF);
5506 if (fputs("\n", fdest) == -1)
5513 if (compose_redirect_write_headers(compose, fdest))
5516 while ((len = fread(buf, sizeof(gchar), sizeof(buf), fp)) > 0) {
5517 if (fwrite(buf, sizeof(gchar), len, fdest) != len)
5530 static gint compose_write_to_file(Compose *compose, FILE *fp, gint action, gboolean attach_parts)
5532 GtkTextBuffer *buffer;
5533 GtkTextIter start, end;
5534 gchar *chars, *tmp_enc_file, *content;
5536 const gchar *out_codeset;
5537 EncodingType encoding = ENC_UNKNOWN;
5538 MimeInfo *mimemsg, *mimetext;
5540 const gchar *src_codeset = CS_INTERNAL;
5541 gchar *from_addr = NULL;
5542 gchar *from_name = NULL;
5545 if (action == COMPOSE_WRITE_FOR_SEND)
5546 attach_parts = TRUE;
5548 /* create message MimeInfo */
5549 mimemsg = procmime_mimeinfo_new();
5550 mimemsg->type = MIMETYPE_MESSAGE;
5551 mimemsg->subtype = g_strdup("rfc822");
5552 mimemsg->content = MIMECONTENT_MEM;
5553 mimemsg->tmp = TRUE; /* must free content later */
5554 mimemsg->data.mem = compose_get_header(compose);
5556 /* Create text part MimeInfo */
5557 /* get all composed text */
5558 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5559 gtk_text_buffer_get_start_iter(buffer, &start);
5560 gtk_text_buffer_get_end_iter(buffer, &end);
5561 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5563 out_codeset = conv_get_charset_str(compose->out_encoding);
5565 if (!out_codeset && is_ascii_str(chars)) {
5566 out_codeset = CS_US_ASCII;
5567 } else if (prefs_common.outgoing_fallback_to_ascii &&
5568 is_ascii_str(chars)) {
5569 out_codeset = CS_US_ASCII;
5570 encoding = ENC_7BIT;
5574 gchar *test_conv_global_out = NULL;
5575 gchar *test_conv_reply = NULL;
5577 /* automatic mode. be automatic. */
5578 codeconv_set_strict(TRUE);
5580 out_codeset = conv_get_outgoing_charset_str();
5582 debug_print("trying to convert to %s\n", out_codeset);
5583 test_conv_global_out = conv_codeset_strdup(chars, src_codeset, out_codeset);
5586 if (!test_conv_global_out && compose->orig_charset
5587 && strcmp(compose->orig_charset, CS_US_ASCII)) {
5588 out_codeset = compose->orig_charset;
5589 debug_print("failure; trying to convert to %s\n", out_codeset);
5590 test_conv_reply = conv_codeset_strdup(chars, src_codeset, out_codeset);
5593 if (!test_conv_global_out && !test_conv_reply) {
5595 out_codeset = CS_INTERNAL;
5596 debug_print("failure; finally using %s\n", out_codeset);
5598 g_free(test_conv_global_out);
5599 g_free(test_conv_reply);
5600 codeconv_set_strict(FALSE);
5603 if (encoding == ENC_UNKNOWN) {
5604 if (prefs_common.encoding_method == CTE_BASE64)
5605 encoding = ENC_BASE64;
5606 else if (prefs_common.encoding_method == CTE_QUOTED_PRINTABLE)
5607 encoding = ENC_QUOTED_PRINTABLE;
5608 else if (prefs_common.encoding_method == CTE_8BIT)
5609 encoding = ENC_8BIT;
5611 encoding = procmime_get_encoding_for_charset(out_codeset);
5614 debug_print("src encoding = %s, out encoding = %s, transfer encoding = %s\n",
5615 src_codeset, out_codeset, procmime_get_encoding_str(encoding));
5617 if (action == COMPOSE_WRITE_FOR_SEND) {
5618 codeconv_set_strict(TRUE);
5619 buf = conv_codeset_strdup(chars, src_codeset, out_codeset);
5620 codeconv_set_strict(FALSE);
5625 msg = g_strdup_printf(_("Can't convert the character encoding of the message \n"
5626 "to the specified %s charset.\n"
5627 "Send it as %s?"), out_codeset, src_codeset);
5628 aval = alertpanel_full(_("Error"), msg, GTK_STOCK_CANCEL,
5629 g_strconcat("+", _("_Send"), NULL), NULL, FALSE,
5630 NULL, ALERT_ERROR, G_ALERTDEFAULT);
5633 if (aval != G_ALERTALTERNATE) {
5638 out_codeset = src_codeset;
5644 out_codeset = src_codeset;
5649 if (encoding == ENC_8BIT || encoding == ENC_7BIT) {
5650 if (!strncmp(buf, "From ", sizeof("From ")-1) ||
5651 strstr(buf, "\nFrom ") != NULL) {
5652 encoding = ENC_QUOTED_PRINTABLE;
5656 mimetext = procmime_mimeinfo_new();
5657 mimetext->content = MIMECONTENT_MEM;
5658 mimetext->tmp = TRUE; /* must free content later */
5659 /* dup'ed because procmime_encode_content can turn it into a tmpfile
5660 * and free the data, which we need later. */
5661 mimetext->data.mem = g_strdup(buf);
5662 mimetext->type = MIMETYPE_TEXT;
5663 mimetext->subtype = g_strdup("plain");
5664 g_hash_table_insert(mimetext->typeparameters, g_strdup("charset"),
5665 g_strdup(out_codeset));
5667 /* protect trailing spaces when signing message */
5668 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5669 privacy_system_can_sign(compose->privacy_system)) {
5670 encoding = ENC_QUOTED_PRINTABLE;
5673 debug_print("main text: %zd bytes encoded as %s in %d\n",
5674 strlen(buf), out_codeset, encoding);
5676 /* check for line length limit */
5677 if (action == COMPOSE_WRITE_FOR_SEND &&
5678 encoding != ENC_QUOTED_PRINTABLE && encoding != ENC_BASE64 &&
5679 check_line_length(buf, 1000, &line) < 0) {
5682 msg = g_strdup_printf
5683 (_("Line %d exceeds the line length limit (998 bytes).\n"
5684 "The contents of the message might be broken on the way to the delivery.\n"
5686 "Send it anyway?"), line + 1);
5687 aval = alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, GTK_STOCK_OK, NULL);
5689 if (aval != G_ALERTALTERNATE) {
5695 if (encoding != ENC_UNKNOWN)
5696 procmime_encode_content(mimetext, encoding);
5698 /* append attachment parts */
5699 if (compose_use_attach(compose) && attach_parts) {
5700 MimeInfo *mimempart;
5701 gchar *boundary = NULL;
5702 mimempart = procmime_mimeinfo_new();
5703 mimempart->content = MIMECONTENT_EMPTY;
5704 mimempart->type = MIMETYPE_MULTIPART;
5705 mimempart->subtype = g_strdup("mixed");
5709 boundary = generate_mime_boundary(NULL);
5710 } while (strstr(buf, boundary) != NULL);
5712 g_hash_table_insert(mimempart->typeparameters, g_strdup("boundary"),
5715 mimetext->disposition = DISPOSITIONTYPE_INLINE;
5717 g_node_append(mimempart->node, mimetext->node);
5718 g_node_append(mimemsg->node, mimempart->node);
5720 if (compose_add_attachments(compose, mimempart) < 0)
5723 g_node_append(mimemsg->node, mimetext->node);
5727 if (strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) != 0) {
5728 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
5729 /* extract name and address */
5730 if (strstr(spec, " <") && strstr(spec, ">")) {
5731 from_addr = g_strdup(strrchr(spec, '<')+1);
5732 *(strrchr(from_addr, '>')) = '\0';
5733 from_name = g_strdup(spec);
5734 *(strrchr(from_name, '<')) = '\0';
5741 /* sign message if sending */
5742 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5743 privacy_system_can_sign(compose->privacy_system))
5744 if (!privacy_sign(compose->privacy_system, mimemsg,
5745 compose->account, from_addr)) {
5753 if (compose->use_encryption) {
5754 if (compose->encdata != NULL &&
5755 strcmp(compose->encdata, "_DONT_ENCRYPT_")) {
5757 /* First, write an unencrypted copy and save it to outbox, if
5758 * user wants that. */
5759 if (compose->account->save_encrypted_as_clear_text) {
5760 debug_print("saving sent message unencrypted...\n");
5761 FILE *tmpfp = get_tmpfile_in_dir(get_mime_tmp_dir(), &tmp_enc_file);
5765 /* fp now points to a file with headers written,
5766 * let's make a copy. */
5768 content = file_read_stream_to_str(fp);
5770 str_write_to_file(content, tmp_enc_file);
5773 /* Now write the unencrypted body. */
5774 if ((tmpfp = g_fopen(tmp_enc_file, "a")) != NULL) {
5775 procmime_write_mimeinfo(mimemsg, tmpfp);
5778 outbox = folder_find_item_from_identifier(compose_get_save_to(compose));
5780 outbox = folder_get_default_outbox();
5782 procmsg_save_to_outbox(outbox, tmp_enc_file, TRUE);
5783 claws_unlink(tmp_enc_file);
5785 g_warning("Can't open file '%s'", tmp_enc_file);
5788 g_warning("couldn't get tempfile");
5791 if (!privacy_encrypt(compose->privacy_system, mimemsg, compose->encdata)) {
5792 debug_print("Couldn't encrypt mime structure: %s.\n",
5793 privacy_get_error());
5794 alertpanel_error(_("Couldn't encrypt the email: %s"),
5795 privacy_get_error());
5800 procmime_write_mimeinfo(mimemsg, fp);
5802 procmime_mimeinfo_free_all(&mimemsg);
5807 static gint compose_write_body_to_file(Compose *compose, const gchar *file)
5809 GtkTextBuffer *buffer;
5810 GtkTextIter start, end;
5815 if ((fp = g_fopen(file, "wb")) == NULL) {
5816 FILE_OP_ERROR(file, "fopen");
5820 /* chmod for security */
5821 if (change_file_mode_rw(fp, file) < 0) {
5822 FILE_OP_ERROR(file, "chmod");
5823 g_warning("can't change file mode");
5826 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5827 gtk_text_buffer_get_start_iter(buffer, &start);
5828 gtk_text_buffer_get_end_iter(buffer, &end);
5829 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5831 chars = conv_codeset_strdup
5832 (tmp, CS_INTERNAL, conv_get_locale_charset_str());
5841 len = strlen(chars);
5842 if (fwrite(chars, sizeof(gchar), len, fp) != len) {
5843 FILE_OP_ERROR(file, "fwrite");
5852 if (fclose(fp) == EOF) {
5853 FILE_OP_ERROR(file, "fclose");
5860 static gint compose_remove_reedit_target(Compose *compose, gboolean force)
5863 MsgInfo *msginfo = compose->targetinfo;
5865 cm_return_val_if_fail(compose->mode == COMPOSE_REEDIT, -1);
5866 if (!msginfo) return -1;
5868 if (!force && MSG_IS_LOCKED(msginfo->flags))
5871 item = msginfo->folder;
5872 cm_return_val_if_fail(item != NULL, -1);
5874 if (procmsg_msg_exist(msginfo) &&
5875 (folder_has_parent_of_type(item, F_QUEUE) ||
5876 folder_has_parent_of_type(item, F_DRAFT)
5877 || msginfo == compose->autosaved_draft)) {
5878 if (folder_item_remove_msg(item, msginfo->msgnum) < 0) {
5879 g_warning("can't remove the old message");
5882 debug_print("removed reedit target %d\n", msginfo->msgnum);
5889 static void compose_remove_draft(Compose *compose)
5892 MsgInfo *msginfo = compose->targetinfo;
5893 drafts = account_get_special_folder(compose->account, F_DRAFT);
5895 if (procmsg_msg_exist(msginfo)) {
5896 folder_item_remove_msg(drafts, msginfo->msgnum);
5901 gint compose_queue(Compose *compose, gint *msgnum, FolderItem **item, gchar **msgpath,
5902 gboolean remove_reedit_target)
5904 return compose_queue_sub (compose, msgnum, item, msgpath, FALSE, remove_reedit_target);
5907 static gboolean compose_warn_encryption(Compose *compose)
5909 const gchar *warning = privacy_get_encrypt_warning(compose->privacy_system);
5910 AlertValue val = G_ALERTALTERNATE;
5912 if (warning == NULL)
5915 val = alertpanel_full(_("Encryption warning"), warning,
5916 GTK_STOCK_CANCEL, g_strconcat("+", _("C_ontinue"), NULL), NULL,
5917 TRUE, NULL, ALERT_WARNING, G_ALERTALTERNATE);
5918 if (val & G_ALERTDISABLE) {
5919 val &= ~G_ALERTDISABLE;
5920 if (val == G_ALERTALTERNATE)
5921 privacy_inhibit_encrypt_warning(compose->privacy_system,
5925 if (val == G_ALERTALTERNATE) {
5932 static gint compose_queue_sub(Compose *compose, gint *msgnum, FolderItem **item,
5933 gchar **msgpath, gboolean check_subject,
5934 gboolean remove_reedit_target)
5941 PrefsAccount *mailac = NULL, *newsac = NULL;
5942 gboolean err = FALSE;
5944 debug_print("queueing message...\n");
5945 cm_return_val_if_fail(compose->account != NULL, -1);
5947 if (compose_check_entries(compose, check_subject) == FALSE) {
5948 if (compose->batch) {
5949 gtk_widget_show_all(compose->window);
5954 if (!compose->to_list && !compose->newsgroup_list) {
5955 g_warning("can't get recipient list.");
5959 if (compose->to_list) {
5960 if (compose->account->protocol != A_NNTP)
5961 mailac = compose->account;
5962 else if (cur_account && cur_account->protocol != A_NNTP)
5963 mailac = cur_account;
5964 else if (!(mailac = compose_current_mail_account())) {
5965 alertpanel_error(_("No account for sending mails available!"));
5970 if (compose->newsgroup_list) {
5971 if (compose->account->protocol == A_NNTP)
5972 newsac = compose->account;
5974 alertpanel_error(_("Selected account isn't NNTP: Posting is impossible."));
5979 /* write queue header */
5980 tmp = g_strdup_printf("%s%cqueue.%p%08x", get_tmp_dir(),
5981 G_DIR_SEPARATOR, compose, (guint) rand());
5982 debug_print("queuing to %s\n", tmp);
5983 if ((fp = g_fopen(tmp, "w+b")) == NULL) {
5984 FILE_OP_ERROR(tmp, "fopen");
5989 if (change_file_mode_rw(fp, tmp) < 0) {
5990 FILE_OP_ERROR(tmp, "chmod");
5991 g_warning("can't change file mode");
5994 /* queueing variables */
5995 err |= (fprintf(fp, "AF:\n") < 0);
5996 err |= (fprintf(fp, "NF:0\n") < 0);
5997 err |= (fprintf(fp, "PS:10\n") < 0);
5998 err |= (fprintf(fp, "SRH:1\n") < 0);
5999 err |= (fprintf(fp, "SFN:\n") < 0);
6000 err |= (fprintf(fp, "DSR:\n") < 0);
6002 err |= (fprintf(fp, "MID:<%s>\n", compose->msgid) < 0);
6004 err |= (fprintf(fp, "MID:\n") < 0);
6005 err |= (fprintf(fp, "CFG:\n") < 0);
6006 err |= (fprintf(fp, "PT:0\n") < 0);
6007 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
6008 err |= (fprintf(fp, "RQ:\n") < 0);
6010 err |= (fprintf(fp, "SSV:%s\n", mailac->smtp_server) < 0);
6012 err |= (fprintf(fp, "SSV:\n") < 0);
6014 err |= (fprintf(fp, "NSV:%s\n", newsac->nntp_server) < 0);
6016 err |= (fprintf(fp, "NSV:\n") < 0);
6017 err |= (fprintf(fp, "SSH:\n") < 0);
6018 /* write recepient list */
6019 if (compose->to_list) {
6020 err |= (fprintf(fp, "R:<%s>", (gchar *)compose->to_list->data) < 0);
6021 for (cur = compose->to_list->next; cur != NULL;
6023 err |= (fprintf(fp, ",<%s>", (gchar *)cur->data) < 0);
6024 err |= (fprintf(fp, "\n") < 0);
6026 /* write newsgroup list */
6027 if (compose->newsgroup_list) {
6028 err |= (fprintf(fp, "NG:") < 0);
6029 err |= (fprintf(fp, "%s", (gchar *)compose->newsgroup_list->data) < 0);
6030 for (cur = compose->newsgroup_list->next; cur != NULL; cur = cur->next)
6031 err |= (fprintf(fp, ",%s", (gchar *)cur->data) < 0);
6032 err |= (fprintf(fp, "\n") < 0);
6034 /* Sylpheed account IDs */
6036 err |= (fprintf(fp, "MAID:%d\n", mailac->account_id) < 0);
6038 err |= (fprintf(fp, "NAID:%d\n", newsac->account_id) < 0);
6041 if (compose->privacy_system != NULL) {
6042 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
6043 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
6044 if (compose->use_encryption) {
6045 if (!compose_warn_encryption(compose)) {
6051 if (mailac && mailac->encrypt_to_self) {
6052 GSList *tmp_list = g_slist_copy(compose->to_list);
6053 tmp_list = g_slist_append(tmp_list, compose->account->address);
6054 compose->encdata = privacy_get_encrypt_data(compose->privacy_system, tmp_list);
6055 g_slist_free(tmp_list);
6057 compose->encdata = privacy_get_encrypt_data(compose->privacy_system, compose->to_list);
6059 if (compose->encdata != NULL) {
6060 if (strcmp(compose->encdata, "_DONT_ENCRYPT_")) {
6061 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
6062 err |= (fprintf(fp, "X-Claws-Encrypt-Data:%s\n",
6063 compose->encdata) < 0);
6064 } /* else we finally dont want to encrypt */
6066 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
6067 /* and if encdata was null, it means there's been a problem in
6070 g_warning("failed to write queue message");
6079 /* Save copy folder */
6080 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
6081 gchar *savefolderid;
6083 savefolderid = compose_get_save_to(compose);
6084 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
6085 g_free(savefolderid);
6087 /* Save copy folder */
6088 if (compose->return_receipt) {
6089 err |= (fprintf(fp, "RRCPT:1\n") < 0);
6091 /* Message-ID of message replying to */
6092 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
6093 gchar *folderid = NULL;
6095 if (compose->replyinfo->folder)
6096 folderid = folder_item_get_identifier(compose->replyinfo->folder);
6097 if (folderid == NULL)
6098 folderid = g_strdup("NULL");
6100 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
6103 /* Message-ID of message forwarding to */
6104 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
6105 gchar *folderid = NULL;
6107 if (compose->fwdinfo->folder)
6108 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
6109 if (folderid == NULL)
6110 folderid = g_strdup("NULL");
6112 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
6116 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
6117 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
6119 /* end of headers */
6120 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
6122 if (compose->redirect_filename != NULL) {
6123 if (compose_redirect_write_to_file(compose, fp) < 0) {
6131 if ((result = compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_SEND, TRUE)) < 0) {
6135 return result - 1; /* -2 for a generic error, -3 for signing error, -4 for encoding */
6139 g_warning("failed to write queue message");
6145 if (fclose(fp) == EOF) {
6146 FILE_OP_ERROR(tmp, "fclose");
6152 if (item && *item) {
6155 queue = account_get_special_folder(compose->account, F_QUEUE);
6158 g_warning("can't find queue folder");
6163 folder_item_scan(queue);
6164 if ((num = folder_item_add_msg(queue, tmp, NULL, FALSE)) < 0) {
6165 g_warning("can't queue the message");
6171 if (msgpath == NULL) {
6177 if (compose->mode == COMPOSE_REEDIT && remove_reedit_target) {
6178 compose_remove_reedit_target(compose, FALSE);
6181 if ((msgnum != NULL) && (item != NULL)) {
6189 static int compose_add_attachments(Compose *compose, MimeInfo *parent)
6192 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
6195 gchar *type, *subtype;
6196 GtkTreeModel *model;
6199 model = gtk_tree_view_get_model(tree_view);
6201 if (!gtk_tree_model_get_iter_first(model, &iter))
6204 gtk_tree_model_get(model, &iter,
6208 if (!is_file_exist(ainfo->file)) {
6209 gchar *msg = g_strdup_printf(_("Attachment %s doesn't exist anymore. Ignore?"), ainfo->file);
6210 AlertValue val = alertpanel_full(_("Warning"), msg, _("Cancel sending"), _("Ignore attachment"),
6211 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
6213 if (val == G_ALERTDEFAULT) {
6218 if (g_stat(ainfo->file, &statbuf) < 0)
6221 mimepart = procmime_mimeinfo_new();
6222 mimepart->content = MIMECONTENT_FILE;
6223 mimepart->data.filename = g_strdup(ainfo->file);
6224 mimepart->tmp = FALSE; /* or we destroy our attachment */
6225 mimepart->offset = 0;
6226 mimepart->length = statbuf.st_size;
6228 type = g_strdup(ainfo->content_type);
6230 if (!strchr(type, '/')) {
6232 type = g_strdup("application/octet-stream");
6235 subtype = strchr(type, '/') + 1;
6236 *(subtype - 1) = '\0';
6237 mimepart->type = procmime_get_media_type(type);
6238 mimepart->subtype = g_strdup(subtype);
6241 if (mimepart->type == MIMETYPE_MESSAGE &&
6242 !g_ascii_strcasecmp(mimepart->subtype, "rfc822")) {
6243 mimepart->disposition = DISPOSITIONTYPE_INLINE;
6244 } else if (mimepart->type == MIMETYPE_TEXT) {
6245 if (!ainfo->name && g_ascii_strcasecmp(mimepart->subtype, "plain")) {
6246 /* Text parts with no name come from multipart/alternative
6247 * forwards. Make sure the recipient won't look at the
6248 * original HTML part by mistake. */
6249 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6250 ainfo->name = g_strdup_printf(_("Original %s part"),
6254 g_hash_table_insert(mimepart->typeparameters,
6255 g_strdup("charset"), g_strdup(ainfo->charset));
6257 if (ainfo->name && mimepart->type != MIMETYPE_MESSAGE) {
6258 if (mimepart->type == MIMETYPE_APPLICATION &&
6259 !strcmp2(mimepart->subtype, "octet-stream"))
6260 g_hash_table_insert(mimepart->typeparameters,
6261 g_strdup("name"), g_strdup(ainfo->name));
6262 g_hash_table_insert(mimepart->dispositionparameters,
6263 g_strdup("filename"), g_strdup(ainfo->name));
6264 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6267 if (mimepart->type == MIMETYPE_MESSAGE
6268 || mimepart->type == MIMETYPE_MULTIPART)
6269 ainfo->encoding = ENC_BINARY;
6270 else if (compose->use_signing) {
6271 if (ainfo->encoding == ENC_7BIT)
6272 ainfo->encoding = ENC_QUOTED_PRINTABLE;
6273 else if (ainfo->encoding == ENC_8BIT)
6274 ainfo->encoding = ENC_BASE64;
6279 procmime_encode_content(mimepart, ainfo->encoding);
6281 g_node_append(parent->node, mimepart->node);
6282 } while (gtk_tree_model_iter_next(model, &iter));
6287 static gchar *compose_quote_list_of_addresses(gchar *str)
6289 GSList *list = NULL, *item = NULL;
6290 gchar *qname = NULL, *faddr = NULL, *result = NULL;
6292 list = address_list_append_with_comments(list, str);
6293 for (item = list; item != NULL; item = item->next) {
6294 gchar *spec = item->data;
6295 gchar *endofname = strstr(spec, " <");
6296 if (endofname != NULL) {
6299 QUOTE_IF_REQUIRED_NORMAL(qname, spec, return NULL);
6300 qqname = escape_internal_quotes(qname, '"');
6302 if (*qname != *spec || qqname != qname) { /* has been quoted, compute new */
6303 gchar *addr = g_strdup(endofname);
6304 gchar *name = (qqname != qname)? qqname: g_strdup(qname);
6305 faddr = g_strconcat(name, addr, NULL);
6308 debug_print("new auto-quoted address: '%s'", faddr);
6312 result = g_strdup((faddr != NULL)? faddr: spec);
6314 result = g_strconcat(result,
6316 (faddr != NULL)? faddr: spec,
6319 if (faddr != NULL) {
6324 slist_free_strings_full(list);
6329 #define IS_IN_CUSTOM_HEADER(header) \
6330 (compose->account->add_customhdr && \
6331 custom_header_find(compose->account->customhdr_list, header) != NULL)
6333 static void compose_add_headerfield_from_headerlist(Compose *compose,
6335 const gchar *fieldname,
6336 const gchar *seperator)
6338 gchar *str, *fieldname_w_colon;
6339 gboolean add_field = FALSE;
6341 ComposeHeaderEntry *headerentry;
6342 const gchar *headerentryname;
6343 const gchar *trans_fieldname;
6346 if (IS_IN_CUSTOM_HEADER(fieldname))
6349 debug_print("Adding %s-fields\n", fieldname);
6351 fieldstr = g_string_sized_new(64);
6353 fieldname_w_colon = g_strconcat(fieldname, ":", NULL);
6354 trans_fieldname = prefs_common_translated_header_name(fieldname_w_colon);
6356 for (list = compose->header_list; list; list = list->next) {
6357 headerentry = ((ComposeHeaderEntry *)list->data);
6358 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
6360 if (!g_utf8_collate(trans_fieldname, headerentryname)) {
6361 gchar * ustr = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
6363 str = compose_quote_list_of_addresses(ustr);
6365 if (str != NULL && str[0] != '\0') {
6367 g_string_append(fieldstr, seperator);
6368 g_string_append(fieldstr, str);
6377 buf = g_new0(gchar, fieldstr->len * 4 + 256);
6378 compose_convert_header
6379 (compose, buf, fieldstr->len * 4 + 256, fieldstr->str,
6380 strlen(fieldname) + 2, TRUE);
6381 g_string_append_printf(header, "%s: %s\n", fieldname, buf);
6385 g_free(fieldname_w_colon);
6386 g_string_free(fieldstr, TRUE);
6391 static gchar *compose_get_manual_headers_info(Compose *compose)
6393 GString *sh_header = g_string_new(" ");
6395 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6397 for (list = compose->header_list; list; list = list->next) {
6398 ComposeHeaderEntry *headerentry;
6401 gchar *headername_wcolon;
6402 const gchar *headername_trans;
6404 gboolean standard_header = FALSE;
6406 headerentry = ((ComposeHeaderEntry *)list->data);
6408 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6410 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6415 if (!strstr(tmp, ":")) {
6416 headername_wcolon = g_strconcat(tmp, ":", NULL);
6417 headername = g_strdup(tmp);
6419 headername_wcolon = g_strdup(tmp);
6420 headername = g_strdup(strtok(tmp, ":"));
6424 string = std_headers;
6425 while (*string != NULL) {
6426 headername_trans = prefs_common_translated_header_name(*string);
6427 if (!strcmp(headername_trans, headername_wcolon))
6428 standard_header = TRUE;
6431 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
6432 g_string_append_printf(sh_header, "%s ", headername);
6434 g_free(headername_wcolon);
6436 g_string_truncate(sh_header, strlen(sh_header->str) - 1); /* remove last space */
6437 return g_string_free(sh_header, FALSE);
6440 static gchar *compose_get_header(Compose *compose)
6442 gchar buf[BUFFSIZE];
6443 const gchar *entry_str;
6447 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6449 gchar *from_name = NULL, *from_address = NULL;
6452 cm_return_val_if_fail(compose->account != NULL, NULL);
6453 cm_return_val_if_fail(compose->account->address != NULL, NULL);
6455 header = g_string_sized_new(64);
6458 get_rfc822_date(buf, sizeof(buf));
6459 g_string_append_printf(header, "Date: %s\n", buf);
6463 if (compose->account->name && *compose->account->name) {
6465 QUOTE_IF_REQUIRED(buf, compose->account->name);
6466 tmp = g_strdup_printf("%s <%s>",
6467 buf, compose->account->address);
6469 tmp = g_strdup_printf("%s",
6470 compose->account->address);
6472 if (!strcmp(gtk_entry_get_text(GTK_ENTRY(compose->from_name)), tmp)
6473 || strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) == 0) {
6475 from_name = compose->account->name ? g_strdup(compose->account->name):NULL;
6476 from_address = g_strdup(compose->account->address);
6478 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
6479 /* extract name and address */
6480 if (strstr(spec, " <") && strstr(spec, ">")) {
6481 from_address = g_strdup(strrchr(spec, '<')+1);
6482 *(strrchr(from_address, '>')) = '\0';
6483 from_name = g_strdup(spec);
6484 *(strrchr(from_name, '<')) = '\0';
6487 from_address = g_strdup(spec);
6494 if (from_name && *from_name) {
6496 compose_convert_header
6497 (compose, buf, sizeof(buf), from_name,
6498 strlen("From: "), TRUE);
6499 QUOTE_IF_REQUIRED(name, buf);
6500 qname = escape_internal_quotes(name, '"');
6502 g_string_append_printf(header, "From: %s <%s>\n",
6503 qname, from_address);
6504 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To") &&
6505 compose->return_receipt) {
6506 compose_convert_header(compose, buf, sizeof(buf), from_name,
6507 strlen("Disposition-Notification-To: "),
6509 g_string_append_printf(header, "Disposition-Notification-To: %s <%s>\n", buf, from_address);
6514 g_string_append_printf(header, "From: %s\n", from_address);
6515 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To") &&
6516 compose->return_receipt)
6517 g_string_append_printf(header, "Disposition-Notification-To: %s\n", from_address);
6521 g_free(from_address);
6524 compose_add_headerfield_from_headerlist(compose, header, "To", ", ");
6527 compose_add_headerfield_from_headerlist(compose, header, "Newsgroups", ",");
6530 compose_add_headerfield_from_headerlist(compose, header, "Cc", ", ");
6534 * If this account is a NNTP account remove Bcc header from
6535 * message body since it otherwise will be publicly shown
6537 if (compose->account->protocol != A_NNTP)
6538 compose_add_headerfield_from_headerlist(compose, header, "Bcc", ", ");
6541 str = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
6543 if (*str != '\0' && !IS_IN_CUSTOM_HEADER("Subject")) {
6546 compose_convert_header(compose, buf, sizeof(buf), str,
6547 strlen("Subject: "), FALSE);
6548 g_string_append_printf(header, "Subject: %s\n", buf);
6554 if (compose->account->set_domain && compose->account->domain) {
6555 g_snprintf(buf, sizeof(buf), "%s", compose->account->domain);
6556 } else if (!strncmp(get_domain_name(), "localhost", strlen("localhost"))) {
6557 g_snprintf(buf, sizeof(buf), "%s",
6558 strchr(compose->account->address, '@') ?
6559 strchr(compose->account->address, '@')+1 :
6560 compose->account->address);
6562 g_snprintf(buf, sizeof(buf), "%s", "");
6565 if (compose->account->gen_msgid) {
6567 if (compose->account->msgid_with_addr) {
6568 addr = compose->account->address;
6570 generate_msgid(buf, sizeof(buf), addr);
6571 g_string_append_printf(header, "Message-ID: <%s>\n", buf);
6573 g_free(compose->msgid);
6574 compose->msgid = g_strdup(buf);
6576 compose->msgid = NULL;
6579 if (compose->remove_references == FALSE) {
6581 if (compose->inreplyto && compose->to_list)
6582 g_string_append_printf(header, "In-Reply-To: <%s>\n", compose->inreplyto);
6585 if (compose->references)
6586 g_string_append_printf(header, "References: %s\n", compose->references);
6590 compose_add_headerfield_from_headerlist(compose, header, "Followup-To", ",");
6593 compose_add_headerfield_from_headerlist(compose, header, "Reply-To", ", ");
6596 if (compose->account->organization &&
6597 strlen(compose->account->organization) &&
6598 !IS_IN_CUSTOM_HEADER("Organization")) {
6599 compose_convert_header(compose, buf, sizeof(buf),
6600 compose->account->organization,
6601 strlen("Organization: "), FALSE);
6602 g_string_append_printf(header, "Organization: %s\n", buf);
6605 /* Program version and system info */
6606 if (compose->account->gen_xmailer &&
6607 g_slist_length(compose->to_list) && !IS_IN_CUSTOM_HEADER("X-Mailer") &&
6608 !compose->newsgroup_list) {
6609 g_string_append_printf(header, "X-Mailer: %s (GTK+ %d.%d.%d; %s)\n",
6611 gtk_major_version, gtk_minor_version, gtk_micro_version,
6614 if (compose->account->gen_xmailer &&
6615 g_slist_length(compose->newsgroup_list) && !IS_IN_CUSTOM_HEADER("X-Newsreader")) {
6616 g_string_append_printf(header, "X-Newsreader: %s (GTK+ %d.%d.%d; %s)\n",
6618 gtk_major_version, gtk_minor_version, gtk_micro_version,
6622 /* custom headers */
6623 if (compose->account->add_customhdr) {
6626 for (cur = compose->account->customhdr_list; cur != NULL;
6628 CustomHeader *chdr = (CustomHeader *)cur->data;
6630 if (custom_header_is_allowed(chdr->name)
6631 && chdr->value != NULL
6632 && *(chdr->value) != '\0') {
6633 compose_convert_header
6634 (compose, buf, sizeof(buf),
6636 strlen(chdr->name) + 2, FALSE);
6637 g_string_append_printf(header, "%s: %s\n", chdr->name, buf);
6642 /* Automatic Faces and X-Faces */
6643 if (get_account_xface (buf, sizeof(buf), compose->account->account_name) == 0) {
6644 g_string_append_printf(header, "X-Face: %s\n", buf);
6646 else if (get_default_xface (buf, sizeof(buf)) == 0) {
6647 g_string_append_printf(header, "X-Face: %s\n", buf);
6649 if (get_account_face (buf, sizeof(buf), compose->account->account_name) == 0) {
6650 g_string_append_printf(header, "Face: %s\n", buf);
6652 else if (get_default_face (buf, sizeof(buf)) == 0) {
6653 g_string_append_printf(header, "Face: %s\n", buf);
6657 switch (compose->priority) {
6658 case PRIORITY_HIGHEST: g_string_append_printf(header, "Importance: high\n"
6659 "X-Priority: 1 (Highest)\n");
6661 case PRIORITY_HIGH: g_string_append_printf(header, "Importance: high\n"
6662 "X-Priority: 2 (High)\n");
6664 case PRIORITY_NORMAL: break;
6665 case PRIORITY_LOW: g_string_append_printf(header, "Importance: low\n"
6666 "X-Priority: 4 (Low)\n");
6668 case PRIORITY_LOWEST: g_string_append_printf(header, "Importance: low\n"
6669 "X-Priority: 5 (Lowest)\n");
6671 default: debug_print("compose: priority unknown : %d\n",
6675 /* get special headers */
6676 for (list = compose->header_list; list; list = list->next) {
6677 ComposeHeaderEntry *headerentry;
6680 gchar *headername_wcolon;
6681 const gchar *headername_trans;
6684 gboolean standard_header = FALSE;
6686 headerentry = ((ComposeHeaderEntry *)list->data);
6688 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6690 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6695 if (!strstr(tmp, ":")) {
6696 headername_wcolon = g_strconcat(tmp, ":", NULL);
6697 headername = g_strdup(tmp);
6699 headername_wcolon = g_strdup(tmp);
6700 headername = g_strdup(strtok(tmp, ":"));
6704 entry_str = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
6705 Xstrdup_a(headervalue, entry_str, return NULL);
6706 subst_char(headervalue, '\r', ' ');
6707 subst_char(headervalue, '\n', ' ');
6708 string = std_headers;
6709 while (*string != NULL) {
6710 headername_trans = prefs_common_translated_header_name(*string);
6711 if (!strcmp(headername_trans, headername_wcolon))
6712 standard_header = TRUE;
6715 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
6716 g_string_append_printf(header, "%s %s\n", headername_wcolon, headervalue);
6719 g_free(headername_wcolon);
6723 g_string_free(header, FALSE);
6728 #undef IS_IN_CUSTOM_HEADER
6730 static void compose_convert_header(Compose *compose, gchar *dest, gint len, gchar *src,
6731 gint header_len, gboolean addr_field)
6733 gchar *tmpstr = NULL;
6734 const gchar *out_codeset = NULL;
6736 cm_return_if_fail(src != NULL);
6737 cm_return_if_fail(dest != NULL);
6739 if (len < 1) return;
6741 tmpstr = g_strdup(src);
6743 subst_char(tmpstr, '\n', ' ');
6744 subst_char(tmpstr, '\r', ' ');
6747 if (!g_utf8_validate(tmpstr, -1, NULL)) {
6748 gchar *mybuf = g_malloc(strlen(tmpstr)*2 +1);
6749 conv_localetodisp(mybuf, strlen(tmpstr)*2 +1, tmpstr);
6754 codeconv_set_strict(TRUE);
6755 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6756 conv_get_charset_str(compose->out_encoding));
6757 codeconv_set_strict(FALSE);
6759 if (!dest || *dest == '\0') {
6760 gchar *test_conv_global_out = NULL;
6761 gchar *test_conv_reply = NULL;
6763 /* automatic mode. be automatic. */
6764 codeconv_set_strict(TRUE);
6766 out_codeset = conv_get_outgoing_charset_str();
6768 debug_print("trying to convert to %s\n", out_codeset);
6769 test_conv_global_out = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6772 if (!test_conv_global_out && compose->orig_charset
6773 && strcmp(compose->orig_charset, CS_US_ASCII)) {
6774 out_codeset = compose->orig_charset;
6775 debug_print("failure; trying to convert to %s\n", out_codeset);
6776 test_conv_reply = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6779 if (!test_conv_global_out && !test_conv_reply) {
6781 out_codeset = CS_INTERNAL;
6782 debug_print("finally using %s\n", out_codeset);
6784 g_free(test_conv_global_out);
6785 g_free(test_conv_reply);
6786 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6788 codeconv_set_strict(FALSE);
6793 static void compose_add_to_addressbook_cb(GtkMenuItem *menuitem, gpointer user_data)
6797 cm_return_if_fail(user_data != NULL);
6799 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(user_data)));
6800 g_strstrip(address);
6801 if (*address != '\0') {
6802 gchar *name = procheader_get_fromname(address);
6803 extract_address(address);
6804 #ifndef USE_ALT_ADDRBOOK
6805 addressbook_add_contact(name, address, NULL, NULL);
6807 debug_print("%s: %s\n", name, address);
6808 if (addressadd_selection(name, address, NULL, NULL)) {
6809 debug_print( "addressbook_add_contact - added\n" );
6816 static void compose_entry_popup_extend(GtkEntry *entry, GtkMenu *menu, gpointer user_data)
6818 GtkWidget *menuitem;
6821 cm_return_if_fail(menu != NULL);
6822 cm_return_if_fail(GTK_IS_MENU_SHELL(menu));
6824 menuitem = gtk_separator_menu_item_new();
6825 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6826 gtk_widget_show(menuitem);
6828 menuitem = gtk_menu_item_new_with_mnemonic(_("Add to address _book"));
6829 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6831 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
6832 g_strstrip(address);
6833 if (*address == '\0') {
6834 gtk_widget_set_sensitive(GTK_WIDGET(menuitem), FALSE);
6837 g_signal_connect(G_OBJECT(menuitem), "activate",
6838 G_CALLBACK(compose_add_to_addressbook_cb), entry);
6839 gtk_widget_show(menuitem);
6842 void compose_add_extra_header(gchar *header, GtkListStore *model)
6845 if (strcmp(header, "")) {
6846 COMBOBOX_ADD(model, header, COMPOSE_TO);
6850 void compose_add_extra_header_entries(GtkListStore *model)
6854 gchar buf[BUFFSIZE];
6857 if (extra_headers == NULL) {
6858 exhrc = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, "extraheaderrc", NULL);
6859 if ((exh = g_fopen(exhrc, "rb")) == NULL) {
6860 debug_print("extra headers file not found\n");
6861 goto extra_headers_done;
6863 while (fgets(buf, BUFFSIZE, exh) != NULL) {
6864 lastc = strlen(buf) - 1; /* remove trailing control chars */
6865 while (lastc >= 0 && buf[lastc] != ':')
6866 buf[lastc--] = '\0';
6867 if (lastc > 0 && buf[0] != '#' && buf[lastc] == ':') {
6868 buf[lastc] = '\0'; /* remove trailing : for comparison */
6869 if (custom_header_is_allowed(buf)) {
6871 extra_headers = g_slist_prepend(extra_headers, g_strdup(buf));
6874 g_message("disallowed extra header line: %s\n", buf);
6878 g_message("invalid extra header line: %s\n", buf);
6884 extra_headers = g_slist_prepend(extra_headers, g_strdup("")); /* end of list */
6885 extra_headers = g_slist_reverse(extra_headers);
6887 g_slist_foreach(extra_headers, (GFunc)compose_add_extra_header, (gpointer)model);
6890 static void compose_create_header_entry(Compose *compose)
6892 gchar *headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6899 const gchar *header = NULL;
6900 ComposeHeaderEntry *headerentry;
6901 gboolean standard_header = FALSE;
6902 GtkListStore *model;
6905 headerentry = g_new0(ComposeHeaderEntry, 1);
6907 /* Combo box model */
6908 model = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_BOOLEAN);
6909 #if !GTK_CHECK_VERSION(2, 24, 0)
6910 combo = gtk_combo_box_entry_new_with_model(GTK_TREE_MODEL(model), 0);
6912 COMBOBOX_ADD(model, prefs_common_translated_header_name("To:"),
6914 COMBOBOX_ADD(model, prefs_common_translated_header_name("Cc:"),
6916 COMBOBOX_ADD(model, prefs_common_translated_header_name("Bcc:"),
6918 COMBOBOX_ADD(model, prefs_common_translated_header_name("Newsgroups:"),
6919 COMPOSE_NEWSGROUPS);
6920 COMBOBOX_ADD(model, prefs_common_translated_header_name("Reply-To:"),
6922 COMBOBOX_ADD(model, prefs_common_translated_header_name("Followup-To:"),
6923 COMPOSE_FOLLOWUPTO);
6924 compose_add_extra_header_entries(model);
6927 #if GTK_CHECK_VERSION(2, 24, 0)
6928 combo = gtk_combo_box_new_with_model_and_entry(GTK_TREE_MODEL(model));
6929 GtkCellRenderer *cell = gtk_cell_renderer_text_new();
6930 gtk_cell_renderer_set_alignment(cell, 0.0, 0.5);
6931 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), cell, TRUE);
6932 gtk_combo_box_set_entry_text_column(GTK_COMBO_BOX(combo), 0);
6934 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
6935 g_signal_connect(G_OBJECT(gtk_bin_get_child(GTK_BIN(combo))), "grab_focus",
6936 G_CALLBACK(compose_grab_focus_cb), compose);
6937 gtk_widget_show(combo);
6939 /* Putting only the combobox child into focus chain of its parent causes
6940 * the parent to be skipped when changing focus via Tab or Shift+Tab.
6941 * This eliminates need to pres Tab twice in order to really get from the
6942 * combobox to next widget. */
6944 l = g_list_prepend(l, gtk_bin_get_child(GTK_BIN(combo)));
6945 gtk_container_set_focus_chain(GTK_CONTAINER(combo), l);
6948 gtk_table_attach(GTK_TABLE(compose->header_table), combo, 0, 1,
6949 compose->header_nextrow, compose->header_nextrow+1,
6950 GTK_SHRINK, GTK_FILL, 0, 0);
6951 if (compose->header_last && (compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN)) {
6952 const gchar *last_header_entry = gtk_entry_get_text(
6953 GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
6955 while (*string != NULL) {
6956 if (!strcmp(prefs_common_translated_header_name(*string), last_header_entry))
6957 standard_header = TRUE;
6960 if (standard_header)
6961 header = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
6963 if (!compose->header_last || !standard_header) {
6964 switch(compose->account->protocol) {
6966 header = prefs_common_translated_header_name("Newsgroups:");
6969 header = prefs_common_translated_header_name("To:");
6974 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((combo)))), header);
6976 gtk_editable_set_editable(
6977 GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((combo)))),
6978 prefs_common.type_any_header);
6980 g_signal_connect_after(G_OBJECT(gtk_bin_get_child(GTK_BIN((combo)))), "grab_focus",
6981 G_CALLBACK(compose_grab_focus_cb), compose);
6983 /* Entry field with cleanup button */
6984 button = gtk_button_new();
6985 gtk_button_set_image(GTK_BUTTON(button),
6986 gtk_image_new_from_stock(GTK_STOCK_CLEAR, GTK_ICON_SIZE_MENU));
6987 gtk_widget_show(button);
6988 CLAWS_SET_TIP(button,
6989 _("Delete entry contents"));
6990 entry = gtk_entry_new();
6991 gtk_widget_show(entry);
6992 CLAWS_SET_TIP(entry,
6993 _("Use <tab> to autocomplete from addressbook"));
6994 hbox = gtk_hbox_new (FALSE, 0);
6995 gtk_widget_show(hbox);
6996 gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, TRUE, 0);
6997 gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
6998 gtk_table_attach(GTK_TABLE(compose->header_table), hbox, 1, 2,
6999 compose->header_nextrow, compose->header_nextrow+1,
7000 GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
7002 g_signal_connect(G_OBJECT(entry), "key-press-event",
7003 G_CALLBACK(compose_headerentry_key_press_event_cb),
7005 g_signal_connect(G_OBJECT(entry), "changed",
7006 G_CALLBACK(compose_headerentry_changed_cb),
7008 g_signal_connect_after(G_OBJECT(entry), "grab_focus",
7009 G_CALLBACK(compose_grab_focus_cb), compose);
7011 g_signal_connect(G_OBJECT(button), "clicked",
7012 G_CALLBACK(compose_headerentry_button_clicked_cb),
7016 gtk_drag_dest_set(entry, GTK_DEST_DEFAULT_ALL, compose_mime_types,
7017 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7018 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7019 g_signal_connect(G_OBJECT(entry), "drag_data_received",
7020 G_CALLBACK(compose_header_drag_received_cb),
7022 g_signal_connect(G_OBJECT(entry), "drag-drop",
7023 G_CALLBACK(compose_drag_drop),
7025 g_signal_connect(G_OBJECT(entry), "populate-popup",
7026 G_CALLBACK(compose_entry_popup_extend),
7029 address_completion_register_entry(GTK_ENTRY(entry), TRUE);
7031 headerentry->compose = compose;
7032 headerentry->combo = combo;
7033 headerentry->entry = entry;
7034 headerentry->button = button;
7035 headerentry->hbox = hbox;
7036 headerentry->headernum = compose->header_nextrow;
7037 headerentry->type = PREF_NONE;
7039 compose->header_nextrow++;
7040 compose->header_last = headerentry;
7041 compose->header_list =
7042 g_slist_append(compose->header_list,
7046 static void compose_add_header_entry(Compose *compose, const gchar *header,
7047 gchar *text, ComposePrefType pref_type)
7049 ComposeHeaderEntry *last_header = compose->header_last;
7050 gchar *tmp = g_strdup(text), *email;
7051 gboolean replyto_hdr;
7053 replyto_hdr = (!strcasecmp(header,
7054 prefs_common_translated_header_name("Reply-To:")) ||
7056 prefs_common_translated_header_name("Followup-To:")) ||
7058 prefs_common_translated_header_name("In-Reply-To:")));
7060 extract_address(tmp);
7061 email = g_utf8_strdown(tmp, -1);
7063 if (replyto_hdr == FALSE &&
7064 g_hash_table_lookup(compose->email_hashtable, email) != NULL)
7066 debug_print("Ignoring duplicate address - %s %s, pref_type: %d\n",
7067 header, text, (gint) pref_type);
7073 if (!strcasecmp(header, prefs_common_translated_header_name("In-Reply-To:")))
7074 gtk_entry_set_text(GTK_ENTRY(
7075 gtk_bin_get_child(GTK_BIN(last_header->combo))), header);
7077 combobox_select_by_text(GTK_COMBO_BOX(last_header->combo), header);
7078 gtk_entry_set_text(GTK_ENTRY(last_header->entry), text);
7079 last_header->type = pref_type;
7081 if (replyto_hdr == FALSE)
7082 g_hash_table_insert(compose->email_hashtable, email,
7083 GUINT_TO_POINTER(1));
7090 static void compose_destroy_headerentry(Compose *compose,
7091 ComposeHeaderEntry *headerentry)
7093 gchar *text = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
7096 extract_address(text);
7097 email = g_utf8_strdown(text, -1);
7098 g_hash_table_remove(compose->email_hashtable, email);
7102 gtk_widget_destroy(headerentry->combo);
7103 gtk_widget_destroy(headerentry->entry);
7104 gtk_widget_destroy(headerentry->button);
7105 gtk_widget_destroy(headerentry->hbox);
7106 g_free(headerentry);
7109 static void compose_remove_header_entries(Compose *compose)
7112 for (list = compose->header_list; list; list = list->next)
7113 compose_destroy_headerentry(compose, (ComposeHeaderEntry *)list->data);
7115 compose->header_last = NULL;
7116 g_slist_free(compose->header_list);
7117 compose->header_list = NULL;
7118 compose->header_nextrow = 1;
7119 compose_create_header_entry(compose);
7122 static GtkWidget *compose_create_header(Compose *compose)
7124 GtkWidget *from_optmenu_hbox;
7125 GtkWidget *header_table_main;
7126 GtkWidget *header_scrolledwin;
7127 GtkWidget *header_table;
7129 /* parent with account selection and from header */
7130 header_table_main = gtk_table_new(2, 2, FALSE);
7131 gtk_widget_show(header_table_main);
7132 gtk_container_set_border_width(GTK_CONTAINER(header_table_main), BORDER_WIDTH);
7134 from_optmenu_hbox = compose_account_option_menu_create(compose);
7135 gtk_table_attach(GTK_TABLE(header_table_main), from_optmenu_hbox,
7136 0, 2, 0, 1, GTK_EXPAND | GTK_FILL, GTK_SHRINK, 0, 0);
7138 /* child with header labels and entries */
7139 header_scrolledwin = gtk_scrolled_window_new(NULL, NULL);
7140 gtk_widget_show(header_scrolledwin);
7141 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
7143 header_table = gtk_table_new(2, 2, FALSE);
7144 gtk_widget_show(header_table);
7145 gtk_container_set_border_width(GTK_CONTAINER(header_table), 0);
7146 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin), header_table);
7147 gtk_container_set_focus_vadjustment(GTK_CONTAINER(header_table),
7148 gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(header_scrolledwin)));
7149 gtk_viewport_set_shadow_type(GTK_VIEWPORT(gtk_bin_get_child(GTK_BIN(header_scrolledwin))), GTK_SHADOW_NONE);
7151 gtk_table_attach(GTK_TABLE(header_table_main), header_scrolledwin,
7152 0, 2, 1, 2, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 2);
7154 compose->header_table = header_table;
7155 compose->header_list = NULL;
7156 compose->header_nextrow = 0;
7158 compose_create_header_entry(compose);
7160 compose->table = NULL;
7162 return header_table_main;
7165 static gboolean popup_attach_button_pressed(GtkWidget *widget, gpointer data)
7167 Compose *compose = (Compose *)data;
7168 GdkEventButton event;
7171 event.time = gtk_get_current_event_time();
7173 return attach_button_pressed(compose->attach_clist, &event, compose);
7176 static GtkWidget *compose_create_attach(Compose *compose)
7178 GtkWidget *attach_scrwin;
7179 GtkWidget *attach_clist;
7181 GtkListStore *store;
7182 GtkCellRenderer *renderer;
7183 GtkTreeViewColumn *column;
7184 GtkTreeSelection *selection;
7186 /* attachment list */
7187 attach_scrwin = gtk_scrolled_window_new(NULL, NULL);
7188 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(attach_scrwin),
7189 GTK_POLICY_AUTOMATIC,
7190 GTK_POLICY_AUTOMATIC);
7191 gtk_widget_set_size_request(attach_scrwin, -1, 80);
7193 store = gtk_list_store_new(N_ATTACH_COLS,
7199 G_TYPE_AUTO_POINTER,
7201 attach_clist = GTK_WIDGET(gtk_tree_view_new_with_model
7202 (GTK_TREE_MODEL(store)));
7203 gtk_container_add(GTK_CONTAINER(attach_scrwin), attach_clist);
7204 g_object_unref(store);
7206 renderer = gtk_cell_renderer_text_new();
7207 column = gtk_tree_view_column_new_with_attributes
7208 (_("Mime type"), renderer, "text",
7209 COL_MIMETYPE, NULL);
7210 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7212 renderer = gtk_cell_renderer_text_new();
7213 column = gtk_tree_view_column_new_with_attributes
7214 (_("Size"), renderer, "text",
7216 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7218 renderer = gtk_cell_renderer_text_new();
7219 column = gtk_tree_view_column_new_with_attributes
7220 (_("Name"), renderer, "text",
7222 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7224 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(attach_clist),
7225 prefs_common.use_stripes_everywhere);
7226 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(attach_clist));
7227 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
7229 g_signal_connect(G_OBJECT(attach_clist), "row_activated",
7230 G_CALLBACK(attach_selected), compose);
7231 g_signal_connect(G_OBJECT(attach_clist), "button_press_event",
7232 G_CALLBACK(attach_button_pressed), compose);
7233 g_signal_connect(G_OBJECT(attach_clist), "popup-menu",
7234 G_CALLBACK(popup_attach_button_pressed), compose);
7235 g_signal_connect(G_OBJECT(attach_clist), "key_press_event",
7236 G_CALLBACK(attach_key_pressed), compose);
7239 gtk_drag_dest_set(attach_clist,
7240 GTK_DEST_DEFAULT_ALL, compose_mime_types,
7241 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7242 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7243 g_signal_connect(G_OBJECT(attach_clist), "drag_data_received",
7244 G_CALLBACK(compose_attach_drag_received_cb),
7246 g_signal_connect(G_OBJECT(attach_clist), "drag-drop",
7247 G_CALLBACK(compose_drag_drop),
7250 compose->attach_scrwin = attach_scrwin;
7251 compose->attach_clist = attach_clist;
7253 return attach_scrwin;
7256 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose);
7257 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose);
7259 static GtkWidget *compose_create_others(Compose *compose)
7262 GtkWidget *savemsg_checkbtn;
7263 GtkWidget *savemsg_combo;
7264 GtkWidget *savemsg_select;
7267 gchar *folderidentifier;
7269 /* Table for settings */
7270 table = gtk_table_new(3, 1, FALSE);
7271 gtk_container_set_border_width(GTK_CONTAINER(table), BORDER_WIDTH);
7272 gtk_widget_show(table);
7273 gtk_table_set_row_spacings(GTK_TABLE(table), VSPACING_NARROW);
7276 /* Save Message to folder */
7277 savemsg_checkbtn = gtk_check_button_new_with_label(_("Save Message to "));
7278 gtk_widget_show(savemsg_checkbtn);
7279 gtk_table_attach(GTK_TABLE(table), savemsg_checkbtn, 0, 1, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7280 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7281 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), prefs_common.savemsg);
7283 g_signal_connect(G_OBJECT(savemsg_checkbtn), "toggled",
7284 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
7286 #if !GTK_CHECK_VERSION(2, 24, 0)
7287 savemsg_combo = gtk_combo_box_entry_new_text();
7289 savemsg_combo = gtk_combo_box_text_new_with_entry();
7291 compose->savemsg_checkbtn = savemsg_checkbtn;
7292 compose->savemsg_combo = savemsg_combo;
7293 gtk_widget_show(savemsg_combo);
7295 if (prefs_common.compose_save_to_history)
7296 #if !GTK_CHECK_VERSION(2, 24, 0)
7297 combobox_set_popdown_strings(GTK_COMBO_BOX(savemsg_combo),
7298 prefs_common.compose_save_to_history);
7300 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(savemsg_combo),
7301 prefs_common.compose_save_to_history);
7303 gtk_table_attach(GTK_TABLE(table), savemsg_combo, 1, 2, rowcount, rowcount + 1, GTK_FILL|GTK_EXPAND, GTK_SHRINK, 0, 0);
7304 gtk_widget_set_sensitive(GTK_WIDGET(savemsg_combo), prefs_common.savemsg);
7305 g_signal_connect_after(G_OBJECT(savemsg_combo), "grab_focus",
7306 G_CALLBACK(compose_grab_focus_cb), compose);
7307 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7308 folderidentifier = folder_item_get_identifier(account_get_special_folder
7309 (compose->account, F_OUTBOX));
7310 compose_set_save_to(compose, folderidentifier);
7311 g_free(folderidentifier);
7314 savemsg_select = gtkut_get_browse_file_btn(_("_Browse"));
7315 gtk_widget_show(savemsg_select);
7316 gtk_table_attach(GTK_TABLE(table), savemsg_select, 2, 3, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7317 g_signal_connect(G_OBJECT(savemsg_select), "clicked",
7318 G_CALLBACK(compose_savemsg_select_cb),
7324 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose)
7326 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo),
7327 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn)));
7330 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose)
7335 dest = foldersel_folder_sel(NULL, FOLDER_SEL_COPY, NULL, FALSE);
7338 path = folder_item_get_identifier(dest);
7340 compose_set_save_to(compose, path);
7344 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry, gboolean wrap,
7345 GdkAtom clip, GtkTextIter *insert_place);
7348 static gboolean text_clicked(GtkWidget *text, GdkEventButton *event,
7352 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
7354 if (event->button == 3) {
7356 GtkTextIter sel_start, sel_end;
7357 gboolean stuff_selected;
7359 /* move the cursor to allow GtkAspell to check the word
7360 * under the mouse */
7361 if (event->x && event->y) {
7362 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7363 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7365 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7368 GtkTextMark *mark = gtk_text_buffer_get_insert(buffer);
7369 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7372 stuff_selected = gtk_text_buffer_get_selection_bounds(
7374 &sel_start, &sel_end);
7376 gtk_text_buffer_place_cursor (buffer, &iter);
7377 /* reselect stuff */
7379 && gtk_text_iter_in_range(&iter, &sel_start, &sel_end)) {
7380 gtk_text_buffer_select_range(buffer,
7381 &sel_start, &sel_end);
7383 return FALSE; /* pass the event so that the right-click goes through */
7386 if (event->button == 2) {
7391 /* get the middle-click position to paste at the correct place */
7392 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7393 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7395 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7398 entry_paste_clipboard(compose, text,
7399 prefs_common.linewrap_pastes,
7400 GDK_SELECTION_PRIMARY, &iter);
7408 static void compose_spell_menu_changed(void *data)
7410 Compose *compose = (Compose *)data;
7412 GtkWidget *menuitem;
7413 GtkWidget *parent_item;
7414 GtkMenu *menu = GTK_MENU(gtk_menu_new());
7417 if (compose->gtkaspell == NULL)
7420 parent_item = gtk_ui_manager_get_widget(compose->ui_manager,
7421 "/Menu/Spelling/Options");
7423 /* setting the submenu removes /Spelling/Options from the factory
7424 * so we need to save it */
7426 if (parent_item == NULL) {
7427 parent_item = compose->aspell_options_menu;
7428 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), NULL);
7430 compose->aspell_options_menu = parent_item;
7432 spell_menu = gtkaspell_make_config_menu(compose->gtkaspell);
7434 spell_menu = g_slist_reverse(spell_menu);
7435 for (items = spell_menu;
7436 items; items = items->next) {
7437 menuitem = GTK_WIDGET(GTK_MENU_ITEM(items->data));
7438 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), GTK_WIDGET(menuitem));
7439 gtk_widget_show(GTK_WIDGET(menuitem));
7441 g_slist_free(spell_menu);
7443 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), GTK_WIDGET(menu));
7444 gtk_widget_show(parent_item);
7447 static void compose_dict_changed(void *data)
7449 Compose *compose = (Compose *) data;
7451 if(!compose->gtkaspell)
7453 if(compose->gtkaspell->recheck_when_changing_dict == FALSE)
7456 gtkaspell_highlight_all(compose->gtkaspell);
7457 claws_spell_entry_recheck_all(CLAWS_SPELL_ENTRY(compose->subject_entry));
7461 static gboolean compose_popup_menu(GtkWidget *widget, gpointer data)
7463 Compose *compose = (Compose *)data;
7464 GdkEventButton event;
7467 event.time = gtk_get_current_event_time();
7471 return text_clicked(compose->text, &event, compose);
7474 static gboolean compose_force_window_origin = TRUE;
7475 static Compose *compose_create(PrefsAccount *account,
7484 GtkWidget *handlebox;
7486 GtkWidget *notebook;
7488 GtkWidget *attach_hbox;
7489 GtkWidget *attach_lab1;
7490 GtkWidget *attach_lab2;
7495 GtkWidget *subject_hbox;
7496 GtkWidget *subject_frame;
7497 GtkWidget *subject_entry;
7501 GtkWidget *edit_vbox;
7502 GtkWidget *ruler_hbox;
7504 GtkWidget *scrolledwin;
7506 GtkTextBuffer *buffer;
7507 GtkClipboard *clipboard;
7509 UndoMain *undostruct;
7511 GtkWidget *popupmenu;
7512 GtkWidget *tmpl_menu;
7513 GtkActionGroup *action_group = NULL;
7516 GtkAspell * gtkaspell = NULL;
7519 static GdkGeometry geometry;
7521 cm_return_val_if_fail(account != NULL, NULL);
7523 debug_print("Creating compose window...\n");
7524 compose = g_new0(Compose, 1);
7526 compose->batch = batch;
7527 compose->account = account;
7528 compose->folder = folder;
7530 compose->mutex = cm_mutex_new();
7531 compose->set_cursor_pos = -1;
7533 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose");
7535 gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
7536 gtk_widget_set_size_request(window, prefs_common.compose_width,
7537 prefs_common.compose_height);
7539 if (!geometry.max_width) {
7540 geometry.max_width = gdk_screen_width();
7541 geometry.max_height = gdk_screen_height();
7544 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7545 &geometry, GDK_HINT_MAX_SIZE);
7546 if (!geometry.min_width) {
7547 geometry.min_width = 600;
7548 geometry.min_height = 440;
7550 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7551 &geometry, GDK_HINT_MIN_SIZE);
7553 #ifndef GENERIC_UMPC
7554 if (compose_force_window_origin)
7555 gtk_window_move(GTK_WINDOW(window), prefs_common.compose_x,
7556 prefs_common.compose_y);
7558 g_signal_connect(G_OBJECT(window), "delete_event",
7559 G_CALLBACK(compose_delete_cb), compose);
7560 MANAGE_WINDOW_SIGNALS_CONNECT(window);
7561 gtk_widget_realize(window);
7563 gtkut_widget_set_composer_icon(window);
7565 vbox = gtk_vbox_new(FALSE, 0);
7566 gtk_container_add(GTK_CONTAINER(window), vbox);
7568 compose->ui_manager = gtk_ui_manager_new();
7569 action_group = cm_menu_create_action_group_full(compose->ui_manager,"Menu", compose_entries,
7570 G_N_ELEMENTS(compose_entries), (gpointer)compose);
7571 gtk_action_group_add_toggle_actions(action_group, compose_toggle_entries,
7572 G_N_ELEMENTS(compose_toggle_entries), (gpointer)compose);
7573 gtk_action_group_add_radio_actions(action_group, compose_radio_rm_entries,
7574 G_N_ELEMENTS(compose_radio_rm_entries), COMPOSE_REPLY, G_CALLBACK(compose_reply_change_mode_cb), (gpointer)compose);
7575 gtk_action_group_add_radio_actions(action_group, compose_radio_prio_entries,
7576 G_N_ELEMENTS(compose_radio_prio_entries), PRIORITY_NORMAL, G_CALLBACK(compose_set_priority_cb), (gpointer)compose);
7577 gtk_action_group_add_radio_actions(action_group, compose_radio_enc_entries,
7578 G_N_ELEMENTS(compose_radio_enc_entries), C_AUTO, G_CALLBACK(compose_set_encoding_cb), (gpointer)compose);
7580 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Menu", NULL, GTK_UI_MANAGER_MENUBAR)
7582 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Message", "Message", GTK_UI_MANAGER_MENU)
7583 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Edit", "Edit", GTK_UI_MANAGER_MENU)
7585 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Spelling", "Spelling", GTK_UI_MANAGER_MENU)
7587 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Options", "Options", GTK_UI_MANAGER_MENU)
7588 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Tools", "Tools", GTK_UI_MANAGER_MENU)
7589 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Help", "Help", GTK_UI_MANAGER_MENU)
7592 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Send", "Message/Send", GTK_UI_MANAGER_MENUITEM)
7593 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "SendLater", "Message/SendLater", GTK_UI_MANAGER_MENUITEM)
7594 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator1", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7595 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "AttachFile", "Message/AttachFile", GTK_UI_MANAGER_MENUITEM)
7596 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertFile", "Message/InsertFile", GTK_UI_MANAGER_MENUITEM)
7597 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertSig", "Message/InsertSig", GTK_UI_MANAGER_MENUITEM)
7598 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "ReplaceSig", "Message/ReplaceSig", GTK_UI_MANAGER_MENUITEM)
7599 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator2", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7600 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Save", "Message/Save", GTK_UI_MANAGER_MENUITEM)
7601 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator3", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7602 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Print", "Message/Print", GTK_UI_MANAGER_MENUITEM)
7603 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator4", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7604 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Close", "Message/Close", GTK_UI_MANAGER_MENUITEM)
7607 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Undo", "Edit/Undo", GTK_UI_MANAGER_MENUITEM)
7608 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Redo", "Edit/Redo", GTK_UI_MANAGER_MENUITEM)
7609 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator1", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7611 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Cut", "Edit/Cut", GTK_UI_MANAGER_MENUITEM)
7612 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Copy", "Edit/Copy", GTK_UI_MANAGER_MENUITEM)
7613 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Paste", "Edit/Paste", GTK_UI_MANAGER_MENUITEM)
7615 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SpecialPaste", "Edit/SpecialPaste", GTK_UI_MANAGER_MENU)
7616 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "AsQuotation", "Edit/SpecialPaste/AsQuotation", GTK_UI_MANAGER_MENUITEM)
7617 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Wrapped", "Edit/SpecialPaste/Wrapped", GTK_UI_MANAGER_MENUITEM)
7618 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Unwrapped", "Edit/SpecialPaste/Unwrapped", GTK_UI_MANAGER_MENUITEM)
7620 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SelectAll", "Edit/SelectAll", GTK_UI_MANAGER_MENUITEM)
7622 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Advanced", "Edit/Advanced", GTK_UI_MANAGER_MENU)
7623 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackChar", "Edit/Advanced/BackChar", GTK_UI_MANAGER_MENUITEM)
7624 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwChar", "Edit/Advanced/ForwChar", GTK_UI_MANAGER_MENUITEM)
7625 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackWord", "Edit/Advanced/BackWord", GTK_UI_MANAGER_MENUITEM)
7626 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwWord", "Edit/Advanced/ForwWord", GTK_UI_MANAGER_MENUITEM)
7627 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BegLine", "Edit/Advanced/BegLine", GTK_UI_MANAGER_MENUITEM)
7628 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "EndLine", "Edit/Advanced/EndLine", GTK_UI_MANAGER_MENUITEM)
7629 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "PrevLine", "Edit/Advanced/PrevLine", GTK_UI_MANAGER_MENUITEM)
7630 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "NextLine", "Edit/Advanced/NextLine", GTK_UI_MANAGER_MENUITEM)
7631 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackChar", "Edit/Advanced/DelBackChar", GTK_UI_MANAGER_MENUITEM)
7632 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwChar", "Edit/Advanced/DelForwChar", GTK_UI_MANAGER_MENUITEM)
7633 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackWord", "Edit/Advanced/DelBackWord", GTK_UI_MANAGER_MENUITEM)
7634 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwWord", "Edit/Advanced/DelForwWord", GTK_UI_MANAGER_MENUITEM)
7635 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelLine", "Edit/Advanced/DelLine", GTK_UI_MANAGER_MENUITEM)
7636 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelEndLine", "Edit/Advanced/DelEndLine", GTK_UI_MANAGER_MENUITEM)
7638 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator2", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7640 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Find", "Edit/Find", GTK_UI_MANAGER_MENUITEM)
7641 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapPara", "Edit/WrapPara", GTK_UI_MANAGER_MENUITEM)
7642 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapAllLines", "Edit/WrapAllLines", GTK_UI_MANAGER_MENUITEM)
7643 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoWrap", "Edit/AutoWrap", GTK_UI_MANAGER_MENUITEM)
7644 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoIndent", "Edit/AutoIndent", GTK_UI_MANAGER_MENUITEM)
7646 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator3", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7648 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "ExtEditor", "Edit/ExtEditor", GTK_UI_MANAGER_MENUITEM)
7652 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckAllSel", "Spelling/CheckAllSel", GTK_UI_MANAGER_MENUITEM)
7653 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "HighlightAll", "Spelling/HighlightAll", GTK_UI_MANAGER_MENUITEM)
7654 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckBackwards", "Spelling/CheckBackwards", GTK_UI_MANAGER_MENUITEM)
7655 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "ForwardNext", "Spelling/ForwardNext", GTK_UI_MANAGER_MENUITEM)
7656 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Separator1", "Spelling/---", GTK_UI_MANAGER_SEPARATOR)
7657 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Options", "Spelling/Options", GTK_UI_MANAGER_MENU)
7661 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "ReplyMode", "Options/ReplyMode", GTK_UI_MANAGER_MENU)
7662 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Normal", "Options/ReplyMode/Normal", GTK_UI_MANAGER_MENUITEM)
7663 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "All", "Options/ReplyMode/All", GTK_UI_MANAGER_MENUITEM)
7664 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Sender", "Options/ReplyMode/Sender", GTK_UI_MANAGER_MENUITEM)
7665 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "List", "Options/ReplyMode/List", GTK_UI_MANAGER_MENUITEM)
7667 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator1", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7668 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "PrivacySystem", "Options/PrivacySystem", GTK_UI_MANAGER_MENU)
7669 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/PrivacySystem", "PlaceHolder", "Options/PrivacySystem/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7670 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Sign", "Options/Sign", GTK_UI_MANAGER_MENUITEM)
7671 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encrypt", "Options/Encrypt", GTK_UI_MANAGER_MENUITEM)
7674 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator2", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7675 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Priority", "Options/Priority", GTK_UI_MANAGER_MENU)
7676 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Highest", "Options/Priority/Highest", GTK_UI_MANAGER_MENUITEM)
7677 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "High", "Options/Priority/High", GTK_UI_MANAGER_MENUITEM)
7678 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Normal", "Options/Priority/Normal", GTK_UI_MANAGER_MENUITEM)
7679 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Low", "Options/Priority/Low", GTK_UI_MANAGER_MENUITEM)
7680 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Lowest", "Options/Priority/Lowest", GTK_UI_MANAGER_MENUITEM)
7682 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator3", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7683 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RequestRetRcpt", "Options/RequestRetRcpt", GTK_UI_MANAGER_MENUITEM)
7684 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator4", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7685 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RemoveReferences", "Options/RemoveReferences", GTK_UI_MANAGER_MENUITEM)
7686 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator5", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7688 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encoding", "Options/Encoding", GTK_UI_MANAGER_MENU)
7690 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_AUTO, "Options/Encoding/"CS_AUTO, GTK_UI_MANAGER_MENUITEM)
7691 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator1", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7692 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_US_ASCII, "Options/Encoding/"CS_US_ASCII, GTK_UI_MANAGER_MENUITEM)
7693 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_UTF_8, "Options/Encoding/"CS_UTF_8, GTK_UI_MANAGER_MENUITEM)
7694 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator2", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7696 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Western", "Options/Encoding/Western", GTK_UI_MANAGER_MENU)
7697 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)
7698 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)
7699 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Western", CS_WINDOWS_1252, "Options/Encoding/Western/"CS_WINDOWS_1252, GTK_UI_MANAGER_MENUITEM)
7701 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_2, "Options/Encoding/"CS_ISO_8859_2, GTK_UI_MANAGER_MENUITEM)
7703 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Baltic", "Options/Encoding/Baltic", GTK_UI_MANAGER_MENU)
7704 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)
7705 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)
7707 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_7, "Options/Encoding/"CS_ISO_8859_7, GTK_UI_MANAGER_MENUITEM)
7709 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Hebrew", "Options/Encoding/Hebrew", GTK_UI_MANAGER_MENU)
7710 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)
7711 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Hebrew", CS_WINDOWS_1255, "Options/Encoding/Hebrew/"CS_WINDOWS_1255, GTK_UI_MANAGER_MENUITEM)
7713 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Arabic", "Options/Encoding/Arabic", GTK_UI_MANAGER_MENU)
7714 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)
7715 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Arabic", CS_WINDOWS_1256, "Options/Encoding/Arabic/"CS_WINDOWS_1256, GTK_UI_MANAGER_MENUITEM)
7717 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_9, "Options/Encoding/"CS_ISO_8859_9, GTK_UI_MANAGER_MENUITEM)
7719 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Cyrillic", "Options/Encoding/Cyrillic", GTK_UI_MANAGER_MENU)
7720 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)
7721 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_R, "Options/Encoding/Cyrillic/"CS_KOI8_R, GTK_UI_MANAGER_MENUITEM)
7722 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_MACCYR, "Options/Encoding/Cyrillic/"CS_MACCYR, GTK_UI_MANAGER_MENUITEM)
7723 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_U, "Options/Encoding/Cyrillic/"CS_KOI8_U, GTK_UI_MANAGER_MENUITEM)
7724 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_WINDOWS_1251, "Options/Encoding/Cyrillic/"CS_WINDOWS_1251, GTK_UI_MANAGER_MENUITEM)
7726 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Japanese", "Options/Encoding/Japanese", GTK_UI_MANAGER_MENU)
7727 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)
7728 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)
7729 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_EUC_JP, "Options/Encoding/Japanese/"CS_EUC_JP, GTK_UI_MANAGER_MENUITEM)
7730 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_SHIFT_JIS, "Options/Encoding/Japanese/"CS_SHIFT_JIS, GTK_UI_MANAGER_MENUITEM)
7732 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Chinese", "Options/Encoding/Chinese", GTK_UI_MANAGER_MENU)
7733 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB18030, "Options/Encoding/Chinese/"CS_GB18030, GTK_UI_MANAGER_MENUITEM)
7734 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB2312, "Options/Encoding/Chinese/"CS_GB2312, GTK_UI_MANAGER_MENUITEM)
7735 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GBK, "Options/Encoding/Chinese/"CS_GBK, GTK_UI_MANAGER_MENUITEM)
7736 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_BIG5, "Options/Encoding/Chinese/"CS_BIG5, GTK_UI_MANAGER_MENUITEM)
7737 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_EUC_TW, "Options/Encoding/Chinese/"CS_EUC_TW, GTK_UI_MANAGER_MENUITEM)
7739 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Korean", "Options/Encoding/Korean", GTK_UI_MANAGER_MENU)
7740 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Korean", CS_EUC_KR, "Options/Encoding/Korean/"CS_EUC_KR, GTK_UI_MANAGER_MENUITEM)
7741 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)
7743 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Thai", "Options/Encoding/Thai", GTK_UI_MANAGER_MENU)
7744 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_TIS_620, "Options/Encoding/Thai/"CS_TIS_620, GTK_UI_MANAGER_MENUITEM)
7745 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_WINDOWS_874, "Options/Encoding/Thai/"CS_WINDOWS_874, GTK_UI_MANAGER_MENUITEM)
7749 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "ShowRuler", "Tools/ShowRuler", GTK_UI_MANAGER_MENUITEM)
7750 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "AddressBook", "Tools/AddressBook", GTK_UI_MANAGER_MENUITEM)
7751 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Template", "Tools/Template", GTK_UI_MANAGER_MENU)
7752 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Template", "PlaceHolder", "Tools/Template/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7753 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Actions", "Tools/Actions", GTK_UI_MANAGER_MENU)
7754 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Actions", "PlaceHolder", "Tools/Actions/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7757 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Help", "About", "Help/About", GTK_UI_MANAGER_MENUITEM)
7759 menubar = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu");
7760 gtk_widget_show_all(menubar);
7762 gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(compose->ui_manager));
7763 gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, TRUE, 0);
7765 if (prefs_common.toolbar_detachable) {
7766 handlebox = gtk_handle_box_new();
7768 handlebox = gtk_hbox_new(FALSE, 0);
7770 gtk_box_pack_start(GTK_BOX(vbox), handlebox, FALSE, FALSE, 0);
7772 gtk_widget_realize(handlebox);
7773 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, handlebox,
7776 vbox2 = gtk_vbox_new(FALSE, 2);
7777 gtk_box_pack_start(GTK_BOX(vbox), vbox2, TRUE, TRUE, 0);
7778 gtk_container_set_border_width(GTK_CONTAINER(vbox2), 0);
7781 notebook = gtk_notebook_new();
7782 gtk_widget_show(notebook);
7784 /* header labels and entries */
7785 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7786 compose_create_header(compose),
7787 gtk_label_new_with_mnemonic(_("Hea_der")));
7788 /* attachment list */
7789 attach_hbox = gtk_hbox_new(FALSE, 0);
7790 gtk_widget_show(attach_hbox);
7792 attach_lab1 = gtk_label_new_with_mnemonic(_("_Attachments"));
7793 gtk_widget_show(attach_lab1);
7794 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab1, TRUE, TRUE, 0);
7796 attach_lab2 = gtk_label_new("");
7797 gtk_widget_show(attach_lab2);
7798 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab2, FALSE, FALSE, 0);
7800 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7801 compose_create_attach(compose),
7804 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7805 compose_create_others(compose),
7806 gtk_label_new_with_mnemonic(_("Othe_rs")));
7809 subject_hbox = gtk_hbox_new(FALSE, 0);
7810 gtk_widget_show(subject_hbox);
7812 subject_frame = gtk_frame_new(NULL);
7813 gtk_frame_set_shadow_type(GTK_FRAME(subject_frame), GTK_SHADOW_NONE);
7814 gtk_box_pack_start(GTK_BOX(subject_hbox), subject_frame, TRUE, TRUE, 0);
7815 gtk_widget_show(subject_frame);
7817 subject = gtk_hbox_new(FALSE, HSPACING_NARROW);
7818 gtk_container_set_border_width(GTK_CONTAINER(subject), 0);
7819 gtk_widget_show(subject);
7821 label = gtk_label_new_with_mnemonic(_("S_ubject:"));
7822 gtk_box_pack_start(GTK_BOX(subject), label, FALSE, FALSE, 0);
7823 gtk_widget_show(label);
7826 subject_entry = claws_spell_entry_new();
7828 subject_entry = gtk_entry_new();
7830 gtk_box_pack_start(GTK_BOX(subject), subject_entry, TRUE, TRUE, 0);
7831 g_signal_connect_after(G_OBJECT(subject_entry), "grab_focus",
7832 G_CALLBACK(compose_grab_focus_cb), compose);
7833 gtk_label_set_mnemonic_widget(GTK_LABEL(label), subject_entry);
7834 gtk_widget_show(subject_entry);
7835 compose->subject_entry = subject_entry;
7836 gtk_container_add(GTK_CONTAINER(subject_frame), subject);
7838 edit_vbox = gtk_vbox_new(FALSE, 0);
7840 gtk_box_pack_start(GTK_BOX(edit_vbox), subject_hbox, FALSE, FALSE, 0);
7843 ruler_hbox = gtk_hbox_new(FALSE, 0);
7844 gtk_box_pack_start(GTK_BOX(edit_vbox), ruler_hbox, FALSE, FALSE, 0);
7846 ruler = gtk_shruler_new(GTK_ORIENTATION_HORIZONTAL);
7847 gtk_shruler_set_range(GTK_SHRULER(ruler), 0.0, 100.0, 1.0);
7848 gtk_box_pack_start(GTK_BOX(ruler_hbox), ruler, TRUE, TRUE,
7852 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
7853 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
7854 GTK_POLICY_AUTOMATIC,
7855 GTK_POLICY_AUTOMATIC);
7856 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
7858 gtk_box_pack_start(GTK_BOX(edit_vbox), scrolledwin, TRUE, TRUE, 0);
7860 text = gtk_text_view_new();
7861 if (prefs_common.show_compose_margin) {
7862 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text), 6);
7863 gtk_text_view_set_right_margin(GTK_TEXT_VIEW(text), 6);
7865 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
7866 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
7867 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), TRUE);
7868 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
7869 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
7871 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
7872 g_signal_connect_after(G_OBJECT(text), "size_allocate",
7873 G_CALLBACK(compose_edit_size_alloc),
7875 g_signal_connect(G_OBJECT(buffer), "changed",
7876 G_CALLBACK(compose_changed_cb), compose);
7877 g_signal_connect(G_OBJECT(text), "grab_focus",
7878 G_CALLBACK(compose_grab_focus_cb), compose);
7879 g_signal_connect(G_OBJECT(buffer), "insert_text",
7880 G_CALLBACK(text_inserted), compose);
7881 g_signal_connect(G_OBJECT(text), "button_press_event",
7882 G_CALLBACK(text_clicked), compose);
7883 g_signal_connect(G_OBJECT(text), "popup-menu",
7884 G_CALLBACK(compose_popup_menu), compose);
7885 g_signal_connect(G_OBJECT(subject_entry), "changed",
7886 G_CALLBACK(compose_changed_cb), compose);
7887 g_signal_connect(G_OBJECT(subject_entry), "activate",
7888 G_CALLBACK(compose_subject_entry_activated), compose);
7891 gtk_drag_dest_set(text, GTK_DEST_DEFAULT_ALL, compose_mime_types,
7892 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7893 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7894 g_signal_connect(G_OBJECT(text), "drag_data_received",
7895 G_CALLBACK(compose_insert_drag_received_cb),
7897 g_signal_connect(G_OBJECT(text), "drag-drop",
7898 G_CALLBACK(compose_drag_drop),
7900 g_signal_connect(G_OBJECT(text), "key-press-event",
7901 G_CALLBACK(completion_set_focus_to_subject),
7903 gtk_widget_show_all(vbox);
7905 /* pane between attach clist and text */
7906 paned = gtk_vpaned_new();
7907 gtk_container_add(GTK_CONTAINER(vbox2), paned);
7908 gtk_paned_pack1(GTK_PANED(paned), notebook, FALSE, FALSE);
7909 gtk_paned_pack2(GTK_PANED(paned), edit_vbox, TRUE, FALSE);
7910 gtk_paned_set_position(GTK_PANED(paned), prefs_common.compose_notebook_height);
7911 g_signal_connect(G_OBJECT(notebook), "size_allocate",
7912 G_CALLBACK(compose_notebook_size_alloc), paned);
7914 gtk_widget_show_all(paned);
7917 if (prefs_common.textfont) {
7918 PangoFontDescription *font_desc;
7920 font_desc = pango_font_description_from_string
7921 (prefs_common.textfont);
7923 gtk_widget_modify_font(text, font_desc);
7924 pango_font_description_free(font_desc);
7928 gtk_action_group_add_actions(action_group, compose_popup_entries,
7929 G_N_ELEMENTS(compose_popup_entries), (gpointer)compose);
7930 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Popup", NULL, GTK_UI_MANAGER_MENUBAR)
7931 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup", "Compose", "Compose", GTK_UI_MANAGER_MENU)
7932 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Add", "Compose/Add", GTK_UI_MANAGER_MENUITEM)
7933 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Remove", "Compose/Remove", GTK_UI_MANAGER_MENUITEM)
7934 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Separator1", "Compose/---", GTK_UI_MANAGER_SEPARATOR)
7935 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Properties", "Compose/Properties", GTK_UI_MANAGER_MENUITEM)
7937 popupmenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(gtk_ui_manager_get_widget(compose->ui_manager, "/Popup/Compose")));
7939 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
7940 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
7941 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
7943 tmpl_menu = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu/Tools/Template");
7945 undostruct = undo_init(text);
7946 undo_set_change_state_func(undostruct, &compose_undo_state_changed,
7949 address_completion_start(window);
7951 compose->window = window;
7952 compose->vbox = vbox;
7953 compose->menubar = menubar;
7954 compose->handlebox = handlebox;
7956 compose->vbox2 = vbox2;
7958 compose->paned = paned;
7960 compose->attach_label = attach_lab2;
7962 compose->notebook = notebook;
7963 compose->edit_vbox = edit_vbox;
7964 compose->ruler_hbox = ruler_hbox;
7965 compose->ruler = ruler;
7966 compose->scrolledwin = scrolledwin;
7967 compose->text = text;
7969 compose->focused_editable = NULL;
7971 compose->popupmenu = popupmenu;
7973 compose->tmpl_menu = tmpl_menu;
7975 compose->mode = mode;
7976 compose->rmode = mode;
7978 compose->targetinfo = NULL;
7979 compose->replyinfo = NULL;
7980 compose->fwdinfo = NULL;
7982 compose->email_hashtable = g_hash_table_new_full(g_str_hash,
7983 g_str_equal, (GDestroyNotify) g_free, NULL);
7985 compose->replyto = NULL;
7987 compose->bcc = NULL;
7988 compose->followup_to = NULL;
7990 compose->ml_post = NULL;
7992 compose->inreplyto = NULL;
7993 compose->references = NULL;
7994 compose->msgid = NULL;
7995 compose->boundary = NULL;
7997 compose->autowrap = prefs_common.autowrap;
7998 compose->autoindent = prefs_common.auto_indent;
7999 compose->use_signing = FALSE;
8000 compose->use_encryption = FALSE;
8001 compose->privacy_system = NULL;
8002 compose->encdata = NULL;
8004 compose->modified = FALSE;
8006 compose->return_receipt = FALSE;
8008 compose->to_list = NULL;
8009 compose->newsgroup_list = NULL;
8011 compose->undostruct = undostruct;
8013 compose->sig_str = NULL;
8015 compose->exteditor_file = NULL;
8016 compose->exteditor_pid = -1;
8017 compose->exteditor_tag = -1;
8018 compose->exteditor_socket = NULL;
8019 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN; /* inhibit auto-drafting while loading */
8021 compose->folder_update_callback_id =
8022 hooks_register_hook(FOLDER_UPDATE_HOOKLIST,
8023 compose_update_folder_hook,
8024 (gpointer) compose);
8027 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
8028 if (mode != COMPOSE_REDIRECT) {
8029 if (prefs_common.enable_aspell && prefs_common.dictionary &&
8030 strcmp(prefs_common.dictionary, "")) {
8031 gtkaspell = gtkaspell_new(prefs_common.dictionary,
8032 prefs_common.alt_dictionary,
8033 conv_get_locale_charset_str(),
8034 prefs_common.misspelled_col,
8035 prefs_common.check_while_typing,
8036 prefs_common.recheck_when_changing_dict,
8037 prefs_common.use_alternate,
8038 prefs_common.use_both_dicts,
8039 GTK_TEXT_VIEW(text),
8040 GTK_WINDOW(compose->window),
8041 compose_dict_changed,
8042 compose_spell_menu_changed,
8045 alertpanel_error(_("Spell checker could not "
8047 gtkaspell_checkers_strerror());
8048 gtkaspell_checkers_reset_error();
8050 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", TRUE);
8054 compose->gtkaspell = gtkaspell;
8055 compose_spell_menu_changed(compose);
8056 claws_spell_entry_set_gtkaspell(CLAWS_SPELL_ENTRY(subject_entry), gtkaspell);
8059 compose_select_account(compose, account, TRUE);
8061 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", prefs_common.autowrap);
8062 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", prefs_common.auto_indent);
8064 if (account->set_autocc && account->auto_cc && mode != COMPOSE_REEDIT)
8065 compose_entry_append(compose, account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8067 if (account->set_autobcc && account->auto_bcc && mode != COMPOSE_REEDIT)
8068 compose_entry_append(compose, account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8070 if (account->set_autoreplyto && account->auto_replyto && mode != COMPOSE_REEDIT)
8071 compose_entry_append(compose, account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8073 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", compose->mode == COMPOSE_REPLY);
8074 if (account->protocol != A_NNTP)
8075 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
8076 prefs_common_translated_header_name("To:"));
8078 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
8079 prefs_common_translated_header_name("Newsgroups:"));
8081 #ifndef USE_ALT_ADDRBOOK
8082 addressbook_set_target_compose(compose);
8084 if (mode != COMPOSE_REDIRECT)
8085 compose_set_template_menu(compose);
8087 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Template", FALSE);
8090 compose_list = g_list_append(compose_list, compose);
8092 if (!prefs_common.show_ruler)
8093 gtk_widget_hide(ruler_hbox);
8095 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Tools/ShowRuler", prefs_common.show_ruler);
8098 compose->priority = PRIORITY_NORMAL;
8099 compose_update_priority_menu_item(compose);
8101 compose_set_out_encoding(compose);
8104 compose_update_actions_menu(compose);
8106 /* Privacy Systems menu */
8107 compose_update_privacy_systems_menu(compose);
8109 activate_privacy_system(compose, account, TRUE);
8110 toolbar_set_style(compose->toolbar->toolbar, compose->handlebox, prefs_common.toolbar_style);
8112 gtk_widget_realize(window);
8114 gtk_widget_show(window);
8120 static GtkWidget *compose_account_option_menu_create(Compose *compose)
8125 GtkWidget *optmenubox;
8126 GtkWidget *fromlabel;
8129 GtkWidget *from_name = NULL;
8131 gint num = 0, def_menu = 0;
8133 accounts = account_get_list();
8134 cm_return_val_if_fail(accounts != NULL, NULL);
8136 optmenubox = gtk_event_box_new();
8137 optmenu = gtkut_sc_combobox_create(optmenubox, FALSE);
8138 menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
8140 hbox = gtk_hbox_new(FALSE, 4);
8141 from_name = gtk_entry_new();
8143 g_signal_connect_after(G_OBJECT(from_name), "grab_focus",
8144 G_CALLBACK(compose_grab_focus_cb), compose);
8145 g_signal_connect_after(G_OBJECT(from_name), "activate",
8146 G_CALLBACK(from_name_activate_cb), optmenu);
8148 for (; accounts != NULL; accounts = accounts->next, num++) {
8149 PrefsAccount *ac = (PrefsAccount *)accounts->data;
8150 gchar *name, *from = NULL;
8152 if (ac == compose->account) def_menu = num;
8154 name = g_markup_printf_escaped("<i>%s</i>",
8157 if (ac == compose->account) {
8158 if (ac->name && *ac->name) {
8160 QUOTE_IF_REQUIRED_NORMAL(buf, ac->name, return NULL);
8161 from = g_strdup_printf("%s <%s>",
8163 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8165 from = g_strdup_printf("%s",
8167 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8170 COMBOBOX_ADD(menu, name, ac->account_id);
8175 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), def_menu);
8177 g_signal_connect(G_OBJECT(optmenu), "changed",
8178 G_CALLBACK(account_activated),
8180 g_signal_connect(G_OBJECT(from_name), "populate-popup",
8181 G_CALLBACK(compose_entry_popup_extend),
8184 fromlabel = gtk_label_new_with_mnemonic(_("_From:"));
8185 gtk_label_set_mnemonic_widget(GTK_LABEL(fromlabel), from_name);
8187 gtk_box_pack_start(GTK_BOX(hbox), fromlabel, FALSE, FALSE, 4);
8188 gtk_box_pack_start(GTK_BOX(hbox), optmenubox, FALSE, FALSE, 0);
8189 gtk_box_pack_start(GTK_BOX(hbox), from_name, TRUE, TRUE, 0);
8191 /* Putting only the GtkEntry into focus chain of parent hbox causes
8192 * the account selector combobox next to it to be unreachable when
8193 * navigating widgets in GtkTable with up/down arrow keys.
8194 * Note: gtk_widget_set_can_focus() was not enough. */
8196 l = g_list_prepend(l, from_name);
8197 gtk_container_set_focus_chain(GTK_CONTAINER(hbox), l);
8200 CLAWS_SET_TIP(optmenubox,
8201 _("Account to use for this email"));
8202 CLAWS_SET_TIP(from_name,
8203 _("Sender address to be used"));
8205 compose->account_combo = optmenu;
8206 compose->from_name = from_name;
8211 static void compose_set_priority_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8213 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8214 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8215 Compose *compose = (Compose *) data;
8217 compose->priority = value;
8221 static void compose_reply_change_mode(Compose *compose,
8224 gboolean was_modified = compose->modified;
8226 gboolean all = FALSE, ml = FALSE, sender = FALSE, followup = FALSE;
8228 cm_return_if_fail(compose->replyinfo != NULL);
8230 if (action == COMPOSE_REPLY && prefs_common.default_reply_list)
8232 if (action == COMPOSE_REPLY && compose->rmode == COMPOSE_FOLLOWUP_AND_REPLY_TO)
8234 if (action == COMPOSE_REPLY_TO_ALL)
8236 if (action == COMPOSE_REPLY_TO_SENDER)
8238 if (action == COMPOSE_REPLY_TO_LIST)
8241 compose_remove_header_entries(compose);
8242 compose_reply_set_entry(compose, compose->replyinfo, all, ml, sender, followup);
8243 if (compose->account->set_autocc && compose->account->auto_cc)
8244 compose_entry_append(compose, compose->account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8246 if (compose->account->set_autobcc && compose->account->auto_bcc)
8247 compose_entry_append(compose, compose->account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8249 if (compose->account->set_autoreplyto && compose->account->auto_replyto)
8250 compose_entry_append(compose, compose->account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8251 compose_show_first_last_header(compose, TRUE);
8252 compose->modified = was_modified;
8253 compose_set_title(compose);
8256 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8258 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8259 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8260 Compose *compose = (Compose *) data;
8263 compose_reply_change_mode(compose, value);
8266 static void compose_update_priority_menu_item(Compose * compose)
8268 GtkWidget *menuitem = NULL;
8269 switch (compose->priority) {
8270 case PRIORITY_HIGHEST:
8271 menuitem = gtk_ui_manager_get_widget
8272 (compose->ui_manager, "/Menu/Options/Priority/Highest");
8275 menuitem = gtk_ui_manager_get_widget
8276 (compose->ui_manager, "/Menu/Options/Priority/High");
8278 case PRIORITY_NORMAL:
8279 menuitem = gtk_ui_manager_get_widget
8280 (compose->ui_manager, "/Menu/Options/Priority/Normal");
8283 menuitem = gtk_ui_manager_get_widget
8284 (compose->ui_manager, "/Menu/Options/Priority/Low");
8286 case PRIORITY_LOWEST:
8287 menuitem = gtk_ui_manager_get_widget
8288 (compose->ui_manager, "/Menu/Options/Priority/Lowest");
8291 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8294 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data)
8296 Compose *compose = (Compose *) data;
8298 gboolean can_sign = FALSE, can_encrypt = FALSE;
8300 cm_return_if_fail(GTK_IS_CHECK_MENU_ITEM(widget));
8302 if (!gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget)))
8305 systemid = g_object_get_data(G_OBJECT(widget), "privacy_system");
8306 g_free(compose->privacy_system);
8307 compose->privacy_system = NULL;
8308 g_free(compose->encdata);
8309 compose->encdata = NULL;
8310 if (systemid != NULL) {
8311 compose->privacy_system = g_strdup(systemid);
8313 can_sign = privacy_system_can_sign(systemid);
8314 can_encrypt = privacy_system_can_encrypt(systemid);
8317 debug_print("activated privacy system: %s\n", systemid != NULL ? systemid : "None");
8319 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8320 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8323 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn)
8325 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8326 GtkWidget *menuitem = NULL;
8327 GList *children, *amenu;
8328 gboolean can_sign = FALSE, can_encrypt = FALSE;
8329 gboolean found = FALSE;
8331 if (compose->privacy_system != NULL) {
8333 menuitem = gtk_menu_item_get_submenu(GTK_MENU_ITEM(
8334 gtk_ui_manager_get_widget(compose->ui_manager, branch_path)));
8335 cm_return_if_fail(menuitem != NULL);
8337 children = gtk_container_get_children(GTK_CONTAINER(GTK_MENU_SHELL(menuitem)));
8340 while (amenu != NULL) {
8341 systemid = g_object_get_data(G_OBJECT(amenu->data), "privacy_system");
8342 if (systemid != NULL) {
8343 if (strcmp(systemid, compose->privacy_system) == 0 &&
8344 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8345 menuitem = GTK_WIDGET(amenu->data);
8347 can_sign = privacy_system_can_sign(systemid);
8348 can_encrypt = privacy_system_can_encrypt(systemid);
8352 } else if (strlen(compose->privacy_system) == 0 &&
8353 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8354 menuitem = GTK_WIDGET(amenu->data);
8357 can_encrypt = FALSE;
8362 amenu = amenu->next;
8364 g_list_free(children);
8365 if (menuitem != NULL)
8366 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8368 if (warn && !found && strlen(compose->privacy_system)) {
8369 alertpanel_warning(_("The privacy system '%s' cannot be loaded. You "
8370 "will not be able to sign or encrypt this message."),
8371 compose->privacy_system);
8375 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8376 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8379 static void compose_set_out_encoding(Compose *compose)
8381 CharSet out_encoding;
8382 const gchar *branch = NULL;
8383 out_encoding = conv_get_charset_from_str(prefs_common.outgoing_charset);
8385 switch(out_encoding) {
8386 case C_AUTO: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8387 case C_US_ASCII: branch = "Menu/Options/Encoding/" CS_US_ASCII; break;
8388 case C_UTF_8: branch = "Menu/Options/Encoding/" CS_UTF_8; break;
8389 case C_ISO_8859_2: branch = "Menu/Options/Encoding/" CS_ISO_8859_2; break;
8390 case C_ISO_8859_7: branch = "Menu/Options/Encoding/" CS_ISO_8859_7; break;
8391 case C_ISO_8859_9: branch = "Menu/Options/Encoding/" CS_ISO_8859_9; break;
8392 case C_ISO_8859_1: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_1; break;
8393 case C_ISO_8859_15: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_15; break;
8394 case C_WINDOWS_1252: branch = "Menu/Options/Encoding/Western/" CS_WINDOWS_1252; break;
8395 case C_ISO_8859_13: branch = "Menu/Options/Encoding/Baltic/" CS_ISO_8859_13; break;
8396 case C_ISO_8859_4: branch = "Menu/Options/Encoding/Baltic" CS_ISO_8859_4; break;
8397 case C_ISO_8859_8: branch = "Menu/Options/Encoding/Hebrew/" CS_ISO_8859_8; break;
8398 case C_WINDOWS_1255: branch = "Menu/Options/Encoding/Hebrew/" CS_WINDOWS_1255; break;
8399 case C_ISO_8859_6: branch = "Menu/Options/Encoding/Arabic/" CS_ISO_8859_6; break;
8400 case C_WINDOWS_1256: branch = "Menu/Options/Encoding/Arabic/" CS_WINDOWS_1256; break;
8401 case C_ISO_8859_5: branch = "Menu/Options/Encoding/Cyrillic/" CS_ISO_8859_5; break;
8402 case C_KOI8_R: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_R; break;
8403 case C_MACCYR: branch = "Menu/Options/Encoding/Cyrillic/" CS_MACCYR; break;
8404 case C_KOI8_U: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_U; break;
8405 case C_WINDOWS_1251: branch = "Menu/Options/Encoding/Cyrillic/" CS_WINDOWS_1251; break;
8406 case C_ISO_2022_JP: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP; break;
8407 case C_ISO_2022_JP_2: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP_2; break;
8408 case C_EUC_JP: branch = "Menu/Options/Encoding/Japanese/" CS_EUC_JP; break;
8409 case C_SHIFT_JIS: branch = "Menu/Options/Encoding/Japanese/" CS_SHIFT_JIS; break;
8410 case C_GB18030: branch = "Menu/Options/Encoding/Chinese/" CS_GB18030; break;
8411 case C_GB2312: branch = "Menu/Options/Encoding/Chinese/" CS_GB2312; break;
8412 case C_GBK: branch = "Menu/Options/Encoding/Chinese/" CS_GBK; break;
8413 case C_BIG5: branch = "Menu/Options/Encoding/Chinese/" CS_BIG5; break;
8414 case C_EUC_TW: branch = "Menu/Options/Encoding/Chinese/" CS_EUC_TW; break;
8415 case C_EUC_KR: branch = "Menu/Options/Encoding/Korean/" CS_EUC_KR; break;
8416 case C_ISO_2022_KR: branch = "Menu/Options/Encoding/Korean/" CS_ISO_2022_KR; break;
8417 case C_TIS_620: branch = "Menu/Options/Encoding/Thai/" CS_TIS_620; break;
8418 case C_WINDOWS_874: branch = "Menu/Options/Encoding/Thai/" CS_WINDOWS_874; break;
8419 default: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8421 cm_toggle_menu_set_active_full(compose->ui_manager, (gchar *)branch, TRUE);
8424 static void compose_set_template_menu(Compose *compose)
8426 GSList *tmpl_list, *cur;
8430 tmpl_list = template_get_config();
8432 menu = gtk_menu_new();
8434 gtk_menu_set_accel_group (GTK_MENU (menu),
8435 gtk_ui_manager_get_accel_group(compose->ui_manager));
8436 for (cur = tmpl_list; cur != NULL; cur = cur->next) {
8437 Template *tmpl = (Template *)cur->data;
8438 gchar *accel_path = NULL;
8439 item = gtk_menu_item_new_with_label(tmpl->name);
8440 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
8441 g_signal_connect(G_OBJECT(item), "activate",
8442 G_CALLBACK(compose_template_activate_cb),
8444 g_object_set_data(G_OBJECT(item), "template", tmpl);
8445 gtk_widget_show(item);
8446 accel_path = g_strconcat("<ComposeTemplates>" , "/", tmpl->name, NULL);
8447 gtk_menu_item_set_accel_path(GTK_MENU_ITEM(item), accel_path);
8451 gtk_widget_show(menu);
8452 gtk_menu_item_set_submenu(GTK_MENU_ITEM(compose->tmpl_menu), menu);
8455 void compose_update_actions_menu(Compose *compose)
8457 action_update_compose_menu(compose->ui_manager, "/Menu/Tools/Actions", compose);
8460 static void compose_update_privacy_systems_menu(Compose *compose)
8462 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8463 GSList *systems, *cur;
8465 GtkWidget *system_none;
8467 GtkWidget *privacy_menuitem = gtk_ui_manager_get_widget(compose->ui_manager, branch_path);
8468 GtkWidget *privacy_menu = gtk_menu_new();
8470 system_none = gtk_radio_menu_item_new_with_mnemonic(NULL, _("_None"));
8471 g_object_set_data_full(G_OBJECT(system_none), "privacy_system", NULL, NULL);
8473 g_signal_connect(G_OBJECT(system_none), "activate",
8474 G_CALLBACK(compose_set_privacy_system_cb), compose);
8476 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), system_none);
8477 gtk_widget_show(system_none);
8479 systems = privacy_get_system_ids();
8480 for (cur = systems; cur != NULL; cur = g_slist_next(cur)) {
8481 gchar *systemid = cur->data;
8483 group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(system_none));
8484 widget = gtk_radio_menu_item_new_with_label(group,
8485 privacy_system_get_name(systemid));
8486 g_object_set_data_full(G_OBJECT(widget), "privacy_system",
8487 g_strdup(systemid), g_free);
8488 g_signal_connect(G_OBJECT(widget), "activate",
8489 G_CALLBACK(compose_set_privacy_system_cb), compose);
8491 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), widget);
8492 gtk_widget_show(widget);
8495 g_slist_free(systems);
8496 gtk_menu_item_set_submenu(GTK_MENU_ITEM(privacy_menuitem), privacy_menu);
8497 gtk_widget_show_all(privacy_menu);
8498 gtk_widget_show_all(privacy_menuitem);
8501 void compose_reflect_prefs_all(void)
8506 for (cur = compose_list; cur != NULL; cur = cur->next) {
8507 compose = (Compose *)cur->data;
8508 compose_set_template_menu(compose);
8512 void compose_reflect_prefs_pixmap_theme(void)
8517 for (cur = compose_list; cur != NULL; cur = cur->next) {
8518 compose = (Compose *)cur->data;
8519 toolbar_update(TOOLBAR_COMPOSE, compose);
8523 static const gchar *compose_quote_char_from_context(Compose *compose)
8525 const gchar *qmark = NULL;
8527 cm_return_val_if_fail(compose != NULL, NULL);
8529 switch (compose->mode) {
8530 /* use forward-specific quote char */
8531 case COMPOSE_FORWARD:
8532 case COMPOSE_FORWARD_AS_ATTACH:
8533 case COMPOSE_FORWARD_INLINE:
8534 if (compose->folder && compose->folder->prefs &&
8535 compose->folder->prefs->forward_with_format)
8536 qmark = compose->folder->prefs->forward_quotemark;
8537 else if (compose->account->forward_with_format)
8538 qmark = compose->account->forward_quotemark;
8540 qmark = prefs_common.fw_quotemark;
8543 /* use reply-specific quote char in all other modes */
8545 if (compose->folder && compose->folder->prefs &&
8546 compose->folder->prefs->reply_with_format)
8547 qmark = compose->folder->prefs->reply_quotemark;
8548 else if (compose->account->reply_with_format)
8549 qmark = compose->account->reply_quotemark;
8551 qmark = prefs_common.quotemark;
8555 if (qmark == NULL || *qmark == '\0')
8561 static void compose_template_apply(Compose *compose, Template *tmpl,
8565 GtkTextBuffer *buffer;
8569 gchar *parsed_str = NULL;
8570 gint cursor_pos = 0;
8571 const gchar *err_msg = _("The body of the template has an error at line %d.");
8574 /* process the body */
8576 text = GTK_TEXT_VIEW(compose->text);
8577 buffer = gtk_text_view_get_buffer(text);
8580 qmark = compose_quote_char_from_context(compose);
8582 if (compose->replyinfo != NULL) {
8585 gtk_text_buffer_set_text(buffer, "", -1);
8586 mark = gtk_text_buffer_get_insert(buffer);
8587 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8589 parsed_str = compose_quote_fmt(compose, compose->replyinfo,
8590 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8592 } else if (compose->fwdinfo != NULL) {
8595 gtk_text_buffer_set_text(buffer, "", -1);
8596 mark = gtk_text_buffer_get_insert(buffer);
8597 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8599 parsed_str = compose_quote_fmt(compose, compose->fwdinfo,
8600 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8603 MsgInfo* dummyinfo = compose_msginfo_new_from_compose(compose);
8605 GtkTextIter start, end;
8608 gtk_text_buffer_get_start_iter(buffer, &start);
8609 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
8610 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
8612 /* clear the buffer now */
8614 gtk_text_buffer_set_text(buffer, "", -1);
8616 parsed_str = compose_quote_fmt(compose, dummyinfo,
8617 tmpl->value, qmark, tmp, FALSE, FALSE, err_msg);
8618 procmsg_msginfo_free( &dummyinfo );
8624 gtk_text_buffer_set_text(buffer, "", -1);
8625 mark = gtk_text_buffer_get_insert(buffer);
8626 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8629 if (replace && parsed_str && compose->account->auto_sig)
8630 compose_insert_sig(compose, FALSE);
8632 if (replace && parsed_str) {
8633 gtk_text_buffer_get_start_iter(buffer, &iter);
8634 gtk_text_buffer_place_cursor(buffer, &iter);
8638 cursor_pos = quote_fmt_get_cursor_pos();
8639 compose->set_cursor_pos = cursor_pos;
8640 if (cursor_pos == -1)
8642 gtk_text_buffer_get_start_iter(buffer, &iter);
8643 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
8644 gtk_text_buffer_place_cursor(buffer, &iter);
8647 /* process the other fields */
8649 compose_template_apply_fields(compose, tmpl);
8650 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
8651 quote_fmt_reset_vartable();
8652 compose_changed_cb(NULL, compose);
8655 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
8656 gtkaspell_highlight_all(compose->gtkaspell);
8660 static void compose_template_apply_fields(Compose *compose, Template *tmpl)
8662 MsgInfo* dummyinfo = NULL;
8663 MsgInfo *msginfo = NULL;
8666 if (compose->replyinfo != NULL)
8667 msginfo = compose->replyinfo;
8668 else if (compose->fwdinfo != NULL)
8669 msginfo = compose->fwdinfo;
8671 dummyinfo = compose_msginfo_new_from_compose(compose);
8672 msginfo = dummyinfo;
8675 if (tmpl->from && *tmpl->from != '\0') {
8677 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8678 compose->gtkaspell);
8680 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8682 quote_fmt_scan_string(tmpl->from);
8685 buf = quote_fmt_get_buffer();
8687 alertpanel_error(_("Template From format error."));
8689 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
8693 if (tmpl->to && *tmpl->to != '\0') {
8695 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8696 compose->gtkaspell);
8698 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8700 quote_fmt_scan_string(tmpl->to);
8703 buf = quote_fmt_get_buffer();
8705 alertpanel_error(_("Template To format error."));
8707 compose_entry_append(compose, buf, COMPOSE_TO, PREF_TEMPLATE);
8711 if (tmpl->cc && *tmpl->cc != '\0') {
8713 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8714 compose->gtkaspell);
8716 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8718 quote_fmt_scan_string(tmpl->cc);
8721 buf = quote_fmt_get_buffer();
8723 alertpanel_error(_("Template Cc format error."));
8725 compose_entry_append(compose, buf, COMPOSE_CC, PREF_TEMPLATE);
8729 if (tmpl->bcc && *tmpl->bcc != '\0') {
8731 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8732 compose->gtkaspell);
8734 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8736 quote_fmt_scan_string(tmpl->bcc);
8739 buf = quote_fmt_get_buffer();
8741 alertpanel_error(_("Template Bcc format error."));
8743 compose_entry_append(compose, buf, COMPOSE_BCC, PREF_TEMPLATE);
8747 if (tmpl->replyto && *tmpl->replyto != '\0') {
8749 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8750 compose->gtkaspell);
8752 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8754 quote_fmt_scan_string(tmpl->replyto);
8757 buf = quote_fmt_get_buffer();
8759 alertpanel_error(_("Template Reply-To format error."));
8761 compose_entry_append(compose, buf, COMPOSE_REPLYTO, PREF_TEMPLATE);
8765 /* process the subject */
8766 if (tmpl->subject && *tmpl->subject != '\0') {
8768 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8769 compose->gtkaspell);
8771 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8773 quote_fmt_scan_string(tmpl->subject);
8776 buf = quote_fmt_get_buffer();
8778 alertpanel_error(_("Template subject format error."));
8780 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
8784 procmsg_msginfo_free( &dummyinfo );
8787 static void compose_destroy(Compose *compose)
8789 GtkAllocation allocation;
8790 GtkTextBuffer *buffer;
8791 GtkClipboard *clipboard;
8793 compose_list = g_list_remove(compose_list, compose);
8795 if (compose->updating) {
8796 debug_print("danger, not destroying anything now\n");
8797 compose->deferred_destroy = TRUE;
8801 /* NOTE: address_completion_end() does nothing with the window
8802 * however this may change. */
8803 address_completion_end(compose->window);
8805 slist_free_strings_full(compose->to_list);
8806 slist_free_strings_full(compose->newsgroup_list);
8807 slist_free_strings_full(compose->header_list);
8809 slist_free_strings_full(extra_headers);
8810 extra_headers = NULL;
8812 compose->header_list = compose->newsgroup_list = compose->to_list = NULL;
8814 g_hash_table_destroy(compose->email_hashtable);
8816 hooks_unregister_hook(FOLDER_UPDATE_HOOKLIST,
8817 compose->folder_update_callback_id);
8819 procmsg_msginfo_free(&(compose->targetinfo));
8820 procmsg_msginfo_free(&(compose->replyinfo));
8821 procmsg_msginfo_free(&(compose->fwdinfo));
8823 g_free(compose->replyto);
8824 g_free(compose->cc);
8825 g_free(compose->bcc);
8826 g_free(compose->newsgroups);
8827 g_free(compose->followup_to);
8829 g_free(compose->ml_post);
8831 g_free(compose->inreplyto);
8832 g_free(compose->references);
8833 g_free(compose->msgid);
8834 g_free(compose->boundary);
8836 g_free(compose->redirect_filename);
8837 if (compose->undostruct)
8838 undo_destroy(compose->undostruct);
8840 g_free(compose->sig_str);
8842 g_free(compose->exteditor_file);
8844 g_free(compose->orig_charset);
8846 g_free(compose->privacy_system);
8847 g_free(compose->encdata);
8849 #ifndef USE_ALT_ADDRBOOK
8850 if (addressbook_get_target_compose() == compose)
8851 addressbook_set_target_compose(NULL);
8854 if (compose->gtkaspell) {
8855 gtkaspell_delete(compose->gtkaspell);
8856 compose->gtkaspell = NULL;
8860 if (!compose->batch) {
8861 gtk_widget_get_allocation(compose->window, &allocation);
8862 prefs_common.compose_width = allocation.width;
8863 prefs_common.compose_height = allocation.height;
8866 if (!gtk_widget_get_parent(compose->paned))
8867 gtk_widget_destroy(compose->paned);
8868 gtk_widget_destroy(compose->popupmenu);
8870 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
8871 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
8872 gtk_text_buffer_remove_selection_clipboard(buffer, clipboard);
8874 gtk_widget_destroy(compose->window);
8875 toolbar_destroy(compose->toolbar);
8876 g_free(compose->toolbar);
8877 cm_mutex_free(compose->mutex);
8881 static void compose_attach_info_free(AttachInfo *ainfo)
8883 g_free(ainfo->file);
8884 g_free(ainfo->content_type);
8885 g_free(ainfo->name);
8886 g_free(ainfo->charset);
8890 static void compose_attach_update_label(Compose *compose)
8895 GtkTreeModel *model;
8900 model = gtk_tree_view_get_model(GTK_TREE_VIEW(compose->attach_clist));
8901 if(!gtk_tree_model_get_iter_first(model, &iter)) {
8902 gtk_label_set_text(GTK_LABEL(compose->attach_label), "");
8906 while(gtk_tree_model_iter_next(model, &iter))
8909 text = g_strdup_printf("(%d)", i);
8910 gtk_label_set_text(GTK_LABEL(compose->attach_label), text);
8914 static void compose_attach_remove_selected(GtkAction *action, gpointer data)
8916 Compose *compose = (Compose *)data;
8917 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
8918 GtkTreeSelection *selection;
8920 GtkTreeModel *model;
8922 selection = gtk_tree_view_get_selection(tree_view);
8923 sel = gtk_tree_selection_get_selected_rows(selection, &model);
8928 for (cur = sel; cur != NULL; cur = cur->next) {
8929 GtkTreePath *path = cur->data;
8930 GtkTreeRowReference *ref = gtk_tree_row_reference_new
8933 gtk_tree_path_free(path);
8936 for (cur = sel; cur != NULL; cur = cur->next) {
8937 GtkTreeRowReference *ref = cur->data;
8938 GtkTreePath *path = gtk_tree_row_reference_get_path(ref);
8941 if (gtk_tree_model_get_iter(model, &iter, path))
8942 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
8944 gtk_tree_path_free(path);
8945 gtk_tree_row_reference_free(ref);
8949 compose_attach_update_label(compose);
8952 static struct _AttachProperty
8955 GtkWidget *mimetype_entry;
8956 GtkWidget *encoding_optmenu;
8957 GtkWidget *path_entry;
8958 GtkWidget *filename_entry;
8960 GtkWidget *cancel_btn;
8963 static void gtk_tree_path_free_(gpointer ptr, gpointer data)
8965 gtk_tree_path_free((GtkTreePath *)ptr);
8968 static void compose_attach_property(GtkAction *action, gpointer data)
8970 Compose *compose = (Compose *)data;
8971 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
8973 GtkComboBox *optmenu;
8974 GtkTreeSelection *selection;
8976 GtkTreeModel *model;
8979 static gboolean cancelled;
8981 /* only if one selected */
8982 selection = gtk_tree_view_get_selection(tree_view);
8983 if (gtk_tree_selection_count_selected_rows(selection) != 1)
8986 sel = gtk_tree_selection_get_selected_rows(selection, &model);
8990 path = (GtkTreePath *) sel->data;
8991 gtk_tree_model_get_iter(model, &iter, path);
8992 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
8995 g_list_foreach(sel, gtk_tree_path_free_, NULL);
9001 if (!attach_prop.window)
9002 compose_attach_property_create(&cancelled);
9003 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), TRUE);
9004 gtk_widget_grab_focus(attach_prop.ok_btn);
9005 gtk_widget_show(attach_prop.window);
9006 gtk_window_set_transient_for(GTK_WINDOW(attach_prop.window),
9007 GTK_WINDOW(compose->window));
9009 optmenu = GTK_COMBO_BOX(attach_prop.encoding_optmenu);
9010 if (ainfo->encoding == ENC_UNKNOWN)
9011 combobox_select_by_data(optmenu, ENC_BASE64);
9013 combobox_select_by_data(optmenu, ainfo->encoding);
9015 gtk_entry_set_text(GTK_ENTRY(attach_prop.mimetype_entry),
9016 ainfo->content_type ? ainfo->content_type : "");
9017 gtk_entry_set_text(GTK_ENTRY(attach_prop.path_entry),
9018 ainfo->file ? ainfo->file : "");
9019 gtk_entry_set_text(GTK_ENTRY(attach_prop.filename_entry),
9020 ainfo->name ? ainfo->name : "");
9023 const gchar *entry_text;
9025 gchar *cnttype = NULL;
9032 gtk_widget_hide(attach_prop.window);
9033 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), FALSE);
9038 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.mimetype_entry));
9039 if (*entry_text != '\0') {
9042 text = g_strstrip(g_strdup(entry_text));
9043 if ((p = strchr(text, '/')) && !strchr(p + 1, '/')) {
9044 cnttype = g_strdup(text);
9047 alertpanel_error(_("Invalid MIME type."));
9053 ainfo->encoding = combobox_get_active_data(optmenu);
9055 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.path_entry));
9056 if (*entry_text != '\0') {
9057 if (is_file_exist(entry_text) &&
9058 (size = get_file_size(entry_text)) > 0)
9059 file = g_strdup(entry_text);
9062 (_("File doesn't exist or is empty."));
9068 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.filename_entry));
9069 if (*entry_text != '\0') {
9070 g_free(ainfo->name);
9071 ainfo->name = g_strdup(entry_text);
9075 g_free(ainfo->content_type);
9076 ainfo->content_type = cnttype;
9079 g_free(ainfo->file);
9083 ainfo->size = (goffset)size;
9085 /* update tree store */
9086 text = to_human_readable(ainfo->size);
9087 gtk_tree_model_get_iter(model, &iter, path);
9088 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
9089 COL_MIMETYPE, ainfo->content_type,
9091 COL_NAME, ainfo->name,
9092 COL_CHARSET, ainfo->charset,
9098 gtk_tree_path_free(path);
9101 #define SET_LABEL_AND_ENTRY(str, entry, top) \
9103 label = gtk_label_new(str); \
9104 gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), \
9105 GTK_FILL, 0, 0, 0); \
9106 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); \
9108 entry = gtk_entry_new(); \
9109 gtk_table_attach(GTK_TABLE(table), entry, 1, 2, top, (top + 1), \
9110 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); \
9113 static void compose_attach_property_create(gboolean *cancelled)
9119 GtkWidget *mimetype_entry;
9122 GtkListStore *optmenu_menu;
9123 GtkWidget *path_entry;
9124 GtkWidget *filename_entry;
9127 GtkWidget *cancel_btn;
9128 GList *mime_type_list, *strlist;
9131 debug_print("Creating attach_property window...\n");
9133 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose_attach_property");
9134 gtk_widget_set_size_request(window, 480, -1);
9135 gtk_container_set_border_width(GTK_CONTAINER(window), 8);
9136 gtk_window_set_title(GTK_WINDOW(window), _("Properties"));
9137 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
9138 g_signal_connect(G_OBJECT(window), "delete_event",
9139 G_CALLBACK(attach_property_delete_event),
9141 g_signal_connect(G_OBJECT(window), "key_press_event",
9142 G_CALLBACK(attach_property_key_pressed),
9145 vbox = gtk_vbox_new(FALSE, 8);
9146 gtk_container_add(GTK_CONTAINER(window), vbox);
9148 table = gtk_table_new(4, 2, FALSE);
9149 gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
9150 gtk_table_set_row_spacings(GTK_TABLE(table), 8);
9151 gtk_table_set_col_spacings(GTK_TABLE(table), 8);
9153 label = gtk_label_new(_("MIME type"));
9154 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, (0 + 1),
9156 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
9157 #if !GTK_CHECK_VERSION(2, 24, 0)
9158 mimetype_entry = gtk_combo_box_entry_new_text();
9160 mimetype_entry = gtk_combo_box_text_new_with_entry();
9162 gtk_table_attach(GTK_TABLE(table), mimetype_entry, 1, 2, 0, (0 + 1),
9163 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
9165 /* stuff with list */
9166 mime_type_list = procmime_get_mime_type_list();
9168 for (; mime_type_list != NULL; mime_type_list = mime_type_list->next) {
9169 MimeType *type = (MimeType *) mime_type_list->data;
9172 tmp = g_strdup_printf("%s/%s", type->type, type->sub_type);
9174 if (g_list_find_custom(strlist, tmp, (GCompareFunc)strcmp2))
9177 strlist = g_list_insert_sorted(strlist, (gpointer)tmp,
9178 (GCompareFunc)strcmp2);
9181 for (mime_type_list = strlist; mime_type_list != NULL;
9182 mime_type_list = mime_type_list->next) {
9183 #if !GTK_CHECK_VERSION(2, 24, 0)
9184 gtk_combo_box_append_text(GTK_COMBO_BOX(mimetype_entry), mime_type_list->data);
9186 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(mimetype_entry), mime_type_list->data);
9188 g_free(mime_type_list->data);
9190 g_list_free(strlist);
9191 gtk_combo_box_set_active(GTK_COMBO_BOX(mimetype_entry), 0);
9192 mimetype_entry = gtk_bin_get_child(GTK_BIN((mimetype_entry)));
9194 label = gtk_label_new(_("Encoding"));
9195 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2,
9197 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
9199 hbox = gtk_hbox_new(FALSE, 0);
9200 gtk_table_attach(GTK_TABLE(table), hbox, 1, 2, 1, 2,
9201 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
9203 optmenu = gtkut_sc_combobox_create(NULL, TRUE);
9204 optmenu_menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
9206 COMBOBOX_ADD(optmenu_menu, "7bit", ENC_7BIT);
9207 COMBOBOX_ADD(optmenu_menu, "8bit", ENC_8BIT);
9208 COMBOBOX_ADD(optmenu_menu, "quoted-printable", ENC_QUOTED_PRINTABLE);
9209 COMBOBOX_ADD(optmenu_menu, "base64", ENC_BASE64);
9210 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), 0);
9212 gtk_box_pack_start(GTK_BOX(hbox), optmenu, TRUE, TRUE, 0);
9214 SET_LABEL_AND_ENTRY(_("Path"), path_entry, 2);
9215 SET_LABEL_AND_ENTRY(_("File name"), filename_entry, 3);
9217 gtkut_stock_button_set_create(&hbbox, &cancel_btn, GTK_STOCK_CANCEL,
9218 &ok_btn, GTK_STOCK_OK,
9220 gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
9221 gtk_widget_grab_default(ok_btn);
9223 g_signal_connect(G_OBJECT(ok_btn), "clicked",
9224 G_CALLBACK(attach_property_ok),
9226 g_signal_connect(G_OBJECT(cancel_btn), "clicked",
9227 G_CALLBACK(attach_property_cancel),
9230 gtk_widget_show_all(vbox);
9232 attach_prop.window = window;
9233 attach_prop.mimetype_entry = mimetype_entry;
9234 attach_prop.encoding_optmenu = optmenu;
9235 attach_prop.path_entry = path_entry;
9236 attach_prop.filename_entry = filename_entry;
9237 attach_prop.ok_btn = ok_btn;
9238 attach_prop.cancel_btn = cancel_btn;
9241 #undef SET_LABEL_AND_ENTRY
9243 static void attach_property_ok(GtkWidget *widget, gboolean *cancelled)
9249 static void attach_property_cancel(GtkWidget *widget, gboolean *cancelled)
9255 static gint attach_property_delete_event(GtkWidget *widget, GdkEventAny *event,
9256 gboolean *cancelled)
9264 static gboolean attach_property_key_pressed(GtkWidget *widget,
9266 gboolean *cancelled)
9268 if (event && event->keyval == GDK_KEY_Escape) {
9272 if (event && event->keyval == GDK_KEY_Return) {
9280 static void compose_exec_ext_editor(Compose *compose)
9285 GdkNativeWindow socket_wid = 0;
9289 tmp = g_strdup_printf("%s%ctmpmsg.%p", get_tmp_dir(),
9290 G_DIR_SEPARATOR, compose);
9292 if (compose_get_ext_editor_uses_socket()) {
9293 /* Only allow one socket */
9294 if (compose->exteditor_socket != NULL) {
9295 if (gtk_widget_is_focus(compose->exteditor_socket)) {
9296 /* Move the focus off of the socket */
9297 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9302 /* Create the receiving GtkSocket */
9303 socket = gtk_socket_new ();
9304 g_signal_connect (GTK_OBJECT(socket), "plug-removed",
9305 G_CALLBACK(compose_ext_editor_plug_removed_cb),
9307 gtk_box_pack_start(GTK_BOX(compose->edit_vbox), socket, TRUE, TRUE, 0);
9308 gtk_widget_set_size_request(socket, prefs_common.compose_width, -1);
9309 /* Realize the socket so that we can use its ID */
9310 gtk_widget_realize(socket);
9311 socket_wid = gtk_socket_get_id(GTK_SOCKET (socket));
9312 compose->exteditor_socket = socket;
9315 if (pipe(pipe_fds) < 0) {
9321 if ((pid = fork()) < 0) {
9328 /* close the write side of the pipe */
9331 compose->exteditor_file = g_strdup(tmp);
9332 compose->exteditor_pid = pid;
9334 compose_set_ext_editor_sensitive(compose, FALSE);
9337 compose->exteditor_ch = g_io_channel_unix_new(pipe_fds[0]);
9339 compose->exteditor_ch = g_io_channel_win32_new_fd(pipe_fds[0]);
9341 compose->exteditor_tag = g_io_add_watch(compose->exteditor_ch,
9345 } else { /* process-monitoring process */
9351 /* close the read side of the pipe */
9354 if (compose_write_body_to_file(compose, tmp) < 0) {
9355 fd_write_all(pipe_fds[1], "2\n", 2);
9359 pid_ed = compose_exec_ext_editor_real(tmp, socket_wid);
9361 fd_write_all(pipe_fds[1], "1\n", 2);
9365 /* wait until editor is terminated */
9366 waitpid(pid_ed, NULL, 0);
9368 fd_write_all(pipe_fds[1], "0\n", 2);
9375 #endif /* G_OS_UNIX */
9379 static gboolean compose_get_ext_editor_cmd_valid()
9381 gboolean has_s = FALSE;
9382 gboolean has_w = FALSE;
9383 const gchar *p = prefs_common_get_ext_editor_cmd();
9386 while ((p = strchr(p, '%'))) {
9392 } else if (*p == 'w') {
9403 static gint compose_exec_ext_editor_real(const gchar *file, GdkNativeWindow socket_wid)
9410 cm_return_val_if_fail(file != NULL, -1);
9412 if ((pid = fork()) < 0) {
9417 if (pid != 0) return pid;
9419 /* grandchild process */
9421 if (setpgid(0, getppid()))
9424 if (compose_get_ext_editor_cmd_valid()) {
9425 if (compose_get_ext_editor_uses_socket()) {
9426 p = g_strdup(prefs_common_get_ext_editor_cmd());
9427 s = strstr(p, "%w");
9429 if (strstr(p, "%s") < s)
9430 g_snprintf(buf, sizeof(buf), p, file, socket_wid);
9432 g_snprintf(buf, sizeof(buf), p, socket_wid, file);
9435 g_snprintf(buf, sizeof(buf),
9436 prefs_common_get_ext_editor_cmd(), file);
9439 if (prefs_common_get_ext_editor_cmd())
9440 g_warning("External editor command-line is invalid: '%s'",
9441 prefs_common_get_ext_editor_cmd());
9442 g_snprintf(buf, sizeof(buf), DEFAULT_EDITOR_CMD, file);
9445 cmdline = strsplit_with_quote(buf, " ", 1024);
9446 execvp(cmdline[0], cmdline);
9449 g_strfreev(cmdline);
9454 static gboolean compose_ext_editor_kill(Compose *compose)
9456 pid_t pgid = compose->exteditor_pid * -1;
9459 ret = kill(pgid, 0);
9461 if (ret == 0 || (ret == -1 && EPERM == errno)) {
9465 msg = g_strdup_printf
9466 (_("The external editor is still working.\n"
9467 "Force terminating the process?\n"
9468 "process group id: %d"), -pgid);
9469 val = alertpanel_full(_("Notice"), msg, GTK_STOCK_NO, GTK_STOCK_YES,
9470 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
9474 if (val == G_ALERTALTERNATE) {
9475 g_source_remove(compose->exteditor_tag);
9476 g_io_channel_shutdown(compose->exteditor_ch,
9478 g_io_channel_unref(compose->exteditor_ch);
9480 if (kill(pgid, SIGTERM) < 0) perror("kill");
9481 waitpid(compose->exteditor_pid, NULL, 0);
9483 g_warning("Terminated process group id: %d. "
9484 "Temporary file: %s", -pgid, compose->exteditor_file);
9486 compose_set_ext_editor_sensitive(compose, TRUE);
9488 g_free(compose->exteditor_file);
9489 compose->exteditor_file = NULL;
9490 compose->exteditor_pid = -1;
9491 compose->exteditor_ch = NULL;
9492 compose->exteditor_tag = -1;
9500 static gboolean compose_input_cb(GIOChannel *source, GIOCondition condition,
9504 Compose *compose = (Compose *)data;
9507 debug_print("Compose: input from monitoring process\n");
9509 if (g_io_channel_read_chars(source, buf, sizeof(buf), &bytes_read, NULL) != G_IO_STATUS_NORMAL) {
9514 g_io_channel_shutdown(source, FALSE, NULL);
9515 g_io_channel_unref(source);
9517 waitpid(compose->exteditor_pid, NULL, 0);
9519 if (buf[0] == '0') { /* success */
9520 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9521 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
9522 GtkTextIter start, end;
9525 gtk_text_buffer_set_text(buffer, "", -1);
9526 compose_insert_file(compose, compose->exteditor_file);
9527 compose_changed_cb(NULL, compose);
9528 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
9530 if (claws_unlink(compose->exteditor_file) < 0)
9531 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9533 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
9534 gtk_text_buffer_get_start_iter(buffer, &start);
9535 gtk_text_buffer_get_end_iter(buffer, &end);
9536 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
9537 if (chars && strlen(chars) > 0)
9538 compose->modified = TRUE;
9540 } else if (buf[0] == '1') { /* failed */
9541 g_warning("Couldn't exec external editor");
9542 if (claws_unlink(compose->exteditor_file) < 0)
9543 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9544 } else if (buf[0] == '2') {
9545 g_warning("Couldn't write to file");
9546 } else if (buf[0] == '3') {
9547 g_warning("Pipe read failed");
9550 compose_set_ext_editor_sensitive(compose, TRUE);
9552 g_free(compose->exteditor_file);
9553 compose->exteditor_file = NULL;
9554 compose->exteditor_pid = -1;
9555 compose->exteditor_ch = NULL;
9556 compose->exteditor_tag = -1;
9557 if (compose->exteditor_socket) {
9558 gtk_widget_destroy(compose->exteditor_socket);
9559 compose->exteditor_socket = NULL;
9566 static char *ext_editor_menu_entries[] = {
9567 "Menu/Message/Send",
9568 "Menu/Message/SendLater",
9569 "Menu/Message/InsertFile",
9570 "Menu/Message/InsertSig",
9571 "Menu/Message/ReplaceSig",
9572 "Menu/Message/Save",
9573 "Menu/Message/Print",
9578 "Menu/Tools/ShowRuler",
9579 "Menu/Tools/Actions",
9584 static void compose_set_ext_editor_sensitive(Compose *compose,
9589 for (i = 0; ext_editor_menu_entries[i]; ++i) {
9590 cm_menu_set_sensitive_full(compose->ui_manager,
9591 ext_editor_menu_entries[i], sensitive);
9594 if (compose_get_ext_editor_uses_socket()) {
9596 if (compose->exteditor_socket)
9597 gtk_widget_hide(compose->exteditor_socket);
9598 gtk_widget_show(compose->scrolledwin);
9599 if (prefs_common.show_ruler)
9600 gtk_widget_show(compose->ruler_hbox);
9601 /* Fix the focus, as it doesn't go anywhere when the
9602 * socket is hidden or destroyed */
9603 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9605 g_assert (compose->exteditor_socket != NULL);
9606 /* Fix the focus, as it doesn't go anywhere when the
9607 * edit box is hidden */
9608 if (gtk_widget_is_focus(compose->text))
9609 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9610 gtk_widget_hide(compose->scrolledwin);
9611 gtk_widget_hide(compose->ruler_hbox);
9612 gtk_widget_show(compose->exteditor_socket);
9615 gtk_widget_set_sensitive(compose->text, sensitive);
9617 if (compose->toolbar->send_btn)
9618 gtk_widget_set_sensitive(compose->toolbar->send_btn, sensitive);
9619 if (compose->toolbar->sendl_btn)
9620 gtk_widget_set_sensitive(compose->toolbar->sendl_btn, sensitive);
9621 if (compose->toolbar->draft_btn)
9622 gtk_widget_set_sensitive(compose->toolbar->draft_btn, sensitive);
9623 if (compose->toolbar->insert_btn)
9624 gtk_widget_set_sensitive(compose->toolbar->insert_btn, sensitive);
9625 if (compose->toolbar->sig_btn)
9626 gtk_widget_set_sensitive(compose->toolbar->sig_btn, sensitive);
9627 if (compose->toolbar->exteditor_btn)
9628 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, sensitive);
9629 if (compose->toolbar->linewrap_current_btn)
9630 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, sensitive);
9631 if (compose->toolbar->linewrap_all_btn)
9632 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, sensitive);
9635 static gboolean compose_get_ext_editor_uses_socket()
9637 return (prefs_common_get_ext_editor_cmd() &&
9638 strstr(prefs_common_get_ext_editor_cmd(), "%w"));
9641 static gboolean compose_ext_editor_plug_removed_cb(GtkSocket *socket, Compose *compose)
9643 compose->exteditor_socket = NULL;
9644 /* returning FALSE allows destruction of the socket */
9647 #endif /* G_OS_UNIX */
9650 * compose_undo_state_changed:
9652 * Change the sensivity of the menuentries undo and redo
9654 static void compose_undo_state_changed(UndoMain *undostruct, gint undo_state,
9655 gint redo_state, gpointer data)
9657 Compose *compose = (Compose *)data;
9659 switch (undo_state) {
9660 case UNDO_STATE_TRUE:
9661 if (!undostruct->undo_state) {
9662 undostruct->undo_state = TRUE;
9663 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", TRUE);
9666 case UNDO_STATE_FALSE:
9667 if (undostruct->undo_state) {
9668 undostruct->undo_state = FALSE;
9669 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
9672 case UNDO_STATE_UNCHANGED:
9674 case UNDO_STATE_REFRESH:
9675 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", undostruct->undo_state);
9678 g_warning("Undo state not recognized");
9682 switch (redo_state) {
9683 case UNDO_STATE_TRUE:
9684 if (!undostruct->redo_state) {
9685 undostruct->redo_state = TRUE;
9686 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", TRUE);
9689 case UNDO_STATE_FALSE:
9690 if (undostruct->redo_state) {
9691 undostruct->redo_state = FALSE;
9692 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
9695 case UNDO_STATE_UNCHANGED:
9697 case UNDO_STATE_REFRESH:
9698 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", undostruct->redo_state);
9701 g_warning("Redo state not recognized");
9706 /* callback functions */
9708 static void compose_notebook_size_alloc(GtkNotebook *notebook,
9709 GtkAllocation *allocation,
9712 prefs_common.compose_notebook_height = gtk_paned_get_position(paned);
9715 /* compose_edit_size_alloc() - called when resized. don't know whether Gtk
9716 * includes "non-client" (windows-izm) in calculation, so this calculation
9717 * may not be accurate.
9719 static gboolean compose_edit_size_alloc(GtkEditable *widget,
9720 GtkAllocation *allocation,
9721 GtkSHRuler *shruler)
9723 if (prefs_common.show_ruler) {
9724 gint char_width = 0, char_height = 0;
9725 gint line_width_in_chars;
9727 gtkut_get_font_size(GTK_WIDGET(widget),
9728 &char_width, &char_height);
9729 line_width_in_chars =
9730 (allocation->width - allocation->x) / char_width;
9732 /* got the maximum */
9733 gtk_shruler_set_range(GTK_SHRULER(shruler),
9734 0.0, line_width_in_chars, 0);
9743 ComposePrefType type;
9744 gboolean entry_marked;
9747 static void account_activated(GtkComboBox *optmenu, gpointer data)
9749 Compose *compose = (Compose *)data;
9752 gchar *folderidentifier;
9753 gint account_id = 0;
9756 GSList *list, *saved_list = NULL;
9757 HeaderEntryState *state;
9758 GtkRcStyle *style = NULL;
9759 #if !GTK_CHECK_VERSION(3, 0, 0)
9760 static GdkColor yellow;
9761 static gboolean color_set = FALSE;
9763 static GdkColor yellow = { (guint32)0, (guint32)0xf5, (guint32)0xf6, (guint32)0xbe };
9766 /* Get ID of active account in the combo box */
9767 menu = gtk_combo_box_get_model(optmenu);
9768 cm_return_if_fail(gtk_combo_box_get_active_iter(optmenu, &iter));
9769 gtk_tree_model_get(menu, &iter, 1, &account_id, -1);
9771 ac = account_find_from_id(account_id);
9772 cm_return_if_fail(ac != NULL);
9774 if (ac != compose->account) {
9775 compose_select_account(compose, ac, FALSE);
9777 for (list = compose->header_list; list; list = list->next) {
9778 ComposeHeaderEntry *hentry=(ComposeHeaderEntry *)list->data;
9780 if (hentry->type == PREF_ACCOUNT || !list->next) {
9781 compose_destroy_headerentry(compose, hentry);
9785 state = g_malloc0(sizeof(HeaderEntryState));
9786 state->header = gtk_editable_get_chars(GTK_EDITABLE(
9787 gtk_bin_get_child(GTK_BIN(hentry->combo))), 0, -1);
9788 state->entry = gtk_editable_get_chars(
9789 GTK_EDITABLE(hentry->entry), 0, -1);
9790 state->type = hentry->type;
9792 #if !GTK_CHECK_VERSION(3, 0, 0)
9794 gdk_color_parse("#f5f6be", &yellow);
9795 color_set = gdk_colormap_alloc_color(
9796 gdk_colormap_get_system(),
9797 &yellow, FALSE, TRUE);
9801 style = gtk_widget_get_modifier_style(hentry->entry);
9802 state->entry_marked = gdk_color_equal(&yellow,
9803 &style->base[GTK_STATE_NORMAL]);
9805 saved_list = g_slist_append(saved_list, state);
9806 compose_destroy_headerentry(compose, hentry);
9809 compose->header_last = NULL;
9810 g_slist_free(compose->header_list);
9811 compose->header_list = NULL;
9812 compose->header_nextrow = 1;
9813 compose_create_header_entry(compose);
9815 if (ac->set_autocc && ac->auto_cc)
9816 compose_entry_append(compose, ac->auto_cc,
9817 COMPOSE_CC, PREF_ACCOUNT);
9819 if (ac->set_autobcc && ac->auto_bcc)
9820 compose_entry_append(compose, ac->auto_bcc,
9821 COMPOSE_BCC, PREF_ACCOUNT);
9823 if (ac->set_autoreplyto && ac->auto_replyto)
9824 compose_entry_append(compose, ac->auto_replyto,
9825 COMPOSE_REPLYTO, PREF_ACCOUNT);
9827 for (list = saved_list; list; list = list->next) {
9828 state = (HeaderEntryState *) list->data;
9830 compose_add_header_entry(compose, state->header,
9831 state->entry, state->type);
9832 if (state->entry_marked)
9833 compose_entry_mark_default_to(compose, state->entry);
9835 g_free(state->header);
9836 g_free(state->entry);
9839 g_slist_free(saved_list);
9841 combobox_select_by_data(GTK_COMBO_BOX(compose->header_last->combo),
9842 (ac->protocol == A_NNTP) ?
9843 COMPOSE_NEWSGROUPS : COMPOSE_TO);
9846 /* Set message save folder */
9847 if (account_get_special_folder(compose->account, F_OUTBOX)) {
9848 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
9850 g_signal_connect(G_OBJECT(compose->savemsg_checkbtn), "toggled",
9851 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
9853 compose_set_save_to(compose, NULL);
9854 if (account_get_special_folder(compose->account, F_OUTBOX)) {
9855 folderidentifier = folder_item_get_identifier(account_get_special_folder
9856 (compose->account, F_OUTBOX));
9857 compose_set_save_to(compose, folderidentifier);
9858 g_free(folderidentifier);
9862 static void attach_selected(GtkTreeView *tree_view, GtkTreePath *tree_path,
9863 GtkTreeViewColumn *column, Compose *compose)
9865 compose_attach_property(NULL, compose);
9868 static gboolean attach_button_pressed(GtkWidget *widget, GdkEventButton *event,
9871 Compose *compose = (Compose *)data;
9872 GtkTreeSelection *attach_selection;
9873 gint attach_nr_selected;
9876 if (!event) return FALSE;
9878 if (event->button == 3) {
9879 attach_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
9880 attach_nr_selected = gtk_tree_selection_count_selected_rows(attach_selection);
9882 /* If no rows, or just one row is selected, right-click should
9883 * open menu relevant to the row being right-clicked on. We
9884 * achieve that by selecting the clicked row first. If more
9885 * than one row is selected, we shouldn't modify the selection,
9886 * as user may want to remove selected rows (attachments). */
9887 if (attach_nr_selected < 2) {
9888 gtk_tree_selection_unselect_all(attach_selection);
9889 attach_nr_selected = 0;
9890 gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget),
9891 event->x, event->y, &path, NULL, NULL, NULL);
9893 gtk_tree_selection_select_path(attach_selection, path);
9894 gtk_tree_path_free(path);
9895 attach_nr_selected++;
9899 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", (attach_nr_selected > 0));
9900 /* Properties menu item makes no sense with more than one row
9901 * selected, the properties dialog can only edit one attachment. */
9902 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", (attach_nr_selected == 1));
9904 gtk_menu_popup(GTK_MENU(compose->popupmenu), NULL, NULL,
9905 NULL, NULL, event->button, event->time);
9912 static gboolean attach_key_pressed(GtkWidget *widget, GdkEventKey *event,
9915 Compose *compose = (Compose *)data;
9917 if (!event) return FALSE;
9919 switch (event->keyval) {
9920 case GDK_KEY_Delete:
9921 compose_attach_remove_selected(NULL, compose);
9927 static void compose_allow_user_actions (Compose *compose, gboolean allow)
9929 toolbar_comp_set_sensitive(compose, allow);
9930 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message", allow);
9931 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", allow);
9933 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", allow);
9935 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", allow);
9936 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools", allow);
9937 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Help", allow);
9939 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), allow);
9943 static void compose_send_cb(GtkAction *action, gpointer data)
9945 Compose *compose = (Compose *)data;
9948 if (compose->exteditor_tag != -1) {
9949 debug_print("ignoring send: external editor still open\n");
9953 if (prefs_common.work_offline &&
9954 !inc_offline_should_override(TRUE,
9955 _("Claws Mail needs network access in order "
9956 "to send this email.")))
9959 if (compose->draft_timeout_tag >= 0) { /* CLAWS: disable draft timeout */
9960 g_source_remove(compose->draft_timeout_tag);
9961 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
9964 compose_send(compose);
9967 static void compose_send_later_cb(GtkAction *action, gpointer data)
9969 Compose *compose = (Compose *)data;
9973 compose_allow_user_actions(compose, FALSE);
9974 val = compose_queue_sub(compose, NULL, NULL, NULL, TRUE, TRUE);
9975 compose_allow_user_actions(compose, TRUE);
9979 compose_close(compose);
9980 } else if (val == -1) {
9981 alertpanel_error(_("Could not queue message."));
9982 } else if (val == -2) {
9983 alertpanel_error(_("Could not queue message:\n\n%s."), g_strerror(errno));
9984 } else if (val == -3) {
9985 if (privacy_peek_error())
9986 alertpanel_error(_("Could not queue message for sending:\n\n"
9987 "Signature failed: %s"), privacy_get_error());
9988 } else if (val == -4) {
9989 alertpanel_error(_("Could not queue message for sending:\n\n"
9990 "Charset conversion failed."));
9991 } else if (val == -5) {
9992 alertpanel_error(_("Could not queue message for sending:\n\n"
9993 "Couldn't get recipient encryption key."));
9994 } else if (val == -6) {
9997 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
10000 #define DRAFTED_AT_EXIT "drafted_at_exit"
10001 static void compose_register_draft(MsgInfo *info)
10003 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10004 DRAFTED_AT_EXIT, NULL);
10005 FILE *fp = g_fopen(filepath, "ab");
10008 fprintf(fp, "%s\t%d\n", folder_item_get_identifier(info->folder),
10016 gboolean compose_draft (gpointer data, guint action)
10018 Compose *compose = (Compose *)data;
10023 MsgFlags flag = {0, 0};
10024 static gboolean lock = FALSE;
10025 MsgInfo *newmsginfo;
10027 gboolean target_locked = FALSE;
10028 gboolean err = FALSE;
10030 if (lock) return FALSE;
10032 if (compose->sending)
10035 draft = account_get_special_folder(compose->account, F_DRAFT);
10036 cm_return_val_if_fail(draft != NULL, FALSE);
10038 if (!g_mutex_trylock(compose->mutex)) {
10039 /* we don't want to lock the mutex once it's available,
10040 * because as the only other part of compose.c locking
10041 * it is compose_close - which means once unlocked,
10042 * the compose struct will be freed */
10043 debug_print("couldn't lock mutex, probably sending\n");
10049 tmp = g_strdup_printf("%s%cdraft.%p", get_tmp_dir(),
10050 G_DIR_SEPARATOR, compose);
10051 if ((fp = g_fopen(tmp, "wb")) == NULL) {
10052 FILE_OP_ERROR(tmp, "fopen");
10056 /* chmod for security */
10057 if (change_file_mode_rw(fp, tmp) < 0) {
10058 FILE_OP_ERROR(tmp, "chmod");
10059 g_warning("can't change file mode");
10062 /* Save draft infos */
10063 err |= (fprintf(fp, "X-Claws-Account-Id:%d\n", compose->account->account_id) < 0);
10064 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
10066 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
10067 gchar *savefolderid;
10069 savefolderid = compose_get_save_to(compose);
10070 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
10071 g_free(savefolderid);
10073 if (compose->return_receipt) {
10074 err |= (fprintf(fp, "RRCPT:1\n") < 0);
10076 if (compose->privacy_system) {
10077 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
10078 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
10079 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
10082 /* Message-ID of message replying to */
10083 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
10084 gchar *folderid = NULL;
10086 if (compose->replyinfo->folder)
10087 folderid = folder_item_get_identifier(compose->replyinfo->folder);
10088 if (folderid == NULL)
10089 folderid = g_strdup("NULL");
10091 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
10094 /* Message-ID of message forwarding to */
10095 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
10096 gchar *folderid = NULL;
10098 if (compose->fwdinfo->folder)
10099 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
10100 if (folderid == NULL)
10101 folderid = g_strdup("NULL");
10103 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
10107 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
10108 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
10110 sheaders = compose_get_manual_headers_info(compose);
10111 err |= (fprintf(fp, "X-Claws-Manual-Headers:%s\n", sheaders) < 0);
10114 /* end of headers */
10115 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
10122 if (compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_STORE, action != COMPOSE_AUTO_SAVE) < 0) {
10126 if (fclose(fp) == EOF) {
10130 flag.perm_flags = MSG_NEW|MSG_UNREAD;
10131 if (compose->targetinfo) {
10132 target_locked = MSG_IS_LOCKED(compose->targetinfo->flags);
10134 flag.perm_flags |= MSG_LOCKED;
10136 flag.tmp_flags = MSG_DRAFT;
10138 folder_item_scan(draft);
10139 if ((msgnum = folder_item_add_msg(draft, tmp, &flag, TRUE)) < 0) {
10140 MsgInfo *tmpinfo = NULL;
10141 debug_print("didn't get msgnum after adding draft [%s]\n", compose->msgid?compose->msgid:"no msgid");
10142 if (compose->msgid) {
10143 tmpinfo = folder_item_get_msginfo_by_msgid(draft, compose->msgid);
10146 msgnum = tmpinfo->msgnum;
10147 procmsg_msginfo_free(&tmpinfo);
10148 debug_print("got draft msgnum %d from scanning\n", msgnum);
10150 debug_print("didn't get draft msgnum after scanning\n");
10153 debug_print("got draft msgnum %d from adding\n", msgnum);
10159 if (action != COMPOSE_AUTO_SAVE) {
10160 if (action != COMPOSE_DRAFT_FOR_EXIT)
10161 alertpanel_error(_("Could not save draft."));
10164 gtkut_window_popup(compose->window);
10165 val = alertpanel_full(_("Could not save draft"),
10166 _("Could not save draft.\n"
10167 "Do you want to cancel exit or discard this email?"),
10168 _("_Cancel exit"), _("_Discard email"), NULL,
10169 FALSE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
10170 if (val == G_ALERTALTERNATE) {
10172 g_mutex_unlock(compose->mutex); /* must be done before closing */
10173 compose_close(compose);
10177 g_mutex_unlock(compose->mutex); /* must be done before closing */
10186 if (compose->mode == COMPOSE_REEDIT) {
10187 compose_remove_reedit_target(compose, TRUE);
10190 newmsginfo = folder_item_get_msginfo(draft, msgnum);
10193 procmsg_msginfo_unset_flags(newmsginfo, ~0, ~0);
10195 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD|MSG_LOCKED, MSG_DRAFT);
10197 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD, MSG_DRAFT);
10198 if (compose_use_attach(compose) && action != COMPOSE_AUTO_SAVE)
10199 procmsg_msginfo_set_flags(newmsginfo, 0,
10200 MSG_HAS_ATTACHMENT);
10202 if (action == COMPOSE_DRAFT_FOR_EXIT) {
10203 compose_register_draft(newmsginfo);
10205 procmsg_msginfo_free(&newmsginfo);
10208 folder_item_scan(draft);
10210 if (action == COMPOSE_QUIT_EDITING || action == COMPOSE_DRAFT_FOR_EXIT) {
10212 g_mutex_unlock(compose->mutex); /* must be done before closing */
10213 compose_close(compose);
10219 path = folder_item_fetch_msg(draft, msgnum);
10220 if (path == NULL) {
10221 debug_print("can't fetch %s:%d\n", draft->path, msgnum);
10224 if (g_stat(path, &s) < 0) {
10225 FILE_OP_ERROR(path, "stat");
10231 procmsg_msginfo_free(&(compose->targetinfo));
10232 compose->targetinfo = procmsg_msginfo_new();
10233 compose->targetinfo->msgnum = msgnum;
10234 compose->targetinfo->size = (goffset)s.st_size;
10235 compose->targetinfo->mtime = s.st_mtime;
10236 compose->targetinfo->folder = draft;
10238 procmsg_msginfo_set_flags(compose->targetinfo, MSG_LOCKED, 0);
10239 compose->mode = COMPOSE_REEDIT;
10241 if (action == COMPOSE_AUTO_SAVE) {
10242 compose->autosaved_draft = compose->targetinfo;
10244 compose->modified = FALSE;
10245 compose_set_title(compose);
10249 g_mutex_unlock(compose->mutex);
10253 void compose_clear_exit_drafts(void)
10255 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10256 DRAFTED_AT_EXIT, NULL);
10257 if (is_file_exist(filepath))
10258 claws_unlink(filepath);
10263 void compose_reopen_exit_drafts(void)
10265 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10266 DRAFTED_AT_EXIT, NULL);
10267 FILE *fp = g_fopen(filepath, "rb");
10271 while (fgets(buf, sizeof(buf), fp)) {
10272 gchar **parts = g_strsplit(buf, "\t", 2);
10273 const gchar *folder = parts[0];
10274 int msgnum = parts[1] ? atoi(parts[1]):-1;
10276 if (folder && *folder && msgnum > -1) {
10277 FolderItem *item = folder_find_item_from_identifier(folder);
10278 MsgInfo *info = folder_item_get_msginfo(item, msgnum);
10280 compose_reedit(info, FALSE);
10287 compose_clear_exit_drafts();
10290 static void compose_save_cb(GtkAction *action, gpointer data)
10292 Compose *compose = (Compose *)data;
10293 compose_draft(compose, COMPOSE_KEEP_EDITING);
10294 compose->rmode = COMPOSE_REEDIT;
10297 void compose_attach_from_list(Compose *compose, GList *file_list, gboolean free_data)
10299 if (compose && file_list) {
10302 for ( tmp = file_list; tmp; tmp = tmp->next) {
10303 gchar *file = (gchar *) tmp->data;
10304 gchar *utf8_filename = conv_filename_to_utf8(file);
10305 compose_attach_append(compose, file, utf8_filename, NULL, NULL);
10306 compose_changed_cb(NULL, compose);
10311 g_free(utf8_filename);
10316 static void compose_attach_cb(GtkAction *action, gpointer data)
10318 Compose *compose = (Compose *)data;
10321 if (compose->redirect_filename != NULL)
10324 /* Set focus_window properly, in case we were called via popup menu,
10325 * which unsets it (via focus_out_event callback on compose window). */
10326 manage_window_focus_in(compose->window, NULL, NULL);
10328 file_list = filesel_select_multiple_files_open(_("Select file"));
10331 compose_attach_from_list(compose, file_list, TRUE);
10332 g_list_free(file_list);
10336 static void compose_insert_file_cb(GtkAction *action, gpointer data)
10338 Compose *compose = (Compose *)data;
10340 gint files_inserted = 0;
10342 file_list = filesel_select_multiple_files_open(_("Select file"));
10347 for ( tmp = file_list; tmp; tmp = tmp->next) {
10348 gchar *file = (gchar *) tmp->data;
10349 gchar *filedup = g_strdup(file);
10350 gchar *shortfile = g_path_get_basename(filedup);
10351 ComposeInsertResult res;
10352 /* insert the file if the file is short or if the user confirmed that
10353 he/she wants to insert the large file */
10354 res = compose_insert_file(compose, file);
10355 if (res == COMPOSE_INSERT_READ_ERROR) {
10356 alertpanel_error(_("File '%s' could not be read."), shortfile);
10357 } else if (res == COMPOSE_INSERT_INVALID_CHARACTER) {
10358 alertpanel_error(_("File '%s' contained invalid characters\n"
10359 "for the current encoding, insertion may be incorrect."),
10361 } else if (res == COMPOSE_INSERT_SUCCESS)
10368 g_list_free(file_list);
10372 if (files_inserted > 0 && compose->gtkaspell &&
10373 compose->gtkaspell->check_while_typing)
10374 gtkaspell_highlight_all(compose->gtkaspell);
10378 static void compose_insert_sig_cb(GtkAction *action, gpointer data)
10380 Compose *compose = (Compose *)data;
10382 compose_insert_sig(compose, FALSE);
10385 static void compose_replace_sig_cb(GtkAction *action, gpointer data)
10387 Compose *compose = (Compose *)data;
10389 compose_insert_sig(compose, TRUE);
10392 static gint compose_delete_cb(GtkWidget *widget, GdkEventAny *event,
10396 Compose *compose = (Compose *)data;
10398 gtkut_widget_get_uposition(widget, &x, &y);
10399 if (!compose->batch) {
10400 prefs_common.compose_x = x;
10401 prefs_common.compose_y = y;
10403 if (compose->sending || compose->updating)
10405 compose_close_cb(NULL, compose);
10409 void compose_close_toolbar(Compose *compose)
10411 compose_close_cb(NULL, compose);
10414 static gboolean compose_can_autosave(Compose *compose)
10416 if (compose->privacy_system && compose->use_encryption)
10417 return prefs_common.autosave && prefs_common.autosave_encrypted;
10419 return prefs_common.autosave;
10422 static void compose_close_cb(GtkAction *action, gpointer data)
10424 Compose *compose = (Compose *)data;
10428 if (compose->exteditor_tag != -1) {
10429 if (!compose_ext_editor_kill(compose))
10434 if (compose->modified) {
10435 gboolean reedit = (compose->rmode == COMPOSE_REEDIT);
10436 if (!g_mutex_trylock(compose->mutex)) {
10437 /* we don't want to lock the mutex once it's available,
10438 * because as the only other part of compose.c locking
10439 * it is compose_close - which means once unlocked,
10440 * the compose struct will be freed */
10441 debug_print("couldn't lock mutex, probably sending\n");
10445 val = alertpanel(_("Discard message"),
10446 _("This message has been modified. Discard it?"),
10447 _("_Discard"), _("_Save to Drafts"), GTK_STOCK_CANCEL);
10449 val = alertpanel(_("Save changes"),
10450 _("This message has been modified. Save the latest changes?"),
10451 _("_Don't save"), g_strconcat("+", _("_Save to Drafts"), NULL),
10454 g_mutex_unlock(compose->mutex);
10456 case G_ALERTDEFAULT:
10457 if (compose_can_autosave(compose) && !reedit)
10458 compose_remove_draft(compose);
10460 case G_ALERTALTERNATE:
10461 compose_draft(data, COMPOSE_QUIT_EDITING);
10468 compose_close(compose);
10471 static void compose_print_cb(GtkAction *action, gpointer data)
10473 Compose *compose = (Compose *) data;
10475 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
10476 if (compose->targetinfo)
10477 messageview_print(compose->targetinfo, FALSE, -1, -1, 0);
10480 static void compose_set_encoding_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
10482 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
10483 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
10484 Compose *compose = (Compose *) data;
10487 compose->out_encoding = (CharSet)value;
10490 static void compose_address_cb(GtkAction *action, gpointer data)
10492 Compose *compose = (Compose *)data;
10494 #ifndef USE_ALT_ADDRBOOK
10495 addressbook_open(compose);
10497 GError* error = NULL;
10498 addressbook_connect_signals(compose);
10499 addressbook_dbus_open(TRUE, &error);
10501 g_warning("%s", error->message);
10502 g_error_free(error);
10507 static void about_show_cb(GtkAction *action, gpointer data)
10512 static void compose_template_activate_cb(GtkWidget *widget, gpointer data)
10514 Compose *compose = (Compose *)data;
10519 tmpl = g_object_get_data(G_OBJECT(widget), "template");
10520 cm_return_if_fail(tmpl != NULL);
10522 msg = g_strdup_printf(_("Do you want to apply the template '%s'?"),
10524 val = alertpanel(_("Apply template"), msg,
10525 _("_Replace"), _("_Insert"), GTK_STOCK_CANCEL);
10528 if (val == G_ALERTDEFAULT)
10529 compose_template_apply(compose, tmpl, TRUE);
10530 else if (val == G_ALERTALTERNATE)
10531 compose_template_apply(compose, tmpl, FALSE);
10534 static void compose_ext_editor_cb(GtkAction *action, gpointer data)
10536 Compose *compose = (Compose *)data;
10538 compose_exec_ext_editor(compose);
10541 static void compose_undo_cb(GtkAction *action, gpointer data)
10543 Compose *compose = (Compose *)data;
10544 gboolean prev_autowrap = compose->autowrap;
10546 compose->autowrap = FALSE;
10547 undo_undo(compose->undostruct);
10548 compose->autowrap = prev_autowrap;
10551 static void compose_redo_cb(GtkAction *action, gpointer data)
10553 Compose *compose = (Compose *)data;
10554 gboolean prev_autowrap = compose->autowrap;
10556 compose->autowrap = FALSE;
10557 undo_redo(compose->undostruct);
10558 compose->autowrap = prev_autowrap;
10561 static void entry_cut_clipboard(GtkWidget *entry)
10563 if (GTK_IS_EDITABLE(entry))
10564 gtk_editable_cut_clipboard (GTK_EDITABLE(entry));
10565 else if (GTK_IS_TEXT_VIEW(entry))
10566 gtk_text_buffer_cut_clipboard(
10567 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10568 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
10572 static void entry_copy_clipboard(GtkWidget *entry)
10574 if (GTK_IS_EDITABLE(entry))
10575 gtk_editable_copy_clipboard (GTK_EDITABLE(entry));
10576 else if (GTK_IS_TEXT_VIEW(entry))
10577 gtk_text_buffer_copy_clipboard(
10578 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10579 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
10582 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry,
10583 gboolean wrap, GdkAtom clip, GtkTextIter *insert_place)
10585 if (GTK_IS_TEXT_VIEW(entry)) {
10586 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10587 GtkTextMark *mark_start = gtk_text_buffer_get_insert(buffer);
10588 GtkTextIter start_iter, end_iter;
10590 gchar *contents = gtk_clipboard_wait_for_text(gtk_clipboard_get(clip));
10592 if (contents == NULL)
10595 /* we shouldn't delete the selection when middle-click-pasting, or we
10596 * can't mid-click-paste our own selection */
10597 if (clip != GDK_SELECTION_PRIMARY) {
10598 undo_paste_clipboard(GTK_TEXT_VIEW(compose->text), compose->undostruct);
10599 gtk_text_buffer_delete_selection(buffer, FALSE, TRUE);
10602 if (insert_place == NULL) {
10603 /* if insert_place isn't specified, insert at the cursor.
10604 * used for Ctrl-V pasting */
10605 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10606 start = gtk_text_iter_get_offset(&start_iter);
10607 gtk_text_buffer_insert(buffer, &start_iter, contents, strlen(contents));
10609 /* if insert_place is specified, paste here.
10610 * used for mid-click-pasting */
10611 start = gtk_text_iter_get_offset(insert_place);
10612 gtk_text_buffer_insert(buffer, insert_place, contents, strlen(contents));
10613 if (prefs_common.primary_paste_unselects)
10614 gtk_text_buffer_select_range(buffer, insert_place, insert_place);
10618 /* paste unwrapped: mark the paste so it's not wrapped later */
10619 end = start + strlen(contents);
10620 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, start);
10621 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, end);
10622 gtk_text_buffer_apply_tag_by_name(buffer, "no_wrap", &start_iter, &end_iter);
10623 } else if (wrap && clip == GDK_SELECTION_PRIMARY) {
10624 /* rewrap paragraph now (after a mid-click-paste) */
10625 mark_start = gtk_text_buffer_get_insert(buffer);
10626 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10627 gtk_text_iter_backward_char(&start_iter);
10628 compose_beautify_paragraph(compose, &start_iter, TRUE);
10630 } else if (GTK_IS_EDITABLE(entry))
10631 gtk_editable_paste_clipboard (GTK_EDITABLE(entry));
10633 compose->modified = TRUE;
10636 static void entry_allsel(GtkWidget *entry)
10638 if (GTK_IS_EDITABLE(entry))
10639 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
10640 else if (GTK_IS_TEXT_VIEW(entry)) {
10641 GtkTextIter startiter, enditer;
10642 GtkTextBuffer *textbuf;
10644 textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10645 gtk_text_buffer_get_start_iter(textbuf, &startiter);
10646 gtk_text_buffer_get_end_iter(textbuf, &enditer);
10648 gtk_text_buffer_move_mark_by_name(textbuf,
10649 "selection_bound", &startiter);
10650 gtk_text_buffer_move_mark_by_name(textbuf,
10651 "insert", &enditer);
10655 static void compose_cut_cb(GtkAction *action, gpointer data)
10657 Compose *compose = (Compose *)data;
10658 if (compose->focused_editable
10659 #ifndef GENERIC_UMPC
10660 && gtk_widget_has_focus(compose->focused_editable)
10663 entry_cut_clipboard(compose->focused_editable);
10666 static void compose_copy_cb(GtkAction *action, gpointer data)
10668 Compose *compose = (Compose *)data;
10669 if (compose->focused_editable
10670 #ifndef GENERIC_UMPC
10671 && gtk_widget_has_focus(compose->focused_editable)
10674 entry_copy_clipboard(compose->focused_editable);
10677 static void compose_paste_cb(GtkAction *action, gpointer data)
10679 Compose *compose = (Compose *)data;
10680 gint prev_autowrap;
10681 GtkTextBuffer *buffer;
10683 if (compose->focused_editable &&
10684 #ifndef GENERIC_UMPC
10685 gtk_widget_has_focus(compose->focused_editable)
10688 entry_paste_clipboard(compose, compose->focused_editable,
10689 prefs_common.linewrap_pastes,
10690 GDK_SELECTION_CLIPBOARD, NULL);
10695 #ifndef GENERIC_UMPC
10696 gtk_widget_has_focus(compose->text) &&
10698 compose->gtkaspell &&
10699 compose->gtkaspell->check_while_typing)
10700 gtkaspell_highlight_all(compose->gtkaspell);
10704 static void compose_paste_as_quote_cb(GtkAction *action, gpointer data)
10706 Compose *compose = (Compose *)data;
10707 gint wrap_quote = prefs_common.linewrap_quote;
10708 if (compose->focused_editable
10709 #ifndef GENERIC_UMPC
10710 && gtk_widget_has_focus(compose->focused_editable)
10713 /* let text_insert() (called directly or at a later time
10714 * after the gtk_editable_paste_clipboard) know that
10715 * text is to be inserted as a quotation. implemented
10716 * by using a simple refcount... */
10717 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data(
10718 G_OBJECT(compose->focused_editable),
10719 "paste_as_quotation"));
10720 g_object_set_data(G_OBJECT(compose->focused_editable),
10721 "paste_as_quotation",
10722 GINT_TO_POINTER(paste_as_quotation + 1));
10723 prefs_common.linewrap_quote = prefs_common.linewrap_pastes;
10724 entry_paste_clipboard(compose, compose->focused_editable,
10725 prefs_common.linewrap_pastes,
10726 GDK_SELECTION_CLIPBOARD, NULL);
10727 prefs_common.linewrap_quote = wrap_quote;
10731 static void compose_paste_no_wrap_cb(GtkAction *action, gpointer data)
10733 Compose *compose = (Compose *)data;
10734 gint prev_autowrap;
10735 GtkTextBuffer *buffer;
10737 if (compose->focused_editable
10738 #ifndef GENERIC_UMPC
10739 && gtk_widget_has_focus(compose->focused_editable)
10742 entry_paste_clipboard(compose, compose->focused_editable, FALSE,
10743 GDK_SELECTION_CLIPBOARD, NULL);
10748 #ifndef GENERIC_UMPC
10749 gtk_widget_has_focus(compose->text) &&
10751 compose->gtkaspell &&
10752 compose->gtkaspell->check_while_typing)
10753 gtkaspell_highlight_all(compose->gtkaspell);
10757 static void compose_paste_wrap_cb(GtkAction *action, gpointer data)
10759 Compose *compose = (Compose *)data;
10760 gint prev_autowrap;
10761 GtkTextBuffer *buffer;
10763 if (compose->focused_editable
10764 #ifndef GENERIC_UMPC
10765 && gtk_widget_has_focus(compose->focused_editable)
10768 entry_paste_clipboard(compose, compose->focused_editable, TRUE,
10769 GDK_SELECTION_CLIPBOARD, NULL);
10774 #ifndef GENERIC_UMPC
10775 gtk_widget_has_focus(compose->text) &&
10777 compose->gtkaspell &&
10778 compose->gtkaspell->check_while_typing)
10779 gtkaspell_highlight_all(compose->gtkaspell);
10783 static void compose_allsel_cb(GtkAction *action, gpointer data)
10785 Compose *compose = (Compose *)data;
10786 if (compose->focused_editable
10787 #ifndef GENERIC_UMPC
10788 && gtk_widget_has_focus(compose->focused_editable)
10791 entry_allsel(compose->focused_editable);
10794 static void textview_move_beginning_of_line (GtkTextView *text)
10796 GtkTextBuffer *buffer;
10800 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10802 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10803 mark = gtk_text_buffer_get_insert(buffer);
10804 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10805 gtk_text_iter_set_line_offset(&ins, 0);
10806 gtk_text_buffer_place_cursor(buffer, &ins);
10809 static void textview_move_forward_character (GtkTextView *text)
10811 GtkTextBuffer *buffer;
10815 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10817 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10818 mark = gtk_text_buffer_get_insert(buffer);
10819 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10820 if (gtk_text_iter_forward_cursor_position(&ins))
10821 gtk_text_buffer_place_cursor(buffer, &ins);
10824 static void textview_move_backward_character (GtkTextView *text)
10826 GtkTextBuffer *buffer;
10830 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10832 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10833 mark = gtk_text_buffer_get_insert(buffer);
10834 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10835 if (gtk_text_iter_backward_cursor_position(&ins))
10836 gtk_text_buffer_place_cursor(buffer, &ins);
10839 static void textview_move_forward_word (GtkTextView *text)
10841 GtkTextBuffer *buffer;
10846 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10848 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10849 mark = gtk_text_buffer_get_insert(buffer);
10850 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10851 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
10852 if (gtk_text_iter_forward_word_ends(&ins, count)) {
10853 gtk_text_iter_backward_word_start(&ins);
10854 gtk_text_buffer_place_cursor(buffer, &ins);
10858 static void textview_move_backward_word (GtkTextView *text)
10860 GtkTextBuffer *buffer;
10864 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10866 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10867 mark = gtk_text_buffer_get_insert(buffer);
10868 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10869 if (gtk_text_iter_backward_word_starts(&ins, 1))
10870 gtk_text_buffer_place_cursor(buffer, &ins);
10873 static void textview_move_end_of_line (GtkTextView *text)
10875 GtkTextBuffer *buffer;
10879 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10881 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10882 mark = gtk_text_buffer_get_insert(buffer);
10883 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10884 if (gtk_text_iter_forward_to_line_end(&ins))
10885 gtk_text_buffer_place_cursor(buffer, &ins);
10888 static void textview_move_next_line (GtkTextView *text)
10890 GtkTextBuffer *buffer;
10895 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10897 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10898 mark = gtk_text_buffer_get_insert(buffer);
10899 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10900 offset = gtk_text_iter_get_line_offset(&ins);
10901 if (gtk_text_iter_forward_line(&ins)) {
10902 gtk_text_iter_set_line_offset(&ins, offset);
10903 gtk_text_buffer_place_cursor(buffer, &ins);
10907 static void textview_move_previous_line (GtkTextView *text)
10909 GtkTextBuffer *buffer;
10914 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10916 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10917 mark = gtk_text_buffer_get_insert(buffer);
10918 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10919 offset = gtk_text_iter_get_line_offset(&ins);
10920 if (gtk_text_iter_backward_line(&ins)) {
10921 gtk_text_iter_set_line_offset(&ins, offset);
10922 gtk_text_buffer_place_cursor(buffer, &ins);
10926 static void textview_delete_forward_character (GtkTextView *text)
10928 GtkTextBuffer *buffer;
10930 GtkTextIter ins, end_iter;
10932 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10934 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10935 mark = gtk_text_buffer_get_insert(buffer);
10936 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10938 if (gtk_text_iter_forward_char(&end_iter)) {
10939 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10943 static void textview_delete_backward_character (GtkTextView *text)
10945 GtkTextBuffer *buffer;
10947 GtkTextIter ins, end_iter;
10949 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10951 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10952 mark = gtk_text_buffer_get_insert(buffer);
10953 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10955 if (gtk_text_iter_backward_char(&end_iter)) {
10956 gtk_text_buffer_delete(buffer, &end_iter, &ins);
10960 static void textview_delete_forward_word (GtkTextView *text)
10962 GtkTextBuffer *buffer;
10964 GtkTextIter ins, end_iter;
10966 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10968 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10969 mark = gtk_text_buffer_get_insert(buffer);
10970 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10972 if (gtk_text_iter_forward_word_end(&end_iter)) {
10973 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10977 static void textview_delete_backward_word (GtkTextView *text)
10979 GtkTextBuffer *buffer;
10981 GtkTextIter ins, end_iter;
10983 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10985 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10986 mark = gtk_text_buffer_get_insert(buffer);
10987 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10989 if (gtk_text_iter_backward_word_start(&end_iter)) {
10990 gtk_text_buffer_delete(buffer, &end_iter, &ins);
10994 static void textview_delete_line (GtkTextView *text)
10996 GtkTextBuffer *buffer;
10998 GtkTextIter ins, start_iter, end_iter;
11000 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11002 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11003 mark = gtk_text_buffer_get_insert(buffer);
11004 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11007 gtk_text_iter_set_line_offset(&start_iter, 0);
11010 if (gtk_text_iter_ends_line(&end_iter)){
11011 if (!gtk_text_iter_forward_char(&end_iter))
11012 gtk_text_iter_backward_char(&start_iter);
11015 gtk_text_iter_forward_to_line_end(&end_iter);
11016 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
11019 static void textview_delete_to_line_end (GtkTextView *text)
11021 GtkTextBuffer *buffer;
11023 GtkTextIter ins, end_iter;
11025 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11027 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11028 mark = gtk_text_buffer_get_insert(buffer);
11029 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11031 if (gtk_text_iter_ends_line(&end_iter))
11032 gtk_text_iter_forward_char(&end_iter);
11034 gtk_text_iter_forward_to_line_end(&end_iter);
11035 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11038 #define DO_ACTION(name, act) { \
11039 if(!strcmp(name, a_name)) { \
11043 static ComposeCallAdvancedAction compose_call_advanced_action_from_path(GtkAction *action)
11045 const gchar *a_name = gtk_action_get_name(action);
11046 DO_ACTION("Edit/Advanced/BackChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER);
11047 DO_ACTION("Edit/Advanced/ForwChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER);
11048 DO_ACTION("Edit/Advanced/BackWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD);
11049 DO_ACTION("Edit/Advanced/ForwWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD);
11050 DO_ACTION("Edit/Advanced/BegLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE);
11051 DO_ACTION("Edit/Advanced/EndLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE);
11052 DO_ACTION("Edit/Advanced/PrevLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE);
11053 DO_ACTION("Edit/Advanced/NextLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE);
11054 DO_ACTION("Edit/Advanced/DelBackChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER);
11055 DO_ACTION("Edit/Advanced/DelForwChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER);
11056 DO_ACTION("Edit/Advanced/DelBackWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD);
11057 DO_ACTION("Edit/Advanced/DelForwWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD);
11058 DO_ACTION("Edit/Advanced/DelLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE);
11059 DO_ACTION("Edit/Advanced/DelEndLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END);
11063 static void compose_advanced_action_cb(GtkAction *gaction, gpointer data)
11065 Compose *compose = (Compose *)data;
11066 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11067 ComposeCallAdvancedAction action = -1;
11069 action = compose_call_advanced_action_from_path(gaction);
11072 void (*do_action) (GtkTextView *text);
11073 } action_table[] = {
11074 {textview_move_beginning_of_line},
11075 {textview_move_forward_character},
11076 {textview_move_backward_character},
11077 {textview_move_forward_word},
11078 {textview_move_backward_word},
11079 {textview_move_end_of_line},
11080 {textview_move_next_line},
11081 {textview_move_previous_line},
11082 {textview_delete_forward_character},
11083 {textview_delete_backward_character},
11084 {textview_delete_forward_word},
11085 {textview_delete_backward_word},
11086 {textview_delete_line},
11087 {textview_delete_to_line_end}
11090 if (!gtk_widget_has_focus(GTK_WIDGET(text))) return;
11092 if (action >= COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE &&
11093 action <= COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END) {
11094 if (action_table[action].do_action)
11095 action_table[action].do_action(text);
11097 g_warning("Not implemented yet.");
11101 static void compose_grab_focus_cb(GtkWidget *widget, Compose *compose)
11103 GtkAllocation allocation;
11107 if (GTK_IS_EDITABLE(widget)) {
11108 str = gtk_editable_get_chars(GTK_EDITABLE(widget), 0, -1);
11109 gtk_editable_set_position(GTK_EDITABLE(widget),
11112 if ((parent = gtk_widget_get_parent(widget))
11113 && (parent = gtk_widget_get_parent(parent))
11114 && (parent = gtk_widget_get_parent(parent))) {
11115 if (GTK_IS_SCROLLED_WINDOW(parent)) {
11116 gtk_widget_get_allocation(widget, &allocation);
11117 gint y = allocation.y;
11118 gint height = allocation.height;
11119 GtkAdjustment *shown = gtk_scrolled_window_get_vadjustment
11120 (GTK_SCROLLED_WINDOW(parent));
11122 gfloat value = gtk_adjustment_get_value(shown);
11123 gfloat upper = gtk_adjustment_get_upper(shown);
11124 gfloat page_size = gtk_adjustment_get_page_size(shown);
11125 if (y < (int)value) {
11126 gtk_adjustment_set_value(shown, y - 1);
11128 if ((y + height) > ((int)value + (int)page_size)) {
11129 if ((y - height - 1) < ((int)upper - (int)page_size)) {
11130 gtk_adjustment_set_value(shown,
11131 y + height - (int)page_size - 1);
11133 gtk_adjustment_set_value(shown,
11134 (int)upper - (int)page_size - 1);
11141 if (GTK_IS_EDITABLE(widget) || GTK_IS_TEXT_VIEW(widget))
11142 compose->focused_editable = widget;
11144 #ifdef GENERIC_UMPC
11145 if (GTK_IS_TEXT_VIEW(widget)
11146 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->edit_vbox) {
11147 g_object_ref(compose->notebook);
11148 g_object_ref(compose->edit_vbox);
11149 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
11150 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
11151 gtk_paned_add1(GTK_PANED(compose->paned), compose->edit_vbox);
11152 gtk_paned_add2(GTK_PANED(compose->paned), compose->notebook);
11153 g_object_unref(compose->notebook);
11154 g_object_unref(compose->edit_vbox);
11155 g_signal_handlers_block_by_func(G_OBJECT(widget),
11156 G_CALLBACK(compose_grab_focus_cb),
11158 gtk_widget_grab_focus(widget);
11159 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
11160 G_CALLBACK(compose_grab_focus_cb),
11162 } else if (!GTK_IS_TEXT_VIEW(widget)
11163 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->notebook) {
11164 g_object_ref(compose->notebook);
11165 g_object_ref(compose->edit_vbox);
11166 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
11167 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
11168 gtk_paned_add1(GTK_PANED(compose->paned), compose->notebook);
11169 gtk_paned_add2(GTK_PANED(compose->paned), compose->edit_vbox);
11170 g_object_unref(compose->notebook);
11171 g_object_unref(compose->edit_vbox);
11172 g_signal_handlers_block_by_func(G_OBJECT(widget),
11173 G_CALLBACK(compose_grab_focus_cb),
11175 gtk_widget_grab_focus(widget);
11176 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
11177 G_CALLBACK(compose_grab_focus_cb),
11183 static void compose_changed_cb(GtkTextBuffer *textbuf, Compose *compose)
11185 compose->modified = TRUE;
11186 // compose_beautify_paragraph(compose, NULL, TRUE);
11187 #ifndef GENERIC_UMPC
11188 compose_set_title(compose);
11192 static void compose_wrap_cb(GtkAction *action, gpointer data)
11194 Compose *compose = (Compose *)data;
11195 compose_beautify_paragraph(compose, NULL, TRUE);
11198 static void compose_wrap_all_cb(GtkAction *action, gpointer data)
11200 Compose *compose = (Compose *)data;
11201 compose_wrap_all_full(compose, TRUE);
11204 static void compose_find_cb(GtkAction *action, gpointer data)
11206 Compose *compose = (Compose *)data;
11208 message_search_compose(compose);
11211 static void compose_toggle_autowrap_cb(GtkToggleAction *action,
11214 Compose *compose = (Compose *)data;
11215 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11216 if (compose->autowrap)
11217 compose_wrap_all_full(compose, TRUE);
11218 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11221 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
11224 Compose *compose = (Compose *)data;
11225 compose->autoindent = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11228 static void compose_toggle_sign_cb(GtkToggleAction *action, gpointer data)
11230 Compose *compose = (Compose *)data;
11232 compose->use_signing = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11235 static void compose_toggle_encrypt_cb(GtkToggleAction *action, gpointer data)
11237 Compose *compose = (Compose *)data;
11239 compose->use_encryption = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11242 static void activate_privacy_system(Compose *compose, PrefsAccount *account, gboolean warn)
11244 g_free(compose->privacy_system);
11245 g_free(compose->encdata);
11247 compose->privacy_system = g_strdup(account->default_privacy_system);
11248 compose_update_privacy_system_menu_item(compose, warn);
11251 static void compose_toggle_ruler_cb(GtkToggleAction *action, gpointer data)
11253 Compose *compose = (Compose *)data;
11255 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action))) {
11256 gtk_widget_show(compose->ruler_hbox);
11257 prefs_common.show_ruler = TRUE;
11259 gtk_widget_hide(compose->ruler_hbox);
11260 gtk_widget_queue_resize(compose->edit_vbox);
11261 prefs_common.show_ruler = FALSE;
11265 static void compose_attach_drag_received_cb (GtkWidget *widget,
11266 GdkDragContext *context,
11269 GtkSelectionData *data,
11272 gpointer user_data)
11274 Compose *compose = (Compose *)user_data;
11278 type = gtk_selection_data_get_data_type(data);
11279 if ((gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list"))
11280 && gtk_drag_get_source_widget(context) !=
11281 summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
11282 list = uri_list_extract_filenames(
11283 (const gchar *)gtk_selection_data_get_data(data));
11284 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11285 gchar *utf8_filename = conv_filename_to_utf8((const gchar *)tmp->data);
11286 compose_attach_append
11287 (compose, (const gchar *)tmp->data,
11288 utf8_filename, NULL, NULL);
11289 g_free(utf8_filename);
11291 if (list) compose_changed_cb(NULL, compose);
11292 list_free_strings(list);
11294 } else if (gtk_drag_get_source_widget(context)
11295 == summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
11296 /* comes from our summaryview */
11297 SummaryView * summaryview = NULL;
11298 GSList * list = NULL, *cur = NULL;
11300 if (mainwindow_get_mainwindow())
11301 summaryview = mainwindow_get_mainwindow()->summaryview;
11304 list = summary_get_selected_msg_list(summaryview);
11306 for (cur = list; cur; cur = cur->next) {
11307 MsgInfo *msginfo = (MsgInfo *)cur->data;
11308 gchar *file = NULL;
11310 file = procmsg_get_message_file_full(msginfo,
11313 compose_attach_append(compose, (const gchar *)file,
11314 (const gchar *)file, "message/rfc822", NULL);
11318 g_slist_free(list);
11322 static gboolean compose_drag_drop(GtkWidget *widget,
11323 GdkDragContext *drag_context,
11325 guint time, gpointer user_data)
11327 /* not handling this signal makes compose_insert_drag_received_cb
11332 static gboolean completion_set_focus_to_subject
11333 (GtkWidget *widget,
11334 GdkEventKey *event,
11337 cm_return_val_if_fail(compose != NULL, FALSE);
11339 /* make backtab move to subject field */
11340 if(event->keyval == GDK_KEY_ISO_Left_Tab) {
11341 gtk_widget_grab_focus(compose->subject_entry);
11347 static void compose_insert_drag_received_cb (GtkWidget *widget,
11348 GdkDragContext *drag_context,
11351 GtkSelectionData *data,
11354 gpointer user_data)
11356 Compose *compose = (Compose *)user_data;
11360 /* strangely, testing data->type == gdk_atom_intern("text/uri-list", TRUE)
11362 type = gtk_selection_data_get_data_type(data);
11363 if (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list")) {
11364 AlertValue val = G_ALERTDEFAULT;
11365 const gchar* ddata = (const gchar *)gtk_selection_data_get_data(data);
11367 list = uri_list_extract_filenames(ddata);
11368 if (list == NULL && strstr(ddata, "://")) {
11369 /* Assume a list of no files, and data has ://, is a remote link */
11370 gchar *tmpdata = g_strstrip(g_strdup(ddata));
11371 gchar *tmpfile = get_tmp_file();
11372 str_write_to_file(tmpdata, tmpfile);
11374 compose_insert_file(compose, tmpfile);
11375 claws_unlink(tmpfile);
11377 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11378 compose_beautify_paragraph(compose, NULL, TRUE);
11381 switch (prefs_common.compose_dnd_mode) {
11382 case COMPOSE_DND_ASK:
11383 val = alertpanel_full(_("Insert or attach?"),
11384 _("Do you want to insert the contents of the file(s) "
11385 "into the message body, or attach it to the email?"),
11386 GTK_STOCK_CANCEL, g_strconcat("+", _("_Insert"), NULL), _("_Attach"),
11387 TRUE, NULL, ALERT_QUESTION, G_ALERTALTERNATE);
11389 case COMPOSE_DND_INSERT:
11390 val = G_ALERTALTERNATE;
11392 case COMPOSE_DND_ATTACH:
11393 val = G_ALERTOTHER;
11396 /* unexpected case */
11397 g_warning("error: unexpected compose_dnd_mode option value in compose_insert_drag_received_cb()");
11400 if (val & G_ALERTDISABLE) {
11401 val &= ~G_ALERTDISABLE;
11402 /* remember what action to perform by default, only if we don't click Cancel */
11403 if (val == G_ALERTALTERNATE)
11404 prefs_common.compose_dnd_mode = COMPOSE_DND_INSERT;
11405 else if (val == G_ALERTOTHER)
11406 prefs_common.compose_dnd_mode = COMPOSE_DND_ATTACH;
11409 if (val == G_ALERTDEFAULT || val == G_ALERTCANCEL) {
11410 gtk_drag_finish(drag_context, FALSE, FALSE, time);
11411 list_free_strings(list);
11414 } else if (val == G_ALERTOTHER) {
11415 compose_attach_drag_received_cb(widget, drag_context, x, y, data, info, time, user_data);
11416 list_free_strings(list);
11421 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11422 compose_insert_file(compose, (const gchar *)tmp->data);
11424 list_free_strings(list);
11426 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11431 static void compose_header_drag_received_cb (GtkWidget *widget,
11432 GdkDragContext *drag_context,
11435 GtkSelectionData *data,
11438 gpointer user_data)
11440 GtkEditable *entry = (GtkEditable *)user_data;
11441 const gchar *email = (const gchar *)gtk_selection_data_get_data(data);
11443 /* strangely, testing data->type == gdk_atom_intern("text/plain", TRUE)
11446 if (!strncmp(email, "mailto:", strlen("mailto:"))) {
11447 gchar *decoded=g_new(gchar, strlen(email));
11450 decode_uri(decoded, email + strlen("mailto:")); /* will fit */
11451 gtk_editable_delete_text(entry, 0, -1);
11452 gtk_editable_insert_text(entry, decoded, strlen(decoded), &start);
11453 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11457 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11460 static void compose_toggle_return_receipt_cb(GtkToggleAction *action, gpointer data)
11462 Compose *compose = (Compose *)data;
11464 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11465 compose->return_receipt = TRUE;
11467 compose->return_receipt = FALSE;
11470 static void compose_toggle_remove_refs_cb(GtkToggleAction *action, gpointer data)
11472 Compose *compose = (Compose *)data;
11474 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11475 compose->remove_references = TRUE;
11477 compose->remove_references = FALSE;
11480 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
11481 ComposeHeaderEntry *headerentry)
11483 gtk_entry_set_text(GTK_ENTRY(headerentry->entry), "");
11487 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
11488 GdkEventKey *event,
11489 ComposeHeaderEntry *headerentry)
11491 if ((g_slist_length(headerentry->compose->header_list) > 0) &&
11492 ((headerentry->headernum + 1) != headerentry->compose->header_nextrow) &&
11493 !(event->state & GDK_MODIFIER_MASK) &&
11494 (event->keyval == GDK_KEY_BackSpace) &&
11495 (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) == 0)) {
11496 gtk_container_remove
11497 (GTK_CONTAINER(headerentry->compose->header_table),
11498 headerentry->combo);
11499 gtk_container_remove
11500 (GTK_CONTAINER(headerentry->compose->header_table),
11501 headerentry->entry);
11502 headerentry->compose->header_list =
11503 g_slist_remove(headerentry->compose->header_list,
11505 g_free(headerentry);
11506 } else if (event->keyval == GDK_KEY_Tab) {
11507 if (headerentry->compose->header_last == headerentry) {
11508 /* Override default next focus, and give it to subject_entry
11509 * instead of notebook tabs
11511 g_signal_stop_emission_by_name(G_OBJECT(entry), "key-press-event");
11512 gtk_widget_grab_focus(headerentry->compose->subject_entry);
11519 static gboolean scroll_postpone(gpointer data)
11521 Compose *compose = (Compose *)data;
11523 if (compose->batch)
11526 GTK_EVENTS_FLUSH();
11527 compose_show_first_last_header(compose, FALSE);
11531 static void compose_headerentry_changed_cb(GtkWidget *entry,
11532 ComposeHeaderEntry *headerentry)
11534 if (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) != 0) {
11535 compose_create_header_entry(headerentry->compose);
11536 g_signal_handlers_disconnect_matched
11537 (G_OBJECT(entry), G_SIGNAL_MATCH_DATA,
11538 0, 0, NULL, NULL, headerentry);
11540 if (!headerentry->compose->batch)
11541 g_timeout_add(0, scroll_postpone, headerentry->compose);
11545 static gboolean compose_defer_auto_save_draft(Compose *compose)
11547 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
11548 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
11552 static void compose_show_first_last_header(Compose *compose, gboolean show_first)
11554 GtkAdjustment *vadj;
11556 cm_return_if_fail(compose);
11561 cm_return_if_fail(GTK_IS_WIDGET(compose->header_table));
11562 cm_return_if_fail(GTK_IS_VIEWPORT(gtk_widget_get_parent(compose->header_table)));
11563 vadj = gtk_viewport_get_vadjustment(GTK_VIEWPORT(
11564 gtk_widget_get_parent(compose->header_table)));
11565 gtk_adjustment_set_value(vadj, (show_first ?
11566 gtk_adjustment_get_lower(vadj) :
11567 (gtk_adjustment_get_upper(vadj) -
11568 gtk_adjustment_get_page_size(vadj))));
11569 gtk_adjustment_changed(vadj);
11572 static void text_inserted(GtkTextBuffer *buffer, GtkTextIter *iter,
11573 const gchar *text, gint len, Compose *compose)
11575 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data
11576 (G_OBJECT(compose->text), "paste_as_quotation"));
11579 cm_return_if_fail(text != NULL);
11581 g_signal_handlers_block_by_func(G_OBJECT(buffer),
11582 G_CALLBACK(text_inserted),
11584 if (paste_as_quotation) {
11586 const gchar *qmark;
11588 GtkTextIter start_iter;
11591 len = strlen(text);
11593 new_text = g_strndup(text, len);
11595 qmark = compose_quote_char_from_context(compose);
11597 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11598 gtk_text_buffer_place_cursor(buffer, iter);
11600 pos = gtk_text_iter_get_offset(iter);
11602 compose_quote_fmt(compose, NULL, "%Q", qmark, new_text, TRUE, FALSE,
11603 _("Quote format error at line %d."));
11604 quote_fmt_reset_vartable();
11606 g_object_set_data(G_OBJECT(compose->text), "paste_as_quotation",
11607 GINT_TO_POINTER(paste_as_quotation - 1));
11609 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11610 gtk_text_buffer_place_cursor(buffer, iter);
11611 gtk_text_buffer_delete_mark(buffer, mark);
11613 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, pos);
11614 mark = gtk_text_buffer_create_mark(buffer, NULL, &start_iter, FALSE);
11615 compose_beautify_paragraph(compose, &start_iter, FALSE);
11616 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark);
11617 gtk_text_buffer_delete_mark(buffer, mark);
11619 if (strcmp(text, "\n") || compose->automatic_break
11620 || gtk_text_iter_starts_line(iter)) {
11621 GtkTextIter before_ins;
11622 gtk_text_buffer_insert(buffer, iter, text, len);
11623 if (!strstr(text, "\n") && gtk_text_iter_has_tag(iter, compose->no_join_tag)) {
11624 before_ins = *iter;
11625 gtk_text_iter_backward_chars(&before_ins, len);
11626 gtk_text_buffer_remove_tag_by_name(buffer, "no_join", &before_ins, iter);
11629 /* check if the preceding is just whitespace or quote */
11630 GtkTextIter start_line;
11631 gchar *tmp = NULL, *quote = NULL;
11632 gint quote_len = 0, is_normal = 0;
11633 start_line = *iter;
11634 gtk_text_iter_set_line_offset(&start_line, 0);
11635 tmp = gtk_text_buffer_get_text(buffer, &start_line, iter, FALSE);
11638 if (*tmp == '\0') {
11641 quote = compose_get_quote_str(buffer, &start_line, "e_len);
11649 gtk_text_buffer_insert(buffer, iter, text, len);
11651 gtk_text_buffer_insert_with_tags_by_name(buffer,
11652 iter, text, len, "no_join", NULL);
11657 if (!paste_as_quotation) {
11658 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11659 compose_beautify_paragraph(compose, iter, FALSE);
11660 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11661 gtk_text_buffer_delete_mark(buffer, mark);
11664 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
11665 G_CALLBACK(text_inserted),
11667 g_signal_stop_emission_by_name(G_OBJECT(buffer), "insert-text");
11669 if (compose_can_autosave(compose) &&
11670 gtk_text_buffer_get_char_count(buffer) % prefs_common.autosave_length == 0 &&
11671 compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN /* disabled while loading */)
11672 compose->draft_timeout_tag = g_timeout_add
11673 (500, (GSourceFunc) compose_defer_auto_save_draft, compose);
11677 static void compose_check_all(GtkAction *action, gpointer data)
11679 Compose *compose = (Compose *)data;
11680 if (!compose->gtkaspell)
11683 if (gtk_widget_has_focus(compose->subject_entry))
11684 claws_spell_entry_check_all(
11685 CLAWS_SPELL_ENTRY(compose->subject_entry));
11687 gtkaspell_check_all(compose->gtkaspell);
11690 static void compose_highlight_all(GtkAction *action, gpointer data)
11692 Compose *compose = (Compose *)data;
11693 if (compose->gtkaspell) {
11694 claws_spell_entry_recheck_all(
11695 CLAWS_SPELL_ENTRY(compose->subject_entry));
11696 gtkaspell_highlight_all(compose->gtkaspell);
11700 static void compose_check_backwards(GtkAction *action, gpointer data)
11702 Compose *compose = (Compose *)data;
11703 if (!compose->gtkaspell) {
11704 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
11708 if (gtk_widget_has_focus(compose->subject_entry))
11709 claws_spell_entry_check_backwards(
11710 CLAWS_SPELL_ENTRY(compose->subject_entry));
11712 gtkaspell_check_backwards(compose->gtkaspell);
11715 static void compose_check_forwards_go(GtkAction *action, gpointer data)
11717 Compose *compose = (Compose *)data;
11718 if (!compose->gtkaspell) {
11719 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
11723 if (gtk_widget_has_focus(compose->subject_entry))
11724 claws_spell_entry_check_forwards_go(
11725 CLAWS_SPELL_ENTRY(compose->subject_entry));
11727 gtkaspell_check_forwards_go(compose->gtkaspell);
11732 *\brief Guess originating forward account from MsgInfo and several
11733 * "common preference" settings. Return NULL if no guess.
11735 static PrefsAccount *compose_guess_forward_account_from_msginfo(MsgInfo *msginfo)
11737 PrefsAccount *account = NULL;
11739 cm_return_val_if_fail(msginfo, NULL);
11740 cm_return_val_if_fail(msginfo->folder, NULL);
11741 cm_return_val_if_fail(msginfo->folder->prefs, NULL);
11743 if (msginfo->folder->prefs->enable_default_account)
11744 account = account_find_from_id(msginfo->folder->prefs->default_account);
11747 account = msginfo->folder->folder->account;
11749 if (!account && msginfo->to && prefs_common.forward_account_autosel) {
11751 Xstrdup_a(to, msginfo->to, return NULL);
11752 extract_address(to);
11753 account = account_find_from_address(to, FALSE);
11756 if (!account && prefs_common.forward_account_autosel) {
11757 gchar cc[BUFFSIZE];
11758 if (!procheader_get_header_from_msginfo
11759 (msginfo, cc,sizeof cc , "Cc:")) {
11760 gchar *buf = cc + strlen("Cc:");
11761 extract_address(buf);
11762 account = account_find_from_address(buf, FALSE);
11766 if (!account && prefs_common.forward_account_autosel) {
11767 gchar deliveredto[BUFFSIZE];
11768 if (!procheader_get_header_from_msginfo
11769 (msginfo, deliveredto,sizeof deliveredto , "Delivered-To:")) {
11770 gchar *buf = deliveredto + strlen("Delivered-To:");
11771 extract_address(buf);
11772 account = account_find_from_address(buf, FALSE);
11779 gboolean compose_close(Compose *compose)
11783 cm_return_val_if_fail(compose, FALSE);
11785 if (!g_mutex_trylock(compose->mutex)) {
11786 /* we have to wait for the (possibly deferred by auto-save)
11787 * drafting to be done, before destroying the compose under
11789 debug_print("waiting for drafting to finish...\n");
11790 compose_allow_user_actions(compose, FALSE);
11791 if (compose->close_timeout_tag == 0) {
11792 compose->close_timeout_tag =
11793 g_timeout_add (500, (GSourceFunc) compose_close,
11799 if (compose->draft_timeout_tag >= 0) {
11800 g_source_remove(compose->draft_timeout_tag);
11801 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN;
11804 gtkut_widget_get_uposition(compose->window, &x, &y);
11805 if (!compose->batch) {
11806 prefs_common.compose_x = x;
11807 prefs_common.compose_y = y;
11809 g_mutex_unlock(compose->mutex);
11810 compose_destroy(compose);
11815 * Add entry field for each address in list.
11816 * \param compose E-Mail composition object.
11817 * \param listAddress List of (formatted) E-Mail addresses.
11819 static void compose_add_field_list( Compose *compose, GList *listAddress ) {
11822 node = listAddress;
11824 addr = ( gchar * ) node->data;
11825 compose_entry_append( compose, addr, COMPOSE_TO, PREF_NONE );
11826 node = g_list_next( node );
11830 static void compose_reply_from_messageview_real(MessageView *msgview, GSList *msginfo_list,
11831 guint action, gboolean opening_multiple)
11833 gchar *body = NULL;
11834 GSList *new_msglist = NULL;
11835 MsgInfo *tmp_msginfo = NULL;
11836 gboolean originally_enc = FALSE;
11837 gboolean originally_sig = FALSE;
11838 Compose *compose = NULL;
11839 gchar *s_system = NULL;
11841 cm_return_if_fail(msgview != NULL);
11843 cm_return_if_fail(msginfo_list != NULL);
11845 if (g_slist_length(msginfo_list) == 1 && !opening_multiple) {
11846 MimeInfo *mimeinfo = messageview_get_selected_mime_part(msgview);
11847 MsgInfo *orig_msginfo = (MsgInfo *)msginfo_list->data;
11849 if (mimeinfo != NULL && mimeinfo->type == MIMETYPE_MESSAGE &&
11850 !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
11851 tmp_msginfo = procmsg_msginfo_new_from_mimeinfo(
11852 orig_msginfo, mimeinfo);
11853 if (tmp_msginfo != NULL) {
11854 new_msglist = g_slist_append(NULL, tmp_msginfo);
11856 originally_enc = MSG_IS_ENCRYPTED(orig_msginfo->flags);
11857 privacy_msginfo_get_signed_state(orig_msginfo, &s_system);
11858 originally_sig = MSG_IS_SIGNED(orig_msginfo->flags);
11860 tmp_msginfo->folder = orig_msginfo->folder;
11861 tmp_msginfo->msgnum = orig_msginfo->msgnum;
11862 if (orig_msginfo->tags) {
11863 tmp_msginfo->tags = g_slist_copy(orig_msginfo->tags);
11864 tmp_msginfo->folder->tags_dirty = TRUE;
11870 if (!opening_multiple)
11871 body = messageview_get_selection(msgview);
11874 compose = compose_reply_mode((ComposeMode)action, new_msglist, body);
11875 procmsg_msginfo_free(&tmp_msginfo);
11876 g_slist_free(new_msglist);
11878 compose = compose_reply_mode((ComposeMode)action, msginfo_list, body);
11880 if (compose && originally_enc) {
11881 compose_force_encryption(compose, compose->account, FALSE, s_system);
11884 if (compose && originally_sig && compose->account->default_sign_reply) {
11885 compose_force_signing(compose, compose->account, s_system);
11889 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
11892 void compose_reply_from_messageview(MessageView *msgview, GSList *msginfo_list,
11895 if ((!prefs_common.forward_as_attachment || action != COMPOSE_FORWARD)
11896 && action != COMPOSE_FORWARD_AS_ATTACH && g_slist_length(msginfo_list) > 1) {
11897 GSList *cur = msginfo_list;
11898 gchar *msg = g_strdup_printf(_("You are about to reply to %d "
11899 "messages. Opening the windows "
11900 "could take some time. Do you "
11901 "want to continue?"),
11902 g_slist_length(msginfo_list));
11903 if (g_slist_length(msginfo_list) > 9
11904 && alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, "+" GTK_STOCK_YES, NULL)
11905 != G_ALERTALTERNATE) {
11910 /* We'll open multiple compose windows */
11911 /* let the WM place the next windows */
11912 compose_force_window_origin = FALSE;
11913 for (; cur; cur = cur->next) {
11915 tmplist.data = cur->data;
11916 tmplist.next = NULL;
11917 compose_reply_from_messageview_real(msgview, &tmplist, action, TRUE);
11919 compose_force_window_origin = TRUE;
11921 /* forwarding multiple mails as attachments is done via a
11922 * single compose window */
11923 compose_reply_from_messageview_real(msgview, msginfo_list, action, FALSE);
11927 void compose_check_for_email_account(Compose *compose)
11929 PrefsAccount *ac = NULL, *curr = NULL;
11935 if (compose->account && compose->account->protocol == A_NNTP) {
11936 ac = account_get_cur_account();
11937 if (ac->protocol == A_NNTP) {
11938 list = account_get_list();
11940 for( ; list != NULL ; list = g_list_next(list)) {
11941 curr = (PrefsAccount *) list->data;
11942 if (curr->protocol != A_NNTP) {
11948 combobox_select_by_data(GTK_COMBO_BOX(compose->account_combo),
11953 void compose_reply_to_address(MessageView *msgview, MsgInfo *msginfo,
11954 const gchar *address)
11956 GSList *msginfo_list = NULL;
11957 gchar *body = messageview_get_selection(msgview);
11960 msginfo_list = g_slist_prepend(msginfo_list, msginfo);
11962 compose = compose_reply_mode(COMPOSE_REPLY_TO_ADDRESS, msginfo_list, body);
11963 compose_check_for_email_account(compose);
11964 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
11965 compose_entry_append(compose, address, COMPOSE_TO, PREF_NONE);
11966 compose_reply_set_subject(compose, msginfo);
11969 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
11972 void compose_set_position(Compose *compose, gint pos)
11974 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11976 gtkut_text_view_set_position(text, pos);
11979 gboolean compose_search_string(Compose *compose,
11980 const gchar *str, gboolean case_sens)
11982 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11984 return gtkut_text_view_search_string(text, str, case_sens);
11987 gboolean compose_search_string_backward(Compose *compose,
11988 const gchar *str, gboolean case_sens)
11990 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11992 return gtkut_text_view_search_string_backward(text, str, case_sens);
11995 /* allocate a msginfo structure and populate its data from a compose data structure */
11996 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose)
11998 MsgInfo *newmsginfo;
12000 gchar buf[BUFFSIZE];
12002 cm_return_val_if_fail( compose != NULL, NULL );
12004 newmsginfo = procmsg_msginfo_new();
12007 get_rfc822_date(buf, sizeof(buf));
12008 newmsginfo->date = g_strdup(buf);
12011 if (compose->from_name) {
12012 newmsginfo->from = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
12013 newmsginfo->fromname = procheader_get_fromname(newmsginfo->from);
12017 if (compose->subject_entry)
12018 newmsginfo->subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
12020 /* to, cc, reply-to, newsgroups */
12021 for (list = compose->header_list; list; list = list->next) {
12022 gchar *header = gtk_editable_get_chars(
12024 gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
12025 gchar *entry = gtk_editable_get_chars(
12026 GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
12028 if ( strcasecmp(header, prefs_common_translated_header_name("To:")) == 0 ) {
12029 if ( newmsginfo->to == NULL ) {
12030 newmsginfo->to = g_strdup(entry);
12031 } else if (entry && *entry) {
12032 gchar *tmp = g_strconcat(newmsginfo->to, ", ", entry, NULL);
12033 g_free(newmsginfo->to);
12034 newmsginfo->to = tmp;
12037 if ( strcasecmp(header, prefs_common_translated_header_name("Cc:")) == 0 ) {
12038 if ( newmsginfo->cc == NULL ) {
12039 newmsginfo->cc = g_strdup(entry);
12040 } else if (entry && *entry) {
12041 gchar *tmp = g_strconcat(newmsginfo->cc, ", ", entry, NULL);
12042 g_free(newmsginfo->cc);
12043 newmsginfo->cc = tmp;
12046 if ( strcasecmp(header,
12047 prefs_common_translated_header_name("Newsgroups:")) == 0 ) {
12048 if ( newmsginfo->newsgroups == NULL ) {
12049 newmsginfo->newsgroups = g_strdup(entry);
12050 } else if (entry && *entry) {
12051 gchar *tmp = g_strconcat(newmsginfo->newsgroups, ", ", entry, NULL);
12052 g_free(newmsginfo->newsgroups);
12053 newmsginfo->newsgroups = tmp;
12061 /* other data is unset */
12067 /* update compose's dictionaries from folder dict settings */
12068 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
12069 FolderItem *folder_item)
12071 cm_return_if_fail(compose != NULL);
12073 if (compose->gtkaspell && folder_item && folder_item->prefs) {
12074 FolderItemPrefs *prefs = folder_item->prefs;
12076 if (prefs->enable_default_dictionary)
12077 gtkaspell_change_dict(compose->gtkaspell,
12078 prefs->default_dictionary, FALSE);
12079 if (folder_item->prefs->enable_default_alt_dictionary)
12080 gtkaspell_change_alt_dict(compose->gtkaspell,
12081 prefs->default_alt_dictionary);
12082 if (prefs->enable_default_dictionary
12083 || prefs->enable_default_alt_dictionary)
12084 compose_spell_menu_changed(compose);
12089 static void compose_subject_entry_activated(GtkWidget *widget, gpointer data)
12091 Compose *compose = (Compose *)data;
12093 cm_return_if_fail(compose != NULL);
12095 gtk_widget_grab_focus(compose->text);
12098 static void from_name_activate_cb(GtkWidget *widget, gpointer data)
12100 gtk_combo_box_popup(GTK_COMBO_BOX(data));