2 * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2016 Hiroyuki Yamamoto and the Claws Mail team
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 3 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
21 #include "claws-features.h"
26 #ifndef PANGO_ENABLE_ENGINE
27 # define PANGO_ENABLE_ENGINE
31 #include <glib/gi18n.h>
32 #include <gdk/gdkkeysyms.h>
35 #include <pango/pango-break.h>
40 #include <sys/types.h>
46 # include <sys/wait.h>
50 #ifndef G_OS_WIN32 /* fixme we should have a configure test. */
54 #if (HAVE_WCTYPE_H && HAVE_WCHAR_H)
61 #include "mainwindow.h"
63 #ifndef USE_ALT_ADDRBOOK
64 #include "addressbook.h"
66 #include "addressbook-dbus.h"
67 #include "addressadd.h"
69 #include "folderview.h"
72 #include "stock_pixmap.h"
73 #include "send_message.h"
76 #include "customheader.h"
77 #include "prefs_common.h"
78 #include "prefs_account.h"
82 #include "procheader.h"
84 #include "statusbar.h"
86 #include "quoted-printable.h"
90 #include "gtkshruler.h"
92 #include "alertpanel.h"
93 #include "manage_window.h"
95 #include "folder_item_prefs.h"
96 #include "addr_compl.h"
97 #include "quote_fmt.h"
99 #include "foldersel.h"
102 #include "message_search.h"
103 #include "combobox.h"
107 #include "autofaces.h"
108 #include "spell_entry.h"
121 #define N_ATTACH_COLS (N_COL_COLUMNS)
125 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE,
126 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER,
127 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER,
128 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD,
129 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD,
130 COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE,
131 COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE,
132 COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE,
133 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER,
134 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER,
135 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD,
136 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD,
137 COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE,
138 COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END
139 } ComposeCallAdvancedAction;
143 PRIORITY_HIGHEST = 1,
152 COMPOSE_INSERT_SUCCESS,
153 COMPOSE_INSERT_READ_ERROR,
154 COMPOSE_INSERT_INVALID_CHARACTER,
155 COMPOSE_INSERT_NO_FILE
156 } ComposeInsertResult;
160 COMPOSE_WRITE_FOR_SEND,
161 COMPOSE_WRITE_FOR_STORE
166 COMPOSE_QUOTE_FORCED,
173 SUBJECT_FIELD_PRESENT,
178 #define B64_LINE_SIZE 57
179 #define B64_BUFFSIZE 77
181 #define MAX_REFERENCES_LEN 999
183 #define COMPOSE_DRAFT_TIMEOUT_UNSET -1
184 #define COMPOSE_DRAFT_TIMEOUT_FORBIDDEN -2
186 static GList *compose_list = NULL;
187 static GSList *extra_headers = NULL;
189 static Compose *compose_generic_new (PrefsAccount *account,
193 GList *listAddress );
195 static Compose *compose_create (PrefsAccount *account,
200 static void compose_entry_mark_default_to (Compose *compose,
201 const gchar *address);
202 static Compose *compose_followup_and_reply_to (MsgInfo *msginfo,
203 ComposeQuoteMode quote_mode,
207 static Compose *compose_forward_multiple (PrefsAccount *account,
208 GSList *msginfo_list);
209 static Compose *compose_reply (MsgInfo *msginfo,
210 ComposeQuoteMode quote_mode,
215 static Compose *compose_reply_mode (ComposeMode mode,
216 GSList *msginfo_list,
218 static void compose_template_apply_fields(Compose *compose, Template *tmpl);
219 static void compose_update_privacy_systems_menu(Compose *compose);
221 static GtkWidget *compose_account_option_menu_create
223 static void compose_set_out_encoding (Compose *compose);
224 static void compose_set_template_menu (Compose *compose);
225 static void compose_destroy (Compose *compose);
227 static MailField compose_entries_set (Compose *compose,
229 ComposeEntryType to_type);
230 static gint compose_parse_header (Compose *compose,
232 static gint compose_parse_manual_headers (Compose *compose,
234 HeaderEntry *entries);
235 static gchar *compose_parse_references (const gchar *ref,
238 static gchar *compose_quote_fmt (Compose *compose,
244 gboolean need_unescape,
245 const gchar *err_msg);
247 static void compose_reply_set_entry (Compose *compose,
253 followup_and_reply_to);
254 static void compose_reedit_set_entry (Compose *compose,
257 static void compose_insert_sig (Compose *compose,
259 static ComposeInsertResult compose_insert_file (Compose *compose,
262 static gboolean compose_attach_append (Compose *compose,
265 const gchar *content_type,
266 const gchar *charset);
267 static void compose_attach_parts (Compose *compose,
270 static gboolean compose_beautify_paragraph (Compose *compose,
271 GtkTextIter *par_iter,
273 static void compose_wrap_all (Compose *compose);
274 static void compose_wrap_all_full (Compose *compose,
277 static void compose_set_title (Compose *compose);
278 static void compose_select_account (Compose *compose,
279 PrefsAccount *account,
282 static PrefsAccount *compose_current_mail_account(void);
283 /* static gint compose_send (Compose *compose); */
284 static gboolean compose_check_for_valid_recipient
286 static gboolean compose_check_entries (Compose *compose,
287 gboolean check_everything);
288 static gint compose_write_to_file (Compose *compose,
291 gboolean attach_parts);
292 static gint compose_write_body_to_file (Compose *compose,
294 static gint compose_remove_reedit_target (Compose *compose,
296 static void compose_remove_draft (Compose *compose);
297 static gint compose_queue_sub (Compose *compose,
301 gboolean check_subject,
302 gboolean remove_reedit_target);
303 static int compose_add_attachments (Compose *compose,
305 static gchar *compose_get_header (Compose *compose);
306 static gchar *compose_get_manual_headers_info (Compose *compose);
308 static void compose_convert_header (Compose *compose,
313 gboolean addr_field);
315 static void compose_attach_info_free (AttachInfo *ainfo);
316 static void compose_attach_remove_selected (GtkAction *action,
319 static void compose_template_apply (Compose *compose,
322 static void compose_attach_property (GtkAction *action,
324 static void compose_attach_property_create (gboolean *cancelled);
325 static void attach_property_ok (GtkWidget *widget,
326 gboolean *cancelled);
327 static void attach_property_cancel (GtkWidget *widget,
328 gboolean *cancelled);
329 static gint attach_property_delete_event (GtkWidget *widget,
331 gboolean *cancelled);
332 static gboolean attach_property_key_pressed (GtkWidget *widget,
334 gboolean *cancelled);
336 static void compose_exec_ext_editor (Compose *compose);
338 static gint compose_exec_ext_editor_real (const gchar *file,
339 GdkNativeWindow socket_wid);
340 static gboolean compose_ext_editor_kill (Compose *compose);
341 static gboolean compose_input_cb (GIOChannel *source,
342 GIOCondition condition,
344 static void compose_set_ext_editor_sensitive (Compose *compose,
346 static gboolean compose_get_ext_editor_cmd_valid();
347 static gboolean compose_get_ext_editor_uses_socket();
348 static gboolean compose_ext_editor_plug_removed_cb
351 #endif /* G_OS_UNIX */
353 static void compose_undo_state_changed (UndoMain *undostruct,
358 static void compose_create_header_entry (Compose *compose);
359 static void compose_add_header_entry (Compose *compose, const gchar *header,
360 gchar *text, ComposePrefType pref_type);
361 static void compose_remove_header_entries(Compose *compose);
363 static void compose_update_priority_menu_item(Compose * compose);
365 static void compose_spell_menu_changed (void *data);
366 static void compose_dict_changed (void *data);
368 static void compose_add_field_list ( Compose *compose,
369 GList *listAddress );
371 /* callback functions */
373 static void compose_notebook_size_alloc (GtkNotebook *notebook,
374 GtkAllocation *allocation,
376 static gboolean compose_edit_size_alloc (GtkEditable *widget,
377 GtkAllocation *allocation,
378 GtkSHRuler *shruler);
379 static void account_activated (GtkComboBox *optmenu,
381 static void attach_selected (GtkTreeView *tree_view,
382 GtkTreePath *tree_path,
383 GtkTreeViewColumn *column,
385 static gboolean attach_button_pressed (GtkWidget *widget,
386 GdkEventButton *event,
388 static gboolean attach_key_pressed (GtkWidget *widget,
391 static void compose_send_cb (GtkAction *action, gpointer data);
392 static void compose_send_later_cb (GtkAction *action, gpointer data);
394 static void compose_save_cb (GtkAction *action,
397 static void compose_attach_cb (GtkAction *action,
399 static void compose_insert_file_cb (GtkAction *action,
401 static void compose_insert_sig_cb (GtkAction *action,
403 static void compose_replace_sig_cb (GtkAction *action,
406 static void compose_close_cb (GtkAction *action,
408 static void compose_print_cb (GtkAction *action,
411 static void compose_set_encoding_cb (GtkAction *action, GtkRadioAction *current, gpointer data);
413 static void compose_address_cb (GtkAction *action,
415 static void about_show_cb (GtkAction *action,
417 static void compose_template_activate_cb(GtkWidget *widget,
420 static void compose_ext_editor_cb (GtkAction *action,
423 static gint compose_delete_cb (GtkWidget *widget,
427 static void compose_undo_cb (GtkAction *action,
429 static void compose_redo_cb (GtkAction *action,
431 static void compose_cut_cb (GtkAction *action,
433 static void compose_copy_cb (GtkAction *action,
435 static void compose_paste_cb (GtkAction *action,
437 static void compose_paste_as_quote_cb (GtkAction *action,
439 static void compose_paste_no_wrap_cb (GtkAction *action,
441 static void compose_paste_wrap_cb (GtkAction *action,
443 static void compose_allsel_cb (GtkAction *action,
446 static void compose_advanced_action_cb (GtkAction *action,
449 static void compose_grab_focus_cb (GtkWidget *widget,
452 static void compose_changed_cb (GtkTextBuffer *textbuf,
455 static void compose_wrap_cb (GtkAction *action,
457 static void compose_wrap_all_cb (GtkAction *action,
459 static void compose_find_cb (GtkAction *action,
461 static void compose_toggle_autowrap_cb (GtkToggleAction *action,
463 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
466 static void compose_toggle_ruler_cb (GtkToggleAction *action,
468 static void compose_toggle_sign_cb (GtkToggleAction *action,
470 static void compose_toggle_encrypt_cb (GtkToggleAction *action,
472 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data);
473 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn);
474 static void activate_privacy_system (Compose *compose,
475 PrefsAccount *account,
477 static void compose_use_signing(Compose *compose, gboolean use_signing);
478 static void compose_use_encryption(Compose *compose, gboolean use_encryption);
479 static void compose_toggle_return_receipt_cb(GtkToggleAction *action,
481 static void compose_toggle_remove_refs_cb(GtkToggleAction *action,
483 static void compose_set_priority_cb (GtkAction *action, GtkRadioAction *current, gpointer data);
484 static void compose_reply_change_mode (Compose *compose, ComposeMode action);
485 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data);
487 static void compose_attach_drag_received_cb (GtkWidget *widget,
488 GdkDragContext *drag_context,
491 GtkSelectionData *data,
495 static void compose_insert_drag_received_cb (GtkWidget *widget,
496 GdkDragContext *drag_context,
499 GtkSelectionData *data,
503 static void compose_header_drag_received_cb (GtkWidget *widget,
504 GdkDragContext *drag_context,
507 GtkSelectionData *data,
512 static gboolean compose_drag_drop (GtkWidget *widget,
513 GdkDragContext *drag_context,
515 guint time, gpointer user_data);
516 static gboolean completion_set_focus_to_subject
521 static void text_inserted (GtkTextBuffer *buffer,
526 static Compose *compose_generic_reply(MsgInfo *msginfo,
527 ComposeQuoteMode quote_mode,
531 gboolean followup_and_reply_to,
534 static void compose_headerentry_changed_cb (GtkWidget *entry,
535 ComposeHeaderEntry *headerentry);
536 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
538 ComposeHeaderEntry *headerentry);
539 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
540 ComposeHeaderEntry *headerentry);
542 static void compose_show_first_last_header (Compose *compose, gboolean show_first);
544 static void compose_allow_user_actions (Compose *compose, gboolean allow);
546 static void compose_nothing_cb (GtkAction *action, gpointer data)
552 static void compose_check_all (GtkAction *action, gpointer data);
553 static void compose_highlight_all (GtkAction *action, gpointer data);
554 static void compose_check_backwards (GtkAction *action, gpointer data);
555 static void compose_check_forwards_go (GtkAction *action, gpointer data);
558 static PrefsAccount *compose_guess_forward_account_from_msginfo (MsgInfo *msginfo);
560 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose);
563 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
564 FolderItem *folder_item);
566 static void compose_attach_update_label(Compose *compose);
567 static void compose_set_folder_prefs(Compose *compose, FolderItem *folder,
568 gboolean respect_default_to);
569 static void compose_subject_entry_activated(GtkWidget *widget, gpointer data);
570 static void from_name_activate_cb(GtkWidget *widget, gpointer data);
572 static GtkActionEntry compose_popup_entries[] =
574 {"Compose", NULL, "Compose" },
575 {"Compose/Add", NULL, N_("_Add..."), NULL, NULL, G_CALLBACK(compose_attach_cb) },
576 {"Compose/Remove", NULL, N_("_Remove"), NULL, NULL, G_CALLBACK(compose_attach_remove_selected) },
577 {"Compose/---", NULL, "---", NULL, NULL, NULL },
578 {"Compose/Properties", NULL, N_("_Properties..."), NULL, NULL, G_CALLBACK(compose_attach_property) },
581 static GtkActionEntry compose_entries[] =
583 {"Menu", NULL, "Menu" },
585 {"Message", NULL, N_("_Message") },
586 {"Edit", NULL, N_("_Edit") },
588 {"Spelling", NULL, N_("_Spelling") },
590 {"Options", NULL, N_("_Options") },
591 {"Tools", NULL, N_("_Tools") },
592 {"Help", NULL, N_("_Help") },
594 {"Message/Send", NULL, N_("S_end"), "<control>Return", NULL, G_CALLBACK(compose_send_cb) },
595 {"Message/SendLater", NULL, N_("Send _later"), "<shift><control>S", NULL, G_CALLBACK(compose_send_later_cb) },
596 {"Message/---", NULL, "---" },
598 {"Message/AttachFile", NULL, N_("_Attach file"), "<control>M", NULL, G_CALLBACK(compose_attach_cb) },
599 {"Message/InsertFile", NULL, N_("_Insert file"), "<control>I", NULL, G_CALLBACK(compose_insert_file_cb) },
600 {"Message/InsertSig", NULL, N_("Insert si_gnature"), "<control>G", NULL, G_CALLBACK(compose_insert_sig_cb) },
601 {"Message/ReplaceSig", NULL, N_("_Replace signature"), NULL, NULL, G_CALLBACK(compose_replace_sig_cb) },
602 /* {"Message/---", NULL, "---" }, */
603 {"Message/Save", NULL, N_("_Save"), "<control>S", NULL, G_CALLBACK(compose_save_cb) }, /*COMPOSE_KEEP_EDITING*/
604 /* {"Message/---", NULL, "---" }, */
605 {"Message/Print", NULL, N_("_Print"), NULL, NULL, G_CALLBACK(compose_print_cb) },
606 /* {"Message/---", NULL, "---" }, */
607 {"Message/Close", NULL, N_("_Close"), "<control>W", NULL, G_CALLBACK(compose_close_cb) },
610 {"Edit/Undo", NULL, N_("_Undo"), "<control>Z", NULL, G_CALLBACK(compose_undo_cb) },
611 {"Edit/Redo", NULL, N_("_Redo"), "<control>Y", NULL, G_CALLBACK(compose_redo_cb) },
612 {"Edit/---", NULL, "---" },
614 {"Edit/Cut", NULL, N_("Cu_t"), "<control>X", NULL, G_CALLBACK(compose_cut_cb) },
615 {"Edit/Copy", NULL, N_("_Copy"), "<control>C", NULL, G_CALLBACK(compose_copy_cb) },
616 {"Edit/Paste", NULL, N_("_Paste"), "<control>V", NULL, G_CALLBACK(compose_paste_cb) },
618 {"Edit/SpecialPaste", NULL, N_("_Special paste") },
619 {"Edit/SpecialPaste/AsQuotation", NULL, N_("As _quotation"), NULL, NULL, G_CALLBACK(compose_paste_as_quote_cb) },
620 {"Edit/SpecialPaste/Wrapped", NULL, N_("_Wrapped"), NULL, NULL, G_CALLBACK(compose_paste_wrap_cb) },
621 {"Edit/SpecialPaste/Unwrapped", NULL, N_("_Unwrapped"), NULL, NULL, G_CALLBACK(compose_paste_no_wrap_cb) },
623 {"Edit/SelectAll", NULL, N_("Select _all"), "<control>A", NULL, G_CALLBACK(compose_allsel_cb) },
625 {"Edit/Advanced", NULL, N_("A_dvanced") },
626 {"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*/
627 {"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*/
628 {"Edit/Advanced/BackWord", NULL, N_("Move a word backward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD*/
629 {"Edit/Advanced/ForwWord", NULL, N_("Move a word forward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD*/
630 {"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*/
631 {"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*/
632 {"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*/
633 {"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*/
634 {"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*/
635 {"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*/
636 {"Edit/Advanced/DelBackWord", NULL, N_("Delete a word backward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD*/
637 {"Edit/Advanced/DelForwWord", NULL, N_("Delete a word forward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD*/
638 {"Edit/Advanced/DelLine", NULL, N_("Delete line"), "<control>U", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE*/
639 {"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*/
641 /* {"Edit/---", NULL, "---" }, */
642 {"Edit/Find", NULL, N_("_Find"), "<control>F", NULL, G_CALLBACK(compose_find_cb) },
644 /* {"Edit/---", NULL, "---" }, */
645 {"Edit/WrapPara", NULL, N_("_Wrap current paragraph"), "<control>L", NULL, G_CALLBACK(compose_wrap_cb) }, /* 0 */
646 {"Edit/WrapAllLines", NULL, N_("Wrap all long _lines"), "<control><alt>L", NULL, G_CALLBACK(compose_wrap_all_cb) }, /* 1 */
647 /* {"Edit/---", NULL, "---" }, */
648 {"Edit/ExtEditor", NULL, N_("Edit with e_xternal editor"), "<shift><control>X", NULL, G_CALLBACK(compose_ext_editor_cb) },
651 {"Spelling/CheckAllSel", NULL, N_("_Check all or check selection"), NULL, NULL, G_CALLBACK(compose_check_all) },
652 {"Spelling/HighlightAll", NULL, N_("_Highlight all misspelled words"), NULL, NULL, G_CALLBACK(compose_highlight_all) },
653 {"Spelling/CheckBackwards", NULL, N_("Check _backwards misspelled word"), NULL, NULL, G_CALLBACK(compose_check_backwards) },
654 {"Spelling/ForwardNext", NULL, N_("_Forward to next misspelled word"), NULL, NULL, G_CALLBACK(compose_check_forwards_go) },
656 {"Spelling/---", NULL, "---" },
657 {"Spelling/Options", NULL, N_("_Options") },
662 {"Options/ReplyMode", NULL, N_("Reply _mode") },
663 {"Options/---", NULL, "---" },
664 {"Options/PrivacySystem", NULL, N_("Privacy _System") },
665 {"Options/PrivacySystem/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
667 /* {"Options/---", NULL, "---" }, */
669 {"Options/Priority", NULL, N_("_Priority") },
671 {"Options/Encoding", NULL, N_("Character _encoding") },
672 {"Options/Encoding/---", NULL, "---" },
673 #define ENC_ACTION(cs_char,c_char,string) \
674 { "Options/Encoding/" cs_char, NULL, N_(string), NULL, NULL, c_char }
676 {"Options/Encoding/Western", NULL, N_("Western European") },
677 {"Options/Encoding/Baltic", NULL, N_("Baltic") },
678 {"Options/Encoding/Hebrew", NULL, N_("Hebrew") },
679 {"Options/Encoding/Arabic", NULL, N_("Arabic") },
680 {"Options/Encoding/Cyrillic", NULL, N_("Cyrillic") },
681 {"Options/Encoding/Japanese", NULL, N_("Japanese") },
682 {"Options/Encoding/Chinese", NULL, N_("Chinese") },
683 {"Options/Encoding/Korean", NULL, N_("Korean") },
684 {"Options/Encoding/Thai", NULL, N_("Thai") },
687 {"Tools/AddressBook", NULL, N_("_Address book"), NULL, NULL, G_CALLBACK(compose_address_cb) },
689 {"Tools/Template", NULL, N_("_Template") },
690 {"Tools/Template/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
691 {"Tools/Actions", NULL, N_("Actio_ns") },
692 {"Tools/Actions/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
695 {"Help/About", NULL, N_("_About"), NULL, NULL, G_CALLBACK(about_show_cb) },
698 static GtkToggleActionEntry compose_toggle_entries[] =
700 {"Edit/AutoWrap", NULL, N_("Aut_o wrapping"), "<shift><control>L", NULL, G_CALLBACK(compose_toggle_autowrap_cb) }, /* TOGGLE */
701 {"Edit/AutoIndent", NULL, N_("Auto _indent"), NULL, NULL, G_CALLBACK(compose_toggle_autoindent_cb) }, /* TOGGLE */
702 {"Options/Sign", NULL, N_("Si_gn"), NULL, NULL, G_CALLBACK(compose_toggle_sign_cb) }, /* Toggle */
703 {"Options/Encrypt", NULL, N_("_Encrypt"), NULL, NULL, G_CALLBACK(compose_toggle_encrypt_cb) }, /* Toggle */
704 {"Options/RequestRetRcpt", NULL, N_("_Request Return Receipt"), NULL, NULL, G_CALLBACK(compose_toggle_return_receipt_cb) }, /* TOGGLE */
705 {"Options/RemoveReferences", NULL, N_("Remo_ve references"), NULL, NULL, G_CALLBACK(compose_toggle_remove_refs_cb) }, /* TOGGLE */
706 {"Tools/ShowRuler", NULL, N_("Show _ruler"), NULL, NULL, G_CALLBACK(compose_toggle_ruler_cb) }, /* Toggle */
709 static GtkRadioActionEntry compose_radio_rm_entries[] =
711 {"Options/ReplyMode/Normal", NULL, N_("_Normal"), NULL, NULL, COMPOSE_REPLY }, /* RADIO compose_reply_change_mode_cb */
712 {"Options/ReplyMode/All", NULL, N_("_All"), NULL, NULL, COMPOSE_REPLY_TO_ALL }, /* RADIO compose_reply_change_mode_cb */
713 {"Options/ReplyMode/Sender", NULL, N_("_Sender"), NULL, NULL, COMPOSE_REPLY_TO_SENDER }, /* RADIO compose_reply_change_mode_cb */
714 {"Options/ReplyMode/List", NULL, N_("_Mailing-list"), NULL, NULL, COMPOSE_REPLY_TO_LIST }, /* RADIO compose_reply_change_mode_cb */
717 static GtkRadioActionEntry compose_radio_prio_entries[] =
719 {"Options/Priority/Highest", NULL, N_("_Highest"), NULL, NULL, PRIORITY_HIGHEST }, /* RADIO compose_set_priority_cb */
720 {"Options/Priority/High", NULL, N_("Hi_gh"), NULL, NULL, PRIORITY_HIGH }, /* RADIO compose_set_priority_cb */
721 {"Options/Priority/Normal", NULL, N_("_Normal"), NULL, NULL, PRIORITY_NORMAL }, /* RADIO compose_set_priority_cb */
722 {"Options/Priority/Low", NULL, N_("Lo_w"), NULL, NULL, PRIORITY_LOW }, /* RADIO compose_set_priority_cb */
723 {"Options/Priority/Lowest", NULL, N_("_Lowest"), NULL, NULL, PRIORITY_LOWEST }, /* RADIO compose_set_priority_cb */
726 static GtkRadioActionEntry compose_radio_enc_entries[] =
728 ENC_ACTION(CS_AUTO, C_AUTO, N_("_Automatic")), /* RADIO compose_set_encoding_cb */
729 ENC_ACTION(CS_US_ASCII, C_US_ASCII, N_("7bit ASCII (US-ASC_II)")), /* RADIO compose_set_encoding_cb */
730 ENC_ACTION(CS_UTF_8, C_UTF_8, N_("Unicode (_UTF-8)")), /* RADIO compose_set_encoding_cb */
731 ENC_ACTION("Western/"CS_ISO_8859_1, C_ISO_8859_1, "ISO-8859-_1"), /* RADIO compose_set_encoding_cb */
732 ENC_ACTION("Western/"CS_ISO_8859_15, C_ISO_8859_15, "ISO-8859-15"), /* RADIO compose_set_encoding_cb */
733 ENC_ACTION("Western/"CS_WINDOWS_1252, C_WINDOWS_1252, "Windows-1252"), /* RADIO compose_set_encoding_cb */
734 ENC_ACTION(CS_ISO_8859_2, C_ISO_8859_2, N_("Central European (ISO-8859-_2)")), /* RADIO compose_set_encoding_cb */
735 ENC_ACTION("Baltic/"CS_ISO_8859_13, C_ISO_8859_13, "ISO-8859-13"), /* RADIO compose_set_encoding_cb */
736 ENC_ACTION("Baltic/"CS_ISO_8859_4, C_ISO_8859_14, "ISO-8859-_4"), /* RADIO compose_set_encoding_cb */
737 ENC_ACTION(CS_ISO_8859_7, C_ISO_8859_7, N_("Greek (ISO-8859-_7)")), /* RADIO compose_set_encoding_cb */
738 ENC_ACTION("Hebrew/"CS_ISO_8859_8, C_ISO_8859_8, "ISO-8859-_8"), /* RADIO compose_set_encoding_cb */
739 ENC_ACTION("Hebrew/"CS_WINDOWS_1255, C_WINDOWS_1255, "Windows-1255"), /* RADIO compose_set_encoding_cb */
740 ENC_ACTION("Arabic/"CS_ISO_8859_6, C_ISO_8859_6, "ISO-8859-_6"), /* RADIO compose_set_encoding_cb */
741 ENC_ACTION("Arabic/"CS_WINDOWS_1256, C_WINDOWS_1256, "Windows-1256"), /* RADIO compose_set_encoding_cb */
742 ENC_ACTION(CS_ISO_8859_9, C_ISO_8859_9, N_("Turkish (ISO-8859-_9)")), /* RADIO compose_set_encoding_cb */
743 ENC_ACTION("Cyrillic/"CS_ISO_8859_5, C_ISO_8859_5, "ISO-8859-_5"), /* RADIO compose_set_encoding_cb */
744 ENC_ACTION("Cyrillic/"CS_KOI8_R, C_KOI8_R, "KOI8-_R"), /* RADIO compose_set_encoding_cb */
745 ENC_ACTION("Cyrillic/"CS_MACCYR, C_MACCYR, "_Mac-Cyrillic"), /* RADIO compose_set_encoding_cb */
746 ENC_ACTION("Cyrillic/"CS_KOI8_U, C_KOI8_U, "KOI8-_U"), /* RADIO compose_set_encoding_cb */
747 ENC_ACTION("Cyrillic/"CS_WINDOWS_1251, C_WINDOWS_1251, "Windows-1251"), /* RADIO compose_set_encoding_cb */
748 ENC_ACTION("Japanese/"CS_ISO_2022_JP, C_ISO_2022_JP, "ISO-2022-_JP"), /* RADIO compose_set_encoding_cb */
749 ENC_ACTION("Japanese/"CS_ISO_2022_JP_2, C_ISO_2022_JP_2, "ISO-2022-JP-_2"), /* RADIO compose_set_encoding_cb */
750 ENC_ACTION("Japanese/"CS_EUC_JP, C_EUC_JP, "_EUC-JP"), /* RADIO compose_set_encoding_cb */
751 ENC_ACTION("Japanese/"CS_SHIFT_JIS, C_SHIFT_JIS, "_Shift-JIS"), /* RADIO compose_set_encoding_cb */
752 ENC_ACTION("Chinese/"CS_GB18030, C_GB18030, "_GB18030"), /* RADIO compose_set_encoding_cb */
753 ENC_ACTION("Chinese/"CS_GB2312, C_GB2312, "_GB2312"), /* RADIO compose_set_encoding_cb */
754 ENC_ACTION("Chinese/"CS_GBK, C_GBK, "GB_K"), /* RADIO compose_set_encoding_cb */
755 ENC_ACTION("Chinese/"CS_BIG5, C_BIG5, "_Big5-JP"), /* RADIO compose_set_encoding_cb */
756 ENC_ACTION("Chinese/"CS_EUC_TW, C_EUC_TW, "EUC-_TW"), /* RADIO compose_set_encoding_cb */
757 ENC_ACTION("Korean/"CS_EUC_KR, C_EUC_KR, "_EUC-KR"), /* RADIO compose_set_encoding_cb */
758 ENC_ACTION("Korean/"CS_ISO_2022_KR, C_ISO_2022_KR, "_ISO-2022-KR"), /* RADIO compose_set_encoding_cb */
759 ENC_ACTION("Thai/"CS_TIS_620, C_TIS_620, "_TIS-620-KR"), /* RADIO compose_set_encoding_cb */
760 ENC_ACTION("Thai/"CS_WINDOWS_874, C_WINDOWS_874, "_Windows-874"), /* RADIO compose_set_encoding_cb */
763 static GtkTargetEntry compose_mime_types[] =
765 {"text/uri-list", 0, 0},
766 {"UTF8_STRING", 0, 0},
770 static gboolean compose_put_existing_to_front(MsgInfo *info)
772 const GList *compose_list = compose_get_compose_list();
773 const GList *elem = NULL;
776 for (elem = compose_list; elem != NULL && elem->data != NULL;
778 Compose *c = (Compose*)elem->data;
780 if (!c->targetinfo || !c->targetinfo->msgid ||
784 if (!strcmp(c->targetinfo->msgid, info->msgid)) {
785 gtkut_window_popup(c->window);
793 static GdkColor quote_color1 =
794 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
795 static GdkColor quote_color2 =
796 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
797 static GdkColor quote_color3 =
798 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
800 static GdkColor quote_bgcolor1 =
801 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
802 static GdkColor quote_bgcolor2 =
803 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
804 static GdkColor quote_bgcolor3 =
805 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
807 static GdkColor signature_color = {
814 static GdkColor uri_color = {
821 static void compose_create_tags(GtkTextView *text, Compose *compose)
823 GtkTextBuffer *buffer;
824 GdkColor black = {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
825 #if !GTK_CHECK_VERSION(2, 24, 0)
832 buffer = gtk_text_view_get_buffer(text);
834 if (prefs_common.enable_color) {
835 /* grab the quote colors, converting from an int to a GdkColor */
836 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_col,
838 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_col,
840 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_col,
842 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_bgcol,
844 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_bgcol,
846 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_bgcol,
848 gtkut_convert_int_to_gdk_color(prefs_common.signature_col,
850 gtkut_convert_int_to_gdk_color(prefs_common.uri_col,
853 signature_color = quote_color1 = quote_color2 = quote_color3 =
854 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 = uri_color = black;
857 if (prefs_common.enable_color && prefs_common.enable_bgcolor) {
858 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
859 "foreground-gdk", "e_color1,
860 "paragraph-background-gdk", "e_bgcolor1,
862 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
863 "foreground-gdk", "e_color2,
864 "paragraph-background-gdk", "e_bgcolor2,
866 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
867 "foreground-gdk", "e_color3,
868 "paragraph-background-gdk", "e_bgcolor3,
871 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
872 "foreground-gdk", "e_color1,
874 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
875 "foreground-gdk", "e_color2,
877 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
878 "foreground-gdk", "e_color3,
882 compose->signature_tag = gtk_text_buffer_create_tag(buffer, "signature",
883 "foreground-gdk", &signature_color,
886 compose->uri_tag = gtk_text_buffer_create_tag(buffer, "link",
887 "foreground-gdk", &uri_color,
889 compose->no_wrap_tag = gtk_text_buffer_create_tag(buffer, "no_wrap", NULL);
890 compose->no_join_tag = gtk_text_buffer_create_tag(buffer, "no_join", NULL);
892 #if !GTK_CHECK_VERSION(2, 24, 0)
893 color[0] = quote_color1;
894 color[1] = quote_color2;
895 color[2] = quote_color3;
896 color[3] = quote_bgcolor1;
897 color[4] = quote_bgcolor2;
898 color[5] = quote_bgcolor3;
899 color[6] = signature_color;
900 color[7] = uri_color;
902 cmap = gdk_drawable_get_colormap(gtk_widget_get_window(compose->window));
903 gdk_colormap_alloc_colors(cmap, color, 8, FALSE, TRUE, success);
905 for (i = 0; i < 8; i++) {
906 if (success[i] == FALSE) {
907 g_warning("Compose: color allocation failed.");
908 quote_color1 = quote_color2 = quote_color3 =
909 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 =
910 signature_color = uri_color = black;
916 Compose *compose_new(PrefsAccount *account, const gchar *mailto,
919 return compose_generic_new(account, mailto, NULL, attach_files, NULL);
922 Compose *compose_new_with_folderitem(PrefsAccount *account, FolderItem *item, const gchar *mailto)
924 return compose_generic_new(account, mailto, item, NULL, NULL);
927 Compose *compose_new_with_list( PrefsAccount *account, GList *listAddress )
929 return compose_generic_new( account, NULL, NULL, NULL, listAddress );
932 #define SCROLL_TO_CURSOR(compose) { \
933 GtkTextMark *cmark = gtk_text_buffer_get_insert( \
934 gtk_text_view_get_buffer( \
935 GTK_TEXT_VIEW(compose->text))); \
936 gtk_text_view_scroll_mark_onscreen( \
937 GTK_TEXT_VIEW(compose->text), \
941 static void compose_set_save_to(Compose *compose, const gchar *folderidentifier)
944 if (folderidentifier) {
945 #if !GTK_CHECK_VERSION(2, 24, 0)
946 combobox_unset_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo));
948 combobox_unset_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo));
950 prefs_common.compose_save_to_history = add_history(
951 prefs_common.compose_save_to_history, folderidentifier);
952 #if !GTK_CHECK_VERSION(2, 24, 0)
953 combobox_set_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo),
954 prefs_common.compose_save_to_history);
956 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo),
957 prefs_common.compose_save_to_history);
961 entry = GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(compose->savemsg_combo)));
962 if (folderidentifier)
963 gtk_entry_set_text(GTK_ENTRY(entry), folderidentifier);
965 gtk_entry_set_text(GTK_ENTRY(entry), "");
968 static gchar *compose_get_save_to(Compose *compose)
971 gchar *result = NULL;
972 entry = GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(compose->savemsg_combo)));
973 result = gtk_editable_get_chars(entry, 0, -1);
976 #if !GTK_CHECK_VERSION(2, 24, 0)
977 combobox_unset_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo));
979 combobox_unset_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo));
981 prefs_common.compose_save_to_history = add_history(
982 prefs_common.compose_save_to_history, result);
983 #if !GTK_CHECK_VERSION(2, 24, 0)
984 combobox_set_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo),
985 prefs_common.compose_save_to_history);
987 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo),
988 prefs_common.compose_save_to_history);
994 Compose *compose_generic_new(PrefsAccount *account, const gchar *mailto, FolderItem *item,
995 GList *attach_files, GList *listAddress )
998 GtkTextView *textview;
999 GtkTextBuffer *textbuf;
1001 const gchar *subject_format = NULL;
1002 const gchar *body_format = NULL;
1003 gchar *mailto_from = NULL;
1004 PrefsAccount *mailto_account = NULL;
1005 MsgInfo* dummyinfo = NULL;
1006 gint cursor_pos = -1;
1007 MailField mfield = NO_FIELD_PRESENT;
1011 /* check if mailto defines a from */
1012 if (mailto && *mailto != '\0') {
1013 scan_mailto_url(mailto, &mailto_from, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
1014 /* mailto defines a from, check if we can get account prefs from it,
1015 if not, the account prefs will be guessed using other ways, but we'll keep
1018 mailto_account = account_find_from_address(mailto_from, TRUE);
1019 if (mailto_account == NULL) {
1021 Xstrdup_a(tmp_from, mailto_from, return NULL);
1022 extract_address(tmp_from);
1023 mailto_account = account_find_from_address(tmp_from, TRUE);
1027 account = mailto_account;
1030 /* if no account prefs set from mailto, set if from folder prefs (if any) */
1031 if (!mailto_account && item && item->prefs && item->prefs->enable_default_account)
1032 account = account_find_from_id(item->prefs->default_account);
1034 /* if no account prefs set, fallback to the current one */
1035 if (!account) account = cur_account;
1036 cm_return_val_if_fail(account != NULL, NULL);
1038 compose = compose_create(account, item, COMPOSE_NEW, FALSE);
1040 /* override from name if mailto asked for it */
1042 gtk_entry_set_text(GTK_ENTRY(compose->from_name), mailto_from);
1043 g_free(mailto_from);
1045 /* override from name according to folder properties */
1046 if (item && item->prefs &&
1047 item->prefs->compose_with_format &&
1048 item->prefs->compose_override_from_format &&
1049 *item->prefs->compose_override_from_format != '\0') {
1054 dummyinfo = compose_msginfo_new_from_compose(compose);
1056 /* decode \-escape sequences in the internal representation of the quote format */
1057 tmp = g_malloc(strlen(item->prefs->compose_override_from_format)+1);
1058 pref_get_unescaped_pref(tmp, item->prefs->compose_override_from_format);
1061 quote_fmt_init(dummyinfo, NULL, NULL, FALSE, compose->account, FALSE,
1062 compose->gtkaspell);
1064 quote_fmt_init(dummyinfo, NULL, NULL, FALSE, compose->account, FALSE);
1066 quote_fmt_scan_string(tmp);
1069 buf = quote_fmt_get_buffer();
1071 alertpanel_error(_("New message From format error."));
1073 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1074 quote_fmt_reset_vartable();
1079 compose->replyinfo = NULL;
1080 compose->fwdinfo = NULL;
1082 textview = GTK_TEXT_VIEW(compose->text);
1083 textbuf = gtk_text_view_get_buffer(textview);
1084 compose_create_tags(textview, compose);
1086 undo_block(compose->undostruct);
1088 compose_set_dictionaries_from_folder_prefs(compose, item);
1091 if (account->auto_sig)
1092 compose_insert_sig(compose, FALSE);
1093 gtk_text_buffer_get_start_iter(textbuf, &iter);
1094 gtk_text_buffer_place_cursor(textbuf, &iter);
1096 if (account->protocol != A_NNTP) {
1097 if (mailto && *mailto != '\0') {
1098 mfield = compose_entries_set(compose, mailto, COMPOSE_TO);
1101 compose_set_folder_prefs(compose, item, TRUE);
1103 if (item && item->ret_rcpt) {
1104 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
1107 if (mailto && *mailto != '\0') {
1108 if (!strchr(mailto, '@'))
1109 mfield = compose_entries_set(compose, mailto, COMPOSE_NEWSGROUPS);
1111 mfield = compose_entries_set(compose, mailto, COMPOSE_TO);
1112 } else if (item && FOLDER_CLASS(item->folder) == news_get_class()) {
1113 compose_entry_append(compose, item->path, COMPOSE_NEWSGROUPS, PREF_FOLDER);
1114 mfield = TO_FIELD_PRESENT;
1117 * CLAWS: just don't allow return receipt request, even if the user
1118 * may want to send an email. simple but foolproof.
1120 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", FALSE);
1122 compose_add_field_list( compose, listAddress );
1124 if (item && item->prefs && item->prefs->compose_with_format) {
1125 subject_format = item->prefs->compose_subject_format;
1126 body_format = item->prefs->compose_body_format;
1127 } else if (account->compose_with_format) {
1128 subject_format = account->compose_subject_format;
1129 body_format = account->compose_body_format;
1130 } else if (prefs_common.compose_with_format) {
1131 subject_format = prefs_common.compose_subject_format;
1132 body_format = prefs_common.compose_body_format;
1135 if (subject_format || body_format) {
1138 && *subject_format != '\0' )
1140 gchar *subject = NULL;
1145 dummyinfo = compose_msginfo_new_from_compose(compose);
1147 /* decode \-escape sequences in the internal representation of the quote format */
1148 tmp = g_malloc(strlen(subject_format)+1);
1149 pref_get_unescaped_pref(tmp, subject_format);
1151 subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1153 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account, FALSE,
1154 compose->gtkaspell);
1156 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account, FALSE);
1158 quote_fmt_scan_string(tmp);
1161 buf = quote_fmt_get_buffer();
1163 alertpanel_error(_("New message subject format error."));
1165 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1166 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1167 quote_fmt_reset_vartable();
1171 mfield = SUBJECT_FIELD_PRESENT;
1175 && *body_format != '\0' )
1178 GtkTextBuffer *buffer;
1179 GtkTextIter start, end;
1183 dummyinfo = compose_msginfo_new_from_compose(compose);
1185 text = GTK_TEXT_VIEW(compose->text);
1186 buffer = gtk_text_view_get_buffer(text);
1187 gtk_text_buffer_get_start_iter(buffer, &start);
1188 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
1189 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
1191 compose_quote_fmt(compose, dummyinfo,
1193 NULL, tmp, FALSE, TRUE,
1194 _("The body of the \"New message\" template has an error at line %d."));
1195 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1196 quote_fmt_reset_vartable();
1200 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1201 gtkaspell_highlight_all(compose->gtkaspell);
1203 mfield = BODY_FIELD_PRESENT;
1207 procmsg_msginfo_free( &dummyinfo );
1213 for (curr = attach_files ; curr != NULL ; curr = curr->next) {
1214 ainfo = (AttachInfo *) curr->data;
1215 compose_attach_append(compose, ainfo->file, ainfo->file,
1216 ainfo->content_type, ainfo->charset);
1220 compose_show_first_last_header(compose, TRUE);
1222 /* Set save folder */
1223 if (item && item->prefs && item->prefs->save_copy_to_folder) {
1224 gchar *folderidentifier;
1226 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
1227 folderidentifier = folder_item_get_identifier(item);
1228 compose_set_save_to(compose, folderidentifier);
1229 g_free(folderidentifier);
1232 /* Place cursor according to provided input (mfield) */
1234 case NO_FIELD_PRESENT:
1235 if (compose->header_last)
1236 gtk_widget_grab_focus(compose->header_last->entry);
1238 case TO_FIELD_PRESENT:
1239 buf = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1241 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1244 gtk_widget_grab_focus(compose->subject_entry);
1246 case SUBJECT_FIELD_PRESENT:
1247 textview = GTK_TEXT_VIEW(compose->text);
1250 textbuf = gtk_text_view_get_buffer(textview);
1253 mark = gtk_text_buffer_get_insert(textbuf);
1254 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
1255 gtk_text_buffer_insert(textbuf, &iter, "", -1);
1257 * SUBJECT_FIELD_PRESENT and BODY_FIELD_PRESENT
1258 * only defers where it comes to the variable body
1259 * is not null. If no body is present compose->text
1260 * will be null in which case you cannot place the
1261 * cursor inside the component so. An empty component
1262 * is therefore created before placing the cursor
1264 case BODY_FIELD_PRESENT:
1265 cursor_pos = quote_fmt_get_cursor_pos();
1266 if (cursor_pos == -1)
1267 gtk_widget_grab_focus(compose->header_last->entry);
1269 gtk_widget_grab_focus(compose->text);
1273 undo_unblock(compose->undostruct);
1275 if (prefs_common.auto_exteditor)
1276 compose_exec_ext_editor(compose);
1278 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
1280 SCROLL_TO_CURSOR(compose);
1282 compose->modified = FALSE;
1283 compose_set_title(compose);
1285 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
1290 static void compose_force_encryption(Compose *compose, PrefsAccount *account,
1291 gboolean override_pref, const gchar *system)
1293 const gchar *privacy = NULL;
1295 cm_return_if_fail(compose != NULL);
1296 cm_return_if_fail(account != NULL);
1298 if (override_pref == FALSE && account->default_encrypt_reply == FALSE)
1301 if (account->default_privacy_system && strlen(account->default_privacy_system))
1302 privacy = account->default_privacy_system;
1306 GSList *privacy_avail = privacy_get_system_ids();
1307 if (privacy_avail && g_slist_length(privacy_avail)) {
1308 privacy = (gchar *)(privacy_avail->data);
1311 if (privacy != NULL) {
1313 g_free(compose->privacy_system);
1314 compose->privacy_system = NULL;
1315 g_free(compose->encdata);
1316 compose->encdata = NULL;
1318 if (compose->privacy_system == NULL)
1319 compose->privacy_system = g_strdup(privacy);
1320 else if (*(compose->privacy_system) == '\0') {
1321 g_free(compose->privacy_system);
1322 g_free(compose->encdata);
1323 compose->encdata = NULL;
1324 compose->privacy_system = g_strdup(privacy);
1326 compose_update_privacy_system_menu_item(compose, FALSE);
1327 compose_use_encryption(compose, TRUE);
1331 static void compose_force_signing(Compose *compose, PrefsAccount *account, const gchar *system)
1333 const gchar *privacy = NULL;
1335 if (account->default_privacy_system && strlen(account->default_privacy_system))
1336 privacy = account->default_privacy_system;
1340 GSList *privacy_avail = privacy_get_system_ids();
1341 if (privacy_avail && g_slist_length(privacy_avail)) {
1342 privacy = (gchar *)(privacy_avail->data);
1346 if (privacy != NULL) {
1348 g_free(compose->privacy_system);
1349 compose->privacy_system = NULL;
1350 g_free(compose->encdata);
1351 compose->encdata = NULL;
1353 if (compose->privacy_system == NULL)
1354 compose->privacy_system = g_strdup(privacy);
1355 compose_update_privacy_system_menu_item(compose, FALSE);
1356 compose_use_signing(compose, TRUE);
1360 static Compose *compose_reply_mode(ComposeMode mode, GSList *msginfo_list, gchar *body)
1364 Compose *compose = NULL;
1366 cm_return_val_if_fail(msginfo_list != NULL, NULL);
1368 msginfo = (MsgInfo*)g_slist_nth_data(msginfo_list, 0);
1369 cm_return_val_if_fail(msginfo != NULL, NULL);
1371 list_len = g_slist_length(msginfo_list);
1375 case COMPOSE_REPLY_TO_ADDRESS:
1376 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1377 FALSE, prefs_common.default_reply_list, FALSE, body);
1379 case COMPOSE_REPLY_WITH_QUOTE:
1380 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1381 FALSE, prefs_common.default_reply_list, FALSE, body);
1383 case COMPOSE_REPLY_WITHOUT_QUOTE:
1384 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1385 FALSE, prefs_common.default_reply_list, FALSE, NULL);
1387 case COMPOSE_REPLY_TO_SENDER:
1388 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1389 FALSE, FALSE, TRUE, body);
1391 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1392 compose = compose_followup_and_reply_to(msginfo,
1393 COMPOSE_QUOTE_CHECK,
1394 FALSE, FALSE, body);
1396 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1397 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1398 FALSE, FALSE, TRUE, body);
1400 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1401 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1402 FALSE, FALSE, TRUE, NULL);
1404 case COMPOSE_REPLY_TO_ALL:
1405 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1406 TRUE, FALSE, FALSE, body);
1408 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1409 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1410 TRUE, FALSE, FALSE, body);
1412 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1413 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1414 TRUE, FALSE, FALSE, NULL);
1416 case COMPOSE_REPLY_TO_LIST:
1417 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1418 FALSE, TRUE, FALSE, body);
1420 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1421 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1422 FALSE, TRUE, FALSE, body);
1424 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1425 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1426 FALSE, TRUE, FALSE, NULL);
1428 case COMPOSE_FORWARD:
1429 if (prefs_common.forward_as_attachment) {
1430 compose = compose_reply_mode(COMPOSE_FORWARD_AS_ATTACH, msginfo_list, body);
1433 compose = compose_reply_mode(COMPOSE_FORWARD_INLINE, msginfo_list, body);
1437 case COMPOSE_FORWARD_INLINE:
1438 /* check if we reply to more than one Message */
1439 if (list_len == 1) {
1440 compose = compose_forward(NULL, msginfo, FALSE, body, FALSE, FALSE);
1443 /* more messages FALL THROUGH */
1444 case COMPOSE_FORWARD_AS_ATTACH:
1445 compose = compose_forward_multiple(NULL, msginfo_list);
1447 case COMPOSE_REDIRECT:
1448 compose = compose_redirect(NULL, msginfo, FALSE);
1451 g_warning("compose_reply_mode(): invalid Compose Mode: %d", mode);
1454 if (compose == NULL) {
1455 alertpanel_error(_("Unable to reply. The original email probably doesn't exist."));
1459 compose->rmode = mode;
1460 switch (compose->rmode) {
1462 case COMPOSE_REPLY_WITH_QUOTE:
1463 case COMPOSE_REPLY_WITHOUT_QUOTE:
1464 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1465 debug_print("reply mode Normal\n");
1466 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/Normal", TRUE);
1467 compose_reply_change_mode(compose, COMPOSE_REPLY); /* force update */
1469 case COMPOSE_REPLY_TO_SENDER:
1470 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1471 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1472 debug_print("reply mode Sender\n");
1473 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/Sender", TRUE);
1475 case COMPOSE_REPLY_TO_ALL:
1476 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1477 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1478 debug_print("reply mode All\n");
1479 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/All", TRUE);
1481 case COMPOSE_REPLY_TO_LIST:
1482 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1483 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1484 debug_print("reply mode List\n");
1485 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/List", TRUE);
1487 case COMPOSE_REPLY_TO_ADDRESS:
1488 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", FALSE);
1496 static Compose *compose_reply(MsgInfo *msginfo,
1497 ComposeQuoteMode quote_mode,
1503 return compose_generic_reply(msginfo, quote_mode, to_all, to_ml,
1504 to_sender, FALSE, body);
1507 static Compose *compose_followup_and_reply_to(MsgInfo *msginfo,
1508 ComposeQuoteMode quote_mode,
1513 return compose_generic_reply(msginfo, quote_mode, to_all, FALSE,
1514 to_sender, TRUE, body);
1517 static void compose_extract_original_charset(Compose *compose)
1519 MsgInfo *info = NULL;
1520 if (compose->replyinfo) {
1521 info = compose->replyinfo;
1522 } else if (compose->fwdinfo) {
1523 info = compose->fwdinfo;
1524 } else if (compose->targetinfo) {
1525 info = compose->targetinfo;
1528 MimeInfo *mimeinfo = procmime_scan_message_short(info);
1529 MimeInfo *partinfo = mimeinfo;
1530 while (partinfo && partinfo->type != MIMETYPE_TEXT)
1531 partinfo = procmime_mimeinfo_next(partinfo);
1533 compose->orig_charset =
1534 g_strdup(procmime_mimeinfo_get_parameter(
1535 partinfo, "charset"));
1537 procmime_mimeinfo_free_all(&mimeinfo);
1541 #define SIGNAL_BLOCK(buffer) { \
1542 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1543 G_CALLBACK(compose_changed_cb), \
1545 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1546 G_CALLBACK(text_inserted), \
1550 #define SIGNAL_UNBLOCK(buffer) { \
1551 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1552 G_CALLBACK(compose_changed_cb), \
1554 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1555 G_CALLBACK(text_inserted), \
1559 static Compose *compose_generic_reply(MsgInfo *msginfo,
1560 ComposeQuoteMode quote_mode,
1561 gboolean to_all, gboolean to_ml,
1563 gboolean followup_and_reply_to,
1567 PrefsAccount *account = NULL;
1568 GtkTextView *textview;
1569 GtkTextBuffer *textbuf;
1570 gboolean quote = FALSE;
1571 const gchar *qmark = NULL;
1572 const gchar *body_fmt = NULL;
1573 gchar *s_system = NULL;
1575 cm_return_val_if_fail(msginfo != NULL, NULL);
1576 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
1578 account = account_get_reply_account(msginfo, prefs_common.reply_account_autosel);
1580 cm_return_val_if_fail(account != NULL, NULL);
1582 compose = compose_create(account, msginfo->folder, COMPOSE_REPLY, FALSE);
1584 compose->updating = TRUE;
1586 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
1587 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", TRUE);
1589 compose->replyinfo = procmsg_msginfo_get_full_info(msginfo);
1590 if (!compose->replyinfo)
1591 compose->replyinfo = procmsg_msginfo_copy(msginfo);
1593 compose_extract_original_charset(compose);
1595 if (msginfo->folder && msginfo->folder->ret_rcpt)
1596 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
1598 /* Set save folder */
1599 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1600 gchar *folderidentifier;
1602 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1603 folderidentifier = folder_item_get_identifier(msginfo->folder);
1604 compose_set_save_to(compose, folderidentifier);
1605 g_free(folderidentifier);
1608 if (compose_parse_header(compose, msginfo) < 0) {
1609 compose->updating = FALSE;
1610 compose_destroy(compose);
1614 /* override from name according to folder properties */
1615 if (msginfo->folder && msginfo->folder->prefs &&
1616 msginfo->folder->prefs->reply_with_format &&
1617 msginfo->folder->prefs->reply_override_from_format &&
1618 *msginfo->folder->prefs->reply_override_from_format != '\0') {
1623 /* decode \-escape sequences in the internal representation of the quote format */
1624 tmp = g_malloc(strlen(msginfo->folder->prefs->reply_override_from_format)+1);
1625 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->reply_override_from_format);
1628 quote_fmt_init(compose->replyinfo, NULL, NULL, FALSE, compose->account, FALSE,
1629 compose->gtkaspell);
1631 quote_fmt_init(compose->replyinfo, NULL, NULL, FALSE, compose->account, FALSE);
1633 quote_fmt_scan_string(tmp);
1636 buf = quote_fmt_get_buffer();
1638 alertpanel_error(_("The \"From\" field of the \"Reply\" template contains an invalid email address."));
1640 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1641 quote_fmt_reset_vartable();
1646 textview = (GTK_TEXT_VIEW(compose->text));
1647 textbuf = gtk_text_view_get_buffer(textview);
1648 compose_create_tags(textview, compose);
1650 undo_block(compose->undostruct);
1652 compose_set_dictionaries_from_folder_prefs(compose, msginfo->folder);
1653 gtkaspell_block_check(compose->gtkaspell);
1656 if (quote_mode == COMPOSE_QUOTE_FORCED ||
1657 (quote_mode == COMPOSE_QUOTE_CHECK && prefs_common.reply_with_quote)) {
1658 /* use the reply format of folder (if enabled), or the account's one
1659 (if enabled) or fallback to the global reply format, which is always
1660 enabled (even if empty), and use the relevant quotemark */
1662 if (msginfo->folder && msginfo->folder->prefs &&
1663 msginfo->folder->prefs->reply_with_format) {
1664 qmark = msginfo->folder->prefs->reply_quotemark;
1665 body_fmt = msginfo->folder->prefs->reply_body_format;
1667 } else if (account->reply_with_format) {
1668 qmark = account->reply_quotemark;
1669 body_fmt = account->reply_body_format;
1672 qmark = prefs_common.quotemark;
1673 if (prefs_common.quotefmt && *prefs_common.quotefmt)
1674 body_fmt = gettext(prefs_common.quotefmt);
1681 /* empty quotemark is not allowed */
1682 if (qmark == NULL || *qmark == '\0')
1684 compose_quote_fmt(compose, compose->replyinfo,
1685 body_fmt, qmark, body, FALSE, TRUE,
1686 _("The body of the \"Reply\" template has an error at line %d."));
1687 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1688 quote_fmt_reset_vartable();
1691 if (MSG_IS_ENCRYPTED(compose->replyinfo->flags)) {
1692 compose_force_encryption(compose, account, FALSE, s_system);
1695 privacy_msginfo_get_signed_state(compose->replyinfo, &s_system);
1696 if (MSG_IS_SIGNED(compose->replyinfo->flags) && account->default_sign_reply) {
1697 compose_force_signing(compose, account, s_system);
1701 SIGNAL_BLOCK(textbuf);
1703 if (account->auto_sig)
1704 compose_insert_sig(compose, FALSE);
1706 compose_wrap_all(compose);
1709 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1710 gtkaspell_highlight_all(compose->gtkaspell);
1711 gtkaspell_unblock_check(compose->gtkaspell);
1713 SIGNAL_UNBLOCK(textbuf);
1715 gtk_widget_grab_focus(compose->text);
1717 undo_unblock(compose->undostruct);
1719 if (prefs_common.auto_exteditor)
1720 compose_exec_ext_editor(compose);
1722 compose->modified = FALSE;
1723 compose_set_title(compose);
1725 compose->updating = FALSE;
1726 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
1727 SCROLL_TO_CURSOR(compose);
1729 if (compose->deferred_destroy) {
1730 compose_destroy(compose);
1738 #define INSERT_FW_HEADER(var, hdr) \
1739 if (msginfo->var && *msginfo->var) { \
1740 gtk_stext_insert(text, NULL, NULL, NULL, hdr, -1); \
1741 gtk_stext_insert(text, NULL, NULL, NULL, msginfo->var, -1); \
1742 gtk_stext_insert(text, NULL, NULL, NULL, "\n", 1); \
1745 Compose *compose_forward(PrefsAccount *account, MsgInfo *msginfo,
1746 gboolean as_attach, const gchar *body,
1747 gboolean no_extedit,
1751 GtkTextView *textview;
1752 GtkTextBuffer *textbuf;
1753 gint cursor_pos = -1;
1756 cm_return_val_if_fail(msginfo != NULL, NULL);
1757 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
1760 !(account = compose_guess_forward_account_from_msginfo
1762 account = cur_account;
1764 if (!prefs_common.forward_as_attachment)
1765 mode = COMPOSE_FORWARD_INLINE;
1767 mode = COMPOSE_FORWARD;
1768 compose = compose_create(account, msginfo->folder, mode, batch);
1770 compose->updating = TRUE;
1771 compose->fwdinfo = procmsg_msginfo_get_full_info(msginfo);
1772 if (!compose->fwdinfo)
1773 compose->fwdinfo = procmsg_msginfo_copy(msginfo);
1775 compose_extract_original_charset(compose);
1777 if (msginfo->subject && *msginfo->subject) {
1778 gchar *buf, *buf2, *p;
1780 buf = p = g_strdup(msginfo->subject);
1781 p += subject_get_prefix_length(p);
1782 memmove(buf, p, strlen(p) + 1);
1784 buf2 = g_strdup_printf("Fw: %s", buf);
1785 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
1791 /* override from name according to folder properties */
1792 if (msginfo->folder && msginfo->folder->prefs &&
1793 msginfo->folder->prefs->forward_with_format &&
1794 msginfo->folder->prefs->forward_override_from_format &&
1795 *msginfo->folder->prefs->forward_override_from_format != '\0') {
1799 MsgInfo *full_msginfo = NULL;
1802 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1804 full_msginfo = procmsg_msginfo_copy(msginfo);
1806 /* decode \-escape sequences in the internal representation of the quote format */
1807 tmp = g_malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
1808 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
1811 gtkaspell_block_check(compose->gtkaspell);
1812 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE,
1813 compose->gtkaspell);
1815 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE);
1817 quote_fmt_scan_string(tmp);
1820 buf = quote_fmt_get_buffer();
1822 alertpanel_error(_("The \"From\" field of the \"Forward\" template contains an invalid email address."));
1824 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1825 quote_fmt_reset_vartable();
1828 procmsg_msginfo_free(&full_msginfo);
1831 textview = GTK_TEXT_VIEW(compose->text);
1832 textbuf = gtk_text_view_get_buffer(textview);
1833 compose_create_tags(textview, compose);
1835 undo_block(compose->undostruct);
1839 msgfile = procmsg_get_message_file(msginfo);
1840 if (!is_file_exist(msgfile))
1841 g_warning("%s: file does not exist", msgfile);
1843 compose_attach_append(compose, msgfile, msgfile,
1844 "message/rfc822", NULL);
1848 const gchar *qmark = NULL;
1849 const gchar *body_fmt = NULL;
1850 MsgInfo *full_msginfo;
1852 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1854 full_msginfo = procmsg_msginfo_copy(msginfo);
1856 /* use the forward format of folder (if enabled), or the account's one
1857 (if enabled) or fallback to the global forward format, which is always
1858 enabled (even if empty), and use the relevant quotemark */
1859 if (msginfo->folder && msginfo->folder->prefs &&
1860 msginfo->folder->prefs->forward_with_format) {
1861 qmark = msginfo->folder->prefs->forward_quotemark;
1862 body_fmt = msginfo->folder->prefs->forward_body_format;
1864 } else if (account->forward_with_format) {
1865 qmark = account->forward_quotemark;
1866 body_fmt = account->forward_body_format;
1869 qmark = prefs_common.fw_quotemark;
1870 if (prefs_common.fw_quotefmt && *prefs_common.fw_quotefmt)
1871 body_fmt = gettext(prefs_common.fw_quotefmt);
1876 /* empty quotemark is not allowed */
1877 if (qmark == NULL || *qmark == '\0')
1880 compose_quote_fmt(compose, full_msginfo,
1881 body_fmt, qmark, body, FALSE, TRUE,
1882 _("The body of the \"Forward\" template has an error at line %d."));
1883 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1884 quote_fmt_reset_vartable();
1885 compose_attach_parts(compose, msginfo);
1887 procmsg_msginfo_free(&full_msginfo);
1890 SIGNAL_BLOCK(textbuf);
1892 if (account->auto_sig)
1893 compose_insert_sig(compose, FALSE);
1895 compose_wrap_all(compose);
1898 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1899 gtkaspell_highlight_all(compose->gtkaspell);
1900 gtkaspell_unblock_check(compose->gtkaspell);
1902 SIGNAL_UNBLOCK(textbuf);
1904 cursor_pos = quote_fmt_get_cursor_pos();
1905 if (cursor_pos == -1)
1906 gtk_widget_grab_focus(compose->header_last->entry);
1908 gtk_widget_grab_focus(compose->text);
1910 if (!no_extedit && prefs_common.auto_exteditor)
1911 compose_exec_ext_editor(compose);
1914 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1915 gchar *folderidentifier;
1917 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1918 folderidentifier = folder_item_get_identifier(msginfo->folder);
1919 compose_set_save_to(compose, folderidentifier);
1920 g_free(folderidentifier);
1923 undo_unblock(compose->undostruct);
1925 compose->modified = FALSE;
1926 compose_set_title(compose);
1928 compose->updating = FALSE;
1929 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
1930 SCROLL_TO_CURSOR(compose);
1932 if (compose->deferred_destroy) {
1933 compose_destroy(compose);
1937 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
1942 #undef INSERT_FW_HEADER
1944 static Compose *compose_forward_multiple(PrefsAccount *account, GSList *msginfo_list)
1947 GtkTextView *textview;
1948 GtkTextBuffer *textbuf;
1952 gboolean single_mail = TRUE;
1954 cm_return_val_if_fail(msginfo_list != NULL, NULL);
1956 if (g_slist_length(msginfo_list) > 1)
1957 single_mail = FALSE;
1959 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next)
1960 if (((MsgInfo *)msginfo->data)->folder == NULL)
1963 /* guess account from first selected message */
1965 !(account = compose_guess_forward_account_from_msginfo
1966 (msginfo_list->data)))
1967 account = cur_account;
1969 cm_return_val_if_fail(account != NULL, NULL);
1971 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
1972 if (msginfo->data) {
1973 MSG_UNSET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_REPLIED);
1974 MSG_SET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_FORWARDED);
1978 if (msginfo_list == NULL || msginfo_list->data == NULL) {
1979 g_warning("no msginfo_list");
1983 compose = compose_create(account, ((MsgInfo *)msginfo_list->data)->folder, COMPOSE_FORWARD, FALSE);
1985 compose->updating = TRUE;
1987 /* override from name according to folder properties */
1988 if (msginfo_list->data) {
1989 MsgInfo *msginfo = msginfo_list->data;
1991 if (msginfo->folder && msginfo->folder->prefs &&
1992 msginfo->folder->prefs->forward_with_format &&
1993 msginfo->folder->prefs->forward_override_from_format &&
1994 *msginfo->folder->prefs->forward_override_from_format != '\0') {
1999 /* decode \-escape sequences in the internal representation of the quote format */
2000 tmp = g_malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
2001 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
2004 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
2005 compose->gtkaspell);
2007 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
2009 quote_fmt_scan_string(tmp);
2012 buf = quote_fmt_get_buffer();
2014 alertpanel_error(_("The \"From\" field of the \"Forward\" template contains an invalid email address."));
2016 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
2017 quote_fmt_reset_vartable();
2023 textview = GTK_TEXT_VIEW(compose->text);
2024 textbuf = gtk_text_view_get_buffer(textview);
2025 compose_create_tags(textview, compose);
2027 undo_block(compose->undostruct);
2028 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
2029 msgfile = procmsg_get_message_file((MsgInfo *)msginfo->data);
2031 if (!is_file_exist(msgfile))
2032 g_warning("%s: file does not exist", msgfile);
2034 compose_attach_append(compose, msgfile, msgfile,
2035 "message/rfc822", NULL);
2040 MsgInfo *info = (MsgInfo *)msginfo_list->data;
2041 if (info->subject && *info->subject) {
2042 gchar *buf, *buf2, *p;
2044 buf = p = g_strdup(info->subject);
2045 p += subject_get_prefix_length(p);
2046 memmove(buf, p, strlen(p) + 1);
2048 buf2 = g_strdup_printf("Fw: %s", buf);
2049 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
2055 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2056 _("Fw: multiple emails"));
2059 SIGNAL_BLOCK(textbuf);
2061 if (account->auto_sig)
2062 compose_insert_sig(compose, FALSE);
2064 compose_wrap_all(compose);
2066 SIGNAL_UNBLOCK(textbuf);
2068 gtk_text_buffer_get_start_iter(textbuf, &iter);
2069 gtk_text_buffer_place_cursor(textbuf, &iter);
2071 if (prefs_common.auto_exteditor)
2072 compose_exec_ext_editor(compose);
2074 gtk_widget_grab_focus(compose->header_last->entry);
2075 undo_unblock(compose->undostruct);
2076 compose->modified = FALSE;
2077 compose_set_title(compose);
2079 compose->updating = FALSE;
2080 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2081 SCROLL_TO_CURSOR(compose);
2083 if (compose->deferred_destroy) {
2084 compose_destroy(compose);
2088 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2093 static gboolean compose_is_sig_separator(Compose *compose, GtkTextBuffer *textbuf, GtkTextIter *iter)
2095 GtkTextIter start = *iter;
2096 GtkTextIter end_iter;
2097 int start_pos = gtk_text_iter_get_offset(&start);
2099 if (!compose->account->sig_sep)
2102 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
2103 start_pos+strlen(compose->account->sig_sep));
2105 /* check sig separator */
2106 str = gtk_text_iter_get_text(&start, &end_iter);
2107 if (!strcmp(str, compose->account->sig_sep)) {
2109 /* check end of line (\n) */
2110 gtk_text_buffer_get_iter_at_offset(textbuf, &start,
2111 start_pos+strlen(compose->account->sig_sep));
2112 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
2113 start_pos+strlen(compose->account->sig_sep)+1);
2114 tmp = gtk_text_iter_get_text(&start, &end_iter);
2115 if (!strcmp(tmp,"\n")) {
2127 static gboolean compose_update_folder_hook(gpointer source, gpointer data)
2129 FolderUpdateData *hookdata = (FolderUpdateData *)source;
2130 Compose *compose = (Compose *)data;
2131 FolderItem *old_item = NULL;
2132 FolderItem *new_item = NULL;
2133 gchar *old_id, *new_id;
2135 if (!(hookdata->update_flags & FOLDER_REMOVE_FOLDERITEM)
2136 && !(hookdata->update_flags & FOLDER_MOVE_FOLDERITEM))
2139 old_item = hookdata->item;
2140 new_item = hookdata->item2;
2142 old_id = folder_item_get_identifier(old_item);
2143 new_id = new_item ? folder_item_get_identifier(new_item) : g_strdup("NULL");
2145 if (compose->targetinfo && compose->targetinfo->folder == old_item) {
2146 debug_print("updating targetinfo folder: %s -> %s\n", old_id, new_id);
2147 compose->targetinfo->folder = new_item;
2150 if (compose->replyinfo && compose->replyinfo->folder == old_item) {
2151 debug_print("updating replyinfo folder: %s -> %s\n", old_id, new_id);
2152 compose->replyinfo->folder = new_item;
2155 if (compose->fwdinfo && compose->fwdinfo->folder == old_item) {
2156 debug_print("updating fwdinfo folder: %s -> %s\n", old_id, new_id);
2157 compose->fwdinfo->folder = new_item;
2165 static void compose_colorize_signature(Compose *compose)
2167 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
2169 GtkTextIter end_iter;
2170 gtk_text_buffer_get_start_iter(buffer, &iter);
2171 while (gtk_text_iter_forward_line(&iter))
2172 if (compose_is_sig_separator(compose, buffer, &iter)) {
2173 gtk_text_buffer_get_end_iter(buffer, &end_iter);
2174 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &end_iter);
2178 #define BLOCK_WRAP() { \
2179 prev_autowrap = compose->autowrap; \
2180 buffer = gtk_text_view_get_buffer( \
2181 GTK_TEXT_VIEW(compose->text)); \
2182 compose->autowrap = FALSE; \
2184 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2185 G_CALLBACK(compose_changed_cb), \
2187 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2188 G_CALLBACK(text_inserted), \
2191 #define UNBLOCK_WRAP() { \
2192 compose->autowrap = prev_autowrap; \
2193 if (compose->autowrap) { \
2194 gint old = compose->draft_timeout_tag; \
2195 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN; \
2196 compose_wrap_all(compose); \
2197 compose->draft_timeout_tag = old; \
2200 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2201 G_CALLBACK(compose_changed_cb), \
2203 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2204 G_CALLBACK(text_inserted), \
2208 Compose *compose_reedit(MsgInfo *msginfo, gboolean batch)
2210 Compose *compose = NULL;
2211 PrefsAccount *account = NULL;
2212 GtkTextView *textview;
2213 GtkTextBuffer *textbuf;
2217 gchar buf[BUFFSIZE];
2218 gboolean use_signing = FALSE;
2219 gboolean use_encryption = FALSE;
2220 gchar *privacy_system = NULL;
2221 int priority = PRIORITY_NORMAL;
2222 MsgInfo *replyinfo = NULL, *fwdinfo = NULL;
2223 gboolean autowrap = prefs_common.autowrap;
2224 gboolean autoindent = prefs_common.auto_indent;
2225 HeaderEntry *manual_headers = NULL;
2227 cm_return_val_if_fail(msginfo != NULL, NULL);
2228 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
2230 if (compose_put_existing_to_front(msginfo)) {
2234 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2235 folder_has_parent_of_type(msginfo->folder, F_DRAFT) ||
2236 folder_has_parent_of_type(msginfo->folder, F_OUTBOX)) {
2237 gchar queueheader_buf[BUFFSIZE];
2240 /* Select Account from queue headers */
2241 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2242 sizeof(queueheader_buf), "X-Claws-Account-Id:")) {
2243 id = atoi(&queueheader_buf[strlen("X-Claws-Account-Id:")]);
2244 account = account_find_from_id(id);
2246 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2247 sizeof(queueheader_buf), "X-Sylpheed-Account-Id:")) {
2248 id = atoi(&queueheader_buf[strlen("X-Sylpheed-Account-Id:")]);
2249 account = account_find_from_id(id);
2251 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2252 sizeof(queueheader_buf), "NAID:")) {
2253 id = atoi(&queueheader_buf[strlen("NAID:")]);
2254 account = account_find_from_id(id);
2256 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2257 sizeof(queueheader_buf), "MAID:")) {
2258 id = atoi(&queueheader_buf[strlen("MAID:")]);
2259 account = account_find_from_id(id);
2261 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2262 sizeof(queueheader_buf), "S:")) {
2263 account = account_find_from_address(queueheader_buf, FALSE);
2265 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2266 sizeof(queueheader_buf), "X-Claws-Sign:")) {
2267 param = atoi(&queueheader_buf[strlen("X-Claws-Sign:")]);
2268 use_signing = param;
2271 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2272 sizeof(queueheader_buf), "X-Sylpheed-Sign:")) {
2273 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Sign:")]);
2274 use_signing = param;
2277 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2278 sizeof(queueheader_buf), "X-Claws-Encrypt:")) {
2279 param = atoi(&queueheader_buf[strlen("X-Claws-Encrypt:")]);
2280 use_encryption = param;
2282 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2283 sizeof(queueheader_buf), "X-Sylpheed-Encrypt:")) {
2284 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Encrypt:")]);
2285 use_encryption = param;
2287 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2288 sizeof(queueheader_buf), "X-Claws-Auto-Wrapping:")) {
2289 param = atoi(&queueheader_buf[strlen("X-Claws-Auto-Wrapping:")]);
2292 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2293 sizeof(queueheader_buf), "X-Claws-Auto-Indent:")) {
2294 param = atoi(&queueheader_buf[strlen("X-Claws-Auto-Indent:")]);
2297 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2298 sizeof(queueheader_buf), "X-Claws-Privacy-System:")) {
2299 privacy_system = g_strdup(&queueheader_buf[strlen("X-Claws-Privacy-System:")]);
2301 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2302 sizeof(queueheader_buf), "X-Sylpheed-Privacy-System:")) {
2303 privacy_system = g_strdup(&queueheader_buf[strlen("X-Sylpheed-Privacy-System:")]);
2305 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2306 sizeof(queueheader_buf), "X-Priority: ")) {
2307 param = atoi(&queueheader_buf[strlen("X-Priority: ")]); /* mind the space */
2310 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2311 sizeof(queueheader_buf), "RMID:")) {
2312 gchar **tokens = g_strsplit(&queueheader_buf[strlen("RMID:")], "\t", 0);
2313 if (tokens[0] && tokens[1] && tokens[2]) {
2314 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2315 if (orig_item != NULL) {
2316 replyinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2321 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2322 sizeof(queueheader_buf), "FMID:")) {
2323 gchar **tokens = g_strsplit(&queueheader_buf[strlen("FMID:")], "\t", 0);
2324 if (tokens[0] && tokens[1] && tokens[2]) {
2325 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2326 if (orig_item != NULL) {
2327 fwdinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2332 /* Get manual headers */
2333 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "X-Claws-Manual-Headers:")) {
2334 gchar *listmh = g_strdup(&queueheader_buf[strlen("X-Claws-Manual-Headers:")]);
2335 if (*listmh != '\0') {
2336 debug_print("Got manual headers: %s\n", listmh);
2337 manual_headers = procheader_entries_from_str(listmh);
2342 account = msginfo->folder->folder->account;
2345 if (!account && prefs_common.reedit_account_autosel) {
2346 gchar from[BUFFSIZE];
2347 if (!procheader_get_header_from_msginfo(msginfo, from, sizeof(from), "FROM:")) {
2348 extract_address(from);
2349 account = account_find_from_address(from, FALSE);
2353 account = cur_account;
2355 cm_return_val_if_fail(account != NULL, NULL);
2357 compose = compose_create(account, msginfo->folder, COMPOSE_REEDIT, batch);
2359 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", autowrap);
2360 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", autoindent);
2361 compose->autowrap = autowrap;
2362 compose->replyinfo = replyinfo;
2363 compose->fwdinfo = fwdinfo;
2365 compose->updating = TRUE;
2366 compose->priority = priority;
2368 if (privacy_system != NULL) {
2369 compose->privacy_system = privacy_system;
2370 compose_use_signing(compose, use_signing);
2371 compose_use_encryption(compose, use_encryption);
2372 compose_update_privacy_system_menu_item(compose, FALSE);
2374 activate_privacy_system(compose, account, FALSE);
2377 compose->targetinfo = procmsg_msginfo_copy(msginfo);
2379 compose_extract_original_charset(compose);
2381 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2382 folder_has_parent_of_type(msginfo->folder, F_DRAFT) ||
2383 folder_has_parent_of_type(msginfo->folder, F_OUTBOX)) {
2384 gchar queueheader_buf[BUFFSIZE];
2386 /* Set message save folder */
2387 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "SCF:")) {
2388 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
2389 compose_set_save_to(compose, &queueheader_buf[4]);
2391 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "RRCPT:")) {
2392 gint active = atoi(&queueheader_buf[strlen("RRCPT:")]);
2394 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
2399 if (compose_parse_header(compose, msginfo) < 0) {
2400 compose->updating = FALSE;
2401 compose_destroy(compose);
2404 compose_reedit_set_entry(compose, msginfo);
2406 textview = GTK_TEXT_VIEW(compose->text);
2407 textbuf = gtk_text_view_get_buffer(textview);
2408 compose_create_tags(textview, compose);
2410 mark = gtk_text_buffer_get_insert(textbuf);
2411 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
2413 g_signal_handlers_block_by_func(G_OBJECT(textbuf),
2414 G_CALLBACK(compose_changed_cb),
2417 if (MSG_IS_ENCRYPTED(msginfo->flags)) {
2418 fp = procmime_get_first_encrypted_text_content(msginfo);
2420 compose_force_encryption(compose, account, TRUE, NULL);
2423 fp = procmime_get_first_text_content(msginfo);
2426 g_warning("Can't get text part");
2430 gboolean prev_autowrap;
2431 GtkTextBuffer *buffer;
2433 while (fgets(buf, sizeof(buf), fp) != NULL) {
2435 gtk_text_buffer_insert(textbuf, &iter, buf, -1);
2441 compose_attach_parts(compose, msginfo);
2443 compose_colorize_signature(compose);
2445 g_signal_handlers_unblock_by_func(G_OBJECT(textbuf),
2446 G_CALLBACK(compose_changed_cb),
2449 if (manual_headers != NULL) {
2450 if (compose_parse_manual_headers(compose, msginfo, manual_headers) < 0) {
2451 procheader_entries_free(manual_headers);
2452 compose->updating = FALSE;
2453 compose_destroy(compose);
2456 procheader_entries_free(manual_headers);
2459 gtk_widget_grab_focus(compose->text);
2461 if (prefs_common.auto_exteditor) {
2462 compose_exec_ext_editor(compose);
2464 compose->modified = FALSE;
2465 compose_set_title(compose);
2467 compose->updating = FALSE;
2468 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2469 SCROLL_TO_CURSOR(compose);
2471 if (compose->deferred_destroy) {
2472 compose_destroy(compose);
2476 compose->sig_str = account_get_signature_str(compose->account);
2478 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2483 Compose *compose_redirect(PrefsAccount *account, MsgInfo *msginfo,
2490 cm_return_val_if_fail(msginfo != NULL, NULL);
2493 account = account_get_reply_account(msginfo,
2494 prefs_common.reply_account_autosel);
2495 cm_return_val_if_fail(account != NULL, NULL);
2497 compose = compose_create(account, msginfo->folder, COMPOSE_REDIRECT, batch);
2499 compose->updating = TRUE;
2501 compose_create_tags(GTK_TEXT_VIEW(compose->text), compose);
2502 compose->replyinfo = NULL;
2503 compose->fwdinfo = NULL;
2505 compose_show_first_last_header(compose, TRUE);
2507 gtk_widget_grab_focus(compose->header_last->entry);
2509 filename = procmsg_get_message_file(msginfo);
2511 if (filename == NULL) {
2512 compose->updating = FALSE;
2513 compose_destroy(compose);
2518 compose->redirect_filename = filename;
2520 /* Set save folder */
2521 item = msginfo->folder;
2522 if (item && item->prefs && item->prefs->save_copy_to_folder) {
2523 gchar *folderidentifier;
2525 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
2526 folderidentifier = folder_item_get_identifier(item);
2527 compose_set_save_to(compose, folderidentifier);
2528 g_free(folderidentifier);
2531 compose_attach_parts(compose, msginfo);
2533 if (msginfo->subject)
2534 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2536 gtk_editable_set_editable(GTK_EDITABLE(compose->subject_entry), FALSE);
2538 compose_quote_fmt(compose, msginfo, "%M", NULL, NULL, FALSE, FALSE,
2539 _("The body of the \"Redirect\" template has an error at line %d."));
2540 quote_fmt_reset_vartable();
2541 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), FALSE);
2543 compose_colorize_signature(compose);
2546 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Add", FALSE);
2547 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", FALSE);
2548 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", FALSE);
2550 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/Save", FALSE);
2551 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertFile", FALSE);
2552 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/AttachFile", FALSE);
2553 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertSig", FALSE);
2554 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/ReplaceSig", FALSE);
2555 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", FALSE);
2556 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", FALSE);
2557 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/ShowRuler", FALSE);
2558 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Actions", FALSE);
2560 if (compose->toolbar->draft_btn)
2561 gtk_widget_set_sensitive(compose->toolbar->draft_btn, FALSE);
2562 if (compose->toolbar->insert_btn)
2563 gtk_widget_set_sensitive(compose->toolbar->insert_btn, FALSE);
2564 if (compose->toolbar->attach_btn)
2565 gtk_widget_set_sensitive(compose->toolbar->attach_btn, FALSE);
2566 if (compose->toolbar->sig_btn)
2567 gtk_widget_set_sensitive(compose->toolbar->sig_btn, FALSE);
2568 if (compose->toolbar->exteditor_btn)
2569 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, FALSE);
2570 if (compose->toolbar->linewrap_current_btn)
2571 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, FALSE);
2572 if (compose->toolbar->linewrap_all_btn)
2573 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, FALSE);
2575 compose->modified = FALSE;
2576 compose_set_title(compose);
2577 compose->updating = FALSE;
2578 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2579 SCROLL_TO_CURSOR(compose);
2581 if (compose->deferred_destroy) {
2582 compose_destroy(compose);
2586 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2591 const GList *compose_get_compose_list(void)
2593 return compose_list;
2596 void compose_entry_append(Compose *compose, const gchar *address,
2597 ComposeEntryType type, ComposePrefType pref_type)
2599 const gchar *header;
2601 gboolean in_quote = FALSE;
2602 if (!address || *address == '\0') return;
2609 header = N_("Bcc:");
2611 case COMPOSE_REPLYTO:
2612 header = N_("Reply-To:");
2614 case COMPOSE_NEWSGROUPS:
2615 header = N_("Newsgroups:");
2617 case COMPOSE_FOLLOWUPTO:
2618 header = N_( "Followup-To:");
2620 case COMPOSE_INREPLYTO:
2621 header = N_( "In-Reply-To:");
2628 header = prefs_common_translated_header_name(header);
2630 cur = begin = (gchar *)address;
2632 /* we separate the line by commas, but not if we're inside a quoted
2634 while (*cur != '\0') {
2636 in_quote = !in_quote;
2637 if (*cur == ',' && !in_quote) {
2638 gchar *tmp = g_strdup(begin);
2640 tmp[cur-begin]='\0';
2643 while (*tmp == ' ' || *tmp == '\t')
2645 compose_add_header_entry(compose, header, tmp, pref_type);
2652 gchar *tmp = g_strdup(begin);
2654 tmp[cur-begin]='\0';
2655 while (*tmp == ' ' || *tmp == '\t')
2657 compose_add_header_entry(compose, header, tmp, pref_type);
2662 static void compose_entry_mark_default_to(Compose *compose, const gchar *mailto)
2664 #if !GTK_CHECK_VERSION(3, 0, 0)
2665 static GdkColor yellow;
2666 static GdkColor black;
2667 static gboolean yellow_initialised = FALSE;
2669 static GdkColor yellow = { (guint32)0, (guint16)0xf5, (guint16)0xf6, (guint16)0xbe };
2670 static GdkColor black = { (guint32)0, (guint16)0x0, (guint16)0x0, (guint16)0x0 };
2675 #if !GTK_CHECK_VERSION(3, 0, 0)
2676 if (!yellow_initialised) {
2677 gdk_color_parse("#f5f6be", &yellow);
2678 gdk_color_parse("#000000", &black);
2679 yellow_initialised = gdk_colormap_alloc_color(
2680 gdk_colormap_get_system(), &yellow, FALSE, TRUE);
2681 yellow_initialised &= gdk_colormap_alloc_color(
2682 gdk_colormap_get_system(), &black, FALSE, TRUE);
2686 for (h_list = compose->header_list; h_list != NULL; h_list = h_list->next) {
2687 entry = GTK_ENTRY(((ComposeHeaderEntry *)h_list->data)->entry);
2688 if (gtk_entry_get_text(entry) &&
2689 !g_utf8_collate(gtk_entry_get_text(entry), mailto)) {
2690 #if !GTK_CHECK_VERSION(3, 0, 0)
2691 if (yellow_initialised) {
2693 gtk_widget_modify_base(
2694 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2695 GTK_STATE_NORMAL, &yellow);
2696 gtk_widget_modify_text(
2697 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2698 GTK_STATE_NORMAL, &black);
2699 #if !GTK_CHECK_VERSION(3, 0, 0)
2706 void compose_toolbar_cb(gint action, gpointer data)
2708 ToolbarItem *toolbar_item = (ToolbarItem*)data;
2709 Compose *compose = (Compose*)toolbar_item->parent;
2711 cm_return_if_fail(compose != NULL);
2715 compose_send_cb(NULL, compose);
2718 compose_send_later_cb(NULL, compose);
2721 compose_draft(compose, COMPOSE_QUIT_EDITING);
2724 compose_insert_file_cb(NULL, compose);
2727 compose_attach_cb(NULL, compose);
2730 compose_insert_sig(compose, FALSE);
2733 compose_insert_sig(compose, TRUE);
2736 compose_ext_editor_cb(NULL, compose);
2738 case A_LINEWRAP_CURRENT:
2739 compose_beautify_paragraph(compose, NULL, TRUE);
2741 case A_LINEWRAP_ALL:
2742 compose_wrap_all_full(compose, TRUE);
2745 compose_address_cb(NULL, compose);
2748 case A_CHECK_SPELLING:
2749 compose_check_all(NULL, compose);
2757 static MailField compose_entries_set(Compose *compose, const gchar *mailto, ComposeEntryType to_type)
2762 gchar *subject = NULL;
2766 gchar **attach = NULL;
2767 gchar *inreplyto = NULL;
2768 MailField mfield = NO_FIELD_PRESENT;
2770 /* get mailto parts but skip from */
2771 scan_mailto_url(mailto, NULL, &to, &cc, &bcc, &subject, &body, &attach, &inreplyto);
2774 compose_entry_append(compose, to, to_type, PREF_MAILTO);
2775 mfield = TO_FIELD_PRESENT;
2778 compose_entry_append(compose, cc, COMPOSE_CC, PREF_MAILTO);
2780 compose_entry_append(compose, bcc, COMPOSE_BCC, PREF_MAILTO);
2782 if (!g_utf8_validate (subject, -1, NULL)) {
2783 temp = g_locale_to_utf8 (subject, -1, NULL, &len, NULL);
2784 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), temp);
2787 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), subject);
2789 mfield = SUBJECT_FIELD_PRESENT;
2792 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2793 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2796 gboolean prev_autowrap = compose->autowrap;
2798 compose->autowrap = FALSE;
2800 mark = gtk_text_buffer_get_insert(buffer);
2801 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2803 if (!g_utf8_validate (body, -1, NULL)) {
2804 temp = g_locale_to_utf8 (body, -1, NULL, &len, NULL);
2805 gtk_text_buffer_insert(buffer, &iter, temp, -1);
2808 gtk_text_buffer_insert(buffer, &iter, body, -1);
2810 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
2812 compose->autowrap = prev_autowrap;
2813 if (compose->autowrap)
2814 compose_wrap_all(compose);
2815 mfield = BODY_FIELD_PRESENT;
2819 gint i = 0, att = 0;
2820 gchar *warn_files = NULL;
2821 while (attach[i] != NULL) {
2822 gchar *utf8_filename = conv_filename_to_utf8(attach[i]);
2823 if (utf8_filename) {
2824 if (compose_attach_append(compose, attach[i], utf8_filename, NULL, NULL)) {
2825 gchar *tmp = g_strdup_printf("%s%s\n",
2826 warn_files?warn_files:"",
2832 g_free(utf8_filename);
2834 alertpanel_error(_("Couldn't attach a file (charset conversion failed)."));
2839 alertpanel_notice(ngettext(
2840 "The following file has been attached: \n%s",
2841 "The following files have been attached: \n%s", att), warn_files);
2846 compose_entry_append(compose, inreplyto, COMPOSE_INREPLYTO, PREF_MAILTO);
2859 static gint compose_parse_header(Compose *compose, MsgInfo *msginfo)
2861 static HeaderEntry hentry[] = {{"Reply-To:", NULL, TRUE},
2862 {"Cc:", NULL, TRUE},
2863 {"References:", NULL, FALSE},
2864 {"Bcc:", NULL, TRUE},
2865 {"Newsgroups:", NULL, TRUE},
2866 {"Followup-To:", NULL, TRUE},
2867 {"List-Post:", NULL, FALSE},
2868 {"X-Priority:", NULL, FALSE},
2869 {NULL, NULL, FALSE}};
2885 cm_return_val_if_fail(msginfo != NULL, -1);
2887 if ((fp = procmsg_open_message(msginfo)) == NULL) return -1;
2888 procheader_get_header_fields(fp, hentry);
2891 if (hentry[H_REPLY_TO].body != NULL) {
2892 if (hentry[H_REPLY_TO].body[0] != '\0') {
2894 conv_unmime_header(hentry[H_REPLY_TO].body,
2897 g_free(hentry[H_REPLY_TO].body);
2898 hentry[H_REPLY_TO].body = NULL;
2900 if (hentry[H_CC].body != NULL) {
2901 compose->cc = conv_unmime_header(hentry[H_CC].body, NULL, TRUE);
2902 g_free(hentry[H_CC].body);
2903 hentry[H_CC].body = NULL;
2905 if (hentry[H_REFERENCES].body != NULL) {
2906 if (compose->mode == COMPOSE_REEDIT)
2907 compose->references = hentry[H_REFERENCES].body;
2909 compose->references = compose_parse_references
2910 (hentry[H_REFERENCES].body, msginfo->msgid);
2911 g_free(hentry[H_REFERENCES].body);
2913 hentry[H_REFERENCES].body = NULL;
2915 if (hentry[H_BCC].body != NULL) {
2916 if (compose->mode == COMPOSE_REEDIT)
2918 conv_unmime_header(hentry[H_BCC].body, NULL, TRUE);
2919 g_free(hentry[H_BCC].body);
2920 hentry[H_BCC].body = NULL;
2922 if (hentry[H_NEWSGROUPS].body != NULL) {
2923 compose->newsgroups = hentry[H_NEWSGROUPS].body;
2924 hentry[H_NEWSGROUPS].body = NULL;
2926 if (hentry[H_FOLLOWUP_TO].body != NULL) {
2927 if (hentry[H_FOLLOWUP_TO].body[0] != '\0') {
2928 compose->followup_to =
2929 conv_unmime_header(hentry[H_FOLLOWUP_TO].body,
2932 g_free(hentry[H_FOLLOWUP_TO].body);
2933 hentry[H_FOLLOWUP_TO].body = NULL;
2935 if (hentry[H_LIST_POST].body != NULL) {
2936 gchar *to = NULL, *start = NULL;
2938 extract_address(hentry[H_LIST_POST].body);
2939 if (hentry[H_LIST_POST].body[0] != '\0') {
2940 start = strstr(hentry[H_LIST_POST].body, "mailto:");
2942 scan_mailto_url(start ? start : hentry[H_LIST_POST].body,
2943 NULL, &to, NULL, NULL, NULL, NULL, NULL, NULL);
2946 g_free(compose->ml_post);
2947 compose->ml_post = to;
2950 g_free(hentry[H_LIST_POST].body);
2951 hentry[H_LIST_POST].body = NULL;
2954 /* CLAWS - X-Priority */
2955 if (compose->mode == COMPOSE_REEDIT)
2956 if (hentry[H_X_PRIORITY].body != NULL) {
2959 priority = atoi(hentry[H_X_PRIORITY].body);
2960 g_free(hentry[H_X_PRIORITY].body);
2962 hentry[H_X_PRIORITY].body = NULL;
2964 if (priority < PRIORITY_HIGHEST ||
2965 priority > PRIORITY_LOWEST)
2966 priority = PRIORITY_NORMAL;
2968 compose->priority = priority;
2971 if (compose->mode == COMPOSE_REEDIT) {
2972 if (msginfo->inreplyto && *msginfo->inreplyto)
2973 compose->inreplyto = g_strdup(msginfo->inreplyto);
2975 if (msginfo->msgid && *msginfo->msgid)
2976 compose->msgid = g_strdup(msginfo->msgid);
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, ">",
3003 static gint compose_parse_manual_headers(Compose *compose, MsgInfo *msginfo, HeaderEntry *entries)
3008 cm_return_val_if_fail(msginfo != NULL, -1);
3010 if ((fp = procmsg_open_message(msginfo)) == NULL) return -1;
3011 procheader_get_header_fields(fp, entries);
3015 while (he != NULL && he->name != NULL) {
3017 GtkListStore *model = NULL;
3019 debug_print("Adding manual header: %s with value %s\n", he->name, he->body);
3020 model = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(compose->header_last->combo)));
3021 COMBOBOX_ADD(model, he->name, COMPOSE_TO);
3022 gtk_combo_box_set_active_iter(GTK_COMBO_BOX(compose->header_last->combo), &iter);
3023 gtk_entry_set_text(GTK_ENTRY(compose->header_last->entry), he->body);
3030 static gchar *compose_parse_references(const gchar *ref, const gchar *msgid)
3032 GSList *ref_id_list, *cur;
3036 ref_id_list = references_list_append(NULL, ref);
3037 if (!ref_id_list) return NULL;
3038 if (msgid && *msgid)
3039 ref_id_list = g_slist_append(ref_id_list, g_strdup(msgid));
3044 for (cur = ref_id_list; cur != NULL; cur = cur->next)
3045 /* "<" + Message-ID + ">" + CR+LF+TAB */
3046 len += strlen((gchar *)cur->data) + 5;
3048 if (len > MAX_REFERENCES_LEN) {
3049 /* remove second message-ID */
3050 if (ref_id_list && ref_id_list->next &&
3051 ref_id_list->next->next) {
3052 g_free(ref_id_list->next->data);
3053 ref_id_list = g_slist_remove
3054 (ref_id_list, ref_id_list->next->data);
3056 slist_free_strings_full(ref_id_list);
3063 new_ref = g_string_new("");
3064 for (cur = ref_id_list; cur != NULL; cur = cur->next) {
3065 if (new_ref->len > 0)
3066 g_string_append(new_ref, "\n\t");
3067 g_string_append_printf(new_ref, "<%s>", (gchar *)cur->data);
3070 slist_free_strings_full(ref_id_list);
3072 new_ref_str = new_ref->str;
3073 g_string_free(new_ref, FALSE);
3078 static gchar *compose_quote_fmt(Compose *compose, MsgInfo *msginfo,
3079 const gchar *fmt, const gchar *qmark,
3080 const gchar *body, gboolean rewrap,
3081 gboolean need_unescape,
3082 const gchar *err_msg)
3084 MsgInfo* dummyinfo = NULL;
3085 gchar *quote_str = NULL;
3087 gboolean prev_autowrap;
3088 const gchar *trimmed_body = body;
3089 gint cursor_pos = -1;
3090 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3091 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3096 SIGNAL_BLOCK(buffer);
3099 dummyinfo = compose_msginfo_new_from_compose(compose);
3100 msginfo = dummyinfo;
3103 if (qmark != NULL) {
3105 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
3106 compose->gtkaspell);
3108 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
3110 quote_fmt_scan_string(qmark);
3113 buf = quote_fmt_get_buffer();
3115 alertpanel_error(_("The \"Quotation mark\" of the template is invalid."));
3117 Xstrdup_a(quote_str, buf, goto error)
3120 if (fmt && *fmt != '\0') {
3123 while (*trimmed_body == '\n')
3127 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE,
3128 compose->gtkaspell);
3130 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE);
3132 if (need_unescape) {
3135 /* decode \-escape sequences in the internal representation of the quote format */
3136 tmp = g_malloc(strlen(fmt)+1);
3137 pref_get_unescaped_pref(tmp, fmt);
3138 quote_fmt_scan_string(tmp);
3142 quote_fmt_scan_string(fmt);
3146 buf = quote_fmt_get_buffer();
3148 gint line = quote_fmt_get_line();
3149 alertpanel_error(err_msg, line);
3155 prev_autowrap = compose->autowrap;
3156 compose->autowrap = FALSE;
3158 mark = gtk_text_buffer_get_insert(buffer);
3159 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3160 if (g_utf8_validate(buf, -1, NULL)) {
3161 gtk_text_buffer_insert(buffer, &iter, buf, -1);
3163 gchar *tmpout = NULL;
3164 tmpout = conv_codeset_strdup
3165 (buf, conv_get_locale_charset_str_no_utf8(),
3167 if (!tmpout || !g_utf8_validate(tmpout, -1, NULL)) {
3169 tmpout = g_malloc(strlen(buf)*2+1);
3170 conv_localetodisp(tmpout, strlen(buf)*2+1, buf);
3172 gtk_text_buffer_insert(buffer, &iter, tmpout, -1);
3176 cursor_pos = quote_fmt_get_cursor_pos();
3177 if (cursor_pos == -1)
3178 cursor_pos = gtk_text_iter_get_offset(&iter);
3179 compose->set_cursor_pos = cursor_pos;
3181 gtk_text_buffer_get_start_iter(buffer, &iter);
3182 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
3183 gtk_text_buffer_place_cursor(buffer, &iter);
3185 compose->autowrap = prev_autowrap;
3186 if (compose->autowrap && rewrap)
3187 compose_wrap_all(compose);
3194 SIGNAL_UNBLOCK(buffer);
3196 procmsg_msginfo_free( &dummyinfo );
3201 /* if ml_post is of type addr@host and from is of type
3202 * addr-anything@host, return TRUE
3204 static gboolean is_subscription(const gchar *ml_post, const gchar *from)
3206 gchar *left_ml = NULL;
3207 gchar *right_ml = NULL;
3208 gchar *left_from = NULL;
3209 gchar *right_from = NULL;
3210 gboolean result = FALSE;
3212 if (!ml_post || !from)
3215 left_ml = g_strdup(ml_post);
3216 if (strstr(left_ml, "@")) {
3217 right_ml = strstr(left_ml, "@")+1;
3218 *(strstr(left_ml, "@")) = '\0';
3221 left_from = g_strdup(from);
3222 if (strstr(left_from, "@")) {
3223 right_from = strstr(left_from, "@")+1;
3224 *(strstr(left_from, "@")) = '\0';
3227 if (right_ml && right_from
3228 && !strncmp(left_from, left_ml, strlen(left_ml))
3229 && !strcmp(right_from, right_ml)) {
3238 static void compose_set_folder_prefs(Compose *compose, FolderItem *folder,
3239 gboolean respect_default_to)
3243 if (!folder || !folder->prefs)
3246 if (respect_default_to && folder->prefs->enable_default_to) {
3247 compose_entry_append(compose, folder->prefs->default_to,
3248 COMPOSE_TO, PREF_FOLDER);
3249 compose_entry_mark_default_to(compose, folder->prefs->default_to);
3251 if (folder->prefs->enable_default_cc)
3252 compose_entry_append(compose, folder->prefs->default_cc,
3253 COMPOSE_CC, PREF_FOLDER);
3254 if (folder->prefs->enable_default_bcc)
3255 compose_entry_append(compose, folder->prefs->default_bcc,
3256 COMPOSE_BCC, PREF_FOLDER);
3257 if (folder->prefs->enable_default_replyto)
3258 compose_entry_append(compose, folder->prefs->default_replyto,
3259 COMPOSE_REPLYTO, PREF_FOLDER);
3262 static void compose_reply_set_subject(Compose *compose, MsgInfo *msginfo)
3267 if (!compose || !msginfo)
3270 if (msginfo->subject && *msginfo->subject) {
3271 buf = p = g_strdup(msginfo->subject);
3272 p += subject_get_prefix_length(p);
3273 memmove(buf, p, strlen(p) + 1);
3275 buf2 = g_strdup_printf("Re: %s", buf);
3276 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
3281 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), "Re: ");
3284 static void compose_reply_set_entry(Compose *compose, MsgInfo *msginfo,
3285 gboolean to_all, gboolean to_ml,
3287 gboolean followup_and_reply_to)
3289 GSList *cc_list = NULL;
3292 gchar *replyto = NULL;
3293 gchar *ac_email = NULL;
3295 gboolean reply_to_ml = FALSE;
3296 gboolean default_reply_to = FALSE;
3298 cm_return_if_fail(compose->account != NULL);
3299 cm_return_if_fail(msginfo != NULL);
3301 reply_to_ml = to_ml && compose->ml_post;
3303 default_reply_to = msginfo->folder &&
3304 msginfo->folder->prefs->enable_default_reply_to;
3306 if (compose->account->protocol != A_NNTP) {
3307 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
3309 if (reply_to_ml && !default_reply_to) {
3311 gboolean is_subscr = is_subscription(compose->ml_post,
3314 /* normal answer to ml post with a reply-to */
3315 compose_entry_append(compose,
3317 COMPOSE_TO, PREF_ML);
3318 if (compose->replyto)
3319 compose_entry_append(compose,
3321 COMPOSE_CC, PREF_ML);
3323 /* answer to subscription confirmation */
3324 if (compose->replyto)
3325 compose_entry_append(compose,
3327 COMPOSE_TO, PREF_ML);
3328 else if (msginfo->from)
3329 compose_entry_append(compose,
3331 COMPOSE_TO, PREF_ML);
3334 else if (!(to_all || to_sender) && default_reply_to) {
3335 compose_entry_append(compose,
3336 msginfo->folder->prefs->default_reply_to,
3337 COMPOSE_TO, PREF_FOLDER);
3338 compose_entry_mark_default_to(compose,
3339 msginfo->folder->prefs->default_reply_to);
3345 compose_entry_append(compose, msginfo->from,
3346 COMPOSE_TO, PREF_NONE);
3348 Xstrdup_a(tmp1, msginfo->from, return);
3349 extract_address(tmp1);
3350 compose_entry_append(compose,
3351 (!account_find_from_address(tmp1, FALSE))
3354 COMPOSE_TO, PREF_NONE);
3355 if (compose->replyto)
3356 compose_entry_append(compose,
3358 COMPOSE_CC, PREF_NONE);
3360 if (!folder_has_parent_of_type(msginfo->folder, F_QUEUE) &&
3361 !folder_has_parent_of_type(msginfo->folder, F_OUTBOX) &&
3362 !folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
3363 if (compose->replyto) {
3364 compose_entry_append(compose,
3366 COMPOSE_TO, PREF_NONE);
3368 compose_entry_append(compose,
3369 msginfo->from ? msginfo->from : "",
3370 COMPOSE_TO, PREF_NONE);
3373 /* replying to own mail, use original recp */
3374 compose_entry_append(compose,
3375 msginfo->to ? msginfo->to : "",
3376 COMPOSE_TO, PREF_NONE);
3377 compose_entry_append(compose,
3378 msginfo->cc ? msginfo->cc : "",
3379 COMPOSE_CC, PREF_NONE);
3384 if (to_sender || (compose->followup_to &&
3385 !strncmp(compose->followup_to, "poster", 6)))
3386 compose_entry_append
3388 (compose->replyto ? compose->replyto :
3389 msginfo->from ? msginfo->from : ""),
3390 COMPOSE_TO, PREF_NONE);
3392 else if (followup_and_reply_to || to_all) {
3393 compose_entry_append
3395 (compose->replyto ? compose->replyto :
3396 msginfo->from ? msginfo->from : ""),
3397 COMPOSE_TO, PREF_NONE);
3399 compose_entry_append
3401 compose->followup_to ? compose->followup_to :
3402 compose->newsgroups ? compose->newsgroups : "",
3403 COMPOSE_NEWSGROUPS, PREF_NONE);
3405 compose_entry_append
3407 msginfo->cc ? msginfo->cc : "",
3408 COMPOSE_CC, PREF_NONE);
3411 compose_entry_append
3413 compose->followup_to ? compose->followup_to :
3414 compose->newsgroups ? compose->newsgroups : "",
3415 COMPOSE_NEWSGROUPS, PREF_NONE);
3417 compose_reply_set_subject(compose, msginfo);
3419 if (to_ml && compose->ml_post) return;
3420 if (!to_all || compose->account->protocol == A_NNTP) return;
3422 if (compose->replyto) {
3423 Xstrdup_a(replyto, compose->replyto, return);
3424 extract_address(replyto);
3426 if (msginfo->from) {
3427 Xstrdup_a(from, msginfo->from, return);
3428 extract_address(from);
3431 if (replyto && from)
3432 cc_list = address_list_append_with_comments(cc_list, from);
3433 if (to_all && msginfo->folder &&
3434 msginfo->folder->prefs->enable_default_reply_to)
3435 cc_list = address_list_append_with_comments(cc_list,
3436 msginfo->folder->prefs->default_reply_to);
3437 cc_list = address_list_append_with_comments(cc_list, msginfo->to);
3438 cc_list = address_list_append_with_comments(cc_list, compose->cc);
3440 ac_email = g_utf8_strdown(compose->account->address, -1);
3443 for (cur = cc_list; cur != NULL; cur = cur->next) {
3444 gchar *addr = g_utf8_strdown(cur->data, -1);
3445 extract_address(addr);
3447 if (strcmp(ac_email, addr))
3448 compose_entry_append(compose, (gchar *)cur->data,
3449 COMPOSE_CC, PREF_NONE);
3451 debug_print("Cc address same as compose account's, ignoring\n");
3456 slist_free_strings_full(cc_list);
3462 #define SET_ENTRY(entry, str) \
3465 gtk_entry_set_text(GTK_ENTRY(compose->entry), str); \
3468 #define SET_ADDRESS(type, str) \
3471 compose_entry_append(compose, str, type, PREF_NONE); \
3474 static void compose_reedit_set_entry(Compose *compose, MsgInfo *msginfo)
3476 cm_return_if_fail(msginfo != NULL);
3478 SET_ENTRY(subject_entry, msginfo->subject);
3479 SET_ENTRY(from_name, msginfo->from);
3480 SET_ADDRESS(COMPOSE_TO, msginfo->to);
3481 SET_ADDRESS(COMPOSE_CC, compose->cc);
3482 SET_ADDRESS(COMPOSE_BCC, compose->bcc);
3483 SET_ADDRESS(COMPOSE_REPLYTO, compose->replyto);
3484 SET_ADDRESS(COMPOSE_NEWSGROUPS, compose->newsgroups);
3485 SET_ADDRESS(COMPOSE_FOLLOWUPTO, compose->followup_to);
3487 compose_update_priority_menu_item(compose);
3488 compose_update_privacy_system_menu_item(compose, FALSE);
3489 compose_show_first_last_header(compose, TRUE);
3495 static void compose_insert_sig(Compose *compose, gboolean replace)
3497 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3498 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3500 GtkTextIter iter, iter_end;
3501 gint cur_pos, ins_pos;
3502 gboolean prev_autowrap;
3503 gboolean found = FALSE;
3504 gboolean exists = FALSE;
3506 cm_return_if_fail(compose->account != NULL);
3510 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3511 G_CALLBACK(compose_changed_cb),
3514 mark = gtk_text_buffer_get_insert(buffer);
3515 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3516 cur_pos = gtk_text_iter_get_offset (&iter);
3519 gtk_text_buffer_get_end_iter(buffer, &iter);
3521 exists = (compose->sig_str != NULL);
3524 GtkTextIter first_iter, start_iter, end_iter;
3526 gtk_text_buffer_get_start_iter(buffer, &first_iter);
3528 if (!exists || compose->sig_str[0] == '\0')
3531 found = gtk_text_iter_forward_to_tag_toggle(&first_iter,
3532 compose->signature_tag);
3535 /* include previous \n\n */
3536 gtk_text_iter_backward_chars(&first_iter, 1);
3537 start_iter = first_iter;
3538 end_iter = first_iter;
3540 found = gtk_text_iter_forward_to_tag_toggle(&end_iter,
3541 compose->signature_tag);
3542 found &= gtk_text_iter_forward_to_tag_toggle(&end_iter,
3543 compose->signature_tag);
3545 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
3551 g_free(compose->sig_str);
3552 compose->sig_str = account_get_signature_str(compose->account);
3554 cur_pos = gtk_text_iter_get_offset(&iter);
3556 if (!compose->sig_str || (replace && !compose->account->auto_sig)) {
3557 g_free(compose->sig_str);
3558 compose->sig_str = NULL;
3560 if (compose->sig_inserted == FALSE)
3561 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
3562 compose->sig_inserted = TRUE;
3564 cur_pos = gtk_text_iter_get_offset(&iter);
3565 gtk_text_buffer_insert(buffer, &iter, compose->sig_str, -1);
3567 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cur_pos);
3568 gtk_text_iter_forward_chars(&iter, 1);
3569 gtk_text_buffer_get_end_iter(buffer, &iter_end);
3570 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &iter_end);
3572 if (cur_pos > gtk_text_buffer_get_char_count (buffer))
3573 cur_pos = gtk_text_buffer_get_char_count (buffer);
3576 /* put the cursor where it should be
3577 * either where the quote_fmt says, either where it was */
3578 if (compose->set_cursor_pos < 0)
3579 gtk_text_buffer_get_iter_at_offset(buffer, &iter, ins_pos);
3581 gtk_text_buffer_get_iter_at_offset(buffer, &iter,
3582 compose->set_cursor_pos);
3584 compose->set_cursor_pos = -1;
3585 gtk_text_buffer_place_cursor(buffer, &iter);
3586 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3587 G_CALLBACK(compose_changed_cb),
3593 static ComposeInsertResult compose_insert_file(Compose *compose, const gchar *file)
3596 GtkTextBuffer *buffer;
3599 const gchar *cur_encoding;
3600 gchar buf[BUFFSIZE];
3603 gboolean prev_autowrap;
3606 GString *file_contents = NULL;
3607 ComposeInsertResult result = COMPOSE_INSERT_SUCCESS;
3609 cm_return_val_if_fail(file != NULL, COMPOSE_INSERT_NO_FILE);
3611 /* get the size of the file we are about to insert */
3612 ret = g_stat(file, &file_stat);
3614 gchar *shortfile = g_path_get_basename(file);
3615 alertpanel_error(_("Could not get size of file '%s'."), shortfile);
3617 return COMPOSE_INSERT_NO_FILE;
3618 } else if (prefs_common.warn_large_insert == TRUE) {
3620 /* ask user for confirmation if the file is large */
3621 if (prefs_common.warn_large_insert_size < 0 ||
3622 file_stat.st_size > (prefs_common.warn_large_insert_size * 1024)) {
3626 msg = g_strdup_printf(_("You are about to insert a file of %s "
3627 "in the message body. Are you sure you want to do that?"),
3628 to_human_readable(file_stat.st_size));
3629 aval = alertpanel_full(_("Are you sure?"), msg, GTK_STOCK_CANCEL,
3630 g_strconcat("+", _("_Insert"), NULL), NULL, TRUE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
3633 /* do we ask for confirmation next time? */
3634 if (aval & G_ALERTDISABLE) {
3635 /* no confirmation next time, disable feature in preferences */
3636 aval &= ~G_ALERTDISABLE;
3637 prefs_common.warn_large_insert = FALSE;
3640 /* abort file insertion if user canceled action */
3641 if (aval != G_ALERTALTERNATE) {
3642 return COMPOSE_INSERT_NO_FILE;
3648 if ((fp = g_fopen(file, "rb")) == NULL) {
3649 FILE_OP_ERROR(file, "fopen");
3650 return COMPOSE_INSERT_READ_ERROR;
3653 prev_autowrap = compose->autowrap;
3654 compose->autowrap = FALSE;
3656 text = GTK_TEXT_VIEW(compose->text);
3657 buffer = gtk_text_view_get_buffer(text);
3658 mark = gtk_text_buffer_get_insert(buffer);
3659 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3661 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3662 G_CALLBACK(text_inserted),
3665 cur_encoding = conv_get_locale_charset_str_no_utf8();
3667 file_contents = g_string_new("");
3668 while (fgets(buf, sizeof(buf), fp) != NULL) {
3671 if (g_utf8_validate(buf, -1, NULL) == TRUE)
3672 str = g_strdup(buf);
3674 codeconv_set_strict(TRUE);
3675 str = conv_codeset_strdup
3676 (buf, cur_encoding, CS_INTERNAL);
3677 codeconv_set_strict(FALSE);
3680 result = COMPOSE_INSERT_INVALID_CHARACTER;
3686 /* strip <CR> if DOS/Windows file,
3687 replace <CR> with <LF> if Macintosh file. */
3690 if (len > 0 && str[len - 1] != '\n') {
3692 if (str[len] == '\r') str[len] = '\n';
3695 file_contents = g_string_append(file_contents, str);
3699 if (result == COMPOSE_INSERT_SUCCESS) {
3700 gtk_text_buffer_insert(buffer, &iter, file_contents->str, -1);
3702 compose_changed_cb(NULL, compose);
3703 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3704 G_CALLBACK(text_inserted),
3706 compose->autowrap = prev_autowrap;
3707 if (compose->autowrap)
3708 compose_wrap_all(compose);
3711 g_string_free(file_contents, TRUE);
3717 static gboolean compose_attach_append(Compose *compose, const gchar *file,
3718 const gchar *filename,
3719 const gchar *content_type,
3720 const gchar *charset)
3728 GtkListStore *store;
3730 gboolean has_binary = FALSE;
3732 if (!is_file_exist(file)) {
3733 gchar *file_from_uri = g_filename_from_uri(file, NULL, NULL);
3734 gboolean result = FALSE;
3735 if (file_from_uri && is_file_exist(file_from_uri)) {
3736 result = compose_attach_append(
3737 compose, file_from_uri,
3738 filename, content_type,
3741 g_free(file_from_uri);
3744 alertpanel_error("File %s doesn't exist\n", filename);
3747 if ((size = get_file_size(file)) < 0) {
3748 alertpanel_error("Can't get file size of %s\n", filename);
3752 /* In batch mode, we allow 0-length files to be attached no questions asked */
3753 if (size == 0 && !compose->batch) {
3754 gchar * msg = g_strdup_printf(_("File %s is empty."), filename);
3755 AlertValue aval = alertpanel_full(_("Empty file"), msg,
3756 GTK_STOCK_CANCEL, g_strconcat("+", _("_Attach anyway"), NULL), NULL, FALSE,
3757 NULL, ALERT_WARNING, G_ALERTDEFAULT);
3760 if (aval != G_ALERTALTERNATE) {
3764 if ((fp = g_fopen(file, "rb")) == NULL) {
3765 alertpanel_error(_("Can't read %s."), filename);
3770 ainfo = g_new0(AttachInfo, 1);
3771 auto_ainfo = g_auto_pointer_new_with_free
3772 (ainfo, (GFreeFunc) compose_attach_info_free);
3773 ainfo->file = g_strdup(file);
3776 ainfo->content_type = g_strdup(content_type);
3777 if (!g_ascii_strcasecmp(content_type, "message/rfc822")) {
3779 MsgFlags flags = {0, 0};
3781 if (procmime_get_encoding_for_text_file(file, &has_binary) == ENC_7BIT)
3782 ainfo->encoding = ENC_7BIT;
3784 ainfo->encoding = ENC_8BIT;
3786 msginfo = procheader_parse_file(file, flags, FALSE, FALSE);
3787 if (msginfo && msginfo->subject)
3788 name = g_strdup(msginfo->subject);
3790 name = g_path_get_basename(filename ? filename : file);
3792 ainfo->name = g_strdup_printf(_("Message: %s"), name);
3794 procmsg_msginfo_free(&msginfo);
3796 if (!g_ascii_strncasecmp(content_type, "text/", 5)) {
3797 ainfo->charset = g_strdup(charset);
3798 ainfo->encoding = procmime_get_encoding_for_text_file(file, &has_binary);
3800 ainfo->encoding = ENC_BASE64;
3802 name = g_path_get_basename(filename ? filename : file);
3803 ainfo->name = g_strdup(name);
3807 ainfo->content_type = procmime_get_mime_type(file);
3808 if (!ainfo->content_type) {
3809 ainfo->content_type =
3810 g_strdup("application/octet-stream");
3811 ainfo->encoding = ENC_BASE64;
3812 } else if (!g_ascii_strncasecmp(ainfo->content_type, "text/", 5))
3814 procmime_get_encoding_for_text_file(file, &has_binary);
3816 ainfo->encoding = ENC_BASE64;
3817 name = g_path_get_basename(filename ? filename : file);
3818 ainfo->name = g_strdup(name);
3822 if (ainfo->name != NULL
3823 && !strcmp(ainfo->name, ".")) {
3824 g_free(ainfo->name);
3828 if (!strcmp(ainfo->content_type, "unknown") || has_binary) {
3829 g_free(ainfo->content_type);
3830 ainfo->content_type = g_strdup("application/octet-stream");
3831 g_free(ainfo->charset);
3832 ainfo->charset = NULL;
3835 ainfo->size = (goffset)size;
3836 size_text = to_human_readable((goffset)size);
3838 store = GTK_LIST_STORE(gtk_tree_view_get_model
3839 (GTK_TREE_VIEW(compose->attach_clist)));
3841 gtk_list_store_append(store, &iter);
3842 gtk_list_store_set(store, &iter,
3843 COL_MIMETYPE, ainfo->content_type,
3844 COL_SIZE, size_text,
3845 COL_NAME, ainfo->name,
3846 COL_CHARSET, ainfo->charset,
3848 COL_AUTODATA, auto_ainfo,
3851 g_auto_pointer_free(auto_ainfo);
3852 compose_attach_update_label(compose);
3856 static void compose_use_signing(Compose *compose, gboolean use_signing)
3858 compose->use_signing = use_signing;
3859 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", use_signing);
3862 static void compose_use_encryption(Compose *compose, gboolean use_encryption)
3864 compose->use_encryption = use_encryption;
3865 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", use_encryption);
3868 #define NEXT_PART_NOT_CHILD(info) \
3870 node = info->node; \
3871 while (node->children) \
3872 node = g_node_last_child(node); \
3873 info = procmime_mimeinfo_next((MimeInfo *)node->data); \
3876 static void compose_attach_parts(Compose *compose, MsgInfo *msginfo)
3880 MimeInfo *firsttext = NULL;
3881 MimeInfo *encrypted = NULL;
3884 const gchar *partname = NULL;
3886 mimeinfo = procmime_scan_message(msginfo);
3887 if (!mimeinfo) return;
3889 if (mimeinfo->node->children == NULL) {
3890 procmime_mimeinfo_free_all(&mimeinfo);
3894 /* find first content part */
3895 child = (MimeInfo *) mimeinfo->node->children->data;
3896 while (child && child->node->children && (child->type == MIMETYPE_MULTIPART))
3897 child = (MimeInfo *)child->node->children->data;
3900 if (child->type == MIMETYPE_TEXT) {
3902 debug_print("First text part found\n");
3903 } else if (compose->mode == COMPOSE_REEDIT &&
3904 child->type == MIMETYPE_APPLICATION &&
3905 !g_ascii_strcasecmp(child->subtype, "pgp-encrypted")) {
3906 encrypted = (MimeInfo *)child->node->parent->data;
3909 child = (MimeInfo *) mimeinfo->node->children->data;
3910 while (child != NULL) {
3913 if (child == encrypted) {
3914 /* skip this part of tree */
3915 NEXT_PART_NOT_CHILD(child);
3919 if (child->type == MIMETYPE_MULTIPART) {
3920 /* get the actual content */
3921 child = procmime_mimeinfo_next(child);
3925 if (child == firsttext) {
3926 child = procmime_mimeinfo_next(child);
3930 outfile = procmime_get_tmp_file_name(child);
3931 if ((err = procmime_get_part(outfile, child)) < 0)
3932 g_warning("Can't get the part of multipart message. (%s)", g_strerror(-err));
3934 gchar *content_type;
3936 content_type = procmime_get_content_type_str(child->type, child->subtype);
3938 /* if we meet a pgp signature, we don't attach it, but
3939 * we force signing. */
3940 if ((strcmp(content_type, "application/pgp-signature") &&
3941 strcmp(content_type, "application/pkcs7-signature") &&
3942 strcmp(content_type, "application/x-pkcs7-signature"))
3943 || compose->mode == COMPOSE_REDIRECT) {
3944 partname = procmime_mimeinfo_get_parameter(child, "filename");
3945 if (partname == NULL)
3946 partname = procmime_mimeinfo_get_parameter(child, "name");
3947 if (partname == NULL)
3949 compose_attach_append(compose, outfile,
3950 partname, content_type,
3951 procmime_mimeinfo_get_parameter(child, "charset"));
3953 compose_force_signing(compose, compose->account, NULL);
3955 g_free(content_type);
3958 NEXT_PART_NOT_CHILD(child);
3960 procmime_mimeinfo_free_all(&mimeinfo);
3963 #undef NEXT_PART_NOT_CHILD
3968 WAIT_FOR_INDENT_CHAR,
3969 WAIT_FOR_INDENT_CHAR_OR_SPACE,
3972 /* return indent length, we allow:
3973 indent characters followed by indent characters or spaces/tabs,
3974 alphabets and numbers immediately followed by indent characters,
3975 and the repeating sequences of the above
3976 If quote ends with multiple spaces, only the first one is included. */
3977 static gchar *compose_get_quote_str(GtkTextBuffer *buffer,
3978 const GtkTextIter *start, gint *len)
3980 GtkTextIter iter = *start;
3984 IndentState state = WAIT_FOR_INDENT_CHAR;
3987 gint alnum_count = 0;
3988 gint space_count = 0;
3991 if (prefs_common.quote_chars == NULL) {
3995 while (!gtk_text_iter_ends_line(&iter)) {
3996 wc = gtk_text_iter_get_char(&iter);
3997 if (g_unichar_iswide(wc))
3999 clen = g_unichar_to_utf8(wc, ch);
4003 is_indent = strchr(prefs_common.quote_chars, ch[0]) ? TRUE : FALSE;
4004 is_space = g_unichar_isspace(wc);
4006 if (state == WAIT_FOR_INDENT_CHAR) {
4007 if (!is_indent && !g_unichar_isalnum(wc))
4010 quote_len += alnum_count + space_count + 1;
4011 alnum_count = space_count = 0;
4012 state = WAIT_FOR_INDENT_CHAR_OR_SPACE;
4015 } else if (state == WAIT_FOR_INDENT_CHAR_OR_SPACE) {
4016 if (!is_indent && !is_space && !g_unichar_isalnum(wc))
4020 else if (is_indent) {
4021 quote_len += alnum_count + space_count + 1;
4022 alnum_count = space_count = 0;
4025 state = WAIT_FOR_INDENT_CHAR;
4029 gtk_text_iter_forward_char(&iter);
4032 if (quote_len > 0 && space_count > 0)
4038 if (quote_len > 0) {
4040 gtk_text_iter_forward_chars(&iter, quote_len);
4041 return gtk_text_buffer_get_text(buffer, start, &iter, FALSE);
4047 /* return >0 if the line is itemized */
4048 static int compose_itemized_length(GtkTextBuffer *buffer,
4049 const GtkTextIter *start)
4051 GtkTextIter iter = *start;
4056 if (gtk_text_iter_ends_line(&iter))
4061 wc = gtk_text_iter_get_char(&iter);
4062 if (!g_unichar_isspace(wc))
4064 gtk_text_iter_forward_char(&iter);
4065 if (gtk_text_iter_ends_line(&iter))
4069 clen = g_unichar_to_utf8(wc, ch);
4073 if (!strchr("*-+", ch[0]))
4076 gtk_text_iter_forward_char(&iter);
4077 if (gtk_text_iter_ends_line(&iter))
4079 wc = gtk_text_iter_get_char(&iter);
4080 if (g_unichar_isspace(wc)) {
4086 /* return the string at the start of the itemization */
4087 static gchar * compose_get_itemized_chars(GtkTextBuffer *buffer,
4088 const GtkTextIter *start)
4090 GtkTextIter iter = *start;
4093 GString *item_chars = g_string_new("");
4096 if (gtk_text_iter_ends_line(&iter))
4101 wc = gtk_text_iter_get_char(&iter);
4102 if (!g_unichar_isspace(wc))
4104 gtk_text_iter_forward_char(&iter);
4105 if (gtk_text_iter_ends_line(&iter))
4107 g_string_append_unichar(item_chars, wc);
4110 str = item_chars->str;
4111 g_string_free(item_chars, FALSE);
4115 /* return the number of spaces at a line's start */
4116 static int compose_left_offset_length(GtkTextBuffer *buffer,
4117 const GtkTextIter *start)
4119 GtkTextIter iter = *start;
4122 if (gtk_text_iter_ends_line(&iter))
4126 wc = gtk_text_iter_get_char(&iter);
4127 if (!g_unichar_isspace(wc))
4130 gtk_text_iter_forward_char(&iter);
4131 if (gtk_text_iter_ends_line(&iter))
4135 gtk_text_iter_forward_char(&iter);
4136 if (gtk_text_iter_ends_line(&iter))
4141 static gboolean compose_get_line_break_pos(GtkTextBuffer *buffer,
4142 const GtkTextIter *start,
4143 GtkTextIter *break_pos,
4147 GtkTextIter iter = *start, line_end = *start;
4148 PangoLogAttr *attrs;
4155 gboolean can_break = FALSE;
4156 gboolean do_break = FALSE;
4157 gboolean was_white = FALSE;
4158 gboolean prev_dont_break = FALSE;
4160 gtk_text_iter_forward_to_line_end(&line_end);
4161 str = gtk_text_buffer_get_text(buffer, &iter, &line_end, FALSE);
4162 len = g_utf8_strlen(str, -1);
4166 g_warning("compose_get_line_break_pos: len = 0!");
4170 /* g_print("breaking line: %d: %s (len = %d)\n",
4171 gtk_text_iter_get_line(&iter), str, len); */
4173 attrs = g_new(PangoLogAttr, len + 1);
4175 pango_default_break(str, -1, NULL, attrs, len + 1);
4179 /* skip quote and leading spaces */
4180 for (i = 0; *p != '\0' && i < len; i++) {
4183 wc = g_utf8_get_char(p);
4184 if (i >= quote_len && !g_unichar_isspace(wc))
4186 if (g_unichar_iswide(wc))
4188 else if (*p == '\t')
4192 p = g_utf8_next_char(p);
4195 for (; *p != '\0' && i < len; i++) {
4196 PangoLogAttr *attr = attrs + i;
4200 if (attr->is_line_break && can_break && was_white && !prev_dont_break)
4203 was_white = attr->is_white;
4205 /* don't wrap URI */
4206 if ((uri_len = get_uri_len(p)) > 0) {
4208 if (pos > 0 && col > max_col) {
4218 wc = g_utf8_get_char(p);
4219 if (g_unichar_iswide(wc)) {
4221 if (prev_dont_break && can_break && attr->is_line_break)
4223 } else if (*p == '\t')
4227 if (pos > 0 && col > max_col) {
4232 if (*p == '-' || *p == '/')
4233 prev_dont_break = TRUE;
4235 prev_dont_break = FALSE;
4237 p = g_utf8_next_char(p);
4241 // debug_print("compose_get_line_break_pos(): do_break = %d, pos = %d, col = %d\n", do_break, pos, col);
4246 *break_pos = *start;
4247 gtk_text_iter_set_line_offset(break_pos, pos);
4252 static gboolean compose_join_next_line(Compose *compose,
4253 GtkTextBuffer *buffer,
4255 const gchar *quote_str)
4257 GtkTextIter iter_ = *iter, cur, prev, next, end;
4258 PangoLogAttr attrs[3];
4260 gchar *next_quote_str;
4263 gboolean keep_cursor = FALSE;
4265 if (!gtk_text_iter_forward_line(&iter_) ||
4266 gtk_text_iter_ends_line(&iter_)) {
4269 next_quote_str = compose_get_quote_str(buffer, &iter_, "e_len);
4271 if ((quote_str || next_quote_str) &&
4272 strcmp2(quote_str, next_quote_str) != 0) {
4273 g_free(next_quote_str);
4276 g_free(next_quote_str);
4279 if (quote_len > 0) {
4280 gtk_text_iter_forward_chars(&end, quote_len);
4281 if (gtk_text_iter_ends_line(&end)) {
4286 /* don't join itemized lines */
4287 if (compose_itemized_length(buffer, &end) > 0) {
4291 /* don't join signature separator */
4292 if (compose_is_sig_separator(compose, buffer, &iter_)) {
4295 /* delete quote str */
4297 gtk_text_buffer_delete(buffer, &iter_, &end);
4299 /* don't join line breaks put by the user */
4301 gtk_text_iter_backward_char(&cur);
4302 if (gtk_text_iter_has_tag(&cur, compose->no_join_tag)) {
4303 gtk_text_iter_forward_char(&cur);
4307 gtk_text_iter_forward_char(&cur);
4308 /* delete linebreak and extra spaces */
4309 while (gtk_text_iter_backward_char(&cur)) {
4310 wc1 = gtk_text_iter_get_char(&cur);
4311 if (!g_unichar_isspace(wc1))
4316 while (!gtk_text_iter_ends_line(&cur)) {
4317 wc1 = gtk_text_iter_get_char(&cur);
4318 if (!g_unichar_isspace(wc1))
4320 gtk_text_iter_forward_char(&cur);
4323 if (!gtk_text_iter_equal(&prev, &next)) {
4326 mark = gtk_text_buffer_get_insert(buffer);
4327 gtk_text_buffer_get_iter_at_mark(buffer, &cur, mark);
4328 if (gtk_text_iter_equal(&prev, &cur))
4330 gtk_text_buffer_delete(buffer, &prev, &next);
4334 /* insert space if required */
4335 gtk_text_iter_backward_char(&prev);
4336 wc1 = gtk_text_iter_get_char(&prev);
4337 wc2 = gtk_text_iter_get_char(&next);
4338 gtk_text_iter_forward_char(&next);
4339 str = gtk_text_buffer_get_text(buffer, &prev, &next, FALSE);
4340 pango_default_break(str, -1, NULL, attrs, 3);
4341 if (!attrs[1].is_line_break ||
4342 (!g_unichar_iswide(wc1) || !g_unichar_iswide(wc2))) {
4343 gtk_text_buffer_insert(buffer, &iter_, " ", 1);
4345 gtk_text_iter_backward_char(&iter_);
4346 gtk_text_buffer_place_cursor(buffer, &iter_);
4355 #define ADD_TXT_POS(bp_, ep_, pti_) \
4356 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
4357 last = last->next; \
4358 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
4359 last->next = NULL; \
4361 g_warning("alloc error scanning URIs"); \
4364 static gboolean compose_beautify_paragraph(Compose *compose, GtkTextIter *par_iter, gboolean force)
4366 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4367 GtkTextBuffer *buffer;
4368 GtkTextIter iter, break_pos, end_of_line;
4369 gchar *quote_str = NULL;
4371 gboolean wrap_quote = force || prefs_common.linewrap_quote;
4372 gboolean prev_autowrap = compose->autowrap;
4373 gint startq_offset = -1, noq_offset = -1;
4374 gint uri_start = -1, uri_stop = -1;
4375 gint nouri_start = -1, nouri_stop = -1;
4376 gint num_blocks = 0;
4377 gint quotelevel = -1;
4378 gboolean modified = force;
4379 gboolean removed = FALSE;
4380 gboolean modified_before_remove = FALSE;
4382 gboolean start = TRUE;
4383 gint itemized_len = 0, rem_item_len = 0;
4384 gchar *itemized_chars = NULL;
4385 gboolean item_continuation = FALSE;
4390 if (compose->draft_timeout_tag == COMPOSE_DRAFT_TIMEOUT_FORBIDDEN) {
4394 compose->autowrap = FALSE;
4396 buffer = gtk_text_view_get_buffer(text);
4397 undo_wrapping(compose->undostruct, TRUE);
4402 mark = gtk_text_buffer_get_insert(buffer);
4403 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
4407 if (compose->draft_timeout_tag == COMPOSE_DRAFT_TIMEOUT_FORBIDDEN) {
4408 if (gtk_text_iter_ends_line(&iter)) {
4409 while (gtk_text_iter_ends_line(&iter) &&
4410 gtk_text_iter_forward_line(&iter))
4413 while (gtk_text_iter_backward_line(&iter)) {
4414 if (gtk_text_iter_ends_line(&iter)) {
4415 gtk_text_iter_forward_line(&iter);
4421 /* move to line start */
4422 gtk_text_iter_set_line_offset(&iter, 0);
4425 itemized_len = compose_itemized_length(buffer, &iter);
4427 if (!itemized_len) {
4428 itemized_len = compose_left_offset_length(buffer, &iter);
4429 item_continuation = TRUE;
4433 itemized_chars = compose_get_itemized_chars(buffer, &iter);
4435 /* go until paragraph end (empty line) */
4436 while (start || !gtk_text_iter_ends_line(&iter)) {
4437 gchar *scanpos = NULL;
4438 /* parse table - in order of priority */
4440 const gchar *needle; /* token */
4442 /* token search function */
4443 gchar *(*search) (const gchar *haystack,
4444 const gchar *needle);
4445 /* part parsing function */
4446 gboolean (*parse) (const gchar *start,
4447 const gchar *scanpos,
4451 /* part to URI function */
4452 gchar *(*build_uri) (const gchar *bp,
4456 static struct table parser[] = {
4457 {"http://", strcasestr, get_uri_part, make_uri_string},
4458 {"https://", strcasestr, get_uri_part, make_uri_string},
4459 {"ftp://", strcasestr, get_uri_part, make_uri_string},
4460 {"sftp://", strcasestr, get_uri_part, make_uri_string},
4461 {"gopher://",strcasestr, get_uri_part, make_uri_string},
4462 {"www.", strcasestr, get_uri_part, make_http_string},
4463 {"mailto:", strcasestr, get_uri_part, make_uri_string},
4464 {"@", strcasestr, get_email_part, make_email_string}
4466 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
4467 gint last_index = PARSE_ELEMS;
4469 gchar *o_walk = NULL, *walk = NULL, *bp = NULL, *ep = NULL;
4473 if (!prev_autowrap && num_blocks == 0) {
4475 g_signal_handlers_block_by_func(G_OBJECT(buffer),
4476 G_CALLBACK(text_inserted),
4479 if (gtk_text_iter_has_tag(&iter, compose->no_wrap_tag) && !force)
4482 uri_start = uri_stop = -1;
4484 quote_str = compose_get_quote_str(buffer, &iter, "e_len);
4487 // debug_print("compose_beautify_paragraph(): quote_str = '%s'\n", quote_str);
4488 if (startq_offset == -1)
4489 startq_offset = gtk_text_iter_get_offset(&iter);
4490 quotelevel = get_quote_level(quote_str, prefs_common.quote_chars);
4491 if (quotelevel > 2) {
4492 /* recycle colors */
4493 if (prefs_common.recycle_quote_colors)
4502 if (startq_offset == -1)
4503 noq_offset = gtk_text_iter_get_offset(&iter);
4507 if (prev_autowrap == FALSE && !force && !wrap_quote) {
4510 if (gtk_text_iter_ends_line(&iter)) {
4512 } else if (compose_get_line_break_pos(buffer, &iter, &break_pos,
4513 prefs_common.linewrap_len,
4515 GtkTextIter prev, next, cur;
4516 if (prev_autowrap != FALSE || force) {
4517 compose->automatic_break = TRUE;
4519 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4520 compose->automatic_break = FALSE;
4521 if (itemized_len && compose->autoindent) {
4522 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4523 if (!item_continuation)
4524 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4526 } else if (quote_str && wrap_quote) {
4527 compose->automatic_break = TRUE;
4529 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4530 compose->automatic_break = FALSE;
4531 if (itemized_len && compose->autoindent) {
4532 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4533 if (!item_continuation)
4534 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4538 /* remove trailing spaces */
4540 rem_item_len = itemized_len;
4541 while (compose->autoindent && rem_item_len-- > 0)
4542 gtk_text_iter_backward_char(&cur);
4543 gtk_text_iter_backward_char(&cur);
4546 while (!gtk_text_iter_starts_line(&cur)) {
4549 gtk_text_iter_backward_char(&cur);
4550 wc = gtk_text_iter_get_char(&cur);
4551 if (!g_unichar_isspace(wc))
4555 if (!gtk_text_iter_equal(&prev, &next)) {
4556 gtk_text_buffer_delete(buffer, &prev, &next);
4558 gtk_text_iter_forward_char(&break_pos);
4562 gtk_text_buffer_insert(buffer, &break_pos,
4566 modified |= compose_join_next_line(compose, buffer, &iter, quote_str);
4568 /* move iter to current line start */
4569 gtk_text_iter_set_line_offset(&iter, 0);
4576 /* move iter to next line start */
4582 if (!prev_autowrap && num_blocks > 0) {
4584 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
4585 G_CALLBACK(text_inserted),
4589 while (!gtk_text_iter_ends_line(&end_of_line)) {
4590 gtk_text_iter_forward_char(&end_of_line);
4592 o_walk = walk = gtk_text_buffer_get_text(buffer, &iter, &end_of_line, FALSE);
4594 nouri_start = gtk_text_iter_get_offset(&iter);
4595 nouri_stop = gtk_text_iter_get_offset(&end_of_line);
4597 walk_pos = gtk_text_iter_get_offset(&iter);
4598 /* FIXME: this looks phony. scanning for anything in the parse table */
4599 for (n = 0; n < PARSE_ELEMS; n++) {
4602 tmp = parser[n].search(walk, parser[n].needle);
4604 if (scanpos == NULL || tmp < scanpos) {
4613 /* check if URI can be parsed */
4614 if (parser[last_index].parse(walk, scanpos, (const gchar **)&bp,
4615 (const gchar **)&ep, FALSE)
4616 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
4620 strlen(parser[last_index].needle);
4623 uri_start = walk_pos + (bp - o_walk);
4624 uri_stop = walk_pos + (ep - o_walk);
4628 gtk_text_iter_forward_line(&iter);
4631 if (startq_offset != -1) {
4632 GtkTextIter startquote, endquote;
4633 gtk_text_buffer_get_iter_at_offset(
4634 buffer, &startquote, startq_offset);
4637 switch (quotelevel) {
4639 if (!gtk_text_iter_has_tag(&startquote, compose->quote0_tag) ||
4640 !gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) {
4641 gtk_text_buffer_apply_tag_by_name(
4642 buffer, "quote0", &startquote, &endquote);
4643 gtk_text_buffer_remove_tag_by_name(
4644 buffer, "quote1", &startquote, &endquote);
4645 gtk_text_buffer_remove_tag_by_name(
4646 buffer, "quote2", &startquote, &endquote);
4651 if (!gtk_text_iter_has_tag(&startquote, compose->quote1_tag) ||
4652 !gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) {
4653 gtk_text_buffer_apply_tag_by_name(
4654 buffer, "quote1", &startquote, &endquote);
4655 gtk_text_buffer_remove_tag_by_name(
4656 buffer, "quote0", &startquote, &endquote);
4657 gtk_text_buffer_remove_tag_by_name(
4658 buffer, "quote2", &startquote, &endquote);
4663 if (!gtk_text_iter_has_tag(&startquote, compose->quote2_tag) ||
4664 !gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag)) {
4665 gtk_text_buffer_apply_tag_by_name(
4666 buffer, "quote2", &startquote, &endquote);
4667 gtk_text_buffer_remove_tag_by_name(
4668 buffer, "quote0", &startquote, &endquote);
4669 gtk_text_buffer_remove_tag_by_name(
4670 buffer, "quote1", &startquote, &endquote);
4676 } else if (noq_offset != -1) {
4677 GtkTextIter startnoquote, endnoquote;
4678 gtk_text_buffer_get_iter_at_offset(
4679 buffer, &startnoquote, noq_offset);
4682 if ((gtk_text_iter_has_tag(&startnoquote, compose->quote0_tag)
4683 && gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) ||
4684 (gtk_text_iter_has_tag(&startnoquote, compose->quote1_tag)
4685 && gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) ||
4686 (gtk_text_iter_has_tag(&startnoquote, compose->quote2_tag)
4687 && gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag))) {
4688 gtk_text_buffer_remove_tag_by_name(
4689 buffer, "quote0", &startnoquote, &endnoquote);
4690 gtk_text_buffer_remove_tag_by_name(
4691 buffer, "quote1", &startnoquote, &endnoquote);
4692 gtk_text_buffer_remove_tag_by_name(
4693 buffer, "quote2", &startnoquote, &endnoquote);
4699 if (uri_start != nouri_start && uri_stop != nouri_stop) {
4700 GtkTextIter nouri_start_iter, nouri_end_iter;
4701 gtk_text_buffer_get_iter_at_offset(
4702 buffer, &nouri_start_iter, nouri_start);
4703 gtk_text_buffer_get_iter_at_offset(
4704 buffer, &nouri_end_iter, nouri_stop);
4705 if (gtk_text_iter_has_tag(&nouri_start_iter, compose->uri_tag) &&
4706 gtk_text_iter_has_tag(&nouri_end_iter, compose->uri_tag)) {
4707 gtk_text_buffer_remove_tag_by_name(
4708 buffer, "link", &nouri_start_iter, &nouri_end_iter);
4709 modified_before_remove = modified;
4714 if (uri_start >= 0 && uri_stop > 0) {
4715 GtkTextIter uri_start_iter, uri_end_iter, back;
4716 gtk_text_buffer_get_iter_at_offset(
4717 buffer, &uri_start_iter, uri_start);
4718 gtk_text_buffer_get_iter_at_offset(
4719 buffer, &uri_end_iter, uri_stop);
4720 back = uri_end_iter;
4721 gtk_text_iter_backward_char(&back);
4722 if (!gtk_text_iter_has_tag(&uri_start_iter, compose->uri_tag) ||
4723 !gtk_text_iter_has_tag(&back, compose->uri_tag)) {
4724 gtk_text_buffer_apply_tag_by_name(
4725 buffer, "link", &uri_start_iter, &uri_end_iter);
4727 if (removed && !modified_before_remove) {
4733 // debug_print("not modified, out after %d lines\n", lines);
4737 // debug_print("modified, out after %d lines\n", lines);
4739 g_free(itemized_chars);
4742 undo_wrapping(compose->undostruct, FALSE);
4743 compose->autowrap = prev_autowrap;
4748 void compose_action_cb(void *data)
4750 Compose *compose = (Compose *)data;
4751 compose_wrap_all(compose);
4754 static void compose_wrap_all(Compose *compose)
4756 compose_wrap_all_full(compose, FALSE);
4759 static void compose_wrap_all_full(Compose *compose, gboolean force)
4761 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4762 GtkTextBuffer *buffer;
4764 gboolean modified = TRUE;
4766 buffer = gtk_text_view_get_buffer(text);
4768 gtk_text_buffer_get_start_iter(buffer, &iter);
4770 undo_wrapping(compose->undostruct, TRUE);
4772 while (!gtk_text_iter_is_end(&iter) && modified)
4773 modified = compose_beautify_paragraph(compose, &iter, force);
4775 undo_wrapping(compose->undostruct, FALSE);
4779 static void compose_set_title(Compose *compose)
4785 edited = compose->modified ? _(" [Edited]") : "";
4787 subject = gtk_editable_get_chars(
4788 GTK_EDITABLE(compose->subject_entry), 0, -1);
4790 #ifndef GENERIC_UMPC
4791 if (subject && strlen(subject))
4792 str = g_strdup_printf(_("%s - Compose message%s"),
4795 str = g_strdup_printf(_("[no subject] - Compose message%s"), edited);
4797 str = g_strdup(_("Compose message"));
4800 gtk_window_set_title(GTK_WINDOW(compose->window), str);
4806 * compose_current_mail_account:
4808 * Find a current mail account (the currently selected account, or the
4809 * default account, if a news account is currently selected). If a
4810 * mail account cannot be found, display an error message.
4812 * Return value: Mail account, or NULL if not found.
4814 static PrefsAccount *
4815 compose_current_mail_account(void)
4819 if (cur_account && cur_account->protocol != A_NNTP)
4822 ac = account_get_default();
4823 if (!ac || ac->protocol == A_NNTP) {
4824 alertpanel_error(_("Account for sending mail is not specified.\n"
4825 "Please select a mail account before sending."));
4832 #define QUOTE_IF_REQUIRED(out, str) \
4834 if (*str != '"' && strpbrk(str, ",.:;[]<>()@\\\"")) { \
4838 len = strlen(str) + 3; \
4839 if ((__tmp = alloca(len)) == NULL) { \
4840 g_warning("can't allocate memory"); \
4841 g_string_free(header, TRUE); \
4844 g_snprintf(__tmp, len, "\"%s\"", str); \
4849 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4850 g_warning("can't allocate memory"); \
4851 g_string_free(header, TRUE); \
4854 strcpy(__tmp, str); \
4860 #define QUOTE_IF_REQUIRED_NORMAL(out, str, errret) \
4862 if (*str != '"' && strpbrk(str, ",.:;[]<>()@\\\"")) { \
4866 len = strlen(str) + 3; \
4867 if ((__tmp = alloca(len)) == NULL) { \
4868 g_warning("can't allocate memory"); \
4871 g_snprintf(__tmp, len, "\"%s\"", str); \
4876 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4877 g_warning("can't allocate memory"); \
4880 strcpy(__tmp, str); \
4886 static void compose_select_account(Compose *compose, PrefsAccount *account,
4889 gchar *from = NULL, *header = NULL;
4890 ComposeHeaderEntry *header_entry;
4891 #if GTK_CHECK_VERSION(2, 24, 0)
4895 cm_return_if_fail(account != NULL);
4897 compose->account = account;
4898 if (account->name && *account->name) {
4900 QUOTE_IF_REQUIRED_NORMAL(buf, account->name, return);
4901 qbuf = escape_internal_quotes(buf, '"');
4902 from = g_strdup_printf("%s <%s>",
4903 qbuf, account->address);
4906 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4908 from = g_strdup_printf("<%s>",
4910 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4915 compose_set_title(compose);
4917 if (account->default_sign && compose->mode != COMPOSE_REDIRECT)
4918 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", TRUE);
4920 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", FALSE);
4921 if (account->default_encrypt && compose->mode != COMPOSE_REDIRECT)
4922 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", TRUE);
4924 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", FALSE);
4926 activate_privacy_system(compose, account, FALSE);
4928 if (!init && compose->mode != COMPOSE_REDIRECT) {
4929 undo_block(compose->undostruct);
4930 compose_insert_sig(compose, TRUE);
4931 undo_unblock(compose->undostruct);
4934 header_entry = (ComposeHeaderEntry *) compose->header_list->data;
4935 #if !GTK_CHECK_VERSION(2, 24, 0)
4936 header = gtk_combo_box_get_active_text(GTK_COMBO_BOX(header_entry->combo));
4938 if (gtk_combo_box_get_active_iter(GTK_COMBO_BOX(header_entry->combo), &iter))
4939 gtk_tree_model_get(gtk_combo_box_get_model(GTK_COMBO_BOX(
4940 header_entry->combo)), &iter, COMBOBOX_TEXT, &header, -1);
4943 if (header && !strlen(gtk_entry_get_text(GTK_ENTRY(header_entry->entry)))) {
4944 if (account->protocol == A_NNTP) {
4945 if (!strcmp(header, _("To:")))
4946 combobox_select_by_text(
4947 GTK_COMBO_BOX(header_entry->combo),
4950 if (!strcmp(header, _("Newsgroups:")))
4951 combobox_select_by_text(
4952 GTK_COMBO_BOX(header_entry->combo),
4960 /* use account's dict info if set */
4961 if (compose->gtkaspell) {
4962 if (account->enable_default_dictionary)
4963 gtkaspell_change_dict(compose->gtkaspell,
4964 account->default_dictionary, FALSE);
4965 if (account->enable_default_alt_dictionary)
4966 gtkaspell_change_alt_dict(compose->gtkaspell,
4967 account->default_alt_dictionary);
4968 if (account->enable_default_dictionary
4969 || account->enable_default_alt_dictionary)
4970 compose_spell_menu_changed(compose);
4975 gboolean compose_check_for_valid_recipient(Compose *compose) {
4976 gchar *recipient_headers_mail[] = {"To:", "Cc:", "Bcc:", NULL};
4977 gchar *recipient_headers_news[] = {"Newsgroups:", NULL};
4978 gboolean recipient_found = FALSE;
4982 /* free to and newsgroup list */
4983 slist_free_strings_full(compose->to_list);
4984 compose->to_list = NULL;
4986 slist_free_strings_full(compose->newsgroup_list);
4987 compose->newsgroup_list = NULL;
4989 /* search header entries for to and newsgroup entries */
4990 for (list = compose->header_list; list; list = list->next) {
4993 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
4994 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4997 if (entry[0] != '\0') {
4998 for (strptr = recipient_headers_mail; *strptr != NULL; strptr++) {
4999 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
5000 compose->to_list = address_list_append(compose->to_list, entry);
5001 recipient_found = TRUE;
5004 for (strptr = recipient_headers_news; *strptr != NULL; strptr++) {
5005 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
5006 compose->newsgroup_list = newsgroup_list_append(compose->newsgroup_list, entry);
5007 recipient_found = TRUE;
5014 return recipient_found;
5017 static gboolean compose_check_for_set_recipients(Compose *compose)
5019 if (compose->account->set_autocc && compose->account->auto_cc) {
5020 gboolean found_other = FALSE;
5022 /* search header entries for to and newsgroup entries */
5023 for (list = compose->header_list; list; list = list->next) {
5026 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5027 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5030 if (strcmp(entry, compose->account->auto_cc)
5031 || strcmp(header, prefs_common_translated_header_name("Cc:"))) {
5041 if (compose->batch) {
5042 gtk_widget_show_all(compose->window);
5044 aval = alertpanel(_("Send"),
5045 _("The only recipient is the default CC address. Send anyway?"),
5046 GTK_STOCK_CANCEL, g_strconcat("+", _("_Send"), NULL), NULL);
5047 if (aval != G_ALERTALTERNATE)
5051 if (compose->account->set_autobcc && compose->account->auto_bcc) {
5052 gboolean found_other = FALSE;
5054 /* search header entries for to and newsgroup entries */
5055 for (list = compose->header_list; list; list = list->next) {
5058 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5059 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5062 if (strcmp(entry, compose->account->auto_bcc)
5063 || strcmp(header, prefs_common_translated_header_name("Bcc:"))) {
5073 if (compose->batch) {
5074 gtk_widget_show_all(compose->window);
5076 aval = alertpanel(_("Send"),
5077 _("The only recipient is the default BCC address. Send anyway?"),
5078 GTK_STOCK_CANCEL, g_strconcat("+", _("_Send"), NULL), NULL);
5079 if (aval != G_ALERTALTERNATE)
5086 static gboolean compose_check_entries(Compose *compose, gboolean check_everything)
5090 if (compose_check_for_valid_recipient(compose) == FALSE) {
5091 if (compose->batch) {
5092 gtk_widget_show_all(compose->window);
5094 alertpanel_error(_("Recipient is not specified."));
5098 if (compose_check_for_set_recipients(compose) == FALSE) {
5102 if (!compose->batch && prefs_common.warn_empty_subj == TRUE) {
5103 str = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5104 if (*str == '\0' && check_everything == TRUE &&
5105 compose->mode != COMPOSE_REDIRECT) {
5107 gchar *button_label;
5110 if (compose->sending)
5111 button_label = g_strconcat("+", _("_Send"), NULL);
5113 button_label = g_strconcat("+", _("_Queue"), NULL);
5114 message = g_strdup_printf(_("Subject is empty. %s"),
5115 compose->sending?_("Send it anyway?"):
5116 _("Queue it anyway?"));
5118 aval = alertpanel_full(compose->sending?_("Send"):_("Send later"), message,
5119 GTK_STOCK_CANCEL, button_label, NULL, TRUE, NULL,
5120 ALERT_QUESTION, G_ALERTDEFAULT);
5122 if (aval & G_ALERTDISABLE) {
5123 aval &= ~G_ALERTDISABLE;
5124 prefs_common.warn_empty_subj = FALSE;
5126 if (aval != G_ALERTALTERNATE)
5131 if (check_everything && hooks_invoke(COMPOSE_CHECK_BEFORE_SEND_HOOKLIST, compose))
5137 gint compose_send(Compose *compose)
5140 FolderItem *folder = NULL;
5142 gchar *msgpath = NULL;
5143 gboolean discard_window = FALSE;
5144 gchar *errstr = NULL;
5145 gchar *tmsgid = NULL;
5146 MainWindow *mainwin = mainwindow_get_mainwindow();
5147 gboolean queued_removed = FALSE;
5149 if (prefs_common.send_dialog_invisible
5150 || compose->batch == TRUE)
5151 discard_window = TRUE;
5153 compose_allow_user_actions (compose, FALSE);
5154 compose->sending = TRUE;
5156 if (compose_check_entries(compose, TRUE) == FALSE) {
5157 if (compose->batch) {
5158 gtk_widget_show_all(compose->window);
5164 val = compose_queue(compose, &msgnum, &folder, &msgpath, TRUE);
5167 if (compose->batch) {
5168 gtk_widget_show_all(compose->window);
5171 alertpanel_error(_("Could not queue message for sending:\n\n"
5172 "Charset conversion failed."));
5173 } else if (val == -5) {
5174 alertpanel_error(_("Could not queue message for sending:\n\n"
5175 "Couldn't get recipient encryption key."));
5176 } else if (val == -6) {
5178 } else if (val == -3) {
5179 if (privacy_peek_error())
5180 alertpanel_error(_("Could not queue message for sending:\n\n"
5181 "Signature failed: %s"), privacy_get_error());
5182 } else if (val == -2 && errno != 0) {
5183 alertpanel_error(_("Could not queue message for sending:\n\n%s."), g_strerror(errno));
5185 alertpanel_error(_("Could not queue message for sending."));
5190 tmsgid = compose->msgid ? g_strdup(compose->msgid) : NULL;
5191 if (discard_window) {
5192 compose->sending = FALSE;
5193 compose_close(compose);
5194 /* No more compose access in the normal codepath
5195 * after this point! */
5200 alertpanel_error(_("The message was queued but could not be "
5201 "sent.\nUse \"Send queued messages\" from "
5202 "the main window to retry."));
5203 if (!discard_window) {
5210 if (msgpath == NULL) {
5211 msgpath = folder_item_fetch_msg(folder, msgnum);
5212 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5215 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5216 claws_unlink(msgpath);
5219 if (!discard_window) {
5221 if (!queued_removed)
5222 folder_item_remove_msg(folder, msgnum);
5223 folder_item_scan(folder);
5225 /* make sure we delete that */
5226 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5228 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5229 folder_item_remove_msg(folder, tmp->msgnum);
5230 procmsg_msginfo_free(&tmp);
5237 if (!queued_removed)
5238 folder_item_remove_msg(folder, msgnum);
5239 folder_item_scan(folder);
5241 /* make sure we delete that */
5242 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5244 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5245 folder_item_remove_msg(folder, tmp->msgnum);
5246 procmsg_msginfo_free(&tmp);
5249 if (!discard_window) {
5250 compose->sending = FALSE;
5251 compose_allow_user_actions (compose, TRUE);
5252 compose_close(compose);
5256 alertpanel_error_log(_("%s\nUse \"Send queued messages\" from "
5257 "the main window to retry."), errstr);
5260 alertpanel_error_log(_("The message was queued but could not be "
5261 "sent.\nUse \"Send queued messages\" from "
5262 "the main window to retry."));
5264 if (!discard_window) {
5273 toolbar_main_set_sensitive(mainwin);
5274 main_window_set_menu_sensitive(mainwin);
5280 compose_allow_user_actions (compose, TRUE);
5281 compose->sending = FALSE;
5282 compose->modified = TRUE;
5283 toolbar_main_set_sensitive(mainwin);
5284 main_window_set_menu_sensitive(mainwin);
5289 static gboolean compose_use_attach(Compose *compose)
5291 GtkTreeModel *model = gtk_tree_view_get_model
5292 (GTK_TREE_VIEW(compose->attach_clist));
5293 return gtk_tree_model_iter_n_children(model, NULL) > 0;
5296 static gint compose_redirect_write_headers_from_headerlist(Compose *compose,
5299 gchar buf[BUFFSIZE];
5301 gboolean first_to_address;
5302 gboolean first_cc_address;
5304 ComposeHeaderEntry *headerentry;
5305 const gchar *headerentryname;
5306 const gchar *cc_hdr;
5307 const gchar *to_hdr;
5308 gboolean err = FALSE;
5310 debug_print("Writing redirect header\n");
5312 cc_hdr = prefs_common_translated_header_name("Cc:");
5313 to_hdr = prefs_common_translated_header_name("To:");
5315 first_to_address = TRUE;
5316 for (list = compose->header_list; list; list = list->next) {
5317 headerentry = ((ComposeHeaderEntry *)list->data);
5318 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5320 if (g_utf8_collate(headerentryname, to_hdr) == 0) {
5321 const gchar *entstr = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5322 Xstrdup_a(str, entstr, return -1);
5324 if (str[0] != '\0') {
5325 compose_convert_header
5326 (compose, buf, sizeof(buf), str,
5327 strlen("Resent-To") + 2, TRUE);
5329 if (first_to_address) {
5330 err |= (fprintf(fp, "Resent-To: ") < 0);
5331 first_to_address = FALSE;
5333 err |= (fprintf(fp, ",") < 0);
5335 err |= (fprintf(fp, "%s", buf) < 0);
5339 if (!first_to_address) {
5340 err |= (fprintf(fp, "\n") < 0);
5343 first_cc_address = TRUE;
5344 for (list = compose->header_list; list; list = list->next) {
5345 headerentry = ((ComposeHeaderEntry *)list->data);
5346 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5348 if (g_utf8_collate(headerentryname, cc_hdr) == 0) {
5349 const gchar *strg = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5350 Xstrdup_a(str, strg, return -1);
5352 if (str[0] != '\0') {
5353 compose_convert_header
5354 (compose, buf, sizeof(buf), str,
5355 strlen("Resent-Cc") + 2, TRUE);
5357 if (first_cc_address) {
5358 err |= (fprintf(fp, "Resent-Cc: ") < 0);
5359 first_cc_address = FALSE;
5361 err |= (fprintf(fp, ",") < 0);
5363 err |= (fprintf(fp, "%s", buf) < 0);
5367 if (!first_cc_address) {
5368 err |= (fprintf(fp, "\n") < 0);
5371 return (err ? -1:0);
5374 static gint compose_redirect_write_headers(Compose *compose, FILE *fp)
5376 gchar buf[BUFFSIZE];
5378 const gchar *entstr;
5379 /* struct utsname utsbuf; */
5380 gboolean err = FALSE;
5382 cm_return_val_if_fail(fp != NULL, -1);
5383 cm_return_val_if_fail(compose->account != NULL, -1);
5384 cm_return_val_if_fail(compose->account->address != NULL, -1);
5387 if (prefs_common.hide_timezone)
5388 get_rfc822_date_hide_tz(buf, sizeof(buf));
5390 get_rfc822_date(buf, sizeof(buf));
5391 err |= (fprintf(fp, "Resent-Date: %s\n", buf) < 0);
5394 if (compose->account->name && *compose->account->name) {
5395 compose_convert_header
5396 (compose, buf, sizeof(buf), compose->account->name,
5397 strlen("From: "), TRUE);
5398 err |= (fprintf(fp, "Resent-From: %s <%s>\n",
5399 buf, compose->account->address) < 0);
5401 err |= (fprintf(fp, "Resent-From: %s\n", compose->account->address) < 0);
5404 entstr = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5405 if (*entstr != '\0') {
5406 Xstrdup_a(str, entstr, return -1);
5409 compose_convert_header(compose, buf, sizeof(buf), str,
5410 strlen("Subject: "), FALSE);
5411 err |= (fprintf(fp, "Subject: %s\n", buf) < 0);
5415 /* Resent-Message-ID */
5416 if (compose->account->gen_msgid) {
5417 gchar *addr = prefs_account_generate_msgid(compose->account);
5418 err |= (fprintf(fp, "Resent-Message-ID: <%s>\n", addr) < 0);
5420 g_free(compose->msgid);
5421 compose->msgid = addr;
5423 compose->msgid = NULL;
5426 if (compose_redirect_write_headers_from_headerlist(compose, fp))
5429 /* separator between header and body */
5430 err |= (fputs("\n", fp) == EOF);
5432 return (err ? -1:0);
5435 static gint compose_redirect_write_to_file(Compose *compose, FILE *fdest)
5439 gchar buf[BUFFSIZE];
5441 gboolean skip = FALSE;
5442 gboolean err = FALSE;
5443 gchar *not_included[]={
5444 "Return-Path:", "Delivered-To:", "Received:",
5445 "Subject:", "X-UIDL:", "AF:",
5446 "NF:", "PS:", "SRH:",
5447 "SFN:", "DSR:", "MID:",
5448 "CFG:", "PT:", "S:",
5449 "RQ:", "SSV:", "NSV:",
5450 "SSH:", "R:", "MAID:",
5451 "NAID:", "RMID:", "FMID:",
5452 "SCF:", "RRCPT:", "NG:",
5453 "X-Claws-Privacy", "X-Claws-Sign:", "X-Claws-Encrypt",
5454 "X-Claws-End-Special-Headers:", "X-Claws-Account-Id:",
5455 "X-Sylpheed-Privacy", "X-Sylpheed-Sign:", "X-Sylpheed-Encrypt",
5456 "X-Sylpheed-End-Special-Headers:", "X-Sylpheed-Account-Id:",
5457 "X-Claws-Auto-Wrapping:", "X-Claws-Auto-Indent:",
5460 if ((fp = g_fopen(compose->redirect_filename, "rb")) == NULL) {
5461 FILE_OP_ERROR(compose->redirect_filename, "fopen");
5465 while (procheader_get_one_field_asis(buf, sizeof(buf), fp) != -1) {
5467 for (i = 0; not_included[i] != NULL; i++) {
5468 if (g_ascii_strncasecmp(buf, not_included[i],
5469 strlen(not_included[i])) == 0) {
5476 if (fputs(buf, fdest) == -1)
5479 if (!prefs_common.redirect_keep_from) {
5480 if (g_ascii_strncasecmp(buf, "From:",
5481 strlen("From:")) == 0) {
5482 err |= (fputs(" (by way of ", fdest) == EOF);
5483 if (compose->account->name
5484 && *compose->account->name) {
5485 compose_convert_header
5486 (compose, buf, sizeof(buf),
5487 compose->account->name,
5490 err |= (fprintf(fdest, "%s <%s>",
5492 compose->account->address) < 0);
5494 err |= (fprintf(fdest, "%s",
5495 compose->account->address) < 0);
5496 err |= (fputs(")", fdest) == EOF);
5500 if (fputs("\n", fdest) == -1)
5507 if (compose_redirect_write_headers(compose, fdest))
5510 while ((len = fread(buf, sizeof(gchar), sizeof(buf), fp)) > 0) {
5511 if (fwrite(buf, sizeof(gchar), len, fdest) != len)
5524 static gint compose_write_to_file(Compose *compose, FILE *fp, gint action, gboolean attach_parts)
5526 GtkTextBuffer *buffer;
5527 GtkTextIter start, end;
5528 gchar *chars, *tmp_enc_file, *content;
5530 const gchar *out_codeset;
5531 EncodingType encoding = ENC_UNKNOWN;
5532 MimeInfo *mimemsg, *mimetext;
5534 const gchar *src_codeset = CS_INTERNAL;
5535 gchar *from_addr = NULL;
5536 gchar *from_name = NULL;
5539 if (action == COMPOSE_WRITE_FOR_SEND) {
5540 attach_parts = TRUE;
5542 /* We're sending the message, generate a Message-ID
5544 if (compose->msgid == NULL &&
5545 compose->account->gen_msgid) {
5546 compose->msgid = prefs_account_generate_msgid(compose->account);
5550 /* create message MimeInfo */
5551 mimemsg = procmime_mimeinfo_new();
5552 mimemsg->type = MIMETYPE_MESSAGE;
5553 mimemsg->subtype = g_strdup("rfc822");
5554 mimemsg->content = MIMECONTENT_MEM;
5555 mimemsg->tmp = TRUE; /* must free content later */
5556 mimemsg->data.mem = compose_get_header(compose);
5558 /* Create text part MimeInfo */
5559 /* get all composed text */
5560 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5561 gtk_text_buffer_get_start_iter(buffer, &start);
5562 gtk_text_buffer_get_end_iter(buffer, &end);
5563 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5565 out_codeset = conv_get_charset_str(compose->out_encoding);
5567 if (!out_codeset && is_ascii_str(chars)) {
5568 out_codeset = CS_US_ASCII;
5569 } else if (prefs_common.outgoing_fallback_to_ascii &&
5570 is_ascii_str(chars)) {
5571 out_codeset = CS_US_ASCII;
5572 encoding = ENC_7BIT;
5576 gchar *test_conv_global_out = NULL;
5577 gchar *test_conv_reply = NULL;
5579 /* automatic mode. be automatic. */
5580 codeconv_set_strict(TRUE);
5582 out_codeset = conv_get_outgoing_charset_str();
5584 debug_print("trying to convert to %s\n", out_codeset);
5585 test_conv_global_out = conv_codeset_strdup(chars, src_codeset, out_codeset);
5588 if (!test_conv_global_out && compose->orig_charset
5589 && strcmp(compose->orig_charset, CS_US_ASCII)) {
5590 out_codeset = compose->orig_charset;
5591 debug_print("failure; trying to convert to %s\n", out_codeset);
5592 test_conv_reply = conv_codeset_strdup(chars, src_codeset, out_codeset);
5595 if (!test_conv_global_out && !test_conv_reply) {
5597 out_codeset = CS_INTERNAL;
5598 debug_print("failure; finally using %s\n", out_codeset);
5600 g_free(test_conv_global_out);
5601 g_free(test_conv_reply);
5602 codeconv_set_strict(FALSE);
5605 if (encoding == ENC_UNKNOWN) {
5606 if (prefs_common.encoding_method == CTE_BASE64)
5607 encoding = ENC_BASE64;
5608 else if (prefs_common.encoding_method == CTE_QUOTED_PRINTABLE)
5609 encoding = ENC_QUOTED_PRINTABLE;
5610 else if (prefs_common.encoding_method == CTE_8BIT)
5611 encoding = ENC_8BIT;
5613 encoding = procmime_get_encoding_for_charset(out_codeset);
5616 debug_print("src encoding = %s, out encoding = %s, transfer encoding = %s\n",
5617 src_codeset, out_codeset, procmime_get_encoding_str(encoding));
5619 if (action == COMPOSE_WRITE_FOR_SEND) {
5620 codeconv_set_strict(TRUE);
5621 buf = conv_codeset_strdup(chars, src_codeset, out_codeset);
5622 codeconv_set_strict(FALSE);
5627 msg = g_strdup_printf(_("Can't convert the character encoding of the message \n"
5628 "to the specified %s charset.\n"
5629 "Send it as %s?"), out_codeset, src_codeset);
5630 aval = alertpanel_full(_("Error"), msg, GTK_STOCK_CANCEL,
5631 g_strconcat("+", _("_Send"), NULL), NULL, FALSE,
5632 NULL, ALERT_ERROR, G_ALERTDEFAULT);
5635 if (aval != G_ALERTALTERNATE) {
5640 out_codeset = src_codeset;
5646 out_codeset = src_codeset;
5651 if (prefs_common.rewrite_first_from && (encoding == ENC_8BIT || encoding == ENC_7BIT)) {
5652 if (!strncmp(buf, "From ", sizeof("From ")-1) ||
5653 strstr(buf, "\nFrom ") != NULL) {
5654 encoding = ENC_QUOTED_PRINTABLE;
5658 mimetext = procmime_mimeinfo_new();
5659 mimetext->content = MIMECONTENT_MEM;
5660 mimetext->tmp = TRUE; /* must free content later */
5661 /* dup'ed because procmime_encode_content can turn it into a tmpfile
5662 * and free the data, which we need later. */
5663 mimetext->data.mem = g_strdup(buf);
5664 mimetext->type = MIMETYPE_TEXT;
5665 mimetext->subtype = g_strdup("plain");
5666 g_hash_table_insert(mimetext->typeparameters, g_strdup("charset"),
5667 g_strdup(out_codeset));
5669 /* protect trailing spaces when signing message */
5670 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5671 privacy_system_can_sign(compose->privacy_system)) {
5672 encoding = ENC_QUOTED_PRINTABLE;
5675 debug_print("main text: %zd bytes encoded as %s in %d\n",
5676 strlen(buf), out_codeset, encoding);
5678 /* check for line length limit */
5679 if (action == COMPOSE_WRITE_FOR_SEND &&
5680 encoding != ENC_QUOTED_PRINTABLE && encoding != ENC_BASE64 &&
5681 check_line_length(buf, 1000, &line) < 0) {
5684 msg = g_strdup_printf
5685 (_("Line %d exceeds the line length limit (998 bytes).\n"
5686 "The contents of the message might be broken on the way to the delivery.\n"
5688 "Send it anyway?"), line + 1);
5689 aval = alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, GTK_STOCK_OK, NULL);
5691 if (aval != G_ALERTALTERNATE) {
5697 if (encoding != ENC_UNKNOWN)
5698 procmime_encode_content(mimetext, encoding);
5700 /* append attachment parts */
5701 if (compose_use_attach(compose) && attach_parts) {
5702 MimeInfo *mimempart;
5703 gchar *boundary = NULL;
5704 mimempart = procmime_mimeinfo_new();
5705 mimempart->content = MIMECONTENT_EMPTY;
5706 mimempart->type = MIMETYPE_MULTIPART;
5707 mimempart->subtype = g_strdup("mixed");
5711 boundary = generate_mime_boundary(NULL);
5712 } while (strstr(buf, boundary) != NULL);
5714 g_hash_table_insert(mimempart->typeparameters, g_strdup("boundary"),
5717 mimetext->disposition = DISPOSITIONTYPE_INLINE;
5719 g_node_append(mimempart->node, mimetext->node);
5720 g_node_append(mimemsg->node, mimempart->node);
5722 if (compose_add_attachments(compose, mimempart) < 0)
5725 g_node_append(mimemsg->node, mimetext->node);
5729 if (strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) != 0) {
5730 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
5731 /* extract name and address */
5732 if (strstr(spec, " <") && strstr(spec, ">")) {
5733 from_addr = g_strdup(strrchr(spec, '<')+1);
5734 *(strrchr(from_addr, '>')) = '\0';
5735 from_name = g_strdup(spec);
5736 *(strrchr(from_name, '<')) = '\0';
5743 /* sign message if sending */
5744 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5745 privacy_system_can_sign(compose->privacy_system))
5746 if (!privacy_sign(compose->privacy_system, mimemsg,
5747 compose->account, from_addr)) {
5755 if (compose->use_encryption) {
5756 if (compose->encdata != NULL &&
5757 strcmp(compose->encdata, "_DONT_ENCRYPT_")) {
5759 /* First, write an unencrypted copy and save it to outbox, if
5760 * user wants that. */
5761 if (compose->account->save_encrypted_as_clear_text) {
5762 debug_print("saving sent message unencrypted...\n");
5763 FILE *tmpfp = get_tmpfile_in_dir(get_mime_tmp_dir(), &tmp_enc_file);
5767 /* fp now points to a file with headers written,
5768 * let's make a copy. */
5770 content = file_read_stream_to_str(fp);
5772 str_write_to_file(content, tmp_enc_file);
5775 /* Now write the unencrypted body. */
5776 if ((tmpfp = g_fopen(tmp_enc_file, "a")) != NULL) {
5777 procmime_write_mimeinfo(mimemsg, tmpfp);
5780 outbox = folder_find_item_from_identifier(compose_get_save_to(compose));
5782 outbox = folder_get_default_outbox();
5784 procmsg_save_to_outbox(outbox, tmp_enc_file, TRUE);
5785 claws_unlink(tmp_enc_file);
5787 g_warning("Can't open file '%s'", tmp_enc_file);
5790 g_warning("couldn't get tempfile");
5793 if (!privacy_encrypt(compose->privacy_system, mimemsg, compose->encdata)) {
5794 debug_print("Couldn't encrypt mime structure: %s.\n",
5795 privacy_get_error());
5796 alertpanel_error(_("Couldn't encrypt the email: %s"),
5797 privacy_get_error());
5802 procmime_write_mimeinfo(mimemsg, fp);
5804 procmime_mimeinfo_free_all(&mimemsg);
5809 static gint compose_write_body_to_file(Compose *compose, const gchar *file)
5811 GtkTextBuffer *buffer;
5812 GtkTextIter start, end;
5817 if ((fp = g_fopen(file, "wb")) == NULL) {
5818 FILE_OP_ERROR(file, "fopen");
5822 /* chmod for security */
5823 if (change_file_mode_rw(fp, file) < 0) {
5824 FILE_OP_ERROR(file, "chmod");
5825 g_warning("can't change file mode");
5828 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5829 gtk_text_buffer_get_start_iter(buffer, &start);
5830 gtk_text_buffer_get_end_iter(buffer, &end);
5831 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5833 chars = conv_codeset_strdup
5834 (tmp, CS_INTERNAL, conv_get_locale_charset_str());
5843 len = strlen(chars);
5844 if (fwrite(chars, sizeof(gchar), len, fp) != len) {
5845 FILE_OP_ERROR(file, "fwrite");
5854 if (fclose(fp) == EOF) {
5855 FILE_OP_ERROR(file, "fclose");
5862 static gint compose_remove_reedit_target(Compose *compose, gboolean force)
5865 MsgInfo *msginfo = compose->targetinfo;
5867 cm_return_val_if_fail(compose->mode == COMPOSE_REEDIT, -1);
5868 if (!msginfo) return -1;
5870 if (!force && MSG_IS_LOCKED(msginfo->flags))
5873 item = msginfo->folder;
5874 cm_return_val_if_fail(item != NULL, -1);
5876 if (procmsg_msg_exist(msginfo) &&
5877 (folder_has_parent_of_type(item, F_QUEUE) ||
5878 folder_has_parent_of_type(item, F_DRAFT)
5879 || msginfo == compose->autosaved_draft)) {
5880 if (folder_item_remove_msg(item, msginfo->msgnum) < 0) {
5881 g_warning("can't remove the old message");
5884 debug_print("removed reedit target %d\n", msginfo->msgnum);
5891 static void compose_remove_draft(Compose *compose)
5894 MsgInfo *msginfo = compose->targetinfo;
5895 drafts = account_get_special_folder(compose->account, F_DRAFT);
5897 if (procmsg_msg_exist(msginfo)) {
5898 folder_item_remove_msg(drafts, msginfo->msgnum);
5903 gint compose_queue(Compose *compose, gint *msgnum, FolderItem **item, gchar **msgpath,
5904 gboolean remove_reedit_target)
5906 return compose_queue_sub (compose, msgnum, item, msgpath, FALSE, remove_reedit_target);
5909 static gboolean compose_warn_encryption(Compose *compose)
5911 const gchar *warning = privacy_get_encrypt_warning(compose->privacy_system);
5912 AlertValue val = G_ALERTALTERNATE;
5914 if (warning == NULL)
5917 val = alertpanel_full(_("Encryption warning"), warning,
5918 GTK_STOCK_CANCEL, g_strconcat("+", _("C_ontinue"), NULL), NULL,
5919 TRUE, NULL, ALERT_WARNING, G_ALERTALTERNATE);
5920 if (val & G_ALERTDISABLE) {
5921 val &= ~G_ALERTDISABLE;
5922 if (val == G_ALERTALTERNATE)
5923 privacy_inhibit_encrypt_warning(compose->privacy_system,
5927 if (val == G_ALERTALTERNATE) {
5934 static gint compose_queue_sub(Compose *compose, gint *msgnum, FolderItem **item,
5935 gchar **msgpath, gboolean check_subject,
5936 gboolean remove_reedit_target)
5943 PrefsAccount *mailac = NULL, *newsac = NULL;
5944 gboolean err = FALSE;
5946 debug_print("queueing message...\n");
5947 cm_return_val_if_fail(compose->account != NULL, -1);
5949 if (compose_check_entries(compose, check_subject) == FALSE) {
5950 if (compose->batch) {
5951 gtk_widget_show_all(compose->window);
5956 if (!compose->to_list && !compose->newsgroup_list) {
5957 g_warning("can't get recipient list.");
5961 if (compose->to_list) {
5962 if (compose->account->protocol != A_NNTP)
5963 mailac = compose->account;
5964 else if (cur_account && cur_account->protocol != A_NNTP)
5965 mailac = cur_account;
5966 else if (!(mailac = compose_current_mail_account())) {
5967 alertpanel_error(_("No account for sending mails available!"));
5972 if (compose->newsgroup_list) {
5973 if (compose->account->protocol == A_NNTP)
5974 newsac = compose->account;
5976 alertpanel_error(_("Selected account isn't NNTP: Posting is impossible."));
5981 /* write queue header */
5982 tmp = g_strdup_printf("%s%cqueue.%p%08x", get_tmp_dir(),
5983 G_DIR_SEPARATOR, compose, (guint) rand());
5984 debug_print("queuing to %s\n", tmp);
5985 if ((fp = g_fopen(tmp, "w+b")) == NULL) {
5986 FILE_OP_ERROR(tmp, "fopen");
5991 if (change_file_mode_rw(fp, tmp) < 0) {
5992 FILE_OP_ERROR(tmp, "chmod");
5993 g_warning("can't change file mode");
5996 /* queueing variables */
5997 err |= (fprintf(fp, "AF:\n") < 0);
5998 err |= (fprintf(fp, "NF:0\n") < 0);
5999 err |= (fprintf(fp, "PS:10\n") < 0);
6000 err |= (fprintf(fp, "SRH:1\n") < 0);
6001 err |= (fprintf(fp, "SFN:\n") < 0);
6002 err |= (fprintf(fp, "DSR:\n") < 0);
6004 err |= (fprintf(fp, "MID:<%s>\n", compose->msgid) < 0);
6006 err |= (fprintf(fp, "MID:\n") < 0);
6007 err |= (fprintf(fp, "CFG:\n") < 0);
6008 err |= (fprintf(fp, "PT:0\n") < 0);
6009 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
6010 err |= (fprintf(fp, "RQ:\n") < 0);
6012 err |= (fprintf(fp, "SSV:%s\n", mailac->smtp_server) < 0);
6014 err |= (fprintf(fp, "SSV:\n") < 0);
6016 err |= (fprintf(fp, "NSV:%s\n", newsac->nntp_server) < 0);
6018 err |= (fprintf(fp, "NSV:\n") < 0);
6019 err |= (fprintf(fp, "SSH:\n") < 0);
6020 /* write recepient list */
6021 if (compose->to_list) {
6022 err |= (fprintf(fp, "R:<%s>", (gchar *)compose->to_list->data) < 0);
6023 for (cur = compose->to_list->next; cur != NULL;
6025 err |= (fprintf(fp, ",<%s>", (gchar *)cur->data) < 0);
6026 err |= (fprintf(fp, "\n") < 0);
6028 /* write newsgroup list */
6029 if (compose->newsgroup_list) {
6030 err |= (fprintf(fp, "NG:") < 0);
6031 err |= (fprintf(fp, "%s", (gchar *)compose->newsgroup_list->data) < 0);
6032 for (cur = compose->newsgroup_list->next; cur != NULL; cur = cur->next)
6033 err |= (fprintf(fp, ",%s", (gchar *)cur->data) < 0);
6034 err |= (fprintf(fp, "\n") < 0);
6036 /* Sylpheed account IDs */
6038 err |= (fprintf(fp, "MAID:%d\n", mailac->account_id) < 0);
6040 err |= (fprintf(fp, "NAID:%d\n", newsac->account_id) < 0);
6043 if (compose->privacy_system != NULL) {
6044 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
6045 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
6046 if (compose->use_encryption) {
6047 if (!compose_warn_encryption(compose)) {
6053 if (mailac && mailac->encrypt_to_self) {
6054 GSList *tmp_list = g_slist_copy(compose->to_list);
6055 tmp_list = g_slist_append(tmp_list, compose->account->address);
6056 compose->encdata = privacy_get_encrypt_data(compose->privacy_system, tmp_list);
6057 g_slist_free(tmp_list);
6059 compose->encdata = privacy_get_encrypt_data(compose->privacy_system, compose->to_list);
6061 if (compose->encdata != NULL) {
6062 if (strcmp(compose->encdata, "_DONT_ENCRYPT_")) {
6063 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
6064 err |= (fprintf(fp, "X-Claws-Encrypt-Data:%s\n",
6065 compose->encdata) < 0);
6066 } /* else we finally dont want to encrypt */
6068 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
6069 /* and if encdata was null, it means there's been a problem in
6072 g_warning("failed to write queue message");
6081 /* Save copy folder */
6082 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
6083 gchar *savefolderid;
6085 savefolderid = compose_get_save_to(compose);
6086 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
6087 g_free(savefolderid);
6089 /* Save copy folder */
6090 if (compose->return_receipt) {
6091 err |= (fprintf(fp, "RRCPT:1\n") < 0);
6093 /* Message-ID of message replying to */
6094 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
6095 gchar *folderid = NULL;
6097 if (compose->replyinfo->folder)
6098 folderid = folder_item_get_identifier(compose->replyinfo->folder);
6099 if (folderid == NULL)
6100 folderid = g_strdup("NULL");
6102 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
6105 /* Message-ID of message forwarding to */
6106 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
6107 gchar *folderid = NULL;
6109 if (compose->fwdinfo->folder)
6110 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
6111 if (folderid == NULL)
6112 folderid = g_strdup("NULL");
6114 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
6118 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
6119 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
6121 /* end of headers */
6122 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
6124 if (compose->redirect_filename != NULL) {
6125 if (compose_redirect_write_to_file(compose, fp) < 0) {
6133 if ((result = compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_SEND, TRUE)) < 0) {
6137 return result - 1; /* -2 for a generic error, -3 for signing error, -4 for encoding */
6141 g_warning("failed to write queue message");
6147 if (fclose(fp) == EOF) {
6148 FILE_OP_ERROR(tmp, "fclose");
6154 if (item && *item) {
6157 queue = account_get_special_folder(compose->account, F_QUEUE);
6160 g_warning("can't find queue folder");
6165 folder_item_scan(queue);
6166 if ((num = folder_item_add_msg(queue, tmp, NULL, FALSE)) < 0) {
6167 g_warning("can't queue the message");
6173 if (msgpath == NULL) {
6179 if (compose->mode == COMPOSE_REEDIT && remove_reedit_target) {
6180 compose_remove_reedit_target(compose, FALSE);
6183 if ((msgnum != NULL) && (item != NULL)) {
6191 static int compose_add_attachments(Compose *compose, MimeInfo *parent)
6194 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
6197 gchar *type, *subtype;
6198 GtkTreeModel *model;
6201 model = gtk_tree_view_get_model(tree_view);
6203 if (!gtk_tree_model_get_iter_first(model, &iter))
6206 gtk_tree_model_get(model, &iter,
6210 if (!is_file_exist(ainfo->file)) {
6211 gchar *msg = g_strdup_printf(_("Attachment %s doesn't exist anymore. Ignore?"), ainfo->file);
6212 AlertValue val = alertpanel_full(_("Warning"), msg, _("Cancel sending"), _("Ignore attachment"),
6213 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
6215 if (val == G_ALERTDEFAULT) {
6220 if (g_stat(ainfo->file, &statbuf) < 0)
6223 mimepart = procmime_mimeinfo_new();
6224 mimepart->content = MIMECONTENT_FILE;
6225 mimepart->data.filename = g_strdup(ainfo->file);
6226 mimepart->tmp = FALSE; /* or we destroy our attachment */
6227 mimepart->offset = 0;
6228 mimepart->length = statbuf.st_size;
6230 type = g_strdup(ainfo->content_type);
6232 if (!strchr(type, '/')) {
6234 type = g_strdup("application/octet-stream");
6237 subtype = strchr(type, '/') + 1;
6238 *(subtype - 1) = '\0';
6239 mimepart->type = procmime_get_media_type(type);
6240 mimepart->subtype = g_strdup(subtype);
6243 if (mimepart->type == MIMETYPE_MESSAGE &&
6244 !g_ascii_strcasecmp(mimepart->subtype, "rfc822")) {
6245 mimepart->disposition = DISPOSITIONTYPE_INLINE;
6246 } else if (mimepart->type == MIMETYPE_TEXT) {
6247 if (!ainfo->name && g_ascii_strcasecmp(mimepart->subtype, "plain")) {
6248 /* Text parts with no name come from multipart/alternative
6249 * forwards. Make sure the recipient won't look at the
6250 * original HTML part by mistake. */
6251 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6252 ainfo->name = g_strdup_printf(_("Original %s part"),
6256 g_hash_table_insert(mimepart->typeparameters,
6257 g_strdup("charset"), g_strdup(ainfo->charset));
6259 if (ainfo->name && mimepart->type != MIMETYPE_MESSAGE) {
6260 if (mimepart->type == MIMETYPE_APPLICATION &&
6261 !strcmp2(mimepart->subtype, "octet-stream"))
6262 g_hash_table_insert(mimepart->typeparameters,
6263 g_strdup("name"), g_strdup(ainfo->name));
6264 g_hash_table_insert(mimepart->dispositionparameters,
6265 g_strdup("filename"), g_strdup(ainfo->name));
6266 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6269 if (mimepart->type == MIMETYPE_MESSAGE
6270 || mimepart->type == MIMETYPE_MULTIPART)
6271 ainfo->encoding = ENC_BINARY;
6272 else if (compose->use_signing) {
6273 if (ainfo->encoding == ENC_7BIT)
6274 ainfo->encoding = ENC_QUOTED_PRINTABLE;
6275 else if (ainfo->encoding == ENC_8BIT)
6276 ainfo->encoding = ENC_BASE64;
6281 procmime_encode_content(mimepart, ainfo->encoding);
6283 g_node_append(parent->node, mimepart->node);
6284 } while (gtk_tree_model_iter_next(model, &iter));
6289 static gchar *compose_quote_list_of_addresses(gchar *str)
6291 GSList *list = NULL, *item = NULL;
6292 gchar *qname = NULL, *faddr = NULL, *result = NULL;
6294 list = address_list_append_with_comments(list, str);
6295 for (item = list; item != NULL; item = item->next) {
6296 gchar *spec = item->data;
6297 gchar *endofname = strstr(spec, " <");
6298 if (endofname != NULL) {
6301 QUOTE_IF_REQUIRED_NORMAL(qname, spec, return NULL);
6302 qqname = escape_internal_quotes(qname, '"');
6304 if (*qname != *spec || qqname != qname) { /* has been quoted, compute new */
6305 gchar *addr = g_strdup(endofname);
6306 gchar *name = (qqname != qname)? qqname: g_strdup(qname);
6307 faddr = g_strconcat(name, addr, NULL);
6310 debug_print("new auto-quoted address: '%s'\n", faddr);
6314 result = g_strdup((faddr != NULL)? faddr: spec);
6316 result = g_strconcat(result,
6318 (faddr != NULL)? faddr: spec,
6321 if (faddr != NULL) {
6326 slist_free_strings_full(list);
6331 #define IS_IN_CUSTOM_HEADER(header) \
6332 (compose->account->add_customhdr && \
6333 custom_header_find(compose->account->customhdr_list, header) != NULL)
6335 static void compose_add_headerfield_from_headerlist(Compose *compose,
6337 const gchar *fieldname,
6338 const gchar *seperator)
6340 gchar *str, *fieldname_w_colon;
6341 gboolean add_field = FALSE;
6343 ComposeHeaderEntry *headerentry;
6344 const gchar *headerentryname;
6345 const gchar *trans_fieldname;
6348 if (IS_IN_CUSTOM_HEADER(fieldname))
6351 debug_print("Adding %s-fields\n", fieldname);
6353 fieldstr = g_string_sized_new(64);
6355 fieldname_w_colon = g_strconcat(fieldname, ":", NULL);
6356 trans_fieldname = prefs_common_translated_header_name(fieldname_w_colon);
6358 for (list = compose->header_list; list; list = list->next) {
6359 headerentry = ((ComposeHeaderEntry *)list->data);
6360 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
6362 if (!g_utf8_collate(trans_fieldname, headerentryname)) {
6363 gchar * ustr = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
6365 str = compose_quote_list_of_addresses(ustr);
6367 if (str != NULL && str[0] != '\0') {
6369 g_string_append(fieldstr, seperator);
6370 g_string_append(fieldstr, str);
6379 buf = g_new0(gchar, fieldstr->len * 4 + 256);
6380 compose_convert_header
6381 (compose, buf, fieldstr->len * 4 + 256, fieldstr->str,
6382 strlen(fieldname) + 2, TRUE);
6383 g_string_append_printf(header, "%s: %s\n", fieldname, buf);
6387 g_free(fieldname_w_colon);
6388 g_string_free(fieldstr, TRUE);
6393 static gchar *compose_get_manual_headers_info(Compose *compose)
6395 GString *sh_header = g_string_new(" ");
6397 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6399 for (list = compose->header_list; list; list = list->next) {
6400 ComposeHeaderEntry *headerentry;
6403 gchar *headername_wcolon;
6404 const gchar *headername_trans;
6406 gboolean standard_header = FALSE;
6408 headerentry = ((ComposeHeaderEntry *)list->data);
6410 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6412 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6417 if (!strstr(tmp, ":")) {
6418 headername_wcolon = g_strconcat(tmp, ":", NULL);
6419 headername = g_strdup(tmp);
6421 headername_wcolon = g_strdup(tmp);
6422 headername = g_strdup(strtok(tmp, ":"));
6426 string = std_headers;
6427 while (*string != NULL) {
6428 headername_trans = prefs_common_translated_header_name(*string);
6429 if (!strcmp(headername_trans, headername_wcolon))
6430 standard_header = TRUE;
6433 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
6434 g_string_append_printf(sh_header, "%s ", headername);
6436 g_free(headername_wcolon);
6438 g_string_truncate(sh_header, strlen(sh_header->str) - 1); /* remove last space */
6439 return g_string_free(sh_header, FALSE);
6442 static gchar *compose_get_header(Compose *compose)
6444 gchar buf[BUFFSIZE];
6445 const gchar *entry_str;
6449 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6451 gchar *from_name = NULL, *from_address = NULL;
6454 cm_return_val_if_fail(compose->account != NULL, NULL);
6455 cm_return_val_if_fail(compose->account->address != NULL, NULL);
6457 header = g_string_sized_new(64);
6460 if (prefs_common.hide_timezone)
6461 get_rfc822_date_hide_tz(buf, sizeof(buf));
6463 get_rfc822_date(buf, sizeof(buf));
6464 g_string_append_printf(header, "Date: %s\n", buf);
6468 if (compose->account->name && *compose->account->name) {
6470 QUOTE_IF_REQUIRED(buf, compose->account->name);
6471 tmp = g_strdup_printf("%s <%s>",
6472 buf, compose->account->address);
6474 tmp = g_strdup_printf("%s",
6475 compose->account->address);
6477 if (!strcmp(gtk_entry_get_text(GTK_ENTRY(compose->from_name)), tmp)
6478 || strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) == 0) {
6480 from_name = compose->account->name ? g_strdup(compose->account->name):NULL;
6481 from_address = g_strdup(compose->account->address);
6483 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
6484 /* extract name and address */
6485 if (strstr(spec, " <") && strstr(spec, ">")) {
6486 from_address = g_strdup(strrchr(spec, '<')+1);
6487 *(strrchr(from_address, '>')) = '\0';
6488 from_name = g_strdup(spec);
6489 *(strrchr(from_name, '<')) = '\0';
6492 from_address = g_strdup(spec);
6499 if (from_name && *from_name) {
6501 compose_convert_header
6502 (compose, buf, sizeof(buf), from_name,
6503 strlen("From: "), TRUE);
6504 QUOTE_IF_REQUIRED(name, buf);
6505 qname = escape_internal_quotes(name, '"');
6507 g_string_append_printf(header, "From: %s <%s>\n",
6508 qname, from_address);
6509 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To") &&
6510 compose->return_receipt) {
6511 compose_convert_header(compose, buf, sizeof(buf), from_name,
6512 strlen("Disposition-Notification-To: "),
6514 g_string_append_printf(header, "Disposition-Notification-To: %s <%s>\n", buf, from_address);
6519 g_string_append_printf(header, "From: %s\n", from_address);
6520 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To") &&
6521 compose->return_receipt)
6522 g_string_append_printf(header, "Disposition-Notification-To: %s\n", from_address);
6526 g_free(from_address);
6529 compose_add_headerfield_from_headerlist(compose, header, "To", ", ");
6532 compose_add_headerfield_from_headerlist(compose, header, "Newsgroups", ",");
6535 compose_add_headerfield_from_headerlist(compose, header, "Cc", ", ");
6539 * If this account is a NNTP account remove Bcc header from
6540 * message body since it otherwise will be publicly shown
6542 if (compose->account->protocol != A_NNTP)
6543 compose_add_headerfield_from_headerlist(compose, header, "Bcc", ", ");
6546 str = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
6548 if (*str != '\0' && !IS_IN_CUSTOM_HEADER("Subject")) {
6551 compose_convert_header(compose, buf, sizeof(buf), str,
6552 strlen("Subject: "), FALSE);
6553 g_string_append_printf(header, "Subject: %s\n", buf);
6559 if (compose->msgid != NULL && strlen(compose->msgid) > 0) {
6560 g_string_append_printf(header, "Message-ID: <%s>\n",
6564 if (compose->remove_references == FALSE) {
6566 if (compose->inreplyto && compose->to_list)
6567 g_string_append_printf(header, "In-Reply-To: <%s>\n", compose->inreplyto);
6570 if (compose->references)
6571 g_string_append_printf(header, "References: %s\n", compose->references);
6575 compose_add_headerfield_from_headerlist(compose, header, "Followup-To", ",");
6578 compose_add_headerfield_from_headerlist(compose, header, "Reply-To", ", ");
6581 if (compose->account->organization &&
6582 strlen(compose->account->organization) &&
6583 !IS_IN_CUSTOM_HEADER("Organization")) {
6584 compose_convert_header(compose, buf, sizeof(buf),
6585 compose->account->organization,
6586 strlen("Organization: "), FALSE);
6587 g_string_append_printf(header, "Organization: %s\n", buf);
6590 /* Program version and system info */
6591 if (compose->account->gen_xmailer &&
6592 g_slist_length(compose->to_list) && !IS_IN_CUSTOM_HEADER("X-Mailer") &&
6593 !compose->newsgroup_list) {
6594 g_string_append_printf(header, "X-Mailer: %s (GTK+ %d.%d.%d; %s)\n",
6596 gtk_major_version, gtk_minor_version, gtk_micro_version,
6599 if (compose->account->gen_xmailer &&
6600 g_slist_length(compose->newsgroup_list) && !IS_IN_CUSTOM_HEADER("X-Newsreader")) {
6601 g_string_append_printf(header, "X-Newsreader: %s (GTK+ %d.%d.%d; %s)\n",
6603 gtk_major_version, gtk_minor_version, gtk_micro_version,
6607 /* custom headers */
6608 if (compose->account->add_customhdr) {
6611 for (cur = compose->account->customhdr_list; cur != NULL;
6613 CustomHeader *chdr = (CustomHeader *)cur->data;
6615 if (custom_header_is_allowed(chdr->name)
6616 && chdr->value != NULL
6617 && *(chdr->value) != '\0') {
6618 compose_convert_header
6619 (compose, buf, sizeof(buf),
6621 strlen(chdr->name) + 2, FALSE);
6622 g_string_append_printf(header, "%s: %s\n", chdr->name, buf);
6627 /* Automatic Faces and X-Faces */
6628 if (get_account_xface (buf, sizeof(buf), compose->account->account_name) == 0) {
6629 g_string_append_printf(header, "X-Face: %s\n", buf);
6631 else if (get_default_xface (buf, sizeof(buf)) == 0) {
6632 g_string_append_printf(header, "X-Face: %s\n", buf);
6634 if (get_account_face (buf, sizeof(buf), compose->account->account_name) == 0) {
6635 g_string_append_printf(header, "Face: %s\n", buf);
6637 else if (get_default_face (buf, sizeof(buf)) == 0) {
6638 g_string_append_printf(header, "Face: %s\n", buf);
6642 switch (compose->priority) {
6643 case PRIORITY_HIGHEST: g_string_append_printf(header, "Importance: high\n"
6644 "X-Priority: 1 (Highest)\n");
6646 case PRIORITY_HIGH: g_string_append_printf(header, "Importance: high\n"
6647 "X-Priority: 2 (High)\n");
6649 case PRIORITY_NORMAL: break;
6650 case PRIORITY_LOW: g_string_append_printf(header, "Importance: low\n"
6651 "X-Priority: 4 (Low)\n");
6653 case PRIORITY_LOWEST: g_string_append_printf(header, "Importance: low\n"
6654 "X-Priority: 5 (Lowest)\n");
6656 default: debug_print("compose: priority unknown : %d\n",
6660 /* get special headers */
6661 for (list = compose->header_list; list; list = list->next) {
6662 ComposeHeaderEntry *headerentry;
6665 gchar *headername_wcolon;
6666 const gchar *headername_trans;
6669 gboolean standard_header = FALSE;
6671 headerentry = ((ComposeHeaderEntry *)list->data);
6673 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6675 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6680 if (!strstr(tmp, ":")) {
6681 headername_wcolon = g_strconcat(tmp, ":", NULL);
6682 headername = g_strdup(tmp);
6684 headername_wcolon = g_strdup(tmp);
6685 headername = g_strdup(strtok(tmp, ":"));
6689 entry_str = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
6690 Xstrdup_a(headervalue, entry_str, return NULL);
6691 subst_char(headervalue, '\r', ' ');
6692 subst_char(headervalue, '\n', ' ');
6693 string = std_headers;
6694 while (*string != NULL) {
6695 headername_trans = prefs_common_translated_header_name(*string);
6696 if (!strcmp(headername_trans, headername_wcolon))
6697 standard_header = TRUE;
6700 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
6701 g_string_append_printf(header, "%s %s\n", headername_wcolon, headervalue);
6704 g_free(headername_wcolon);
6708 g_string_free(header, FALSE);
6713 #undef IS_IN_CUSTOM_HEADER
6715 static void compose_convert_header(Compose *compose, gchar *dest, gint len, gchar *src,
6716 gint header_len, gboolean addr_field)
6718 gchar *tmpstr = NULL;
6719 const gchar *out_codeset = NULL;
6721 cm_return_if_fail(src != NULL);
6722 cm_return_if_fail(dest != NULL);
6724 if (len < 1) return;
6726 tmpstr = g_strdup(src);
6728 subst_char(tmpstr, '\n', ' ');
6729 subst_char(tmpstr, '\r', ' ');
6732 if (!g_utf8_validate(tmpstr, -1, NULL)) {
6733 gchar *mybuf = g_malloc(strlen(tmpstr)*2 +1);
6734 conv_localetodisp(mybuf, strlen(tmpstr)*2 +1, tmpstr);
6739 codeconv_set_strict(TRUE);
6740 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6741 conv_get_charset_str(compose->out_encoding));
6742 codeconv_set_strict(FALSE);
6744 if (!dest || *dest == '\0') {
6745 gchar *test_conv_global_out = NULL;
6746 gchar *test_conv_reply = NULL;
6748 /* automatic mode. be automatic. */
6749 codeconv_set_strict(TRUE);
6751 out_codeset = conv_get_outgoing_charset_str();
6753 debug_print("trying to convert to %s\n", out_codeset);
6754 test_conv_global_out = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6757 if (!test_conv_global_out && compose->orig_charset
6758 && strcmp(compose->orig_charset, CS_US_ASCII)) {
6759 out_codeset = compose->orig_charset;
6760 debug_print("failure; trying to convert to %s\n", out_codeset);
6761 test_conv_reply = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6764 if (!test_conv_global_out && !test_conv_reply) {
6766 out_codeset = CS_INTERNAL;
6767 debug_print("finally using %s\n", out_codeset);
6769 g_free(test_conv_global_out);
6770 g_free(test_conv_reply);
6771 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6773 codeconv_set_strict(FALSE);
6778 static void compose_add_to_addressbook_cb(GtkMenuItem *menuitem, gpointer user_data)
6782 cm_return_if_fail(user_data != NULL);
6784 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(user_data)));
6785 g_strstrip(address);
6786 if (*address != '\0') {
6787 gchar *name = procheader_get_fromname(address);
6788 extract_address(address);
6789 #ifndef USE_ALT_ADDRBOOK
6790 addressbook_add_contact(name, address, NULL, NULL);
6792 debug_print("%s: %s\n", name, address);
6793 if (addressadd_selection(name, address, NULL, NULL)) {
6794 debug_print( "addressbook_add_contact - added\n" );
6801 static void compose_entry_popup_extend(GtkEntry *entry, GtkMenu *menu, gpointer user_data)
6803 GtkWidget *menuitem;
6806 cm_return_if_fail(menu != NULL);
6807 cm_return_if_fail(GTK_IS_MENU_SHELL(menu));
6809 menuitem = gtk_separator_menu_item_new();
6810 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6811 gtk_widget_show(menuitem);
6813 menuitem = gtk_menu_item_new_with_mnemonic(_("Add to address _book"));
6814 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6816 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
6817 g_strstrip(address);
6818 if (*address == '\0') {
6819 gtk_widget_set_sensitive(GTK_WIDGET(menuitem), FALSE);
6822 g_signal_connect(G_OBJECT(menuitem), "activate",
6823 G_CALLBACK(compose_add_to_addressbook_cb), entry);
6824 gtk_widget_show(menuitem);
6827 void compose_add_extra_header(gchar *header, GtkListStore *model)
6830 if (strcmp(header, "")) {
6831 COMBOBOX_ADD(model, header, COMPOSE_TO);
6835 void compose_add_extra_header_entries(GtkListStore *model)
6839 gchar buf[BUFFSIZE];
6842 if (extra_headers == NULL) {
6843 exhrc = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, "extraheaderrc", NULL);
6844 if ((exh = g_fopen(exhrc, "rb")) == NULL) {
6845 debug_print("extra headers file not found\n");
6846 goto extra_headers_done;
6848 while (fgets(buf, BUFFSIZE, exh) != NULL) {
6849 lastc = strlen(buf) - 1; /* remove trailing control chars */
6850 while (lastc >= 0 && buf[lastc] != ':')
6851 buf[lastc--] = '\0';
6852 if (lastc > 0 && buf[0] != '#' && buf[lastc] == ':') {
6853 buf[lastc] = '\0'; /* remove trailing : for comparison */
6854 if (custom_header_is_allowed(buf)) {
6856 extra_headers = g_slist_prepend(extra_headers, g_strdup(buf));
6859 g_message("disallowed extra header line: %s\n", buf);
6863 g_message("invalid extra header line: %s\n", buf);
6869 extra_headers = g_slist_prepend(extra_headers, g_strdup("")); /* end of list */
6870 extra_headers = g_slist_reverse(extra_headers);
6872 g_slist_foreach(extra_headers, (GFunc)compose_add_extra_header, (gpointer)model);
6875 static void compose_create_header_entry(Compose *compose)
6877 gchar *headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6884 const gchar *header = NULL;
6885 ComposeHeaderEntry *headerentry;
6886 gboolean standard_header = FALSE;
6887 GtkListStore *model;
6890 headerentry = g_new0(ComposeHeaderEntry, 1);
6892 /* Combo box model */
6893 model = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_BOOLEAN);
6894 #if !GTK_CHECK_VERSION(2, 24, 0)
6895 combo = gtk_combo_box_entry_new_with_model(GTK_TREE_MODEL(model), 0);
6897 COMBOBOX_ADD(model, prefs_common_translated_header_name("To:"),
6899 COMBOBOX_ADD(model, prefs_common_translated_header_name("Cc:"),
6901 COMBOBOX_ADD(model, prefs_common_translated_header_name("Bcc:"),
6903 COMBOBOX_ADD(model, prefs_common_translated_header_name("Newsgroups:"),
6904 COMPOSE_NEWSGROUPS);
6905 COMBOBOX_ADD(model, prefs_common_translated_header_name("Reply-To:"),
6907 COMBOBOX_ADD(model, prefs_common_translated_header_name("Followup-To:"),
6908 COMPOSE_FOLLOWUPTO);
6909 compose_add_extra_header_entries(model);
6912 #if GTK_CHECK_VERSION(2, 24, 0)
6913 combo = gtk_combo_box_new_with_model_and_entry(GTK_TREE_MODEL(model));
6914 GtkCellRenderer *cell = gtk_cell_renderer_text_new();
6915 gtk_cell_renderer_set_alignment(cell, 0.0, 0.5);
6916 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), cell, TRUE);
6917 gtk_combo_box_set_entry_text_column(GTK_COMBO_BOX(combo), 0);
6919 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
6920 g_signal_connect(G_OBJECT(gtk_bin_get_child(GTK_BIN(combo))), "grab_focus",
6921 G_CALLBACK(compose_grab_focus_cb), compose);
6922 gtk_widget_show(combo);
6924 /* Putting only the combobox child into focus chain of its parent causes
6925 * the parent to be skipped when changing focus via Tab or Shift+Tab.
6926 * This eliminates need to pres Tab twice in order to really get from the
6927 * combobox to next widget. */
6929 l = g_list_prepend(l, gtk_bin_get_child(GTK_BIN(combo)));
6930 gtk_container_set_focus_chain(GTK_CONTAINER(combo), l);
6933 gtk_table_attach(GTK_TABLE(compose->header_table), combo, 0, 1,
6934 compose->header_nextrow, compose->header_nextrow+1,
6935 GTK_SHRINK, GTK_FILL, 0, 0);
6936 if (compose->header_last && (compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN)) {
6937 const gchar *last_header_entry = gtk_entry_get_text(
6938 GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
6940 while (*string != NULL) {
6941 if (!strcmp(prefs_common_translated_header_name(*string), last_header_entry))
6942 standard_header = TRUE;
6945 if (standard_header)
6946 header = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
6948 if (!compose->header_last || !standard_header) {
6949 switch(compose->account->protocol) {
6951 header = prefs_common_translated_header_name("Newsgroups:");
6954 header = prefs_common_translated_header_name("To:");
6959 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((combo)))), header);
6961 gtk_editable_set_editable(
6962 GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((combo)))),
6963 prefs_common.type_any_header);
6965 g_signal_connect_after(G_OBJECT(gtk_bin_get_child(GTK_BIN((combo)))), "grab_focus",
6966 G_CALLBACK(compose_grab_focus_cb), compose);
6968 /* Entry field with cleanup button */
6969 button = gtk_button_new();
6970 gtk_button_set_image(GTK_BUTTON(button),
6971 gtk_image_new_from_stock(GTK_STOCK_CLEAR, GTK_ICON_SIZE_MENU));
6972 gtk_widget_show(button);
6973 CLAWS_SET_TIP(button,
6974 _("Delete entry contents"));
6975 entry = gtk_entry_new();
6976 gtk_widget_show(entry);
6977 CLAWS_SET_TIP(entry,
6978 _("Use <tab> to autocomplete from addressbook"));
6979 hbox = gtk_hbox_new (FALSE, 0);
6980 gtk_widget_show(hbox);
6981 gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, TRUE, 0);
6982 gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
6983 gtk_table_attach(GTK_TABLE(compose->header_table), hbox, 1, 2,
6984 compose->header_nextrow, compose->header_nextrow+1,
6985 GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
6987 g_signal_connect(G_OBJECT(entry), "key-press-event",
6988 G_CALLBACK(compose_headerentry_key_press_event_cb),
6990 g_signal_connect(G_OBJECT(entry), "changed",
6991 G_CALLBACK(compose_headerentry_changed_cb),
6993 g_signal_connect_after(G_OBJECT(entry), "grab_focus",
6994 G_CALLBACK(compose_grab_focus_cb), compose);
6996 g_signal_connect(G_OBJECT(button), "clicked",
6997 G_CALLBACK(compose_headerentry_button_clicked_cb),
7001 gtk_drag_dest_set(entry, GTK_DEST_DEFAULT_ALL, compose_mime_types,
7002 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7003 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7004 g_signal_connect(G_OBJECT(entry), "drag_data_received",
7005 G_CALLBACK(compose_header_drag_received_cb),
7007 g_signal_connect(G_OBJECT(entry), "drag-drop",
7008 G_CALLBACK(compose_drag_drop),
7010 g_signal_connect(G_OBJECT(entry), "populate-popup",
7011 G_CALLBACK(compose_entry_popup_extend),
7014 address_completion_register_entry(GTK_ENTRY(entry), TRUE);
7016 headerentry->compose = compose;
7017 headerentry->combo = combo;
7018 headerentry->entry = entry;
7019 headerentry->button = button;
7020 headerentry->hbox = hbox;
7021 headerentry->headernum = compose->header_nextrow;
7022 headerentry->type = PREF_NONE;
7024 compose->header_nextrow++;
7025 compose->header_last = headerentry;
7026 compose->header_list =
7027 g_slist_append(compose->header_list,
7031 static void compose_add_header_entry(Compose *compose, const gchar *header,
7032 gchar *text, ComposePrefType pref_type)
7034 ComposeHeaderEntry *last_header = compose->header_last;
7035 gchar *tmp = g_strdup(text), *email;
7036 gboolean replyto_hdr;
7038 replyto_hdr = (!strcasecmp(header,
7039 prefs_common_translated_header_name("Reply-To:")) ||
7041 prefs_common_translated_header_name("Followup-To:")) ||
7043 prefs_common_translated_header_name("In-Reply-To:")));
7045 extract_address(tmp);
7046 email = g_utf8_strdown(tmp, -1);
7048 if (replyto_hdr == FALSE &&
7049 g_hash_table_lookup(compose->email_hashtable, email) != NULL)
7051 debug_print("Ignoring duplicate address - %s %s, pref_type: %d\n",
7052 header, text, (gint) pref_type);
7058 if (!strcasecmp(header, prefs_common_translated_header_name("In-Reply-To:")))
7059 gtk_entry_set_text(GTK_ENTRY(
7060 gtk_bin_get_child(GTK_BIN(last_header->combo))), header);
7062 combobox_select_by_text(GTK_COMBO_BOX(last_header->combo), header);
7063 gtk_entry_set_text(GTK_ENTRY(last_header->entry), text);
7064 last_header->type = pref_type;
7066 if (replyto_hdr == FALSE)
7067 g_hash_table_insert(compose->email_hashtable, email,
7068 GUINT_TO_POINTER(1));
7075 static void compose_destroy_headerentry(Compose *compose,
7076 ComposeHeaderEntry *headerentry)
7078 gchar *text = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
7081 extract_address(text);
7082 email = g_utf8_strdown(text, -1);
7083 g_hash_table_remove(compose->email_hashtable, email);
7087 gtk_widget_destroy(headerentry->combo);
7088 gtk_widget_destroy(headerentry->entry);
7089 gtk_widget_destroy(headerentry->button);
7090 gtk_widget_destroy(headerentry->hbox);
7091 g_free(headerentry);
7094 static void compose_remove_header_entries(Compose *compose)
7097 for (list = compose->header_list; list; list = list->next)
7098 compose_destroy_headerentry(compose, (ComposeHeaderEntry *)list->data);
7100 compose->header_last = NULL;
7101 g_slist_free(compose->header_list);
7102 compose->header_list = NULL;
7103 compose->header_nextrow = 1;
7104 compose_create_header_entry(compose);
7107 static GtkWidget *compose_create_header(Compose *compose)
7109 GtkWidget *from_optmenu_hbox;
7110 GtkWidget *header_table_main;
7111 GtkWidget *header_scrolledwin;
7112 GtkWidget *header_table;
7114 /* parent with account selection and from header */
7115 header_table_main = gtk_table_new(2, 2, FALSE);
7116 gtk_widget_show(header_table_main);
7117 gtk_container_set_border_width(GTK_CONTAINER(header_table_main), BORDER_WIDTH);
7119 from_optmenu_hbox = compose_account_option_menu_create(compose);
7120 gtk_table_attach(GTK_TABLE(header_table_main), from_optmenu_hbox,
7121 0, 2, 0, 1, GTK_EXPAND | GTK_FILL, GTK_SHRINK, 0, 0);
7123 /* child with header labels and entries */
7124 header_scrolledwin = gtk_scrolled_window_new(NULL, NULL);
7125 gtk_widget_show(header_scrolledwin);
7126 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
7128 header_table = gtk_table_new(2, 2, FALSE);
7129 gtk_widget_show(header_table);
7130 gtk_container_set_border_width(GTK_CONTAINER(header_table), 0);
7131 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin), header_table);
7132 gtk_container_set_focus_vadjustment(GTK_CONTAINER(header_table),
7133 gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(header_scrolledwin)));
7134 gtk_viewport_set_shadow_type(GTK_VIEWPORT(gtk_bin_get_child(GTK_BIN(header_scrolledwin))), GTK_SHADOW_NONE);
7136 gtk_table_attach(GTK_TABLE(header_table_main), header_scrolledwin,
7137 0, 2, 1, 2, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 2);
7139 compose->header_table = header_table;
7140 compose->header_list = NULL;
7141 compose->header_nextrow = 0;
7143 compose_create_header_entry(compose);
7145 compose->table = NULL;
7147 return header_table_main;
7150 static gboolean popup_attach_button_pressed(GtkWidget *widget, gpointer data)
7152 Compose *compose = (Compose *)data;
7153 GdkEventButton event;
7156 event.time = gtk_get_current_event_time();
7158 return attach_button_pressed(compose->attach_clist, &event, compose);
7161 static GtkWidget *compose_create_attach(Compose *compose)
7163 GtkWidget *attach_scrwin;
7164 GtkWidget *attach_clist;
7166 GtkListStore *store;
7167 GtkCellRenderer *renderer;
7168 GtkTreeViewColumn *column;
7169 GtkTreeSelection *selection;
7171 /* attachment list */
7172 attach_scrwin = gtk_scrolled_window_new(NULL, NULL);
7173 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(attach_scrwin),
7174 GTK_POLICY_AUTOMATIC,
7175 GTK_POLICY_AUTOMATIC);
7176 gtk_widget_set_size_request(attach_scrwin, -1, 80);
7178 store = gtk_list_store_new(N_ATTACH_COLS,
7184 G_TYPE_AUTO_POINTER,
7186 attach_clist = GTK_WIDGET(gtk_tree_view_new_with_model
7187 (GTK_TREE_MODEL(store)));
7188 gtk_container_add(GTK_CONTAINER(attach_scrwin), attach_clist);
7189 g_object_unref(store);
7191 renderer = gtk_cell_renderer_text_new();
7192 column = gtk_tree_view_column_new_with_attributes
7193 (_("Mime type"), renderer, "text",
7194 COL_MIMETYPE, NULL);
7195 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7197 renderer = gtk_cell_renderer_text_new();
7198 column = gtk_tree_view_column_new_with_attributes
7199 (_("Size"), renderer, "text",
7201 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7203 renderer = gtk_cell_renderer_text_new();
7204 column = gtk_tree_view_column_new_with_attributes
7205 (_("Name"), renderer, "text",
7207 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7209 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(attach_clist),
7210 prefs_common.use_stripes_everywhere);
7211 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(attach_clist));
7212 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
7214 g_signal_connect(G_OBJECT(attach_clist), "row_activated",
7215 G_CALLBACK(attach_selected), compose);
7216 g_signal_connect(G_OBJECT(attach_clist), "button_press_event",
7217 G_CALLBACK(attach_button_pressed), compose);
7218 g_signal_connect(G_OBJECT(attach_clist), "popup-menu",
7219 G_CALLBACK(popup_attach_button_pressed), compose);
7220 g_signal_connect(G_OBJECT(attach_clist), "key_press_event",
7221 G_CALLBACK(attach_key_pressed), compose);
7224 gtk_drag_dest_set(attach_clist,
7225 GTK_DEST_DEFAULT_ALL, compose_mime_types,
7226 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7227 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7228 g_signal_connect(G_OBJECT(attach_clist), "drag_data_received",
7229 G_CALLBACK(compose_attach_drag_received_cb),
7231 g_signal_connect(G_OBJECT(attach_clist), "drag-drop",
7232 G_CALLBACK(compose_drag_drop),
7235 compose->attach_scrwin = attach_scrwin;
7236 compose->attach_clist = attach_clist;
7238 return attach_scrwin;
7241 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose);
7242 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose);
7244 static GtkWidget *compose_create_others(Compose *compose)
7247 GtkWidget *savemsg_checkbtn;
7248 GtkWidget *savemsg_combo;
7249 GtkWidget *savemsg_select;
7252 gchar *folderidentifier;
7254 /* Table for settings */
7255 table = gtk_table_new(3, 1, FALSE);
7256 gtk_container_set_border_width(GTK_CONTAINER(table), BORDER_WIDTH);
7257 gtk_widget_show(table);
7258 gtk_table_set_row_spacings(GTK_TABLE(table), VSPACING_NARROW);
7261 /* Save Message to folder */
7262 savemsg_checkbtn = gtk_check_button_new_with_label(_("Save Message to "));
7263 gtk_widget_show(savemsg_checkbtn);
7264 gtk_table_attach(GTK_TABLE(table), savemsg_checkbtn, 0, 1, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7265 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7266 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), prefs_common.savemsg);
7268 g_signal_connect(G_OBJECT(savemsg_checkbtn), "toggled",
7269 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
7271 #if !GTK_CHECK_VERSION(2, 24, 0)
7272 savemsg_combo = gtk_combo_box_entry_new_text();
7274 savemsg_combo = gtk_combo_box_text_new_with_entry();
7276 compose->savemsg_checkbtn = savemsg_checkbtn;
7277 compose->savemsg_combo = savemsg_combo;
7278 gtk_widget_show(savemsg_combo);
7280 if (prefs_common.compose_save_to_history)
7281 #if !GTK_CHECK_VERSION(2, 24, 0)
7282 combobox_set_popdown_strings(GTK_COMBO_BOX(savemsg_combo),
7283 prefs_common.compose_save_to_history);
7285 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(savemsg_combo),
7286 prefs_common.compose_save_to_history);
7288 gtk_table_attach(GTK_TABLE(table), savemsg_combo, 1, 2, rowcount, rowcount + 1, GTK_FILL|GTK_EXPAND, GTK_SHRINK, 0, 0);
7289 gtk_widget_set_sensitive(GTK_WIDGET(savemsg_combo), prefs_common.savemsg);
7290 g_signal_connect_after(G_OBJECT(savemsg_combo), "grab_focus",
7291 G_CALLBACK(compose_grab_focus_cb), compose);
7292 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7293 folderidentifier = folder_item_get_identifier(account_get_special_folder
7294 (compose->account, F_OUTBOX));
7295 compose_set_save_to(compose, folderidentifier);
7296 g_free(folderidentifier);
7299 savemsg_select = gtkut_get_browse_file_btn(_("_Browse"));
7300 gtk_widget_show(savemsg_select);
7301 gtk_table_attach(GTK_TABLE(table), savemsg_select, 2, 3, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7302 g_signal_connect(G_OBJECT(savemsg_select), "clicked",
7303 G_CALLBACK(compose_savemsg_select_cb),
7309 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose)
7311 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo),
7312 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn)));
7315 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose)
7320 dest = foldersel_folder_sel(NULL, FOLDER_SEL_COPY, NULL, FALSE);
7323 path = folder_item_get_identifier(dest);
7325 compose_set_save_to(compose, path);
7329 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry, gboolean wrap,
7330 GdkAtom clip, GtkTextIter *insert_place);
7333 static gboolean text_clicked(GtkWidget *text, GdkEventButton *event,
7337 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
7339 if (event->button == 3) {
7341 GtkTextIter sel_start, sel_end;
7342 gboolean stuff_selected;
7344 /* move the cursor to allow GtkAspell to check the word
7345 * under the mouse */
7346 if (event->x && event->y) {
7347 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7348 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7350 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7353 GtkTextMark *mark = gtk_text_buffer_get_insert(buffer);
7354 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7357 stuff_selected = gtk_text_buffer_get_selection_bounds(
7359 &sel_start, &sel_end);
7361 gtk_text_buffer_place_cursor (buffer, &iter);
7362 /* reselect stuff */
7364 && gtk_text_iter_in_range(&iter, &sel_start, &sel_end)) {
7365 gtk_text_buffer_select_range(buffer,
7366 &sel_start, &sel_end);
7368 return FALSE; /* pass the event so that the right-click goes through */
7371 if (event->button == 2) {
7376 /* get the middle-click position to paste at the correct place */
7377 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7378 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7380 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7383 entry_paste_clipboard(compose, text,
7384 prefs_common.linewrap_pastes,
7385 GDK_SELECTION_PRIMARY, &iter);
7393 static void compose_spell_menu_changed(void *data)
7395 Compose *compose = (Compose *)data;
7397 GtkWidget *menuitem;
7398 GtkWidget *parent_item;
7399 GtkMenu *menu = GTK_MENU(gtk_menu_new());
7402 if (compose->gtkaspell == NULL)
7405 parent_item = gtk_ui_manager_get_widget(compose->ui_manager,
7406 "/Menu/Spelling/Options");
7408 /* setting the submenu removes /Spelling/Options from the factory
7409 * so we need to save it */
7411 if (parent_item == NULL) {
7412 parent_item = compose->aspell_options_menu;
7413 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), NULL);
7415 compose->aspell_options_menu = parent_item;
7417 spell_menu = gtkaspell_make_config_menu(compose->gtkaspell);
7419 spell_menu = g_slist_reverse(spell_menu);
7420 for (items = spell_menu;
7421 items; items = items->next) {
7422 menuitem = GTK_WIDGET(GTK_MENU_ITEM(items->data));
7423 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), GTK_WIDGET(menuitem));
7424 gtk_widget_show(GTK_WIDGET(menuitem));
7426 g_slist_free(spell_menu);
7428 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), GTK_WIDGET(menu));
7429 gtk_widget_show(parent_item);
7432 static void compose_dict_changed(void *data)
7434 Compose *compose = (Compose *) data;
7436 if(!compose->gtkaspell)
7438 if(compose->gtkaspell->recheck_when_changing_dict == FALSE)
7441 gtkaspell_highlight_all(compose->gtkaspell);
7442 claws_spell_entry_recheck_all(CLAWS_SPELL_ENTRY(compose->subject_entry));
7446 static gboolean compose_popup_menu(GtkWidget *widget, gpointer data)
7448 Compose *compose = (Compose *)data;
7449 GdkEventButton event;
7452 event.time = gtk_get_current_event_time();
7456 return text_clicked(compose->text, &event, compose);
7459 static gboolean compose_force_window_origin = TRUE;
7460 static Compose *compose_create(PrefsAccount *account,
7469 GtkWidget *handlebox;
7471 GtkWidget *notebook;
7473 GtkWidget *attach_hbox;
7474 GtkWidget *attach_lab1;
7475 GtkWidget *attach_lab2;
7480 GtkWidget *subject_hbox;
7481 GtkWidget *subject_frame;
7482 GtkWidget *subject_entry;
7486 GtkWidget *edit_vbox;
7487 GtkWidget *ruler_hbox;
7489 GtkWidget *scrolledwin;
7491 GtkTextBuffer *buffer;
7492 GtkClipboard *clipboard;
7494 UndoMain *undostruct;
7496 GtkWidget *popupmenu;
7497 GtkWidget *tmpl_menu;
7498 GtkActionGroup *action_group = NULL;
7501 GtkAspell * gtkaspell = NULL;
7504 static GdkGeometry geometry;
7506 cm_return_val_if_fail(account != NULL, NULL);
7508 debug_print("Creating compose window...\n");
7509 compose = g_new0(Compose, 1);
7511 compose->batch = batch;
7512 compose->account = account;
7513 compose->folder = folder;
7515 compose->mutex = cm_mutex_new();
7516 compose->set_cursor_pos = -1;
7518 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose");
7520 gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
7521 gtk_widget_set_size_request(window, prefs_common.compose_width,
7522 prefs_common.compose_height);
7524 if (!geometry.max_width) {
7525 geometry.max_width = gdk_screen_width();
7526 geometry.max_height = gdk_screen_height();
7529 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7530 &geometry, GDK_HINT_MAX_SIZE);
7531 if (!geometry.min_width) {
7532 geometry.min_width = 600;
7533 geometry.min_height = 440;
7535 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7536 &geometry, GDK_HINT_MIN_SIZE);
7538 #ifndef GENERIC_UMPC
7539 if (compose_force_window_origin)
7540 gtk_window_move(GTK_WINDOW(window), prefs_common.compose_x,
7541 prefs_common.compose_y);
7543 g_signal_connect(G_OBJECT(window), "delete_event",
7544 G_CALLBACK(compose_delete_cb), compose);
7545 MANAGE_WINDOW_SIGNALS_CONNECT(window);
7546 gtk_widget_realize(window);
7548 gtkut_widget_set_composer_icon(window);
7550 vbox = gtk_vbox_new(FALSE, 0);
7551 gtk_container_add(GTK_CONTAINER(window), vbox);
7553 compose->ui_manager = gtk_ui_manager_new();
7554 action_group = cm_menu_create_action_group_full(compose->ui_manager,"Menu", compose_entries,
7555 G_N_ELEMENTS(compose_entries), (gpointer)compose);
7556 gtk_action_group_add_toggle_actions(action_group, compose_toggle_entries,
7557 G_N_ELEMENTS(compose_toggle_entries), (gpointer)compose);
7558 gtk_action_group_add_radio_actions(action_group, compose_radio_rm_entries,
7559 G_N_ELEMENTS(compose_radio_rm_entries), COMPOSE_REPLY, G_CALLBACK(compose_reply_change_mode_cb), (gpointer)compose);
7560 gtk_action_group_add_radio_actions(action_group, compose_radio_prio_entries,
7561 G_N_ELEMENTS(compose_radio_prio_entries), PRIORITY_NORMAL, G_CALLBACK(compose_set_priority_cb), (gpointer)compose);
7562 gtk_action_group_add_radio_actions(action_group, compose_radio_enc_entries,
7563 G_N_ELEMENTS(compose_radio_enc_entries), C_AUTO, G_CALLBACK(compose_set_encoding_cb), (gpointer)compose);
7565 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Menu", NULL, GTK_UI_MANAGER_MENUBAR)
7567 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Message", "Message", GTK_UI_MANAGER_MENU)
7568 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Edit", "Edit", GTK_UI_MANAGER_MENU)
7570 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Spelling", "Spelling", GTK_UI_MANAGER_MENU)
7572 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Options", "Options", GTK_UI_MANAGER_MENU)
7573 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Tools", "Tools", GTK_UI_MANAGER_MENU)
7574 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Help", "Help", GTK_UI_MANAGER_MENU)
7577 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Send", "Message/Send", GTK_UI_MANAGER_MENUITEM)
7578 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "SendLater", "Message/SendLater", GTK_UI_MANAGER_MENUITEM)
7579 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator1", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7580 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "AttachFile", "Message/AttachFile", GTK_UI_MANAGER_MENUITEM)
7581 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertFile", "Message/InsertFile", GTK_UI_MANAGER_MENUITEM)
7582 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertSig", "Message/InsertSig", GTK_UI_MANAGER_MENUITEM)
7583 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "ReplaceSig", "Message/ReplaceSig", GTK_UI_MANAGER_MENUITEM)
7584 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator2", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7585 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Save", "Message/Save", GTK_UI_MANAGER_MENUITEM)
7586 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator3", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7587 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Print", "Message/Print", GTK_UI_MANAGER_MENUITEM)
7588 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator4", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7589 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Close", "Message/Close", GTK_UI_MANAGER_MENUITEM)
7592 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Undo", "Edit/Undo", GTK_UI_MANAGER_MENUITEM)
7593 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Redo", "Edit/Redo", GTK_UI_MANAGER_MENUITEM)
7594 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator1", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7596 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Cut", "Edit/Cut", GTK_UI_MANAGER_MENUITEM)
7597 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Copy", "Edit/Copy", GTK_UI_MANAGER_MENUITEM)
7598 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Paste", "Edit/Paste", GTK_UI_MANAGER_MENUITEM)
7600 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SpecialPaste", "Edit/SpecialPaste", GTK_UI_MANAGER_MENU)
7601 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "AsQuotation", "Edit/SpecialPaste/AsQuotation", GTK_UI_MANAGER_MENUITEM)
7602 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Wrapped", "Edit/SpecialPaste/Wrapped", GTK_UI_MANAGER_MENUITEM)
7603 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Unwrapped", "Edit/SpecialPaste/Unwrapped", GTK_UI_MANAGER_MENUITEM)
7605 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SelectAll", "Edit/SelectAll", GTK_UI_MANAGER_MENUITEM)
7607 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Advanced", "Edit/Advanced", GTK_UI_MANAGER_MENU)
7608 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackChar", "Edit/Advanced/BackChar", GTK_UI_MANAGER_MENUITEM)
7609 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwChar", "Edit/Advanced/ForwChar", GTK_UI_MANAGER_MENUITEM)
7610 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackWord", "Edit/Advanced/BackWord", GTK_UI_MANAGER_MENUITEM)
7611 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwWord", "Edit/Advanced/ForwWord", GTK_UI_MANAGER_MENUITEM)
7612 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BegLine", "Edit/Advanced/BegLine", GTK_UI_MANAGER_MENUITEM)
7613 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "EndLine", "Edit/Advanced/EndLine", GTK_UI_MANAGER_MENUITEM)
7614 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "PrevLine", "Edit/Advanced/PrevLine", GTK_UI_MANAGER_MENUITEM)
7615 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "NextLine", "Edit/Advanced/NextLine", GTK_UI_MANAGER_MENUITEM)
7616 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackChar", "Edit/Advanced/DelBackChar", GTK_UI_MANAGER_MENUITEM)
7617 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwChar", "Edit/Advanced/DelForwChar", GTK_UI_MANAGER_MENUITEM)
7618 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackWord", "Edit/Advanced/DelBackWord", GTK_UI_MANAGER_MENUITEM)
7619 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwWord", "Edit/Advanced/DelForwWord", GTK_UI_MANAGER_MENUITEM)
7620 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelLine", "Edit/Advanced/DelLine", GTK_UI_MANAGER_MENUITEM)
7621 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelEndLine", "Edit/Advanced/DelEndLine", GTK_UI_MANAGER_MENUITEM)
7623 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator2", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7625 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Find", "Edit/Find", GTK_UI_MANAGER_MENUITEM)
7626 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapPara", "Edit/WrapPara", GTK_UI_MANAGER_MENUITEM)
7627 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapAllLines", "Edit/WrapAllLines", GTK_UI_MANAGER_MENUITEM)
7628 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoWrap", "Edit/AutoWrap", GTK_UI_MANAGER_MENUITEM)
7629 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoIndent", "Edit/AutoIndent", GTK_UI_MANAGER_MENUITEM)
7631 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator3", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7633 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "ExtEditor", "Edit/ExtEditor", GTK_UI_MANAGER_MENUITEM)
7637 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckAllSel", "Spelling/CheckAllSel", GTK_UI_MANAGER_MENUITEM)
7638 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "HighlightAll", "Spelling/HighlightAll", GTK_UI_MANAGER_MENUITEM)
7639 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckBackwards", "Spelling/CheckBackwards", GTK_UI_MANAGER_MENUITEM)
7640 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "ForwardNext", "Spelling/ForwardNext", GTK_UI_MANAGER_MENUITEM)
7641 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Separator1", "Spelling/---", GTK_UI_MANAGER_SEPARATOR)
7642 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Options", "Spelling/Options", GTK_UI_MANAGER_MENU)
7646 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "ReplyMode", "Options/ReplyMode", GTK_UI_MANAGER_MENU)
7647 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Normal", "Options/ReplyMode/Normal", GTK_UI_MANAGER_MENUITEM)
7648 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "All", "Options/ReplyMode/All", GTK_UI_MANAGER_MENUITEM)
7649 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Sender", "Options/ReplyMode/Sender", GTK_UI_MANAGER_MENUITEM)
7650 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "List", "Options/ReplyMode/List", GTK_UI_MANAGER_MENUITEM)
7652 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator1", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7653 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "PrivacySystem", "Options/PrivacySystem", GTK_UI_MANAGER_MENU)
7654 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/PrivacySystem", "PlaceHolder", "Options/PrivacySystem/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7655 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Sign", "Options/Sign", GTK_UI_MANAGER_MENUITEM)
7656 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encrypt", "Options/Encrypt", GTK_UI_MANAGER_MENUITEM)
7659 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator2", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7660 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Priority", "Options/Priority", GTK_UI_MANAGER_MENU)
7661 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Highest", "Options/Priority/Highest", GTK_UI_MANAGER_MENUITEM)
7662 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "High", "Options/Priority/High", GTK_UI_MANAGER_MENUITEM)
7663 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Normal", "Options/Priority/Normal", GTK_UI_MANAGER_MENUITEM)
7664 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Low", "Options/Priority/Low", GTK_UI_MANAGER_MENUITEM)
7665 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Lowest", "Options/Priority/Lowest", GTK_UI_MANAGER_MENUITEM)
7667 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator3", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7668 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RequestRetRcpt", "Options/RequestRetRcpt", GTK_UI_MANAGER_MENUITEM)
7669 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator4", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7670 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RemoveReferences", "Options/RemoveReferences", GTK_UI_MANAGER_MENUITEM)
7671 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator5", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7673 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encoding", "Options/Encoding", GTK_UI_MANAGER_MENU)
7675 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_AUTO, "Options/Encoding/"CS_AUTO, GTK_UI_MANAGER_MENUITEM)
7676 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator1", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7677 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_US_ASCII, "Options/Encoding/"CS_US_ASCII, GTK_UI_MANAGER_MENUITEM)
7678 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_UTF_8, "Options/Encoding/"CS_UTF_8, GTK_UI_MANAGER_MENUITEM)
7679 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator2", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7681 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Western", "Options/Encoding/Western", GTK_UI_MANAGER_MENU)
7682 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)
7683 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)
7684 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Western", CS_WINDOWS_1252, "Options/Encoding/Western/"CS_WINDOWS_1252, GTK_UI_MANAGER_MENUITEM)
7686 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_2, "Options/Encoding/"CS_ISO_8859_2, GTK_UI_MANAGER_MENUITEM)
7688 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Baltic", "Options/Encoding/Baltic", GTK_UI_MANAGER_MENU)
7689 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)
7690 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)
7692 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_7, "Options/Encoding/"CS_ISO_8859_7, GTK_UI_MANAGER_MENUITEM)
7694 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Hebrew", "Options/Encoding/Hebrew", GTK_UI_MANAGER_MENU)
7695 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)
7696 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Hebrew", CS_WINDOWS_1255, "Options/Encoding/Hebrew/"CS_WINDOWS_1255, GTK_UI_MANAGER_MENUITEM)
7698 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Arabic", "Options/Encoding/Arabic", GTK_UI_MANAGER_MENU)
7699 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)
7700 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Arabic", CS_WINDOWS_1256, "Options/Encoding/Arabic/"CS_WINDOWS_1256, GTK_UI_MANAGER_MENUITEM)
7702 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_9, "Options/Encoding/"CS_ISO_8859_9, GTK_UI_MANAGER_MENUITEM)
7704 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Cyrillic", "Options/Encoding/Cyrillic", GTK_UI_MANAGER_MENU)
7705 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)
7706 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_R, "Options/Encoding/Cyrillic/"CS_KOI8_R, GTK_UI_MANAGER_MENUITEM)
7707 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_MACCYR, "Options/Encoding/Cyrillic/"CS_MACCYR, GTK_UI_MANAGER_MENUITEM)
7708 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_U, "Options/Encoding/Cyrillic/"CS_KOI8_U, GTK_UI_MANAGER_MENUITEM)
7709 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_WINDOWS_1251, "Options/Encoding/Cyrillic/"CS_WINDOWS_1251, GTK_UI_MANAGER_MENUITEM)
7711 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Japanese", "Options/Encoding/Japanese", GTK_UI_MANAGER_MENU)
7712 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)
7713 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)
7714 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_EUC_JP, "Options/Encoding/Japanese/"CS_EUC_JP, GTK_UI_MANAGER_MENUITEM)
7715 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_SHIFT_JIS, "Options/Encoding/Japanese/"CS_SHIFT_JIS, GTK_UI_MANAGER_MENUITEM)
7717 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Chinese", "Options/Encoding/Chinese", GTK_UI_MANAGER_MENU)
7718 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB18030, "Options/Encoding/Chinese/"CS_GB18030, GTK_UI_MANAGER_MENUITEM)
7719 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB2312, "Options/Encoding/Chinese/"CS_GB2312, GTK_UI_MANAGER_MENUITEM)
7720 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GBK, "Options/Encoding/Chinese/"CS_GBK, GTK_UI_MANAGER_MENUITEM)
7721 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_BIG5, "Options/Encoding/Chinese/"CS_BIG5, GTK_UI_MANAGER_MENUITEM)
7722 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_EUC_TW, "Options/Encoding/Chinese/"CS_EUC_TW, GTK_UI_MANAGER_MENUITEM)
7724 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Korean", "Options/Encoding/Korean", GTK_UI_MANAGER_MENU)
7725 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Korean", CS_EUC_KR, "Options/Encoding/Korean/"CS_EUC_KR, GTK_UI_MANAGER_MENUITEM)
7726 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)
7728 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Thai", "Options/Encoding/Thai", GTK_UI_MANAGER_MENU)
7729 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_TIS_620, "Options/Encoding/Thai/"CS_TIS_620, GTK_UI_MANAGER_MENUITEM)
7730 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_WINDOWS_874, "Options/Encoding/Thai/"CS_WINDOWS_874, GTK_UI_MANAGER_MENUITEM)
7734 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "ShowRuler", "Tools/ShowRuler", GTK_UI_MANAGER_MENUITEM)
7735 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "AddressBook", "Tools/AddressBook", GTK_UI_MANAGER_MENUITEM)
7736 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Template", "Tools/Template", GTK_UI_MANAGER_MENU)
7737 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Template", "PlaceHolder", "Tools/Template/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7738 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Actions", "Tools/Actions", GTK_UI_MANAGER_MENU)
7739 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Actions", "PlaceHolder", "Tools/Actions/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7742 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Help", "About", "Help/About", GTK_UI_MANAGER_MENUITEM)
7744 menubar = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu");
7745 gtk_widget_show_all(menubar);
7747 gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(compose->ui_manager));
7748 gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, TRUE, 0);
7750 if (prefs_common.toolbar_detachable) {
7751 handlebox = gtk_handle_box_new();
7753 handlebox = gtk_hbox_new(FALSE, 0);
7755 gtk_box_pack_start(GTK_BOX(vbox), handlebox, FALSE, FALSE, 0);
7757 gtk_widget_realize(handlebox);
7758 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, handlebox,
7761 vbox2 = gtk_vbox_new(FALSE, 2);
7762 gtk_box_pack_start(GTK_BOX(vbox), vbox2, TRUE, TRUE, 0);
7763 gtk_container_set_border_width(GTK_CONTAINER(vbox2), 0);
7766 notebook = gtk_notebook_new();
7767 gtk_widget_show(notebook);
7769 /* header labels and entries */
7770 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7771 compose_create_header(compose),
7772 gtk_label_new_with_mnemonic(_("Hea_der")));
7773 /* attachment list */
7774 attach_hbox = gtk_hbox_new(FALSE, 0);
7775 gtk_widget_show(attach_hbox);
7777 attach_lab1 = gtk_label_new_with_mnemonic(_("_Attachments"));
7778 gtk_widget_show(attach_lab1);
7779 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab1, TRUE, TRUE, 0);
7781 attach_lab2 = gtk_label_new("");
7782 gtk_widget_show(attach_lab2);
7783 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab2, FALSE, FALSE, 0);
7785 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7786 compose_create_attach(compose),
7789 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7790 compose_create_others(compose),
7791 gtk_label_new_with_mnemonic(_("Othe_rs")));
7794 subject_hbox = gtk_hbox_new(FALSE, 0);
7795 gtk_widget_show(subject_hbox);
7797 subject_frame = gtk_frame_new(NULL);
7798 gtk_frame_set_shadow_type(GTK_FRAME(subject_frame), GTK_SHADOW_NONE);
7799 gtk_box_pack_start(GTK_BOX(subject_hbox), subject_frame, TRUE, TRUE, 0);
7800 gtk_widget_show(subject_frame);
7802 subject = gtk_hbox_new(FALSE, HSPACING_NARROW);
7803 gtk_container_set_border_width(GTK_CONTAINER(subject), 0);
7804 gtk_widget_show(subject);
7806 label = gtk_label_new_with_mnemonic(_("S_ubject:"));
7807 gtk_box_pack_start(GTK_BOX(subject), label, FALSE, FALSE, 0);
7808 gtk_widget_show(label);
7811 subject_entry = claws_spell_entry_new();
7813 subject_entry = gtk_entry_new();
7815 gtk_box_pack_start(GTK_BOX(subject), subject_entry, TRUE, TRUE, 0);
7816 g_signal_connect_after(G_OBJECT(subject_entry), "grab_focus",
7817 G_CALLBACK(compose_grab_focus_cb), compose);
7818 gtk_label_set_mnemonic_widget(GTK_LABEL(label), subject_entry);
7819 gtk_widget_show(subject_entry);
7820 compose->subject_entry = subject_entry;
7821 gtk_container_add(GTK_CONTAINER(subject_frame), subject);
7823 edit_vbox = gtk_vbox_new(FALSE, 0);
7825 gtk_box_pack_start(GTK_BOX(edit_vbox), subject_hbox, FALSE, FALSE, 0);
7828 ruler_hbox = gtk_hbox_new(FALSE, 0);
7829 gtk_box_pack_start(GTK_BOX(edit_vbox), ruler_hbox, FALSE, FALSE, 0);
7831 ruler = gtk_shruler_new(GTK_ORIENTATION_HORIZONTAL);
7832 gtk_shruler_set_range(GTK_SHRULER(ruler), 0.0, 100.0, 1.0);
7833 gtk_box_pack_start(GTK_BOX(ruler_hbox), ruler, TRUE, TRUE,
7837 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
7838 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
7839 GTK_POLICY_AUTOMATIC,
7840 GTK_POLICY_AUTOMATIC);
7841 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
7843 gtk_box_pack_start(GTK_BOX(edit_vbox), scrolledwin, TRUE, TRUE, 0);
7845 text = gtk_text_view_new();
7846 if (prefs_common.show_compose_margin) {
7847 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text), 6);
7848 gtk_text_view_set_right_margin(GTK_TEXT_VIEW(text), 6);
7850 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
7851 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
7852 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), TRUE);
7853 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
7854 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
7856 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
7857 g_signal_connect_after(G_OBJECT(text), "size_allocate",
7858 G_CALLBACK(compose_edit_size_alloc),
7860 g_signal_connect(G_OBJECT(buffer), "changed",
7861 G_CALLBACK(compose_changed_cb), compose);
7862 g_signal_connect(G_OBJECT(text), "grab_focus",
7863 G_CALLBACK(compose_grab_focus_cb), compose);
7864 g_signal_connect(G_OBJECT(buffer), "insert_text",
7865 G_CALLBACK(text_inserted), compose);
7866 g_signal_connect(G_OBJECT(text), "button_press_event",
7867 G_CALLBACK(text_clicked), compose);
7868 g_signal_connect(G_OBJECT(text), "popup-menu",
7869 G_CALLBACK(compose_popup_menu), compose);
7870 g_signal_connect(G_OBJECT(subject_entry), "changed",
7871 G_CALLBACK(compose_changed_cb), compose);
7872 g_signal_connect(G_OBJECT(subject_entry), "activate",
7873 G_CALLBACK(compose_subject_entry_activated), compose);
7876 gtk_drag_dest_set(text, GTK_DEST_DEFAULT_ALL, compose_mime_types,
7877 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7878 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7879 g_signal_connect(G_OBJECT(text), "drag_data_received",
7880 G_CALLBACK(compose_insert_drag_received_cb),
7882 g_signal_connect(G_OBJECT(text), "drag-drop",
7883 G_CALLBACK(compose_drag_drop),
7885 g_signal_connect(G_OBJECT(text), "key-press-event",
7886 G_CALLBACK(completion_set_focus_to_subject),
7888 gtk_widget_show_all(vbox);
7890 /* pane between attach clist and text */
7891 paned = gtk_vpaned_new();
7892 gtk_container_add(GTK_CONTAINER(vbox2), paned);
7893 gtk_paned_pack1(GTK_PANED(paned), notebook, FALSE, FALSE);
7894 gtk_paned_pack2(GTK_PANED(paned), edit_vbox, TRUE, FALSE);
7895 gtk_paned_set_position(GTK_PANED(paned), prefs_common.compose_notebook_height);
7896 g_signal_connect(G_OBJECT(notebook), "size_allocate",
7897 G_CALLBACK(compose_notebook_size_alloc), paned);
7899 gtk_widget_show_all(paned);
7902 if (prefs_common.textfont) {
7903 PangoFontDescription *font_desc;
7905 font_desc = pango_font_description_from_string
7906 (prefs_common.textfont);
7908 gtk_widget_modify_font(text, font_desc);
7909 pango_font_description_free(font_desc);
7913 gtk_action_group_add_actions(action_group, compose_popup_entries,
7914 G_N_ELEMENTS(compose_popup_entries), (gpointer)compose);
7915 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Popup", NULL, GTK_UI_MANAGER_MENUBAR)
7916 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup", "Compose", "Compose", GTK_UI_MANAGER_MENU)
7917 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Add", "Compose/Add", GTK_UI_MANAGER_MENUITEM)
7918 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Remove", "Compose/Remove", GTK_UI_MANAGER_MENUITEM)
7919 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Separator1", "Compose/---", GTK_UI_MANAGER_SEPARATOR)
7920 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Properties", "Compose/Properties", GTK_UI_MANAGER_MENUITEM)
7922 popupmenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(gtk_ui_manager_get_widget(compose->ui_manager, "/Popup/Compose")));
7924 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
7925 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
7926 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
7928 tmpl_menu = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu/Tools/Template");
7930 undostruct = undo_init(text);
7931 undo_set_change_state_func(undostruct, &compose_undo_state_changed,
7934 address_completion_start(window);
7936 compose->window = window;
7937 compose->vbox = vbox;
7938 compose->menubar = menubar;
7939 compose->handlebox = handlebox;
7941 compose->vbox2 = vbox2;
7943 compose->paned = paned;
7945 compose->attach_label = attach_lab2;
7947 compose->notebook = notebook;
7948 compose->edit_vbox = edit_vbox;
7949 compose->ruler_hbox = ruler_hbox;
7950 compose->ruler = ruler;
7951 compose->scrolledwin = scrolledwin;
7952 compose->text = text;
7954 compose->focused_editable = NULL;
7956 compose->popupmenu = popupmenu;
7958 compose->tmpl_menu = tmpl_menu;
7960 compose->mode = mode;
7961 compose->rmode = mode;
7963 compose->targetinfo = NULL;
7964 compose->replyinfo = NULL;
7965 compose->fwdinfo = NULL;
7967 compose->email_hashtable = g_hash_table_new_full(g_str_hash,
7968 g_str_equal, (GDestroyNotify) g_free, NULL);
7970 compose->replyto = NULL;
7972 compose->bcc = NULL;
7973 compose->followup_to = NULL;
7975 compose->ml_post = NULL;
7977 compose->inreplyto = NULL;
7978 compose->references = NULL;
7979 compose->msgid = NULL;
7980 compose->boundary = NULL;
7982 compose->autowrap = prefs_common.autowrap;
7983 compose->autoindent = prefs_common.auto_indent;
7984 compose->use_signing = FALSE;
7985 compose->use_encryption = FALSE;
7986 compose->privacy_system = NULL;
7987 compose->encdata = NULL;
7989 compose->modified = FALSE;
7991 compose->return_receipt = FALSE;
7993 compose->to_list = NULL;
7994 compose->newsgroup_list = NULL;
7996 compose->undostruct = undostruct;
7998 compose->sig_str = NULL;
8000 compose->exteditor_file = NULL;
8001 compose->exteditor_pid = -1;
8002 compose->exteditor_tag = -1;
8003 compose->exteditor_socket = NULL;
8004 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN; /* inhibit auto-drafting while loading */
8006 compose->folder_update_callback_id =
8007 hooks_register_hook(FOLDER_UPDATE_HOOKLIST,
8008 compose_update_folder_hook,
8009 (gpointer) compose);
8012 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
8013 if (mode != COMPOSE_REDIRECT) {
8014 if (prefs_common.enable_aspell && prefs_common.dictionary &&
8015 strcmp(prefs_common.dictionary, "")) {
8016 gtkaspell = gtkaspell_new(prefs_common.dictionary,
8017 prefs_common.alt_dictionary,
8018 conv_get_locale_charset_str(),
8019 prefs_common.misspelled_col,
8020 prefs_common.check_while_typing,
8021 prefs_common.recheck_when_changing_dict,
8022 prefs_common.use_alternate,
8023 prefs_common.use_both_dicts,
8024 GTK_TEXT_VIEW(text),
8025 GTK_WINDOW(compose->window),
8026 compose_dict_changed,
8027 compose_spell_menu_changed,
8030 alertpanel_error(_("Spell checker could not "
8032 gtkaspell_checkers_strerror());
8033 gtkaspell_checkers_reset_error();
8035 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", TRUE);
8039 compose->gtkaspell = gtkaspell;
8040 compose_spell_menu_changed(compose);
8041 claws_spell_entry_set_gtkaspell(CLAWS_SPELL_ENTRY(subject_entry), gtkaspell);
8044 compose_select_account(compose, account, TRUE);
8046 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", prefs_common.autowrap);
8047 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", prefs_common.auto_indent);
8049 if (account->set_autocc && account->auto_cc && mode != COMPOSE_REEDIT)
8050 compose_entry_append(compose, account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8052 if (account->set_autobcc && account->auto_bcc && mode != COMPOSE_REEDIT)
8053 compose_entry_append(compose, account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8055 if (account->set_autoreplyto && account->auto_replyto && mode != COMPOSE_REEDIT)
8056 compose_entry_append(compose, account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8058 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", compose->mode == COMPOSE_REPLY);
8059 if (account->protocol != A_NNTP)
8060 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
8061 prefs_common_translated_header_name("To:"));
8063 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
8064 prefs_common_translated_header_name("Newsgroups:"));
8066 #ifndef USE_ALT_ADDRBOOK
8067 addressbook_set_target_compose(compose);
8069 if (mode != COMPOSE_REDIRECT)
8070 compose_set_template_menu(compose);
8072 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Template", FALSE);
8075 compose_list = g_list_append(compose_list, compose);
8077 if (!prefs_common.show_ruler)
8078 gtk_widget_hide(ruler_hbox);
8080 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Tools/ShowRuler", prefs_common.show_ruler);
8083 compose->priority = PRIORITY_NORMAL;
8084 compose_update_priority_menu_item(compose);
8086 compose_set_out_encoding(compose);
8089 compose_update_actions_menu(compose);
8091 /* Privacy Systems menu */
8092 compose_update_privacy_systems_menu(compose);
8094 activate_privacy_system(compose, account, TRUE);
8095 toolbar_set_style(compose->toolbar->toolbar, compose->handlebox, prefs_common.toolbar_style);
8097 gtk_widget_realize(window);
8099 gtk_widget_show(window);
8105 static GtkWidget *compose_account_option_menu_create(Compose *compose)
8110 GtkWidget *optmenubox;
8111 GtkWidget *fromlabel;
8114 GtkWidget *from_name = NULL;
8116 gint num = 0, def_menu = 0;
8118 accounts = account_get_list();
8119 cm_return_val_if_fail(accounts != NULL, NULL);
8121 optmenubox = gtk_event_box_new();
8122 optmenu = gtkut_sc_combobox_create(optmenubox, FALSE);
8123 menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
8125 hbox = gtk_hbox_new(FALSE, 4);
8126 from_name = gtk_entry_new();
8128 g_signal_connect_after(G_OBJECT(from_name), "grab_focus",
8129 G_CALLBACK(compose_grab_focus_cb), compose);
8130 g_signal_connect_after(G_OBJECT(from_name), "activate",
8131 G_CALLBACK(from_name_activate_cb), optmenu);
8133 for (; accounts != NULL; accounts = accounts->next, num++) {
8134 PrefsAccount *ac = (PrefsAccount *)accounts->data;
8135 gchar *name, *from = NULL;
8137 if (ac == compose->account) def_menu = num;
8139 name = g_markup_printf_escaped("<i>%s</i>",
8142 if (ac == compose->account) {
8143 if (ac->name && *ac->name) {
8145 QUOTE_IF_REQUIRED_NORMAL(buf, ac->name, return NULL);
8146 from = g_strdup_printf("%s <%s>",
8148 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8150 from = g_strdup_printf("%s",
8152 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8155 COMBOBOX_ADD(menu, name, ac->account_id);
8160 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), def_menu);
8162 g_signal_connect(G_OBJECT(optmenu), "changed",
8163 G_CALLBACK(account_activated),
8165 g_signal_connect(G_OBJECT(from_name), "populate-popup",
8166 G_CALLBACK(compose_entry_popup_extend),
8169 fromlabel = gtk_label_new_with_mnemonic(_("_From:"));
8170 gtk_label_set_mnemonic_widget(GTK_LABEL(fromlabel), from_name);
8172 gtk_box_pack_start(GTK_BOX(hbox), fromlabel, FALSE, FALSE, 4);
8173 gtk_box_pack_start(GTK_BOX(hbox), optmenubox, FALSE, FALSE, 0);
8174 gtk_box_pack_start(GTK_BOX(hbox), from_name, TRUE, TRUE, 0);
8176 /* Putting only the GtkEntry into focus chain of parent hbox causes
8177 * the account selector combobox next to it to be unreachable when
8178 * navigating widgets in GtkTable with up/down arrow keys.
8179 * Note: gtk_widget_set_can_focus() was not enough. */
8181 l = g_list_prepend(l, from_name);
8182 gtk_container_set_focus_chain(GTK_CONTAINER(hbox), l);
8185 CLAWS_SET_TIP(optmenubox,
8186 _("Account to use for this email"));
8187 CLAWS_SET_TIP(from_name,
8188 _("Sender address to be used"));
8190 compose->account_combo = optmenu;
8191 compose->from_name = from_name;
8196 static void compose_set_priority_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8198 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8199 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8200 Compose *compose = (Compose *) data;
8202 compose->priority = value;
8206 static void compose_reply_change_mode(Compose *compose,
8209 gboolean was_modified = compose->modified;
8211 gboolean all = FALSE, ml = FALSE, sender = FALSE, followup = FALSE;
8213 cm_return_if_fail(compose->replyinfo != NULL);
8215 if (action == COMPOSE_REPLY && prefs_common.default_reply_list)
8217 if (action == COMPOSE_REPLY && compose->rmode == COMPOSE_FOLLOWUP_AND_REPLY_TO)
8219 if (action == COMPOSE_REPLY_TO_ALL)
8221 if (action == COMPOSE_REPLY_TO_SENDER)
8223 if (action == COMPOSE_REPLY_TO_LIST)
8226 compose_remove_header_entries(compose);
8227 compose_reply_set_entry(compose, compose->replyinfo, all, ml, sender, followup);
8228 if (compose->account->set_autocc && compose->account->auto_cc)
8229 compose_entry_append(compose, compose->account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8231 if (compose->account->set_autobcc && compose->account->auto_bcc)
8232 compose_entry_append(compose, compose->account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8234 if (compose->account->set_autoreplyto && compose->account->auto_replyto)
8235 compose_entry_append(compose, compose->account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8236 compose_show_first_last_header(compose, TRUE);
8237 compose->modified = was_modified;
8238 compose_set_title(compose);
8241 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8243 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8244 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8245 Compose *compose = (Compose *) data;
8248 compose_reply_change_mode(compose, value);
8251 static void compose_update_priority_menu_item(Compose * compose)
8253 GtkWidget *menuitem = NULL;
8254 switch (compose->priority) {
8255 case PRIORITY_HIGHEST:
8256 menuitem = gtk_ui_manager_get_widget
8257 (compose->ui_manager, "/Menu/Options/Priority/Highest");
8260 menuitem = gtk_ui_manager_get_widget
8261 (compose->ui_manager, "/Menu/Options/Priority/High");
8263 case PRIORITY_NORMAL:
8264 menuitem = gtk_ui_manager_get_widget
8265 (compose->ui_manager, "/Menu/Options/Priority/Normal");
8268 menuitem = gtk_ui_manager_get_widget
8269 (compose->ui_manager, "/Menu/Options/Priority/Low");
8271 case PRIORITY_LOWEST:
8272 menuitem = gtk_ui_manager_get_widget
8273 (compose->ui_manager, "/Menu/Options/Priority/Lowest");
8276 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8279 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data)
8281 Compose *compose = (Compose *) data;
8283 gboolean can_sign = FALSE, can_encrypt = FALSE;
8285 cm_return_if_fail(GTK_IS_CHECK_MENU_ITEM(widget));
8287 if (!gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget)))
8290 systemid = g_object_get_data(G_OBJECT(widget), "privacy_system");
8291 g_free(compose->privacy_system);
8292 compose->privacy_system = NULL;
8293 g_free(compose->encdata);
8294 compose->encdata = NULL;
8295 if (systemid != NULL) {
8296 compose->privacy_system = g_strdup(systemid);
8298 can_sign = privacy_system_can_sign(systemid);
8299 can_encrypt = privacy_system_can_encrypt(systemid);
8302 debug_print("activated privacy system: %s\n", systemid != NULL ? systemid : "None");
8304 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8305 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8308 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn)
8310 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8311 GtkWidget *menuitem = NULL;
8312 GList *children, *amenu;
8313 gboolean can_sign = FALSE, can_encrypt = FALSE;
8314 gboolean found = FALSE;
8316 if (compose->privacy_system != NULL) {
8318 menuitem = gtk_menu_item_get_submenu(GTK_MENU_ITEM(
8319 gtk_ui_manager_get_widget(compose->ui_manager, branch_path)));
8320 cm_return_if_fail(menuitem != NULL);
8322 children = gtk_container_get_children(GTK_CONTAINER(GTK_MENU_SHELL(menuitem)));
8325 while (amenu != NULL) {
8326 systemid = g_object_get_data(G_OBJECT(amenu->data), "privacy_system");
8327 if (systemid != NULL) {
8328 if (strcmp(systemid, compose->privacy_system) == 0 &&
8329 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8330 menuitem = GTK_WIDGET(amenu->data);
8332 can_sign = privacy_system_can_sign(systemid);
8333 can_encrypt = privacy_system_can_encrypt(systemid);
8337 } else if (strlen(compose->privacy_system) == 0 &&
8338 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8339 menuitem = GTK_WIDGET(amenu->data);
8342 can_encrypt = FALSE;
8347 amenu = amenu->next;
8349 g_list_free(children);
8350 if (menuitem != NULL)
8351 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8353 if (warn && !found && strlen(compose->privacy_system)) {
8354 alertpanel_warning(_("The privacy system '%s' cannot be loaded. You "
8355 "will not be able to sign or encrypt this message."),
8356 compose->privacy_system);
8360 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8361 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8364 static void compose_set_out_encoding(Compose *compose)
8366 CharSet out_encoding;
8367 const gchar *branch = NULL;
8368 out_encoding = conv_get_charset_from_str(prefs_common.outgoing_charset);
8370 switch(out_encoding) {
8371 case C_AUTO: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8372 case C_US_ASCII: branch = "Menu/Options/Encoding/" CS_US_ASCII; break;
8373 case C_UTF_8: branch = "Menu/Options/Encoding/" CS_UTF_8; break;
8374 case C_ISO_8859_2: branch = "Menu/Options/Encoding/" CS_ISO_8859_2; break;
8375 case C_ISO_8859_7: branch = "Menu/Options/Encoding/" CS_ISO_8859_7; break;
8376 case C_ISO_8859_9: branch = "Menu/Options/Encoding/" CS_ISO_8859_9; break;
8377 case C_ISO_8859_1: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_1; break;
8378 case C_ISO_8859_15: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_15; break;
8379 case C_WINDOWS_1252: branch = "Menu/Options/Encoding/Western/" CS_WINDOWS_1252; break;
8380 case C_ISO_8859_13: branch = "Menu/Options/Encoding/Baltic/" CS_ISO_8859_13; break;
8381 case C_ISO_8859_4: branch = "Menu/Options/Encoding/Baltic" CS_ISO_8859_4; break;
8382 case C_ISO_8859_8: branch = "Menu/Options/Encoding/Hebrew/" CS_ISO_8859_8; break;
8383 case C_WINDOWS_1255: branch = "Menu/Options/Encoding/Hebrew/" CS_WINDOWS_1255; break;
8384 case C_ISO_8859_6: branch = "Menu/Options/Encoding/Arabic/" CS_ISO_8859_6; break;
8385 case C_WINDOWS_1256: branch = "Menu/Options/Encoding/Arabic/" CS_WINDOWS_1256; break;
8386 case C_ISO_8859_5: branch = "Menu/Options/Encoding/Cyrillic/" CS_ISO_8859_5; break;
8387 case C_KOI8_R: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_R; break;
8388 case C_MACCYR: branch = "Menu/Options/Encoding/Cyrillic/" CS_MACCYR; break;
8389 case C_KOI8_U: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_U; break;
8390 case C_WINDOWS_1251: branch = "Menu/Options/Encoding/Cyrillic/" CS_WINDOWS_1251; break;
8391 case C_ISO_2022_JP: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP; break;
8392 case C_ISO_2022_JP_2: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP_2; break;
8393 case C_EUC_JP: branch = "Menu/Options/Encoding/Japanese/" CS_EUC_JP; break;
8394 case C_SHIFT_JIS: branch = "Menu/Options/Encoding/Japanese/" CS_SHIFT_JIS; break;
8395 case C_GB18030: branch = "Menu/Options/Encoding/Chinese/" CS_GB18030; break;
8396 case C_GB2312: branch = "Menu/Options/Encoding/Chinese/" CS_GB2312; break;
8397 case C_GBK: branch = "Menu/Options/Encoding/Chinese/" CS_GBK; break;
8398 case C_BIG5: branch = "Menu/Options/Encoding/Chinese/" CS_BIG5; break;
8399 case C_EUC_TW: branch = "Menu/Options/Encoding/Chinese/" CS_EUC_TW; break;
8400 case C_EUC_KR: branch = "Menu/Options/Encoding/Korean/" CS_EUC_KR; break;
8401 case C_ISO_2022_KR: branch = "Menu/Options/Encoding/Korean/" CS_ISO_2022_KR; break;
8402 case C_TIS_620: branch = "Menu/Options/Encoding/Thai/" CS_TIS_620; break;
8403 case C_WINDOWS_874: branch = "Menu/Options/Encoding/Thai/" CS_WINDOWS_874; break;
8404 default: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8406 cm_toggle_menu_set_active_full(compose->ui_manager, (gchar *)branch, TRUE);
8409 static void compose_set_template_menu(Compose *compose)
8411 GSList *tmpl_list, *cur;
8415 tmpl_list = template_get_config();
8417 menu = gtk_menu_new();
8419 gtk_menu_set_accel_group (GTK_MENU (menu),
8420 gtk_ui_manager_get_accel_group(compose->ui_manager));
8421 for (cur = tmpl_list; cur != NULL; cur = cur->next) {
8422 Template *tmpl = (Template *)cur->data;
8423 gchar *accel_path = NULL;
8424 item = gtk_menu_item_new_with_label(tmpl->name);
8425 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
8426 g_signal_connect(G_OBJECT(item), "activate",
8427 G_CALLBACK(compose_template_activate_cb),
8429 g_object_set_data(G_OBJECT(item), "template", tmpl);
8430 gtk_widget_show(item);
8431 accel_path = g_strconcat("<ComposeTemplates>" , "/", tmpl->name, NULL);
8432 gtk_menu_item_set_accel_path(GTK_MENU_ITEM(item), accel_path);
8436 gtk_widget_show(menu);
8437 gtk_menu_item_set_submenu(GTK_MENU_ITEM(compose->tmpl_menu), menu);
8440 void compose_update_actions_menu(Compose *compose)
8442 action_update_compose_menu(compose->ui_manager, "/Menu/Tools/Actions", compose);
8445 static void compose_update_privacy_systems_menu(Compose *compose)
8447 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8448 GSList *systems, *cur;
8450 GtkWidget *system_none;
8452 GtkWidget *privacy_menuitem = gtk_ui_manager_get_widget(compose->ui_manager, branch_path);
8453 GtkWidget *privacy_menu = gtk_menu_new();
8455 system_none = gtk_radio_menu_item_new_with_mnemonic(NULL, _("_None"));
8456 g_object_set_data_full(G_OBJECT(system_none), "privacy_system", NULL, NULL);
8458 g_signal_connect(G_OBJECT(system_none), "activate",
8459 G_CALLBACK(compose_set_privacy_system_cb), compose);
8461 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), system_none);
8462 gtk_widget_show(system_none);
8464 systems = privacy_get_system_ids();
8465 for (cur = systems; cur != NULL; cur = g_slist_next(cur)) {
8466 gchar *systemid = cur->data;
8468 group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(system_none));
8469 widget = gtk_radio_menu_item_new_with_label(group,
8470 privacy_system_get_name(systemid));
8471 g_object_set_data_full(G_OBJECT(widget), "privacy_system",
8472 g_strdup(systemid), g_free);
8473 g_signal_connect(G_OBJECT(widget), "activate",
8474 G_CALLBACK(compose_set_privacy_system_cb), compose);
8476 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), widget);
8477 gtk_widget_show(widget);
8480 g_slist_free(systems);
8481 gtk_menu_item_set_submenu(GTK_MENU_ITEM(privacy_menuitem), privacy_menu);
8482 gtk_widget_show_all(privacy_menu);
8483 gtk_widget_show_all(privacy_menuitem);
8486 void compose_reflect_prefs_all(void)
8491 for (cur = compose_list; cur != NULL; cur = cur->next) {
8492 compose = (Compose *)cur->data;
8493 compose_set_template_menu(compose);
8497 void compose_reflect_prefs_pixmap_theme(void)
8502 for (cur = compose_list; cur != NULL; cur = cur->next) {
8503 compose = (Compose *)cur->data;
8504 toolbar_update(TOOLBAR_COMPOSE, compose);
8508 static const gchar *compose_quote_char_from_context(Compose *compose)
8510 const gchar *qmark = NULL;
8512 cm_return_val_if_fail(compose != NULL, NULL);
8514 switch (compose->mode) {
8515 /* use forward-specific quote char */
8516 case COMPOSE_FORWARD:
8517 case COMPOSE_FORWARD_AS_ATTACH:
8518 case COMPOSE_FORWARD_INLINE:
8519 if (compose->folder && compose->folder->prefs &&
8520 compose->folder->prefs->forward_with_format)
8521 qmark = compose->folder->prefs->forward_quotemark;
8522 else if (compose->account->forward_with_format)
8523 qmark = compose->account->forward_quotemark;
8525 qmark = prefs_common.fw_quotemark;
8528 /* use reply-specific quote char in all other modes */
8530 if (compose->folder && compose->folder->prefs &&
8531 compose->folder->prefs->reply_with_format)
8532 qmark = compose->folder->prefs->reply_quotemark;
8533 else if (compose->account->reply_with_format)
8534 qmark = compose->account->reply_quotemark;
8536 qmark = prefs_common.quotemark;
8540 if (qmark == NULL || *qmark == '\0')
8546 static void compose_template_apply(Compose *compose, Template *tmpl,
8550 GtkTextBuffer *buffer;
8554 gchar *parsed_str = NULL;
8555 gint cursor_pos = 0;
8556 const gchar *err_msg = _("The body of the template has an error at line %d.");
8559 /* process the body */
8561 text = GTK_TEXT_VIEW(compose->text);
8562 buffer = gtk_text_view_get_buffer(text);
8565 qmark = compose_quote_char_from_context(compose);
8567 if (compose->replyinfo != NULL) {
8570 gtk_text_buffer_set_text(buffer, "", -1);
8571 mark = gtk_text_buffer_get_insert(buffer);
8572 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8574 parsed_str = compose_quote_fmt(compose, compose->replyinfo,
8575 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8577 } else if (compose->fwdinfo != NULL) {
8580 gtk_text_buffer_set_text(buffer, "", -1);
8581 mark = gtk_text_buffer_get_insert(buffer);
8582 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8584 parsed_str = compose_quote_fmt(compose, compose->fwdinfo,
8585 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8588 MsgInfo* dummyinfo = compose_msginfo_new_from_compose(compose);
8590 GtkTextIter start, end;
8593 gtk_text_buffer_get_start_iter(buffer, &start);
8594 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
8595 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
8597 /* clear the buffer now */
8599 gtk_text_buffer_set_text(buffer, "", -1);
8601 parsed_str = compose_quote_fmt(compose, dummyinfo,
8602 tmpl->value, qmark, tmp, FALSE, FALSE, err_msg);
8603 procmsg_msginfo_free( &dummyinfo );
8609 gtk_text_buffer_set_text(buffer, "", -1);
8610 mark = gtk_text_buffer_get_insert(buffer);
8611 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8614 if (replace && parsed_str && compose->account->auto_sig)
8615 compose_insert_sig(compose, FALSE);
8617 if (replace && parsed_str) {
8618 gtk_text_buffer_get_start_iter(buffer, &iter);
8619 gtk_text_buffer_place_cursor(buffer, &iter);
8623 cursor_pos = quote_fmt_get_cursor_pos();
8624 compose->set_cursor_pos = cursor_pos;
8625 if (cursor_pos == -1)
8627 gtk_text_buffer_get_start_iter(buffer, &iter);
8628 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
8629 gtk_text_buffer_place_cursor(buffer, &iter);
8632 /* process the other fields */
8634 compose_template_apply_fields(compose, tmpl);
8635 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
8636 quote_fmt_reset_vartable();
8637 compose_changed_cb(NULL, compose);
8640 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
8641 gtkaspell_highlight_all(compose->gtkaspell);
8645 static void compose_template_apply_fields(Compose *compose, Template *tmpl)
8647 MsgInfo* dummyinfo = NULL;
8648 MsgInfo *msginfo = NULL;
8651 if (compose->replyinfo != NULL)
8652 msginfo = compose->replyinfo;
8653 else if (compose->fwdinfo != NULL)
8654 msginfo = compose->fwdinfo;
8656 dummyinfo = compose_msginfo_new_from_compose(compose);
8657 msginfo = dummyinfo;
8660 if (tmpl->from && *tmpl->from != '\0') {
8662 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8663 compose->gtkaspell);
8665 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8667 quote_fmt_scan_string(tmpl->from);
8670 buf = quote_fmt_get_buffer();
8672 alertpanel_error(_("Template From format error."));
8674 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
8678 if (tmpl->to && *tmpl->to != '\0') {
8680 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8681 compose->gtkaspell);
8683 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8685 quote_fmt_scan_string(tmpl->to);
8688 buf = quote_fmt_get_buffer();
8690 alertpanel_error(_("Template To format error."));
8692 compose_entry_append(compose, buf, COMPOSE_TO, PREF_TEMPLATE);
8696 if (tmpl->cc && *tmpl->cc != '\0') {
8698 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8699 compose->gtkaspell);
8701 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8703 quote_fmt_scan_string(tmpl->cc);
8706 buf = quote_fmt_get_buffer();
8708 alertpanel_error(_("Template Cc format error."));
8710 compose_entry_append(compose, buf, COMPOSE_CC, PREF_TEMPLATE);
8714 if (tmpl->bcc && *tmpl->bcc != '\0') {
8716 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8717 compose->gtkaspell);
8719 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8721 quote_fmt_scan_string(tmpl->bcc);
8724 buf = quote_fmt_get_buffer();
8726 alertpanel_error(_("Template Bcc format error."));
8728 compose_entry_append(compose, buf, COMPOSE_BCC, PREF_TEMPLATE);
8732 if (tmpl->replyto && *tmpl->replyto != '\0') {
8734 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8735 compose->gtkaspell);
8737 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8739 quote_fmt_scan_string(tmpl->replyto);
8742 buf = quote_fmt_get_buffer();
8744 alertpanel_error(_("Template Reply-To format error."));
8746 compose_entry_append(compose, buf, COMPOSE_REPLYTO, PREF_TEMPLATE);
8750 /* process the subject */
8751 if (tmpl->subject && *tmpl->subject != '\0') {
8753 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8754 compose->gtkaspell);
8756 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8758 quote_fmt_scan_string(tmpl->subject);
8761 buf = quote_fmt_get_buffer();
8763 alertpanel_error(_("Template subject format error."));
8765 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
8769 procmsg_msginfo_free( &dummyinfo );
8772 static void compose_destroy(Compose *compose)
8774 GtkAllocation allocation;
8775 GtkTextBuffer *buffer;
8776 GtkClipboard *clipboard;
8778 compose_list = g_list_remove(compose_list, compose);
8780 if (compose->updating) {
8781 debug_print("danger, not destroying anything now\n");
8782 compose->deferred_destroy = TRUE;
8786 /* NOTE: address_completion_end() does nothing with the window
8787 * however this may change. */
8788 address_completion_end(compose->window);
8790 slist_free_strings_full(compose->to_list);
8791 slist_free_strings_full(compose->newsgroup_list);
8792 slist_free_strings_full(compose->header_list);
8794 slist_free_strings_full(extra_headers);
8795 extra_headers = NULL;
8797 compose->header_list = compose->newsgroup_list = compose->to_list = NULL;
8799 g_hash_table_destroy(compose->email_hashtable);
8801 hooks_unregister_hook(FOLDER_UPDATE_HOOKLIST,
8802 compose->folder_update_callback_id);
8804 procmsg_msginfo_free(&(compose->targetinfo));
8805 procmsg_msginfo_free(&(compose->replyinfo));
8806 procmsg_msginfo_free(&(compose->fwdinfo));
8808 g_free(compose->replyto);
8809 g_free(compose->cc);
8810 g_free(compose->bcc);
8811 g_free(compose->newsgroups);
8812 g_free(compose->followup_to);
8814 g_free(compose->ml_post);
8816 g_free(compose->inreplyto);
8817 g_free(compose->references);
8818 g_free(compose->msgid);
8819 g_free(compose->boundary);
8821 g_free(compose->redirect_filename);
8822 if (compose->undostruct)
8823 undo_destroy(compose->undostruct);
8825 g_free(compose->sig_str);
8827 g_free(compose->exteditor_file);
8829 g_free(compose->orig_charset);
8831 g_free(compose->privacy_system);
8832 g_free(compose->encdata);
8834 #ifndef USE_ALT_ADDRBOOK
8835 if (addressbook_get_target_compose() == compose)
8836 addressbook_set_target_compose(NULL);
8839 if (compose->gtkaspell) {
8840 gtkaspell_delete(compose->gtkaspell);
8841 compose->gtkaspell = NULL;
8845 if (!compose->batch) {
8846 gtk_widget_get_allocation(compose->window, &allocation);
8847 prefs_common.compose_width = allocation.width;
8848 prefs_common.compose_height = allocation.height;
8851 if (!gtk_widget_get_parent(compose->paned))
8852 gtk_widget_destroy(compose->paned);
8853 gtk_widget_destroy(compose->popupmenu);
8855 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
8856 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
8857 gtk_text_buffer_remove_selection_clipboard(buffer, clipboard);
8859 gtk_widget_destroy(compose->window);
8860 toolbar_destroy(compose->toolbar);
8861 g_free(compose->toolbar);
8862 cm_mutex_free(compose->mutex);
8866 static void compose_attach_info_free(AttachInfo *ainfo)
8868 g_free(ainfo->file);
8869 g_free(ainfo->content_type);
8870 g_free(ainfo->name);
8871 g_free(ainfo->charset);
8875 static void compose_attach_update_label(Compose *compose)
8880 GtkTreeModel *model;
8885 model = gtk_tree_view_get_model(GTK_TREE_VIEW(compose->attach_clist));
8886 if(!gtk_tree_model_get_iter_first(model, &iter)) {
8887 gtk_label_set_text(GTK_LABEL(compose->attach_label), "");
8891 while(gtk_tree_model_iter_next(model, &iter))
8894 text = g_strdup_printf("(%d)", i);
8895 gtk_label_set_text(GTK_LABEL(compose->attach_label), text);
8899 static void compose_attach_remove_selected(GtkAction *action, gpointer data)
8901 Compose *compose = (Compose *)data;
8902 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
8903 GtkTreeSelection *selection;
8905 GtkTreeModel *model;
8907 selection = gtk_tree_view_get_selection(tree_view);
8908 sel = gtk_tree_selection_get_selected_rows(selection, &model);
8913 for (cur = sel; cur != NULL; cur = cur->next) {
8914 GtkTreePath *path = cur->data;
8915 GtkTreeRowReference *ref = gtk_tree_row_reference_new
8918 gtk_tree_path_free(path);
8921 for (cur = sel; cur != NULL; cur = cur->next) {
8922 GtkTreeRowReference *ref = cur->data;
8923 GtkTreePath *path = gtk_tree_row_reference_get_path(ref);
8926 if (gtk_tree_model_get_iter(model, &iter, path))
8927 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
8929 gtk_tree_path_free(path);
8930 gtk_tree_row_reference_free(ref);
8934 compose_attach_update_label(compose);
8937 static struct _AttachProperty
8940 GtkWidget *mimetype_entry;
8941 GtkWidget *encoding_optmenu;
8942 GtkWidget *path_entry;
8943 GtkWidget *filename_entry;
8945 GtkWidget *cancel_btn;
8948 static void gtk_tree_path_free_(gpointer ptr, gpointer data)
8950 gtk_tree_path_free((GtkTreePath *)ptr);
8953 static void compose_attach_property(GtkAction *action, gpointer data)
8955 Compose *compose = (Compose *)data;
8956 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
8958 GtkComboBox *optmenu;
8959 GtkTreeSelection *selection;
8961 GtkTreeModel *model;
8964 static gboolean cancelled;
8966 /* only if one selected */
8967 selection = gtk_tree_view_get_selection(tree_view);
8968 if (gtk_tree_selection_count_selected_rows(selection) != 1)
8971 sel = gtk_tree_selection_get_selected_rows(selection, &model);
8975 path = (GtkTreePath *) sel->data;
8976 gtk_tree_model_get_iter(model, &iter, path);
8977 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
8980 g_list_foreach(sel, gtk_tree_path_free_, NULL);
8986 if (!attach_prop.window)
8987 compose_attach_property_create(&cancelled);
8988 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), TRUE);
8989 gtk_widget_grab_focus(attach_prop.ok_btn);
8990 gtk_widget_show(attach_prop.window);
8991 gtk_window_set_transient_for(GTK_WINDOW(attach_prop.window),
8992 GTK_WINDOW(compose->window));
8994 optmenu = GTK_COMBO_BOX(attach_prop.encoding_optmenu);
8995 if (ainfo->encoding == ENC_UNKNOWN)
8996 combobox_select_by_data(optmenu, ENC_BASE64);
8998 combobox_select_by_data(optmenu, ainfo->encoding);
9000 gtk_entry_set_text(GTK_ENTRY(attach_prop.mimetype_entry),
9001 ainfo->content_type ? ainfo->content_type : "");
9002 gtk_entry_set_text(GTK_ENTRY(attach_prop.path_entry),
9003 ainfo->file ? ainfo->file : "");
9004 gtk_entry_set_text(GTK_ENTRY(attach_prop.filename_entry),
9005 ainfo->name ? ainfo->name : "");
9008 const gchar *entry_text;
9010 gchar *cnttype = NULL;
9017 gtk_widget_hide(attach_prop.window);
9018 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), FALSE);
9023 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.mimetype_entry));
9024 if (*entry_text != '\0') {
9027 text = g_strstrip(g_strdup(entry_text));
9028 if ((p = strchr(text, '/')) && !strchr(p + 1, '/')) {
9029 cnttype = g_strdup(text);
9032 alertpanel_error(_("Invalid MIME type."));
9038 ainfo->encoding = combobox_get_active_data(optmenu);
9040 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.path_entry));
9041 if (*entry_text != '\0') {
9042 if (is_file_exist(entry_text) &&
9043 (size = get_file_size(entry_text)) > 0)
9044 file = g_strdup(entry_text);
9047 (_("File doesn't exist or is empty."));
9053 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.filename_entry));
9054 if (*entry_text != '\0') {
9055 g_free(ainfo->name);
9056 ainfo->name = g_strdup(entry_text);
9060 g_free(ainfo->content_type);
9061 ainfo->content_type = cnttype;
9064 g_free(ainfo->file);
9068 ainfo->size = (goffset)size;
9070 /* update tree store */
9071 text = to_human_readable(ainfo->size);
9072 gtk_tree_model_get_iter(model, &iter, path);
9073 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
9074 COL_MIMETYPE, ainfo->content_type,
9076 COL_NAME, ainfo->name,
9077 COL_CHARSET, ainfo->charset,
9083 gtk_tree_path_free(path);
9086 #define SET_LABEL_AND_ENTRY(str, entry, top) \
9088 label = gtk_label_new(str); \
9089 gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), \
9090 GTK_FILL, 0, 0, 0); \
9091 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); \
9093 entry = gtk_entry_new(); \
9094 gtk_table_attach(GTK_TABLE(table), entry, 1, 2, top, (top + 1), \
9095 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); \
9098 static void compose_attach_property_create(gboolean *cancelled)
9104 GtkWidget *mimetype_entry;
9107 GtkListStore *optmenu_menu;
9108 GtkWidget *path_entry;
9109 GtkWidget *filename_entry;
9112 GtkWidget *cancel_btn;
9113 GList *mime_type_list, *strlist;
9116 debug_print("Creating attach_property window...\n");
9118 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose_attach_property");
9119 gtk_widget_set_size_request(window, 480, -1);
9120 gtk_container_set_border_width(GTK_CONTAINER(window), 8);
9121 gtk_window_set_title(GTK_WINDOW(window), _("Properties"));
9122 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
9123 g_signal_connect(G_OBJECT(window), "delete_event",
9124 G_CALLBACK(attach_property_delete_event),
9126 g_signal_connect(G_OBJECT(window), "key_press_event",
9127 G_CALLBACK(attach_property_key_pressed),
9130 vbox = gtk_vbox_new(FALSE, 8);
9131 gtk_container_add(GTK_CONTAINER(window), vbox);
9133 table = gtk_table_new(4, 2, FALSE);
9134 gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
9135 gtk_table_set_row_spacings(GTK_TABLE(table), 8);
9136 gtk_table_set_col_spacings(GTK_TABLE(table), 8);
9138 label = gtk_label_new(_("MIME type"));
9139 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, (0 + 1),
9141 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
9142 #if !GTK_CHECK_VERSION(2, 24, 0)
9143 mimetype_entry = gtk_combo_box_entry_new_text();
9145 mimetype_entry = gtk_combo_box_text_new_with_entry();
9147 gtk_table_attach(GTK_TABLE(table), mimetype_entry, 1, 2, 0, (0 + 1),
9148 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
9150 /* stuff with list */
9151 mime_type_list = procmime_get_mime_type_list();
9153 for (; mime_type_list != NULL; mime_type_list = mime_type_list->next) {
9154 MimeType *type = (MimeType *) mime_type_list->data;
9157 tmp = g_strdup_printf("%s/%s", type->type, type->sub_type);
9159 if (g_list_find_custom(strlist, tmp, (GCompareFunc)strcmp2))
9162 strlist = g_list_insert_sorted(strlist, (gpointer)tmp,
9163 (GCompareFunc)strcmp2);
9166 for (mime_type_list = strlist; mime_type_list != NULL;
9167 mime_type_list = mime_type_list->next) {
9168 #if !GTK_CHECK_VERSION(2, 24, 0)
9169 gtk_combo_box_append_text(GTK_COMBO_BOX(mimetype_entry), mime_type_list->data);
9171 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(mimetype_entry), mime_type_list->data);
9173 g_free(mime_type_list->data);
9175 g_list_free(strlist);
9176 gtk_combo_box_set_active(GTK_COMBO_BOX(mimetype_entry), 0);
9177 mimetype_entry = gtk_bin_get_child(GTK_BIN((mimetype_entry)));
9179 label = gtk_label_new(_("Encoding"));
9180 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2,
9182 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
9184 hbox = gtk_hbox_new(FALSE, 0);
9185 gtk_table_attach(GTK_TABLE(table), hbox, 1, 2, 1, 2,
9186 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
9188 optmenu = gtkut_sc_combobox_create(NULL, TRUE);
9189 optmenu_menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
9191 COMBOBOX_ADD(optmenu_menu, "7bit", ENC_7BIT);
9192 COMBOBOX_ADD(optmenu_menu, "8bit", ENC_8BIT);
9193 COMBOBOX_ADD(optmenu_menu, "quoted-printable", ENC_QUOTED_PRINTABLE);
9194 COMBOBOX_ADD(optmenu_menu, "base64", ENC_BASE64);
9195 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), 0);
9197 gtk_box_pack_start(GTK_BOX(hbox), optmenu, TRUE, TRUE, 0);
9199 SET_LABEL_AND_ENTRY(_("Path"), path_entry, 2);
9200 SET_LABEL_AND_ENTRY(_("File name"), filename_entry, 3);
9202 gtkut_stock_button_set_create(&hbbox, &cancel_btn, GTK_STOCK_CANCEL,
9203 &ok_btn, GTK_STOCK_OK,
9205 gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
9206 gtk_widget_grab_default(ok_btn);
9208 g_signal_connect(G_OBJECT(ok_btn), "clicked",
9209 G_CALLBACK(attach_property_ok),
9211 g_signal_connect(G_OBJECT(cancel_btn), "clicked",
9212 G_CALLBACK(attach_property_cancel),
9215 gtk_widget_show_all(vbox);
9217 attach_prop.window = window;
9218 attach_prop.mimetype_entry = mimetype_entry;
9219 attach_prop.encoding_optmenu = optmenu;
9220 attach_prop.path_entry = path_entry;
9221 attach_prop.filename_entry = filename_entry;
9222 attach_prop.ok_btn = ok_btn;
9223 attach_prop.cancel_btn = cancel_btn;
9226 #undef SET_LABEL_AND_ENTRY
9228 static void attach_property_ok(GtkWidget *widget, gboolean *cancelled)
9234 static void attach_property_cancel(GtkWidget *widget, gboolean *cancelled)
9240 static gint attach_property_delete_event(GtkWidget *widget, GdkEventAny *event,
9241 gboolean *cancelled)
9249 static gboolean attach_property_key_pressed(GtkWidget *widget,
9251 gboolean *cancelled)
9253 if (event && event->keyval == GDK_KEY_Escape) {
9257 if (event && event->keyval == GDK_KEY_Return) {
9265 static void compose_exec_ext_editor(Compose *compose)
9270 GdkNativeWindow socket_wid = 0;
9274 tmp = g_strdup_printf("%s%ctmpmsg.%p", get_tmp_dir(),
9275 G_DIR_SEPARATOR, compose);
9277 if (compose_get_ext_editor_uses_socket()) {
9278 /* Only allow one socket */
9279 if (compose->exteditor_socket != NULL) {
9280 if (gtk_widget_is_focus(compose->exteditor_socket)) {
9281 /* Move the focus off of the socket */
9282 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9287 /* Create the receiving GtkSocket */
9288 socket = gtk_socket_new ();
9289 g_signal_connect (GTK_OBJECT(socket), "plug-removed",
9290 G_CALLBACK(compose_ext_editor_plug_removed_cb),
9292 gtk_box_pack_start(GTK_BOX(compose->edit_vbox), socket, TRUE, TRUE, 0);
9293 gtk_widget_set_size_request(socket, prefs_common.compose_width, -1);
9294 /* Realize the socket so that we can use its ID */
9295 gtk_widget_realize(socket);
9296 socket_wid = gtk_socket_get_id(GTK_SOCKET (socket));
9297 compose->exteditor_socket = socket;
9300 if (pipe(pipe_fds) < 0) {
9306 if ((pid = fork()) < 0) {
9313 /* close the write side of the pipe */
9316 compose->exteditor_file = g_strdup(tmp);
9317 compose->exteditor_pid = pid;
9319 compose_set_ext_editor_sensitive(compose, FALSE);
9322 compose->exteditor_ch = g_io_channel_unix_new(pipe_fds[0]);
9324 compose->exteditor_ch = g_io_channel_win32_new_fd(pipe_fds[0]);
9326 compose->exteditor_tag = g_io_add_watch(compose->exteditor_ch,
9330 } else { /* process-monitoring process */
9336 /* close the read side of the pipe */
9339 if (compose_write_body_to_file(compose, tmp) < 0) {
9340 fd_write_all(pipe_fds[1], "2\n", 2);
9344 pid_ed = compose_exec_ext_editor_real(tmp, socket_wid);
9346 fd_write_all(pipe_fds[1], "1\n", 2);
9350 /* wait until editor is terminated */
9351 waitpid(pid_ed, NULL, 0);
9353 fd_write_all(pipe_fds[1], "0\n", 2);
9360 #endif /* G_OS_UNIX */
9364 static gboolean compose_get_ext_editor_cmd_valid()
9366 gboolean has_s = FALSE;
9367 gboolean has_w = FALSE;
9368 const gchar *p = prefs_common_get_ext_editor_cmd();
9371 while ((p = strchr(p, '%'))) {
9377 } else if (*p == 'w') {
9388 static gint compose_exec_ext_editor_real(const gchar *file, GdkNativeWindow socket_wid)
9395 cm_return_val_if_fail(file != NULL, -1);
9397 if ((pid = fork()) < 0) {
9402 if (pid != 0) return pid;
9404 /* grandchild process */
9406 if (setpgid(0, getppid()))
9409 if (compose_get_ext_editor_cmd_valid()) {
9410 if (compose_get_ext_editor_uses_socket()) {
9411 p = g_strdup(prefs_common_get_ext_editor_cmd());
9412 s = strstr(p, "%w");
9414 if (strstr(p, "%s") < s)
9415 g_snprintf(buf, sizeof(buf), p, file, socket_wid);
9417 g_snprintf(buf, sizeof(buf), p, socket_wid, file);
9420 g_snprintf(buf, sizeof(buf),
9421 prefs_common_get_ext_editor_cmd(), file);
9424 if (prefs_common_get_ext_editor_cmd())
9425 g_warning("External editor command-line is invalid: '%s'",
9426 prefs_common_get_ext_editor_cmd());
9427 g_snprintf(buf, sizeof(buf), DEFAULT_EDITOR_CMD, file);
9430 cmdline = strsplit_with_quote(buf, " ", 1024);
9431 execvp(cmdline[0], cmdline);
9434 g_strfreev(cmdline);
9439 static gboolean compose_ext_editor_kill(Compose *compose)
9441 pid_t pgid = compose->exteditor_pid * -1;
9444 ret = kill(pgid, 0);
9446 if (ret == 0 || (ret == -1 && EPERM == errno)) {
9450 msg = g_strdup_printf
9451 (_("The external editor is still working.\n"
9452 "Force terminating the process?\n"
9453 "process group id: %d"), -pgid);
9454 val = alertpanel_full(_("Notice"), msg, GTK_STOCK_NO, GTK_STOCK_YES,
9455 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
9459 if (val == G_ALERTALTERNATE) {
9460 g_source_remove(compose->exteditor_tag);
9461 g_io_channel_shutdown(compose->exteditor_ch,
9463 g_io_channel_unref(compose->exteditor_ch);
9465 if (kill(pgid, SIGTERM) < 0) perror("kill");
9466 waitpid(compose->exteditor_pid, NULL, 0);
9468 g_warning("Terminated process group id: %d. "
9469 "Temporary file: %s", -pgid, compose->exteditor_file);
9471 compose_set_ext_editor_sensitive(compose, TRUE);
9473 g_free(compose->exteditor_file);
9474 compose->exteditor_file = NULL;
9475 compose->exteditor_pid = -1;
9476 compose->exteditor_ch = NULL;
9477 compose->exteditor_tag = -1;
9485 static gboolean compose_input_cb(GIOChannel *source, GIOCondition condition,
9489 Compose *compose = (Compose *)data;
9492 debug_print("Compose: input from monitoring process\n");
9494 if (g_io_channel_read_chars(source, buf, sizeof(buf), &bytes_read, NULL) != G_IO_STATUS_NORMAL) {
9499 g_io_channel_shutdown(source, FALSE, NULL);
9500 g_io_channel_unref(source);
9502 waitpid(compose->exteditor_pid, NULL, 0);
9504 if (buf[0] == '0') { /* success */
9505 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9506 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
9507 GtkTextIter start, end;
9510 gtk_text_buffer_set_text(buffer, "", -1);
9511 compose_insert_file(compose, compose->exteditor_file);
9512 compose_changed_cb(NULL, compose);
9513 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
9515 if (claws_unlink(compose->exteditor_file) < 0)
9516 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9518 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
9519 gtk_text_buffer_get_start_iter(buffer, &start);
9520 gtk_text_buffer_get_end_iter(buffer, &end);
9521 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
9522 if (chars && strlen(chars) > 0)
9523 compose->modified = TRUE;
9525 } else if (buf[0] == '1') { /* failed */
9526 g_warning("Couldn't exec external editor");
9527 if (claws_unlink(compose->exteditor_file) < 0)
9528 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9529 } else if (buf[0] == '2') {
9530 g_warning("Couldn't write to file");
9531 } else if (buf[0] == '3') {
9532 g_warning("Pipe read failed");
9535 compose_set_ext_editor_sensitive(compose, TRUE);
9537 g_free(compose->exteditor_file);
9538 compose->exteditor_file = NULL;
9539 compose->exteditor_pid = -1;
9540 compose->exteditor_ch = NULL;
9541 compose->exteditor_tag = -1;
9542 if (compose->exteditor_socket) {
9543 gtk_widget_destroy(compose->exteditor_socket);
9544 compose->exteditor_socket = NULL;
9551 static char *ext_editor_menu_entries[] = {
9552 "Menu/Message/Send",
9553 "Menu/Message/SendLater",
9554 "Menu/Message/InsertFile",
9555 "Menu/Message/InsertSig",
9556 "Menu/Message/ReplaceSig",
9557 "Menu/Message/Save",
9558 "Menu/Message/Print",
9563 "Menu/Tools/ShowRuler",
9564 "Menu/Tools/Actions",
9569 static void compose_set_ext_editor_sensitive(Compose *compose,
9574 for (i = 0; ext_editor_menu_entries[i]; ++i) {
9575 cm_menu_set_sensitive_full(compose->ui_manager,
9576 ext_editor_menu_entries[i], sensitive);
9579 if (compose_get_ext_editor_uses_socket()) {
9581 if (compose->exteditor_socket)
9582 gtk_widget_hide(compose->exteditor_socket);
9583 gtk_widget_show(compose->scrolledwin);
9584 if (prefs_common.show_ruler)
9585 gtk_widget_show(compose->ruler_hbox);
9586 /* Fix the focus, as it doesn't go anywhere when the
9587 * socket is hidden or destroyed */
9588 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9590 g_assert (compose->exteditor_socket != NULL);
9591 /* Fix the focus, as it doesn't go anywhere when the
9592 * edit box is hidden */
9593 if (gtk_widget_is_focus(compose->text))
9594 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9595 gtk_widget_hide(compose->scrolledwin);
9596 gtk_widget_hide(compose->ruler_hbox);
9597 gtk_widget_show(compose->exteditor_socket);
9600 gtk_widget_set_sensitive(compose->text, sensitive);
9602 if (compose->toolbar->send_btn)
9603 gtk_widget_set_sensitive(compose->toolbar->send_btn, sensitive);
9604 if (compose->toolbar->sendl_btn)
9605 gtk_widget_set_sensitive(compose->toolbar->sendl_btn, sensitive);
9606 if (compose->toolbar->draft_btn)
9607 gtk_widget_set_sensitive(compose->toolbar->draft_btn, sensitive);
9608 if (compose->toolbar->insert_btn)
9609 gtk_widget_set_sensitive(compose->toolbar->insert_btn, sensitive);
9610 if (compose->toolbar->sig_btn)
9611 gtk_widget_set_sensitive(compose->toolbar->sig_btn, sensitive);
9612 if (compose->toolbar->exteditor_btn)
9613 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, sensitive);
9614 if (compose->toolbar->linewrap_current_btn)
9615 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, sensitive);
9616 if (compose->toolbar->linewrap_all_btn)
9617 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, sensitive);
9620 static gboolean compose_get_ext_editor_uses_socket()
9622 return (prefs_common_get_ext_editor_cmd() &&
9623 strstr(prefs_common_get_ext_editor_cmd(), "%w"));
9626 static gboolean compose_ext_editor_plug_removed_cb(GtkSocket *socket, Compose *compose)
9628 compose->exteditor_socket = NULL;
9629 /* returning FALSE allows destruction of the socket */
9632 #endif /* G_OS_UNIX */
9635 * compose_undo_state_changed:
9637 * Change the sensivity of the menuentries undo and redo
9639 static void compose_undo_state_changed(UndoMain *undostruct, gint undo_state,
9640 gint redo_state, gpointer data)
9642 Compose *compose = (Compose *)data;
9644 switch (undo_state) {
9645 case UNDO_STATE_TRUE:
9646 if (!undostruct->undo_state) {
9647 undostruct->undo_state = TRUE;
9648 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", TRUE);
9651 case UNDO_STATE_FALSE:
9652 if (undostruct->undo_state) {
9653 undostruct->undo_state = FALSE;
9654 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
9657 case UNDO_STATE_UNCHANGED:
9659 case UNDO_STATE_REFRESH:
9660 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", undostruct->undo_state);
9663 g_warning("Undo state not recognized");
9667 switch (redo_state) {
9668 case UNDO_STATE_TRUE:
9669 if (!undostruct->redo_state) {
9670 undostruct->redo_state = TRUE;
9671 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", TRUE);
9674 case UNDO_STATE_FALSE:
9675 if (undostruct->redo_state) {
9676 undostruct->redo_state = FALSE;
9677 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
9680 case UNDO_STATE_UNCHANGED:
9682 case UNDO_STATE_REFRESH:
9683 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", undostruct->redo_state);
9686 g_warning("Redo state not recognized");
9691 /* callback functions */
9693 static void compose_notebook_size_alloc(GtkNotebook *notebook,
9694 GtkAllocation *allocation,
9697 prefs_common.compose_notebook_height = gtk_paned_get_position(paned);
9700 /* compose_edit_size_alloc() - called when resized. don't know whether Gtk
9701 * includes "non-client" (windows-izm) in calculation, so this calculation
9702 * may not be accurate.
9704 static gboolean compose_edit_size_alloc(GtkEditable *widget,
9705 GtkAllocation *allocation,
9706 GtkSHRuler *shruler)
9708 if (prefs_common.show_ruler) {
9709 gint char_width = 0, char_height = 0;
9710 gint line_width_in_chars;
9712 gtkut_get_font_size(GTK_WIDGET(widget),
9713 &char_width, &char_height);
9714 line_width_in_chars =
9715 (allocation->width - allocation->x) / char_width;
9717 /* got the maximum */
9718 gtk_shruler_set_range(GTK_SHRULER(shruler),
9719 0.0, line_width_in_chars, 0);
9728 ComposePrefType type;
9729 gboolean entry_marked;
9732 static void account_activated(GtkComboBox *optmenu, gpointer data)
9734 Compose *compose = (Compose *)data;
9737 gchar *folderidentifier;
9738 gint account_id = 0;
9741 GSList *list, *saved_list = NULL;
9742 HeaderEntryState *state;
9743 GtkRcStyle *style = NULL;
9744 #if !GTK_CHECK_VERSION(3, 0, 0)
9745 static GdkColor yellow;
9746 static gboolean color_set = FALSE;
9748 static GdkColor yellow = { (guint32)0, (guint32)0xf5, (guint32)0xf6, (guint32)0xbe };
9751 /* Get ID of active account in the combo box */
9752 menu = gtk_combo_box_get_model(optmenu);
9753 cm_return_if_fail(gtk_combo_box_get_active_iter(optmenu, &iter));
9754 gtk_tree_model_get(menu, &iter, 1, &account_id, -1);
9756 ac = account_find_from_id(account_id);
9757 cm_return_if_fail(ac != NULL);
9759 if (ac != compose->account) {
9760 compose_select_account(compose, ac, FALSE);
9762 for (list = compose->header_list; list; list = list->next) {
9763 ComposeHeaderEntry *hentry=(ComposeHeaderEntry *)list->data;
9765 if (hentry->type == PREF_ACCOUNT || !list->next) {
9766 compose_destroy_headerentry(compose, hentry);
9770 state = g_malloc0(sizeof(HeaderEntryState));
9771 state->header = gtk_editable_get_chars(GTK_EDITABLE(
9772 gtk_bin_get_child(GTK_BIN(hentry->combo))), 0, -1);
9773 state->entry = gtk_editable_get_chars(
9774 GTK_EDITABLE(hentry->entry), 0, -1);
9775 state->type = hentry->type;
9777 #if !GTK_CHECK_VERSION(3, 0, 0)
9779 gdk_color_parse("#f5f6be", &yellow);
9780 color_set = gdk_colormap_alloc_color(
9781 gdk_colormap_get_system(),
9782 &yellow, FALSE, TRUE);
9786 style = gtk_widget_get_modifier_style(hentry->entry);
9787 state->entry_marked = gdk_color_equal(&yellow,
9788 &style->base[GTK_STATE_NORMAL]);
9790 saved_list = g_slist_append(saved_list, state);
9791 compose_destroy_headerentry(compose, hentry);
9794 compose->header_last = NULL;
9795 g_slist_free(compose->header_list);
9796 compose->header_list = NULL;
9797 compose->header_nextrow = 1;
9798 compose_create_header_entry(compose);
9800 if (ac->set_autocc && ac->auto_cc)
9801 compose_entry_append(compose, ac->auto_cc,
9802 COMPOSE_CC, PREF_ACCOUNT);
9804 if (ac->set_autobcc && ac->auto_bcc)
9805 compose_entry_append(compose, ac->auto_bcc,
9806 COMPOSE_BCC, PREF_ACCOUNT);
9808 if (ac->set_autoreplyto && ac->auto_replyto)
9809 compose_entry_append(compose, ac->auto_replyto,
9810 COMPOSE_REPLYTO, PREF_ACCOUNT);
9812 for (list = saved_list; list; list = list->next) {
9813 state = (HeaderEntryState *) list->data;
9815 compose_add_header_entry(compose, state->header,
9816 state->entry, state->type);
9817 if (state->entry_marked)
9818 compose_entry_mark_default_to(compose, state->entry);
9820 g_free(state->header);
9821 g_free(state->entry);
9824 g_slist_free(saved_list);
9826 combobox_select_by_data(GTK_COMBO_BOX(compose->header_last->combo),
9827 (ac->protocol == A_NNTP) ?
9828 COMPOSE_NEWSGROUPS : COMPOSE_TO);
9831 /* Set message save folder */
9832 if (account_get_special_folder(compose->account, F_OUTBOX)) {
9833 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
9835 g_signal_connect(G_OBJECT(compose->savemsg_checkbtn), "toggled",
9836 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
9838 compose_set_save_to(compose, NULL);
9839 if (account_get_special_folder(compose->account, F_OUTBOX)) {
9840 folderidentifier = folder_item_get_identifier(account_get_special_folder
9841 (compose->account, F_OUTBOX));
9842 compose_set_save_to(compose, folderidentifier);
9843 g_free(folderidentifier);
9847 static void attach_selected(GtkTreeView *tree_view, GtkTreePath *tree_path,
9848 GtkTreeViewColumn *column, Compose *compose)
9850 compose_attach_property(NULL, compose);
9853 static gboolean attach_button_pressed(GtkWidget *widget, GdkEventButton *event,
9856 Compose *compose = (Compose *)data;
9857 GtkTreeSelection *attach_selection;
9858 gint attach_nr_selected;
9861 if (!event) return FALSE;
9863 if (event->button == 3) {
9864 attach_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
9865 attach_nr_selected = gtk_tree_selection_count_selected_rows(attach_selection);
9867 /* If no rows, or just one row is selected, right-click should
9868 * open menu relevant to the row being right-clicked on. We
9869 * achieve that by selecting the clicked row first. If more
9870 * than one row is selected, we shouldn't modify the selection,
9871 * as user may want to remove selected rows (attachments). */
9872 if (attach_nr_selected < 2) {
9873 gtk_tree_selection_unselect_all(attach_selection);
9874 attach_nr_selected = 0;
9875 gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget),
9876 event->x, event->y, &path, NULL, NULL, NULL);
9878 gtk_tree_selection_select_path(attach_selection, path);
9879 gtk_tree_path_free(path);
9880 attach_nr_selected++;
9884 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", (attach_nr_selected > 0));
9885 /* Properties menu item makes no sense with more than one row
9886 * selected, the properties dialog can only edit one attachment. */
9887 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", (attach_nr_selected == 1));
9889 gtk_menu_popup(GTK_MENU(compose->popupmenu), NULL, NULL,
9890 NULL, NULL, event->button, event->time);
9897 static gboolean attach_key_pressed(GtkWidget *widget, GdkEventKey *event,
9900 Compose *compose = (Compose *)data;
9902 if (!event) return FALSE;
9904 switch (event->keyval) {
9905 case GDK_KEY_Delete:
9906 compose_attach_remove_selected(NULL, compose);
9912 static void compose_allow_user_actions (Compose *compose, gboolean allow)
9914 toolbar_comp_set_sensitive(compose, allow);
9915 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message", allow);
9916 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", allow);
9918 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", allow);
9920 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", allow);
9921 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools", allow);
9922 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Help", allow);
9924 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), allow);
9928 static void compose_send_cb(GtkAction *action, gpointer data)
9930 Compose *compose = (Compose *)data;
9933 if (compose->exteditor_tag != -1) {
9934 debug_print("ignoring send: external editor still open\n");
9938 if (prefs_common.work_offline &&
9939 !inc_offline_should_override(TRUE,
9940 _("Claws Mail needs network access in order "
9941 "to send this email.")))
9944 if (compose->draft_timeout_tag >= 0) { /* CLAWS: disable draft timeout */
9945 g_source_remove(compose->draft_timeout_tag);
9946 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
9949 compose_send(compose);
9952 static void compose_send_later_cb(GtkAction *action, gpointer data)
9954 Compose *compose = (Compose *)data;
9958 compose_allow_user_actions(compose, FALSE);
9959 val = compose_queue_sub(compose, NULL, NULL, NULL, TRUE, TRUE);
9960 compose_allow_user_actions(compose, TRUE);
9964 compose_close(compose);
9965 } else if (val == -1) {
9966 alertpanel_error(_("Could not queue message."));
9967 } else if (val == -2) {
9968 alertpanel_error(_("Could not queue message:\n\n%s."), g_strerror(errno));
9969 } else if (val == -3) {
9970 if (privacy_peek_error())
9971 alertpanel_error(_("Could not queue message for sending:\n\n"
9972 "Signature failed: %s"), privacy_get_error());
9973 } else if (val == -4) {
9974 alertpanel_error(_("Could not queue message for sending:\n\n"
9975 "Charset conversion failed."));
9976 } else if (val == -5) {
9977 alertpanel_error(_("Could not queue message for sending:\n\n"
9978 "Couldn't get recipient encryption key."));
9979 } else if (val == -6) {
9982 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
9985 #define DRAFTED_AT_EXIT "drafted_at_exit"
9986 static void compose_register_draft(MsgInfo *info)
9988 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
9989 DRAFTED_AT_EXIT, NULL);
9990 FILE *fp = g_fopen(filepath, "ab");
9993 fprintf(fp, "%s\t%d\n", folder_item_get_identifier(info->folder),
10001 gboolean compose_draft (gpointer data, guint action)
10003 Compose *compose = (Compose *)data;
10008 MsgFlags flag = {0, 0};
10009 static gboolean lock = FALSE;
10010 MsgInfo *newmsginfo;
10012 gboolean target_locked = FALSE;
10013 gboolean err = FALSE;
10015 if (lock) return FALSE;
10017 if (compose->sending)
10020 draft = account_get_special_folder(compose->account, F_DRAFT);
10021 cm_return_val_if_fail(draft != NULL, FALSE);
10023 if (!g_mutex_trylock(compose->mutex)) {
10024 /* we don't want to lock the mutex once it's available,
10025 * because as the only other part of compose.c locking
10026 * it is compose_close - which means once unlocked,
10027 * the compose struct will be freed */
10028 debug_print("couldn't lock mutex, probably sending\n");
10034 tmp = g_strdup_printf("%s%cdraft.%p", get_tmp_dir(),
10035 G_DIR_SEPARATOR, compose);
10036 if ((fp = g_fopen(tmp, "wb")) == NULL) {
10037 FILE_OP_ERROR(tmp, "fopen");
10041 /* chmod for security */
10042 if (change_file_mode_rw(fp, tmp) < 0) {
10043 FILE_OP_ERROR(tmp, "chmod");
10044 g_warning("can't change file mode");
10047 /* Save draft infos */
10048 err |= (fprintf(fp, "X-Claws-Account-Id:%d\n", compose->account->account_id) < 0);
10049 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
10051 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
10052 gchar *savefolderid;
10054 savefolderid = compose_get_save_to(compose);
10055 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
10056 g_free(savefolderid);
10058 if (compose->return_receipt) {
10059 err |= (fprintf(fp, "RRCPT:1\n") < 0);
10061 if (compose->privacy_system) {
10062 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
10063 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
10064 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
10067 /* Message-ID of message replying to */
10068 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
10069 gchar *folderid = NULL;
10071 if (compose->replyinfo->folder)
10072 folderid = folder_item_get_identifier(compose->replyinfo->folder);
10073 if (folderid == NULL)
10074 folderid = g_strdup("NULL");
10076 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
10079 /* Message-ID of message forwarding to */
10080 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
10081 gchar *folderid = NULL;
10083 if (compose->fwdinfo->folder)
10084 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
10085 if (folderid == NULL)
10086 folderid = g_strdup("NULL");
10088 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
10092 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
10093 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
10095 sheaders = compose_get_manual_headers_info(compose);
10096 err |= (fprintf(fp, "X-Claws-Manual-Headers:%s\n", sheaders) < 0);
10099 /* end of headers */
10100 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
10107 if (compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_STORE, action != COMPOSE_AUTO_SAVE) < 0) {
10111 if (fclose(fp) == EOF) {
10115 flag.perm_flags = MSG_NEW|MSG_UNREAD;
10116 if (compose->targetinfo) {
10117 target_locked = MSG_IS_LOCKED(compose->targetinfo->flags);
10119 flag.perm_flags |= MSG_LOCKED;
10121 flag.tmp_flags = MSG_DRAFT;
10123 folder_item_scan(draft);
10124 if ((msgnum = folder_item_add_msg(draft, tmp, &flag, TRUE)) < 0) {
10125 MsgInfo *tmpinfo = NULL;
10126 debug_print("didn't get msgnum after adding draft [%s]\n", compose->msgid?compose->msgid:"no msgid");
10127 if (compose->msgid) {
10128 tmpinfo = folder_item_get_msginfo_by_msgid(draft, compose->msgid);
10131 msgnum = tmpinfo->msgnum;
10132 procmsg_msginfo_free(&tmpinfo);
10133 debug_print("got draft msgnum %d from scanning\n", msgnum);
10135 debug_print("didn't get draft msgnum after scanning\n");
10138 debug_print("got draft msgnum %d from adding\n", msgnum);
10144 if (action != COMPOSE_AUTO_SAVE) {
10145 if (action != COMPOSE_DRAFT_FOR_EXIT)
10146 alertpanel_error(_("Could not save draft."));
10149 gtkut_window_popup(compose->window);
10150 val = alertpanel_full(_("Could not save draft"),
10151 _("Could not save draft.\n"
10152 "Do you want to cancel exit or discard this email?"),
10153 _("_Cancel exit"), _("_Discard email"), NULL,
10154 FALSE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
10155 if (val == G_ALERTALTERNATE) {
10157 g_mutex_unlock(compose->mutex); /* must be done before closing */
10158 compose_close(compose);
10162 g_mutex_unlock(compose->mutex); /* must be done before closing */
10171 if (compose->mode == COMPOSE_REEDIT) {
10172 compose_remove_reedit_target(compose, TRUE);
10175 newmsginfo = folder_item_get_msginfo(draft, msgnum);
10178 procmsg_msginfo_unset_flags(newmsginfo, ~0, ~0);
10180 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD|MSG_LOCKED, MSG_DRAFT);
10182 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD, MSG_DRAFT);
10183 if (compose_use_attach(compose) && action != COMPOSE_AUTO_SAVE)
10184 procmsg_msginfo_set_flags(newmsginfo, 0,
10185 MSG_HAS_ATTACHMENT);
10187 if (action == COMPOSE_DRAFT_FOR_EXIT) {
10188 compose_register_draft(newmsginfo);
10190 procmsg_msginfo_free(&newmsginfo);
10193 folder_item_scan(draft);
10195 if (action == COMPOSE_QUIT_EDITING || action == COMPOSE_DRAFT_FOR_EXIT) {
10197 g_mutex_unlock(compose->mutex); /* must be done before closing */
10198 compose_close(compose);
10204 path = folder_item_fetch_msg(draft, msgnum);
10205 if (path == NULL) {
10206 debug_print("can't fetch %s:%d\n", draft->path, msgnum);
10209 if (g_stat(path, &s) < 0) {
10210 FILE_OP_ERROR(path, "stat");
10216 procmsg_msginfo_free(&(compose->targetinfo));
10217 compose->targetinfo = procmsg_msginfo_new();
10218 compose->targetinfo->msgnum = msgnum;
10219 compose->targetinfo->size = (goffset)s.st_size;
10220 compose->targetinfo->mtime = s.st_mtime;
10221 compose->targetinfo->folder = draft;
10223 procmsg_msginfo_set_flags(compose->targetinfo, MSG_LOCKED, 0);
10224 compose->mode = COMPOSE_REEDIT;
10226 if (action == COMPOSE_AUTO_SAVE) {
10227 compose->autosaved_draft = compose->targetinfo;
10229 compose->modified = FALSE;
10230 compose_set_title(compose);
10234 g_mutex_unlock(compose->mutex);
10238 void compose_clear_exit_drafts(void)
10240 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10241 DRAFTED_AT_EXIT, NULL);
10242 if (is_file_exist(filepath))
10243 claws_unlink(filepath);
10248 void compose_reopen_exit_drafts(void)
10250 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10251 DRAFTED_AT_EXIT, NULL);
10252 FILE *fp = g_fopen(filepath, "rb");
10256 while (fgets(buf, sizeof(buf), fp)) {
10257 gchar **parts = g_strsplit(buf, "\t", 2);
10258 const gchar *folder = parts[0];
10259 int msgnum = parts[1] ? atoi(parts[1]):-1;
10261 if (folder && *folder && msgnum > -1) {
10262 FolderItem *item = folder_find_item_from_identifier(folder);
10263 MsgInfo *info = folder_item_get_msginfo(item, msgnum);
10265 compose_reedit(info, FALSE);
10272 compose_clear_exit_drafts();
10275 static void compose_save_cb(GtkAction *action, gpointer data)
10277 Compose *compose = (Compose *)data;
10278 compose_draft(compose, COMPOSE_KEEP_EDITING);
10279 compose->rmode = COMPOSE_REEDIT;
10282 void compose_attach_from_list(Compose *compose, GList *file_list, gboolean free_data)
10284 if (compose && file_list) {
10287 for ( tmp = file_list; tmp; tmp = tmp->next) {
10288 gchar *file = (gchar *) tmp->data;
10289 gchar *utf8_filename = conv_filename_to_utf8(file);
10290 compose_attach_append(compose, file, utf8_filename, NULL, NULL);
10291 compose_changed_cb(NULL, compose);
10296 g_free(utf8_filename);
10301 static void compose_attach_cb(GtkAction *action, gpointer data)
10303 Compose *compose = (Compose *)data;
10306 if (compose->redirect_filename != NULL)
10309 /* Set focus_window properly, in case we were called via popup menu,
10310 * which unsets it (via focus_out_event callback on compose window). */
10311 manage_window_focus_in(compose->window, NULL, NULL);
10313 file_list = filesel_select_multiple_files_open(_("Select file"), NULL);
10316 compose_attach_from_list(compose, file_list, TRUE);
10317 g_list_free(file_list);
10321 static void compose_insert_file_cb(GtkAction *action, gpointer data)
10323 Compose *compose = (Compose *)data;
10325 gint files_inserted = 0;
10327 file_list = filesel_select_multiple_files_open(_("Select file"), NULL);
10332 for ( tmp = file_list; tmp; tmp = tmp->next) {
10333 gchar *file = (gchar *) tmp->data;
10334 gchar *filedup = g_strdup(file);
10335 gchar *shortfile = g_path_get_basename(filedup);
10336 ComposeInsertResult res;
10337 /* insert the file if the file is short or if the user confirmed that
10338 he/she wants to insert the large file */
10339 res = compose_insert_file(compose, file);
10340 if (res == COMPOSE_INSERT_READ_ERROR) {
10341 alertpanel_error(_("File '%s' could not be read."), shortfile);
10342 } else if (res == COMPOSE_INSERT_INVALID_CHARACTER) {
10343 alertpanel_error(_("File '%s' contained invalid characters\n"
10344 "for the current encoding, insertion may be incorrect."),
10346 } else if (res == COMPOSE_INSERT_SUCCESS)
10353 g_list_free(file_list);
10357 if (files_inserted > 0 && compose->gtkaspell &&
10358 compose->gtkaspell->check_while_typing)
10359 gtkaspell_highlight_all(compose->gtkaspell);
10363 static void compose_insert_sig_cb(GtkAction *action, gpointer data)
10365 Compose *compose = (Compose *)data;
10367 compose_insert_sig(compose, FALSE);
10370 static void compose_replace_sig_cb(GtkAction *action, gpointer data)
10372 Compose *compose = (Compose *)data;
10374 compose_insert_sig(compose, TRUE);
10377 static gint compose_delete_cb(GtkWidget *widget, GdkEventAny *event,
10381 Compose *compose = (Compose *)data;
10383 gtkut_widget_get_uposition(widget, &x, &y);
10384 if (!compose->batch) {
10385 prefs_common.compose_x = x;
10386 prefs_common.compose_y = y;
10388 if (compose->sending || compose->updating)
10390 compose_close_cb(NULL, compose);
10394 void compose_close_toolbar(Compose *compose)
10396 compose_close_cb(NULL, compose);
10399 static gboolean compose_can_autosave(Compose *compose)
10401 if (compose->privacy_system && compose->use_encryption)
10402 return prefs_common.autosave && prefs_common.autosave_encrypted;
10404 return prefs_common.autosave;
10407 static void compose_close_cb(GtkAction *action, gpointer data)
10409 Compose *compose = (Compose *)data;
10413 if (compose->exteditor_tag != -1) {
10414 if (!compose_ext_editor_kill(compose))
10419 if (compose->modified) {
10420 gboolean reedit = (compose->rmode == COMPOSE_REEDIT);
10421 if (!g_mutex_trylock(compose->mutex)) {
10422 /* we don't want to lock the mutex once it's available,
10423 * because as the only other part of compose.c locking
10424 * it is compose_close - which means once unlocked,
10425 * the compose struct will be freed */
10426 debug_print("couldn't lock mutex, probably sending\n");
10430 val = alertpanel(_("Discard message"),
10431 _("This message has been modified. Discard it?"),
10432 _("_Discard"), _("_Save to Drafts"), GTK_STOCK_CANCEL);
10434 val = alertpanel(_("Save changes"),
10435 _("This message has been modified. Save the latest changes?"),
10436 _("_Don't save"), g_strconcat("+", _("_Save to Drafts"), NULL),
10439 g_mutex_unlock(compose->mutex);
10441 case G_ALERTDEFAULT:
10442 if (compose_can_autosave(compose) && !reedit)
10443 compose_remove_draft(compose);
10445 case G_ALERTALTERNATE:
10446 compose_draft(data, COMPOSE_QUIT_EDITING);
10453 compose_close(compose);
10456 static void compose_print_cb(GtkAction *action, gpointer data)
10458 Compose *compose = (Compose *) data;
10460 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
10461 if (compose->targetinfo)
10462 messageview_print(compose->targetinfo, FALSE, -1, -1, 0);
10465 static void compose_set_encoding_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
10467 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
10468 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
10469 Compose *compose = (Compose *) data;
10472 compose->out_encoding = (CharSet)value;
10475 static void compose_address_cb(GtkAction *action, gpointer data)
10477 Compose *compose = (Compose *)data;
10479 #ifndef USE_ALT_ADDRBOOK
10480 addressbook_open(compose);
10482 GError* error = NULL;
10483 addressbook_connect_signals(compose);
10484 addressbook_dbus_open(TRUE, &error);
10486 g_warning("%s", error->message);
10487 g_error_free(error);
10492 static void about_show_cb(GtkAction *action, gpointer data)
10497 static void compose_template_activate_cb(GtkWidget *widget, gpointer data)
10499 Compose *compose = (Compose *)data;
10504 tmpl = g_object_get_data(G_OBJECT(widget), "template");
10505 cm_return_if_fail(tmpl != NULL);
10507 msg = g_strdup_printf(_("Do you want to apply the template '%s'?"),
10509 val = alertpanel(_("Apply template"), msg,
10510 _("_Replace"), _("_Insert"), GTK_STOCK_CANCEL);
10513 if (val == G_ALERTDEFAULT)
10514 compose_template_apply(compose, tmpl, TRUE);
10515 else if (val == G_ALERTALTERNATE)
10516 compose_template_apply(compose, tmpl, FALSE);
10519 static void compose_ext_editor_cb(GtkAction *action, gpointer data)
10521 Compose *compose = (Compose *)data;
10524 if (compose->exteditor_tag != -1) {
10525 debug_print("ignoring open external editor: external editor still open\n");
10529 compose_exec_ext_editor(compose);
10532 static void compose_undo_cb(GtkAction *action, gpointer data)
10534 Compose *compose = (Compose *)data;
10535 gboolean prev_autowrap = compose->autowrap;
10537 compose->autowrap = FALSE;
10538 undo_undo(compose->undostruct);
10539 compose->autowrap = prev_autowrap;
10542 static void compose_redo_cb(GtkAction *action, gpointer data)
10544 Compose *compose = (Compose *)data;
10545 gboolean prev_autowrap = compose->autowrap;
10547 compose->autowrap = FALSE;
10548 undo_redo(compose->undostruct);
10549 compose->autowrap = prev_autowrap;
10552 static void entry_cut_clipboard(GtkWidget *entry)
10554 if (GTK_IS_EDITABLE(entry))
10555 gtk_editable_cut_clipboard (GTK_EDITABLE(entry));
10556 else if (GTK_IS_TEXT_VIEW(entry))
10557 gtk_text_buffer_cut_clipboard(
10558 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10559 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
10563 static void entry_copy_clipboard(GtkWidget *entry)
10565 if (GTK_IS_EDITABLE(entry))
10566 gtk_editable_copy_clipboard (GTK_EDITABLE(entry));
10567 else if (GTK_IS_TEXT_VIEW(entry))
10568 gtk_text_buffer_copy_clipboard(
10569 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10570 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
10573 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry,
10574 gboolean wrap, GdkAtom clip, GtkTextIter *insert_place)
10576 if (GTK_IS_TEXT_VIEW(entry)) {
10577 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10578 GtkTextMark *mark_start = gtk_text_buffer_get_insert(buffer);
10579 GtkTextIter start_iter, end_iter;
10581 gchar *contents = gtk_clipboard_wait_for_text(gtk_clipboard_get(clip));
10583 if (contents == NULL)
10586 /* we shouldn't delete the selection when middle-click-pasting, or we
10587 * can't mid-click-paste our own selection */
10588 if (clip != GDK_SELECTION_PRIMARY) {
10589 undo_paste_clipboard(GTK_TEXT_VIEW(compose->text), compose->undostruct);
10590 gtk_text_buffer_delete_selection(buffer, FALSE, TRUE);
10593 if (insert_place == NULL) {
10594 /* if insert_place isn't specified, insert at the cursor.
10595 * used for Ctrl-V pasting */
10596 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10597 start = gtk_text_iter_get_offset(&start_iter);
10598 gtk_text_buffer_insert(buffer, &start_iter, contents, strlen(contents));
10600 /* if insert_place is specified, paste here.
10601 * used for mid-click-pasting */
10602 start = gtk_text_iter_get_offset(insert_place);
10603 gtk_text_buffer_insert(buffer, insert_place, contents, strlen(contents));
10604 if (prefs_common.primary_paste_unselects)
10605 gtk_text_buffer_select_range(buffer, insert_place, insert_place);
10609 /* paste unwrapped: mark the paste so it's not wrapped later */
10610 end = start + strlen(contents);
10611 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, start);
10612 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, end);
10613 gtk_text_buffer_apply_tag_by_name(buffer, "no_wrap", &start_iter, &end_iter);
10614 } else if (wrap && clip == GDK_SELECTION_PRIMARY) {
10615 /* rewrap paragraph now (after a mid-click-paste) */
10616 mark_start = gtk_text_buffer_get_insert(buffer);
10617 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10618 gtk_text_iter_backward_char(&start_iter);
10619 compose_beautify_paragraph(compose, &start_iter, TRUE);
10621 } else if (GTK_IS_EDITABLE(entry))
10622 gtk_editable_paste_clipboard (GTK_EDITABLE(entry));
10624 compose->modified = TRUE;
10627 static void entry_allsel(GtkWidget *entry)
10629 if (GTK_IS_EDITABLE(entry))
10630 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
10631 else if (GTK_IS_TEXT_VIEW(entry)) {
10632 GtkTextIter startiter, enditer;
10633 GtkTextBuffer *textbuf;
10635 textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10636 gtk_text_buffer_get_start_iter(textbuf, &startiter);
10637 gtk_text_buffer_get_end_iter(textbuf, &enditer);
10639 gtk_text_buffer_move_mark_by_name(textbuf,
10640 "selection_bound", &startiter);
10641 gtk_text_buffer_move_mark_by_name(textbuf,
10642 "insert", &enditer);
10646 static void compose_cut_cb(GtkAction *action, gpointer data)
10648 Compose *compose = (Compose *)data;
10649 if (compose->focused_editable
10650 #ifndef GENERIC_UMPC
10651 && gtk_widget_has_focus(compose->focused_editable)
10654 entry_cut_clipboard(compose->focused_editable);
10657 static void compose_copy_cb(GtkAction *action, gpointer data)
10659 Compose *compose = (Compose *)data;
10660 if (compose->focused_editable
10661 #ifndef GENERIC_UMPC
10662 && gtk_widget_has_focus(compose->focused_editable)
10665 entry_copy_clipboard(compose->focused_editable);
10668 static void compose_paste_cb(GtkAction *action, gpointer data)
10670 Compose *compose = (Compose *)data;
10671 gint prev_autowrap;
10672 GtkTextBuffer *buffer;
10674 if (compose->focused_editable &&
10675 #ifndef GENERIC_UMPC
10676 gtk_widget_has_focus(compose->focused_editable)
10679 entry_paste_clipboard(compose, compose->focused_editable,
10680 prefs_common.linewrap_pastes,
10681 GDK_SELECTION_CLIPBOARD, NULL);
10686 #ifndef GENERIC_UMPC
10687 gtk_widget_has_focus(compose->text) &&
10689 compose->gtkaspell &&
10690 compose->gtkaspell->check_while_typing)
10691 gtkaspell_highlight_all(compose->gtkaspell);
10695 static void compose_paste_as_quote_cb(GtkAction *action, gpointer data)
10697 Compose *compose = (Compose *)data;
10698 gint wrap_quote = prefs_common.linewrap_quote;
10699 if (compose->focused_editable
10700 #ifndef GENERIC_UMPC
10701 && gtk_widget_has_focus(compose->focused_editable)
10704 /* let text_insert() (called directly or at a later time
10705 * after the gtk_editable_paste_clipboard) know that
10706 * text is to be inserted as a quotation. implemented
10707 * by using a simple refcount... */
10708 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data(
10709 G_OBJECT(compose->focused_editable),
10710 "paste_as_quotation"));
10711 g_object_set_data(G_OBJECT(compose->focused_editable),
10712 "paste_as_quotation",
10713 GINT_TO_POINTER(paste_as_quotation + 1));
10714 prefs_common.linewrap_quote = prefs_common.linewrap_pastes;
10715 entry_paste_clipboard(compose, compose->focused_editable,
10716 prefs_common.linewrap_pastes,
10717 GDK_SELECTION_CLIPBOARD, NULL);
10718 prefs_common.linewrap_quote = wrap_quote;
10722 static void compose_paste_no_wrap_cb(GtkAction *action, gpointer data)
10724 Compose *compose = (Compose *)data;
10725 gint prev_autowrap;
10726 GtkTextBuffer *buffer;
10728 if (compose->focused_editable
10729 #ifndef GENERIC_UMPC
10730 && gtk_widget_has_focus(compose->focused_editable)
10733 entry_paste_clipboard(compose, compose->focused_editable, FALSE,
10734 GDK_SELECTION_CLIPBOARD, NULL);
10739 #ifndef GENERIC_UMPC
10740 gtk_widget_has_focus(compose->text) &&
10742 compose->gtkaspell &&
10743 compose->gtkaspell->check_while_typing)
10744 gtkaspell_highlight_all(compose->gtkaspell);
10748 static void compose_paste_wrap_cb(GtkAction *action, gpointer data)
10750 Compose *compose = (Compose *)data;
10751 gint prev_autowrap;
10752 GtkTextBuffer *buffer;
10754 if (compose->focused_editable
10755 #ifndef GENERIC_UMPC
10756 && gtk_widget_has_focus(compose->focused_editable)
10759 entry_paste_clipboard(compose, compose->focused_editable, TRUE,
10760 GDK_SELECTION_CLIPBOARD, NULL);
10765 #ifndef GENERIC_UMPC
10766 gtk_widget_has_focus(compose->text) &&
10768 compose->gtkaspell &&
10769 compose->gtkaspell->check_while_typing)
10770 gtkaspell_highlight_all(compose->gtkaspell);
10774 static void compose_allsel_cb(GtkAction *action, gpointer data)
10776 Compose *compose = (Compose *)data;
10777 if (compose->focused_editable
10778 #ifndef GENERIC_UMPC
10779 && gtk_widget_has_focus(compose->focused_editable)
10782 entry_allsel(compose->focused_editable);
10785 static void textview_move_beginning_of_line (GtkTextView *text)
10787 GtkTextBuffer *buffer;
10791 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10793 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10794 mark = gtk_text_buffer_get_insert(buffer);
10795 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10796 gtk_text_iter_set_line_offset(&ins, 0);
10797 gtk_text_buffer_place_cursor(buffer, &ins);
10800 static void textview_move_forward_character (GtkTextView *text)
10802 GtkTextBuffer *buffer;
10806 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10808 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10809 mark = gtk_text_buffer_get_insert(buffer);
10810 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10811 if (gtk_text_iter_forward_cursor_position(&ins))
10812 gtk_text_buffer_place_cursor(buffer, &ins);
10815 static void textview_move_backward_character (GtkTextView *text)
10817 GtkTextBuffer *buffer;
10821 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10823 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10824 mark = gtk_text_buffer_get_insert(buffer);
10825 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10826 if (gtk_text_iter_backward_cursor_position(&ins))
10827 gtk_text_buffer_place_cursor(buffer, &ins);
10830 static void textview_move_forward_word (GtkTextView *text)
10832 GtkTextBuffer *buffer;
10837 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10839 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10840 mark = gtk_text_buffer_get_insert(buffer);
10841 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10842 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
10843 if (gtk_text_iter_forward_word_ends(&ins, count)) {
10844 gtk_text_iter_backward_word_start(&ins);
10845 gtk_text_buffer_place_cursor(buffer, &ins);
10849 static void textview_move_backward_word (GtkTextView *text)
10851 GtkTextBuffer *buffer;
10855 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10857 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10858 mark = gtk_text_buffer_get_insert(buffer);
10859 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10860 if (gtk_text_iter_backward_word_starts(&ins, 1))
10861 gtk_text_buffer_place_cursor(buffer, &ins);
10864 static void textview_move_end_of_line (GtkTextView *text)
10866 GtkTextBuffer *buffer;
10870 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10872 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10873 mark = gtk_text_buffer_get_insert(buffer);
10874 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10875 if (gtk_text_iter_forward_to_line_end(&ins))
10876 gtk_text_buffer_place_cursor(buffer, &ins);
10879 static void textview_move_next_line (GtkTextView *text)
10881 GtkTextBuffer *buffer;
10886 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10888 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10889 mark = gtk_text_buffer_get_insert(buffer);
10890 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10891 offset = gtk_text_iter_get_line_offset(&ins);
10892 if (gtk_text_iter_forward_line(&ins)) {
10893 gtk_text_iter_set_line_offset(&ins, offset);
10894 gtk_text_buffer_place_cursor(buffer, &ins);
10898 static void textview_move_previous_line (GtkTextView *text)
10900 GtkTextBuffer *buffer;
10905 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10907 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10908 mark = gtk_text_buffer_get_insert(buffer);
10909 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10910 offset = gtk_text_iter_get_line_offset(&ins);
10911 if (gtk_text_iter_backward_line(&ins)) {
10912 gtk_text_iter_set_line_offset(&ins, offset);
10913 gtk_text_buffer_place_cursor(buffer, &ins);
10917 static void textview_delete_forward_character (GtkTextView *text)
10919 GtkTextBuffer *buffer;
10921 GtkTextIter ins, end_iter;
10923 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10925 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10926 mark = gtk_text_buffer_get_insert(buffer);
10927 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10929 if (gtk_text_iter_forward_char(&end_iter)) {
10930 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10934 static void textview_delete_backward_character (GtkTextView *text)
10936 GtkTextBuffer *buffer;
10938 GtkTextIter ins, end_iter;
10940 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10942 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10943 mark = gtk_text_buffer_get_insert(buffer);
10944 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10946 if (gtk_text_iter_backward_char(&end_iter)) {
10947 gtk_text_buffer_delete(buffer, &end_iter, &ins);
10951 static void textview_delete_forward_word (GtkTextView *text)
10953 GtkTextBuffer *buffer;
10955 GtkTextIter ins, end_iter;
10957 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10959 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10960 mark = gtk_text_buffer_get_insert(buffer);
10961 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10963 if (gtk_text_iter_forward_word_end(&end_iter)) {
10964 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10968 static void textview_delete_backward_word (GtkTextView *text)
10970 GtkTextBuffer *buffer;
10972 GtkTextIter ins, end_iter;
10974 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10976 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10977 mark = gtk_text_buffer_get_insert(buffer);
10978 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10980 if (gtk_text_iter_backward_word_start(&end_iter)) {
10981 gtk_text_buffer_delete(buffer, &end_iter, &ins);
10985 static void textview_delete_line (GtkTextView *text)
10987 GtkTextBuffer *buffer;
10989 GtkTextIter ins, start_iter, end_iter;
10991 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10993 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10994 mark = gtk_text_buffer_get_insert(buffer);
10995 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10998 gtk_text_iter_set_line_offset(&start_iter, 0);
11001 if (gtk_text_iter_ends_line(&end_iter)){
11002 if (!gtk_text_iter_forward_char(&end_iter))
11003 gtk_text_iter_backward_char(&start_iter);
11006 gtk_text_iter_forward_to_line_end(&end_iter);
11007 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
11010 static void textview_delete_to_line_end (GtkTextView *text)
11012 GtkTextBuffer *buffer;
11014 GtkTextIter ins, end_iter;
11016 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11018 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11019 mark = gtk_text_buffer_get_insert(buffer);
11020 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11022 if (gtk_text_iter_ends_line(&end_iter))
11023 gtk_text_iter_forward_char(&end_iter);
11025 gtk_text_iter_forward_to_line_end(&end_iter);
11026 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11029 #define DO_ACTION(name, act) { \
11030 if(!strcmp(name, a_name)) { \
11034 static ComposeCallAdvancedAction compose_call_advanced_action_from_path(GtkAction *action)
11036 const gchar *a_name = gtk_action_get_name(action);
11037 DO_ACTION("Edit/Advanced/BackChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER);
11038 DO_ACTION("Edit/Advanced/ForwChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER);
11039 DO_ACTION("Edit/Advanced/BackWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD);
11040 DO_ACTION("Edit/Advanced/ForwWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD);
11041 DO_ACTION("Edit/Advanced/BegLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE);
11042 DO_ACTION("Edit/Advanced/EndLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE);
11043 DO_ACTION("Edit/Advanced/PrevLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE);
11044 DO_ACTION("Edit/Advanced/NextLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE);
11045 DO_ACTION("Edit/Advanced/DelBackChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER);
11046 DO_ACTION("Edit/Advanced/DelForwChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER);
11047 DO_ACTION("Edit/Advanced/DelBackWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD);
11048 DO_ACTION("Edit/Advanced/DelForwWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD);
11049 DO_ACTION("Edit/Advanced/DelLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE);
11050 DO_ACTION("Edit/Advanced/DelEndLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END);
11054 static void compose_advanced_action_cb(GtkAction *gaction, gpointer data)
11056 Compose *compose = (Compose *)data;
11057 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11058 ComposeCallAdvancedAction action = -1;
11060 action = compose_call_advanced_action_from_path(gaction);
11063 void (*do_action) (GtkTextView *text);
11064 } action_table[] = {
11065 {textview_move_beginning_of_line},
11066 {textview_move_forward_character},
11067 {textview_move_backward_character},
11068 {textview_move_forward_word},
11069 {textview_move_backward_word},
11070 {textview_move_end_of_line},
11071 {textview_move_next_line},
11072 {textview_move_previous_line},
11073 {textview_delete_forward_character},
11074 {textview_delete_backward_character},
11075 {textview_delete_forward_word},
11076 {textview_delete_backward_word},
11077 {textview_delete_line},
11078 {textview_delete_to_line_end}
11081 if (!gtk_widget_has_focus(GTK_WIDGET(text))) return;
11083 if (action >= COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE &&
11084 action <= COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END) {
11085 if (action_table[action].do_action)
11086 action_table[action].do_action(text);
11088 g_warning("Not implemented yet.");
11092 static void compose_grab_focus_cb(GtkWidget *widget, Compose *compose)
11094 GtkAllocation allocation;
11098 if (GTK_IS_EDITABLE(widget)) {
11099 str = gtk_editable_get_chars(GTK_EDITABLE(widget), 0, -1);
11100 gtk_editable_set_position(GTK_EDITABLE(widget),
11103 if ((parent = gtk_widget_get_parent(widget))
11104 && (parent = gtk_widget_get_parent(parent))
11105 && (parent = gtk_widget_get_parent(parent))) {
11106 if (GTK_IS_SCROLLED_WINDOW(parent)) {
11107 gtk_widget_get_allocation(widget, &allocation);
11108 gint y = allocation.y;
11109 gint height = allocation.height;
11110 GtkAdjustment *shown = gtk_scrolled_window_get_vadjustment
11111 (GTK_SCROLLED_WINDOW(parent));
11113 gfloat value = gtk_adjustment_get_value(shown);
11114 gfloat upper = gtk_adjustment_get_upper(shown);
11115 gfloat page_size = gtk_adjustment_get_page_size(shown);
11116 if (y < (int)value) {
11117 gtk_adjustment_set_value(shown, y - 1);
11119 if ((y + height) > ((int)value + (int)page_size)) {
11120 if ((y - height - 1) < ((int)upper - (int)page_size)) {
11121 gtk_adjustment_set_value(shown,
11122 y + height - (int)page_size - 1);
11124 gtk_adjustment_set_value(shown,
11125 (int)upper - (int)page_size - 1);
11132 if (GTK_IS_EDITABLE(widget) || GTK_IS_TEXT_VIEW(widget))
11133 compose->focused_editable = widget;
11135 #ifdef GENERIC_UMPC
11136 if (GTK_IS_TEXT_VIEW(widget)
11137 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->edit_vbox) {
11138 g_object_ref(compose->notebook);
11139 g_object_ref(compose->edit_vbox);
11140 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
11141 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
11142 gtk_paned_add1(GTK_PANED(compose->paned), compose->edit_vbox);
11143 gtk_paned_add2(GTK_PANED(compose->paned), compose->notebook);
11144 g_object_unref(compose->notebook);
11145 g_object_unref(compose->edit_vbox);
11146 g_signal_handlers_block_by_func(G_OBJECT(widget),
11147 G_CALLBACK(compose_grab_focus_cb),
11149 gtk_widget_grab_focus(widget);
11150 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
11151 G_CALLBACK(compose_grab_focus_cb),
11153 } else if (!GTK_IS_TEXT_VIEW(widget)
11154 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->notebook) {
11155 g_object_ref(compose->notebook);
11156 g_object_ref(compose->edit_vbox);
11157 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
11158 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
11159 gtk_paned_add1(GTK_PANED(compose->paned), compose->notebook);
11160 gtk_paned_add2(GTK_PANED(compose->paned), compose->edit_vbox);
11161 g_object_unref(compose->notebook);
11162 g_object_unref(compose->edit_vbox);
11163 g_signal_handlers_block_by_func(G_OBJECT(widget),
11164 G_CALLBACK(compose_grab_focus_cb),
11166 gtk_widget_grab_focus(widget);
11167 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
11168 G_CALLBACK(compose_grab_focus_cb),
11174 static void compose_changed_cb(GtkTextBuffer *textbuf, Compose *compose)
11176 compose->modified = TRUE;
11177 // compose_beautify_paragraph(compose, NULL, TRUE);
11178 #ifndef GENERIC_UMPC
11179 compose_set_title(compose);
11183 static void compose_wrap_cb(GtkAction *action, gpointer data)
11185 Compose *compose = (Compose *)data;
11186 compose_beautify_paragraph(compose, NULL, TRUE);
11189 static void compose_wrap_all_cb(GtkAction *action, gpointer data)
11191 Compose *compose = (Compose *)data;
11192 compose_wrap_all_full(compose, TRUE);
11195 static void compose_find_cb(GtkAction *action, gpointer data)
11197 Compose *compose = (Compose *)data;
11199 message_search_compose(compose);
11202 static void compose_toggle_autowrap_cb(GtkToggleAction *action,
11205 Compose *compose = (Compose *)data;
11206 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11207 if (compose->autowrap)
11208 compose_wrap_all_full(compose, TRUE);
11209 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11212 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
11215 Compose *compose = (Compose *)data;
11216 compose->autoindent = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11219 static void compose_toggle_sign_cb(GtkToggleAction *action, gpointer data)
11221 Compose *compose = (Compose *)data;
11223 compose->use_signing = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11226 static void compose_toggle_encrypt_cb(GtkToggleAction *action, gpointer data)
11228 Compose *compose = (Compose *)data;
11230 compose->use_encryption = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11233 static void activate_privacy_system(Compose *compose, PrefsAccount *account, gboolean warn)
11235 g_free(compose->privacy_system);
11236 g_free(compose->encdata);
11238 compose->privacy_system = g_strdup(account->default_privacy_system);
11239 compose_update_privacy_system_menu_item(compose, warn);
11242 static void compose_toggle_ruler_cb(GtkToggleAction *action, gpointer data)
11244 Compose *compose = (Compose *)data;
11246 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action))) {
11247 gtk_widget_show(compose->ruler_hbox);
11248 prefs_common.show_ruler = TRUE;
11250 gtk_widget_hide(compose->ruler_hbox);
11251 gtk_widget_queue_resize(compose->edit_vbox);
11252 prefs_common.show_ruler = FALSE;
11256 static void compose_attach_drag_received_cb (GtkWidget *widget,
11257 GdkDragContext *context,
11260 GtkSelectionData *data,
11263 gpointer user_data)
11265 Compose *compose = (Compose *)user_data;
11269 type = gtk_selection_data_get_data_type(data);
11270 if ((gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list"))
11271 && gtk_drag_get_source_widget(context) !=
11272 summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
11273 list = uri_list_extract_filenames(
11274 (const gchar *)gtk_selection_data_get_data(data));
11275 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11276 gchar *utf8_filename = conv_filename_to_utf8((const gchar *)tmp->data);
11277 compose_attach_append
11278 (compose, (const gchar *)tmp->data,
11279 utf8_filename, NULL, NULL);
11280 g_free(utf8_filename);
11282 if (list) compose_changed_cb(NULL, compose);
11283 list_free_strings(list);
11285 } else if (gtk_drag_get_source_widget(context)
11286 == summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
11287 /* comes from our summaryview */
11288 SummaryView * summaryview = NULL;
11289 GSList * list = NULL, *cur = NULL;
11291 if (mainwindow_get_mainwindow())
11292 summaryview = mainwindow_get_mainwindow()->summaryview;
11295 list = summary_get_selected_msg_list(summaryview);
11297 for (cur = list; cur; cur = cur->next) {
11298 MsgInfo *msginfo = (MsgInfo *)cur->data;
11299 gchar *file = NULL;
11301 file = procmsg_get_message_file_full(msginfo,
11304 compose_attach_append(compose, (const gchar *)file,
11305 (const gchar *)file, "message/rfc822", NULL);
11309 g_slist_free(list);
11313 static gboolean compose_drag_drop(GtkWidget *widget,
11314 GdkDragContext *drag_context,
11316 guint time, gpointer user_data)
11318 /* not handling this signal makes compose_insert_drag_received_cb
11323 static gboolean completion_set_focus_to_subject
11324 (GtkWidget *widget,
11325 GdkEventKey *event,
11328 cm_return_val_if_fail(compose != NULL, FALSE);
11330 /* make backtab move to subject field */
11331 if(event->keyval == GDK_KEY_ISO_Left_Tab) {
11332 gtk_widget_grab_focus(compose->subject_entry);
11338 static void compose_insert_drag_received_cb (GtkWidget *widget,
11339 GdkDragContext *drag_context,
11342 GtkSelectionData *data,
11345 gpointer user_data)
11347 Compose *compose = (Compose *)user_data;
11351 /* strangely, testing data->type == gdk_atom_intern("text/uri-list", TRUE)
11353 type = gtk_selection_data_get_data_type(data);
11354 if (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list")) {
11355 AlertValue val = G_ALERTDEFAULT;
11356 const gchar* ddata = (const gchar *)gtk_selection_data_get_data(data);
11358 list = uri_list_extract_filenames(ddata);
11359 if (list == NULL && strstr(ddata, "://")) {
11360 /* Assume a list of no files, and data has ://, is a remote link */
11361 gchar *tmpdata = g_strstrip(g_strdup(ddata));
11362 gchar *tmpfile = get_tmp_file();
11363 str_write_to_file(tmpdata, tmpfile);
11365 compose_insert_file(compose, tmpfile);
11366 claws_unlink(tmpfile);
11368 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11369 compose_beautify_paragraph(compose, NULL, TRUE);
11372 switch (prefs_common.compose_dnd_mode) {
11373 case COMPOSE_DND_ASK:
11374 val = alertpanel_full(_("Insert or attach?"),
11375 _("Do you want to insert the contents of the file(s) "
11376 "into the message body, or attach it to the email?"),
11377 GTK_STOCK_CANCEL, g_strconcat("+", _("_Insert"), NULL), _("_Attach"),
11378 TRUE, NULL, ALERT_QUESTION, G_ALERTALTERNATE);
11380 case COMPOSE_DND_INSERT:
11381 val = G_ALERTALTERNATE;
11383 case COMPOSE_DND_ATTACH:
11384 val = G_ALERTOTHER;
11387 /* unexpected case */
11388 g_warning("error: unexpected compose_dnd_mode option value in compose_insert_drag_received_cb()");
11391 if (val & G_ALERTDISABLE) {
11392 val &= ~G_ALERTDISABLE;
11393 /* remember what action to perform by default, only if we don't click Cancel */
11394 if (val == G_ALERTALTERNATE)
11395 prefs_common.compose_dnd_mode = COMPOSE_DND_INSERT;
11396 else if (val == G_ALERTOTHER)
11397 prefs_common.compose_dnd_mode = COMPOSE_DND_ATTACH;
11400 if (val == G_ALERTDEFAULT || val == G_ALERTCANCEL) {
11401 gtk_drag_finish(drag_context, FALSE, FALSE, time);
11402 list_free_strings(list);
11405 } else if (val == G_ALERTOTHER) {
11406 compose_attach_drag_received_cb(widget, drag_context, x, y, data, info, time, user_data);
11407 list_free_strings(list);
11412 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11413 compose_insert_file(compose, (const gchar *)tmp->data);
11415 list_free_strings(list);
11417 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11422 static void compose_header_drag_received_cb (GtkWidget *widget,
11423 GdkDragContext *drag_context,
11426 GtkSelectionData *data,
11429 gpointer user_data)
11431 GtkEditable *entry = (GtkEditable *)user_data;
11432 const gchar *email = (const gchar *)gtk_selection_data_get_data(data);
11434 /* strangely, testing data->type == gdk_atom_intern("text/plain", TRUE)
11437 if (!strncmp(email, "mailto:", strlen("mailto:"))) {
11438 gchar *decoded=g_new(gchar, strlen(email));
11441 decode_uri(decoded, email + strlen("mailto:")); /* will fit */
11442 gtk_editable_delete_text(entry, 0, -1);
11443 gtk_editable_insert_text(entry, decoded, strlen(decoded), &start);
11444 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11448 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11451 static void compose_toggle_return_receipt_cb(GtkToggleAction *action, gpointer data)
11453 Compose *compose = (Compose *)data;
11455 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11456 compose->return_receipt = TRUE;
11458 compose->return_receipt = FALSE;
11461 static void compose_toggle_remove_refs_cb(GtkToggleAction *action, gpointer data)
11463 Compose *compose = (Compose *)data;
11465 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11466 compose->remove_references = TRUE;
11468 compose->remove_references = FALSE;
11471 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
11472 ComposeHeaderEntry *headerentry)
11474 gtk_entry_set_text(GTK_ENTRY(headerentry->entry), "");
11478 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
11479 GdkEventKey *event,
11480 ComposeHeaderEntry *headerentry)
11482 if ((g_slist_length(headerentry->compose->header_list) > 0) &&
11483 ((headerentry->headernum + 1) != headerentry->compose->header_nextrow) &&
11484 !(event->state & GDK_MODIFIER_MASK) &&
11485 (event->keyval == GDK_KEY_BackSpace) &&
11486 (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) == 0)) {
11487 gtk_container_remove
11488 (GTK_CONTAINER(headerentry->compose->header_table),
11489 headerentry->combo);
11490 gtk_container_remove
11491 (GTK_CONTAINER(headerentry->compose->header_table),
11492 headerentry->entry);
11493 headerentry->compose->header_list =
11494 g_slist_remove(headerentry->compose->header_list,
11496 g_free(headerentry);
11497 } else if (event->keyval == GDK_KEY_Tab) {
11498 if (headerentry->compose->header_last == headerentry) {
11499 /* Override default next focus, and give it to subject_entry
11500 * instead of notebook tabs
11502 g_signal_stop_emission_by_name(G_OBJECT(entry), "key-press-event");
11503 gtk_widget_grab_focus(headerentry->compose->subject_entry);
11510 static gboolean scroll_postpone(gpointer data)
11512 Compose *compose = (Compose *)data;
11514 if (compose->batch)
11517 GTK_EVENTS_FLUSH();
11518 compose_show_first_last_header(compose, FALSE);
11522 static void compose_headerentry_changed_cb(GtkWidget *entry,
11523 ComposeHeaderEntry *headerentry)
11525 if (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) != 0) {
11526 compose_create_header_entry(headerentry->compose);
11527 g_signal_handlers_disconnect_matched
11528 (G_OBJECT(entry), G_SIGNAL_MATCH_DATA,
11529 0, 0, NULL, NULL, headerentry);
11531 if (!headerentry->compose->batch)
11532 g_timeout_add(0, scroll_postpone, headerentry->compose);
11536 static gboolean compose_defer_auto_save_draft(Compose *compose)
11538 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
11539 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
11543 static void compose_show_first_last_header(Compose *compose, gboolean show_first)
11545 GtkAdjustment *vadj;
11547 cm_return_if_fail(compose);
11552 cm_return_if_fail(GTK_IS_WIDGET(compose->header_table));
11553 cm_return_if_fail(GTK_IS_VIEWPORT(gtk_widget_get_parent(compose->header_table)));
11554 vadj = gtk_viewport_get_vadjustment(GTK_VIEWPORT(
11555 gtk_widget_get_parent(compose->header_table)));
11556 gtk_adjustment_set_value(vadj, (show_first ?
11557 gtk_adjustment_get_lower(vadj) :
11558 (gtk_adjustment_get_upper(vadj) -
11559 gtk_adjustment_get_page_size(vadj))));
11560 gtk_adjustment_changed(vadj);
11563 static void text_inserted(GtkTextBuffer *buffer, GtkTextIter *iter,
11564 const gchar *text, gint len, Compose *compose)
11566 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data
11567 (G_OBJECT(compose->text), "paste_as_quotation"));
11570 cm_return_if_fail(text != NULL);
11572 g_signal_handlers_block_by_func(G_OBJECT(buffer),
11573 G_CALLBACK(text_inserted),
11575 if (paste_as_quotation) {
11577 const gchar *qmark;
11579 GtkTextIter start_iter;
11582 len = strlen(text);
11584 new_text = g_strndup(text, len);
11586 qmark = compose_quote_char_from_context(compose);
11588 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11589 gtk_text_buffer_place_cursor(buffer, iter);
11591 pos = gtk_text_iter_get_offset(iter);
11593 compose_quote_fmt(compose, NULL, "%Q", qmark, new_text, TRUE, FALSE,
11594 _("Quote format error at line %d."));
11595 quote_fmt_reset_vartable();
11597 g_object_set_data(G_OBJECT(compose->text), "paste_as_quotation",
11598 GINT_TO_POINTER(paste_as_quotation - 1));
11600 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11601 gtk_text_buffer_place_cursor(buffer, iter);
11602 gtk_text_buffer_delete_mark(buffer, mark);
11604 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, pos);
11605 mark = gtk_text_buffer_create_mark(buffer, NULL, &start_iter, FALSE);
11606 compose_beautify_paragraph(compose, &start_iter, FALSE);
11607 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark);
11608 gtk_text_buffer_delete_mark(buffer, mark);
11610 if (strcmp(text, "\n") || compose->automatic_break
11611 || gtk_text_iter_starts_line(iter)) {
11612 GtkTextIter before_ins;
11613 gtk_text_buffer_insert(buffer, iter, text, len);
11614 if (!strstr(text, "\n") && gtk_text_iter_has_tag(iter, compose->no_join_tag)) {
11615 before_ins = *iter;
11616 gtk_text_iter_backward_chars(&before_ins, len);
11617 gtk_text_buffer_remove_tag_by_name(buffer, "no_join", &before_ins, iter);
11620 /* check if the preceding is just whitespace or quote */
11621 GtkTextIter start_line;
11622 gchar *tmp = NULL, *quote = NULL;
11623 gint quote_len = 0, is_normal = 0;
11624 start_line = *iter;
11625 gtk_text_iter_set_line_offset(&start_line, 0);
11626 tmp = gtk_text_buffer_get_text(buffer, &start_line, iter, FALSE);
11629 if (*tmp == '\0') {
11632 quote = compose_get_quote_str(buffer, &start_line, "e_len);
11640 gtk_text_buffer_insert(buffer, iter, text, len);
11642 gtk_text_buffer_insert_with_tags_by_name(buffer,
11643 iter, text, len, "no_join", NULL);
11648 if (!paste_as_quotation) {
11649 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11650 compose_beautify_paragraph(compose, iter, FALSE);
11651 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11652 gtk_text_buffer_delete_mark(buffer, mark);
11655 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
11656 G_CALLBACK(text_inserted),
11658 g_signal_stop_emission_by_name(G_OBJECT(buffer), "insert-text");
11660 if (compose_can_autosave(compose) &&
11661 gtk_text_buffer_get_char_count(buffer) % prefs_common.autosave_length == 0 &&
11662 compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN /* disabled while loading */)
11663 compose->draft_timeout_tag = g_timeout_add
11664 (500, (GSourceFunc) compose_defer_auto_save_draft, compose);
11668 static void compose_check_all(GtkAction *action, gpointer data)
11670 Compose *compose = (Compose *)data;
11671 if (!compose->gtkaspell)
11674 if (gtk_widget_has_focus(compose->subject_entry))
11675 claws_spell_entry_check_all(
11676 CLAWS_SPELL_ENTRY(compose->subject_entry));
11678 gtkaspell_check_all(compose->gtkaspell);
11681 static void compose_highlight_all(GtkAction *action, gpointer data)
11683 Compose *compose = (Compose *)data;
11684 if (compose->gtkaspell) {
11685 claws_spell_entry_recheck_all(
11686 CLAWS_SPELL_ENTRY(compose->subject_entry));
11687 gtkaspell_highlight_all(compose->gtkaspell);
11691 static void compose_check_backwards(GtkAction *action, gpointer data)
11693 Compose *compose = (Compose *)data;
11694 if (!compose->gtkaspell) {
11695 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
11699 if (gtk_widget_has_focus(compose->subject_entry))
11700 claws_spell_entry_check_backwards(
11701 CLAWS_SPELL_ENTRY(compose->subject_entry));
11703 gtkaspell_check_backwards(compose->gtkaspell);
11706 static void compose_check_forwards_go(GtkAction *action, gpointer data)
11708 Compose *compose = (Compose *)data;
11709 if (!compose->gtkaspell) {
11710 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
11714 if (gtk_widget_has_focus(compose->subject_entry))
11715 claws_spell_entry_check_forwards_go(
11716 CLAWS_SPELL_ENTRY(compose->subject_entry));
11718 gtkaspell_check_forwards_go(compose->gtkaspell);
11723 *\brief Guess originating forward account from MsgInfo and several
11724 * "common preference" settings. Return NULL if no guess.
11726 static PrefsAccount *compose_guess_forward_account_from_msginfo(MsgInfo *msginfo)
11728 PrefsAccount *account = NULL;
11730 cm_return_val_if_fail(msginfo, NULL);
11731 cm_return_val_if_fail(msginfo->folder, NULL);
11732 cm_return_val_if_fail(msginfo->folder->prefs, NULL);
11734 if (msginfo->folder->prefs->enable_default_account)
11735 account = account_find_from_id(msginfo->folder->prefs->default_account);
11738 account = msginfo->folder->folder->account;
11740 if (!account && msginfo->to && prefs_common.forward_account_autosel) {
11742 Xstrdup_a(to, msginfo->to, return NULL);
11743 extract_address(to);
11744 account = account_find_from_address(to, FALSE);
11747 if (!account && prefs_common.forward_account_autosel) {
11748 gchar cc[BUFFSIZE];
11749 if (!procheader_get_header_from_msginfo
11750 (msginfo, cc,sizeof cc , "Cc:")) {
11751 gchar *buf = cc + strlen("Cc:");
11752 extract_address(buf);
11753 account = account_find_from_address(buf, FALSE);
11757 if (!account && prefs_common.forward_account_autosel) {
11758 gchar deliveredto[BUFFSIZE];
11759 if (!procheader_get_header_from_msginfo
11760 (msginfo, deliveredto,sizeof deliveredto , "Delivered-To:")) {
11761 gchar *buf = deliveredto + strlen("Delivered-To:");
11762 extract_address(buf);
11763 account = account_find_from_address(buf, FALSE);
11770 gboolean compose_close(Compose *compose)
11774 cm_return_val_if_fail(compose, FALSE);
11776 if (!g_mutex_trylock(compose->mutex)) {
11777 /* we have to wait for the (possibly deferred by auto-save)
11778 * drafting to be done, before destroying the compose under
11780 debug_print("waiting for drafting to finish...\n");
11781 compose_allow_user_actions(compose, FALSE);
11782 if (compose->close_timeout_tag == 0) {
11783 compose->close_timeout_tag =
11784 g_timeout_add (500, (GSourceFunc) compose_close,
11790 if (compose->draft_timeout_tag >= 0) {
11791 g_source_remove(compose->draft_timeout_tag);
11792 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN;
11795 gtkut_widget_get_uposition(compose->window, &x, &y);
11796 if (!compose->batch) {
11797 prefs_common.compose_x = x;
11798 prefs_common.compose_y = y;
11800 g_mutex_unlock(compose->mutex);
11801 compose_destroy(compose);
11806 * Add entry field for each address in list.
11807 * \param compose E-Mail composition object.
11808 * \param listAddress List of (formatted) E-Mail addresses.
11810 static void compose_add_field_list( Compose *compose, GList *listAddress ) {
11813 node = listAddress;
11815 addr = ( gchar * ) node->data;
11816 compose_entry_append( compose, addr, COMPOSE_TO, PREF_NONE );
11817 node = g_list_next( node );
11821 static void compose_reply_from_messageview_real(MessageView *msgview, GSList *msginfo_list,
11822 guint action, gboolean opening_multiple)
11824 gchar *body = NULL;
11825 GSList *new_msglist = NULL;
11826 MsgInfo *tmp_msginfo = NULL;
11827 gboolean originally_enc = FALSE;
11828 gboolean originally_sig = FALSE;
11829 Compose *compose = NULL;
11830 gchar *s_system = NULL;
11832 cm_return_if_fail(msgview != NULL);
11834 cm_return_if_fail(msginfo_list != NULL);
11836 if (g_slist_length(msginfo_list) == 1 && !opening_multiple) {
11837 MimeInfo *mimeinfo = messageview_get_selected_mime_part(msgview);
11838 MsgInfo *orig_msginfo = (MsgInfo *)msginfo_list->data;
11840 if (mimeinfo != NULL && mimeinfo->type == MIMETYPE_MESSAGE &&
11841 !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
11842 tmp_msginfo = procmsg_msginfo_new_from_mimeinfo(
11843 orig_msginfo, mimeinfo);
11844 if (tmp_msginfo != NULL) {
11845 new_msglist = g_slist_append(NULL, tmp_msginfo);
11847 originally_enc = MSG_IS_ENCRYPTED(orig_msginfo->flags);
11848 privacy_msginfo_get_signed_state(orig_msginfo, &s_system);
11849 originally_sig = MSG_IS_SIGNED(orig_msginfo->flags);
11851 tmp_msginfo->folder = orig_msginfo->folder;
11852 tmp_msginfo->msgnum = orig_msginfo->msgnum;
11853 if (orig_msginfo->tags) {
11854 tmp_msginfo->tags = g_slist_copy(orig_msginfo->tags);
11855 tmp_msginfo->folder->tags_dirty = TRUE;
11861 if (!opening_multiple)
11862 body = messageview_get_selection(msgview);
11865 compose = compose_reply_mode((ComposeMode)action, new_msglist, body);
11866 procmsg_msginfo_free(&tmp_msginfo);
11867 g_slist_free(new_msglist);
11869 compose = compose_reply_mode((ComposeMode)action, msginfo_list, body);
11871 if (compose && originally_enc) {
11872 compose_force_encryption(compose, compose->account, FALSE, s_system);
11875 if (compose && originally_sig && compose->account->default_sign_reply) {
11876 compose_force_signing(compose, compose->account, s_system);
11880 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
11883 void compose_reply_from_messageview(MessageView *msgview, GSList *msginfo_list,
11886 if ((!prefs_common.forward_as_attachment || action != COMPOSE_FORWARD)
11887 && action != COMPOSE_FORWARD_AS_ATTACH && g_slist_length(msginfo_list) > 1) {
11888 GSList *cur = msginfo_list;
11889 gchar *msg = g_strdup_printf(_("You are about to reply to %d "
11890 "messages. Opening the windows "
11891 "could take some time. Do you "
11892 "want to continue?"),
11893 g_slist_length(msginfo_list));
11894 if (g_slist_length(msginfo_list) > 9
11895 && alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, "+" GTK_STOCK_YES, NULL)
11896 != G_ALERTALTERNATE) {
11901 /* We'll open multiple compose windows */
11902 /* let the WM place the next windows */
11903 compose_force_window_origin = FALSE;
11904 for (; cur; cur = cur->next) {
11906 tmplist.data = cur->data;
11907 tmplist.next = NULL;
11908 compose_reply_from_messageview_real(msgview, &tmplist, action, TRUE);
11910 compose_force_window_origin = TRUE;
11912 /* forwarding multiple mails as attachments is done via a
11913 * single compose window */
11914 compose_reply_from_messageview_real(msgview, msginfo_list, action, FALSE);
11918 void compose_check_for_email_account(Compose *compose)
11920 PrefsAccount *ac = NULL, *curr = NULL;
11926 if (compose->account && compose->account->protocol == A_NNTP) {
11927 ac = account_get_cur_account();
11928 if (ac->protocol == A_NNTP) {
11929 list = account_get_list();
11931 for( ; list != NULL ; list = g_list_next(list)) {
11932 curr = (PrefsAccount *) list->data;
11933 if (curr->protocol != A_NNTP) {
11939 combobox_select_by_data(GTK_COMBO_BOX(compose->account_combo),
11944 void compose_reply_to_address(MessageView *msgview, MsgInfo *msginfo,
11945 const gchar *address)
11947 GSList *msginfo_list = NULL;
11948 gchar *body = messageview_get_selection(msgview);
11951 msginfo_list = g_slist_prepend(msginfo_list, msginfo);
11953 compose = compose_reply_mode(COMPOSE_REPLY_TO_ADDRESS, msginfo_list, body);
11954 compose_check_for_email_account(compose);
11955 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
11956 compose_entry_append(compose, address, COMPOSE_TO, PREF_NONE);
11957 compose_reply_set_subject(compose, msginfo);
11960 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
11963 void compose_set_position(Compose *compose, gint pos)
11965 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11967 gtkut_text_view_set_position(text, pos);
11970 gboolean compose_search_string(Compose *compose,
11971 const gchar *str, gboolean case_sens)
11973 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11975 return gtkut_text_view_search_string(text, str, case_sens);
11978 gboolean compose_search_string_backward(Compose *compose,
11979 const gchar *str, gboolean case_sens)
11981 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11983 return gtkut_text_view_search_string_backward(text, str, case_sens);
11986 /* allocate a msginfo structure and populate its data from a compose data structure */
11987 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose)
11989 MsgInfo *newmsginfo;
11991 gchar buf[BUFFSIZE];
11993 cm_return_val_if_fail( compose != NULL, NULL );
11995 newmsginfo = procmsg_msginfo_new();
11998 get_rfc822_date(buf, sizeof(buf));
11999 newmsginfo->date = g_strdup(buf);
12002 if (compose->from_name) {
12003 newmsginfo->from = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
12004 newmsginfo->fromname = procheader_get_fromname(newmsginfo->from);
12008 if (compose->subject_entry)
12009 newmsginfo->subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
12011 /* to, cc, reply-to, newsgroups */
12012 for (list = compose->header_list; list; list = list->next) {
12013 gchar *header = gtk_editable_get_chars(
12015 gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
12016 gchar *entry = gtk_editable_get_chars(
12017 GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
12019 if ( strcasecmp(header, prefs_common_translated_header_name("To:")) == 0 ) {
12020 if ( newmsginfo->to == NULL ) {
12021 newmsginfo->to = g_strdup(entry);
12022 } else if (entry && *entry) {
12023 gchar *tmp = g_strconcat(newmsginfo->to, ", ", entry, NULL);
12024 g_free(newmsginfo->to);
12025 newmsginfo->to = tmp;
12028 if ( strcasecmp(header, prefs_common_translated_header_name("Cc:")) == 0 ) {
12029 if ( newmsginfo->cc == NULL ) {
12030 newmsginfo->cc = g_strdup(entry);
12031 } else if (entry && *entry) {
12032 gchar *tmp = g_strconcat(newmsginfo->cc, ", ", entry, NULL);
12033 g_free(newmsginfo->cc);
12034 newmsginfo->cc = tmp;
12037 if ( strcasecmp(header,
12038 prefs_common_translated_header_name("Newsgroups:")) == 0 ) {
12039 if ( newmsginfo->newsgroups == NULL ) {
12040 newmsginfo->newsgroups = g_strdup(entry);
12041 } else if (entry && *entry) {
12042 gchar *tmp = g_strconcat(newmsginfo->newsgroups, ", ", entry, NULL);
12043 g_free(newmsginfo->newsgroups);
12044 newmsginfo->newsgroups = tmp;
12052 /* other data is unset */
12058 /* update compose's dictionaries from folder dict settings */
12059 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
12060 FolderItem *folder_item)
12062 cm_return_if_fail(compose != NULL);
12064 if (compose->gtkaspell && folder_item && folder_item->prefs) {
12065 FolderItemPrefs *prefs = folder_item->prefs;
12067 if (prefs->enable_default_dictionary)
12068 gtkaspell_change_dict(compose->gtkaspell,
12069 prefs->default_dictionary, FALSE);
12070 if (folder_item->prefs->enable_default_alt_dictionary)
12071 gtkaspell_change_alt_dict(compose->gtkaspell,
12072 prefs->default_alt_dictionary);
12073 if (prefs->enable_default_dictionary
12074 || prefs->enable_default_alt_dictionary)
12075 compose_spell_menu_changed(compose);
12080 static void compose_subject_entry_activated(GtkWidget *widget, gpointer data)
12082 Compose *compose = (Compose *)data;
12084 cm_return_if_fail(compose != NULL);
12086 gtk_widget_grab_focus(compose->text);
12089 static void from_name_activate_cb(GtkWidget *widget, gpointer data)
12091 gtk_combo_box_popup(GTK_COMBO_BOX(data));