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;
5676 debug_print("main text: %Id bytes encoded as %s in %d\n",
5678 debug_print("main text: %zd bytes encoded as %s in %d\n",
5680 strlen(buf), out_codeset, encoding);
5682 /* check for line length limit */
5683 if (action == COMPOSE_WRITE_FOR_SEND &&
5684 encoding != ENC_QUOTED_PRINTABLE && encoding != ENC_BASE64 &&
5685 check_line_length(buf, 1000, &line) < 0) {
5688 msg = g_strdup_printf
5689 (_("Line %d exceeds the line length limit (998 bytes).\n"
5690 "The contents of the message might be broken on the way to the delivery.\n"
5692 "Send it anyway?"), line + 1);
5693 aval = alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, GTK_STOCK_OK, NULL);
5695 if (aval != G_ALERTALTERNATE) {
5701 if (encoding != ENC_UNKNOWN)
5702 procmime_encode_content(mimetext, encoding);
5704 /* append attachment parts */
5705 if (compose_use_attach(compose) && attach_parts) {
5706 MimeInfo *mimempart;
5707 gchar *boundary = NULL;
5708 mimempart = procmime_mimeinfo_new();
5709 mimempart->content = MIMECONTENT_EMPTY;
5710 mimempart->type = MIMETYPE_MULTIPART;
5711 mimempart->subtype = g_strdup("mixed");
5715 boundary = generate_mime_boundary(NULL);
5716 } while (strstr(buf, boundary) != NULL);
5718 g_hash_table_insert(mimempart->typeparameters, g_strdup("boundary"),
5721 mimetext->disposition = DISPOSITIONTYPE_INLINE;
5723 g_node_append(mimempart->node, mimetext->node);
5724 g_node_append(mimemsg->node, mimempart->node);
5726 if (compose_add_attachments(compose, mimempart) < 0)
5729 g_node_append(mimemsg->node, mimetext->node);
5733 if (strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) != 0) {
5734 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
5735 /* extract name and address */
5736 if (strstr(spec, " <") && strstr(spec, ">")) {
5737 from_addr = g_strdup(strrchr(spec, '<')+1);
5738 *(strrchr(from_addr, '>')) = '\0';
5739 from_name = g_strdup(spec);
5740 *(strrchr(from_name, '<')) = '\0';
5747 /* sign message if sending */
5748 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5749 privacy_system_can_sign(compose->privacy_system))
5750 if (!privacy_sign(compose->privacy_system, mimemsg,
5751 compose->account, from_addr)) {
5759 if (compose->use_encryption) {
5760 if (compose->encdata != NULL &&
5761 strcmp(compose->encdata, "_DONT_ENCRYPT_")) {
5763 /* First, write an unencrypted copy and save it to outbox, if
5764 * user wants that. */
5765 if (compose->account->save_encrypted_as_clear_text) {
5766 debug_print("saving sent message unencrypted...\n");
5767 FILE *tmpfp = get_tmpfile_in_dir(get_mime_tmp_dir(), &tmp_enc_file);
5771 /* fp now points to a file with headers written,
5772 * let's make a copy. */
5774 content = file_read_stream_to_str(fp);
5776 str_write_to_file(content, tmp_enc_file);
5779 /* Now write the unencrypted body. */
5780 if ((tmpfp = g_fopen(tmp_enc_file, "a")) != NULL) {
5781 procmime_write_mimeinfo(mimemsg, tmpfp);
5784 outbox = folder_find_item_from_identifier(compose_get_save_to(compose));
5786 outbox = folder_get_default_outbox();
5788 procmsg_save_to_outbox(outbox, tmp_enc_file, TRUE);
5789 claws_unlink(tmp_enc_file);
5791 g_warning("Can't open file '%s'", tmp_enc_file);
5794 g_warning("couldn't get tempfile");
5797 if (!privacy_encrypt(compose->privacy_system, mimemsg, compose->encdata)) {
5798 debug_print("Couldn't encrypt mime structure: %s.\n",
5799 privacy_get_error());
5800 alertpanel_error(_("Couldn't encrypt the email: %s"),
5801 privacy_get_error());
5806 procmime_write_mimeinfo(mimemsg, fp);
5808 procmime_mimeinfo_free_all(&mimemsg);
5813 static gint compose_write_body_to_file(Compose *compose, const gchar *file)
5815 GtkTextBuffer *buffer;
5816 GtkTextIter start, end;
5821 if ((fp = g_fopen(file, "wb")) == NULL) {
5822 FILE_OP_ERROR(file, "fopen");
5826 /* chmod for security */
5827 if (change_file_mode_rw(fp, file) < 0) {
5828 FILE_OP_ERROR(file, "chmod");
5829 g_warning("can't change file mode");
5832 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5833 gtk_text_buffer_get_start_iter(buffer, &start);
5834 gtk_text_buffer_get_end_iter(buffer, &end);
5835 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5837 chars = conv_codeset_strdup
5838 (tmp, CS_INTERNAL, conv_get_locale_charset_str());
5847 len = strlen(chars);
5848 if (fwrite(chars, sizeof(gchar), len, fp) != len) {
5849 FILE_OP_ERROR(file, "fwrite");
5858 if (fclose(fp) == EOF) {
5859 FILE_OP_ERROR(file, "fclose");
5866 static gint compose_remove_reedit_target(Compose *compose, gboolean force)
5869 MsgInfo *msginfo = compose->targetinfo;
5871 cm_return_val_if_fail(compose->mode == COMPOSE_REEDIT, -1);
5872 if (!msginfo) return -1;
5874 if (!force && MSG_IS_LOCKED(msginfo->flags))
5877 item = msginfo->folder;
5878 cm_return_val_if_fail(item != NULL, -1);
5880 if (procmsg_msg_exist(msginfo) &&
5881 (folder_has_parent_of_type(item, F_QUEUE) ||
5882 folder_has_parent_of_type(item, F_DRAFT)
5883 || msginfo == compose->autosaved_draft)) {
5884 if (folder_item_remove_msg(item, msginfo->msgnum) < 0) {
5885 g_warning("can't remove the old message");
5888 debug_print("removed reedit target %d\n", msginfo->msgnum);
5895 static void compose_remove_draft(Compose *compose)
5898 MsgInfo *msginfo = compose->targetinfo;
5899 drafts = account_get_special_folder(compose->account, F_DRAFT);
5901 if (procmsg_msg_exist(msginfo)) {
5902 folder_item_remove_msg(drafts, msginfo->msgnum);
5907 gint compose_queue(Compose *compose, gint *msgnum, FolderItem **item, gchar **msgpath,
5908 gboolean remove_reedit_target)
5910 return compose_queue_sub (compose, msgnum, item, msgpath, FALSE, remove_reedit_target);
5913 static gboolean compose_warn_encryption(Compose *compose)
5915 const gchar *warning = privacy_get_encrypt_warning(compose->privacy_system);
5916 AlertValue val = G_ALERTALTERNATE;
5918 if (warning == NULL)
5921 val = alertpanel_full(_("Encryption warning"), warning,
5922 GTK_STOCK_CANCEL, g_strconcat("+", _("C_ontinue"), NULL), NULL,
5923 TRUE, NULL, ALERT_WARNING, G_ALERTALTERNATE);
5924 if (val & G_ALERTDISABLE) {
5925 val &= ~G_ALERTDISABLE;
5926 if (val == G_ALERTALTERNATE)
5927 privacy_inhibit_encrypt_warning(compose->privacy_system,
5931 if (val == G_ALERTALTERNATE) {
5938 static gint compose_queue_sub(Compose *compose, gint *msgnum, FolderItem **item,
5939 gchar **msgpath, gboolean check_subject,
5940 gboolean remove_reedit_target)
5947 PrefsAccount *mailac = NULL, *newsac = NULL;
5948 gboolean err = FALSE;
5950 debug_print("queueing message...\n");
5951 cm_return_val_if_fail(compose->account != NULL, -1);
5953 if (compose_check_entries(compose, check_subject) == FALSE) {
5954 if (compose->batch) {
5955 gtk_widget_show_all(compose->window);
5960 if (!compose->to_list && !compose->newsgroup_list) {
5961 g_warning("can't get recipient list.");
5965 if (compose->to_list) {
5966 if (compose->account->protocol != A_NNTP)
5967 mailac = compose->account;
5968 else if (cur_account && cur_account->protocol != A_NNTP)
5969 mailac = cur_account;
5970 else if (!(mailac = compose_current_mail_account())) {
5971 alertpanel_error(_("No account for sending mails available!"));
5976 if (compose->newsgroup_list) {
5977 if (compose->account->protocol == A_NNTP)
5978 newsac = compose->account;
5980 alertpanel_error(_("Selected account isn't NNTP: Posting is impossible."));
5985 /* write queue header */
5986 tmp = g_strdup_printf("%s%cqueue.%p%08x", get_tmp_dir(),
5987 G_DIR_SEPARATOR, compose, (guint) rand());
5988 debug_print("queuing to %s\n", tmp);
5989 if ((fp = g_fopen(tmp, "w+b")) == NULL) {
5990 FILE_OP_ERROR(tmp, "fopen");
5995 if (change_file_mode_rw(fp, tmp) < 0) {
5996 FILE_OP_ERROR(tmp, "chmod");
5997 g_warning("can't change file mode");
6000 /* queueing variables */
6001 err |= (fprintf(fp, "AF:\n") < 0);
6002 err |= (fprintf(fp, "NF:0\n") < 0);
6003 err |= (fprintf(fp, "PS:10\n") < 0);
6004 err |= (fprintf(fp, "SRH:1\n") < 0);
6005 err |= (fprintf(fp, "SFN:\n") < 0);
6006 err |= (fprintf(fp, "DSR:\n") < 0);
6008 err |= (fprintf(fp, "MID:<%s>\n", compose->msgid) < 0);
6010 err |= (fprintf(fp, "MID:\n") < 0);
6011 err |= (fprintf(fp, "CFG:\n") < 0);
6012 err |= (fprintf(fp, "PT:0\n") < 0);
6013 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
6014 err |= (fprintf(fp, "RQ:\n") < 0);
6016 err |= (fprintf(fp, "SSV:%s\n", mailac->smtp_server) < 0);
6018 err |= (fprintf(fp, "SSV:\n") < 0);
6020 err |= (fprintf(fp, "NSV:%s\n", newsac->nntp_server) < 0);
6022 err |= (fprintf(fp, "NSV:\n") < 0);
6023 err |= (fprintf(fp, "SSH:\n") < 0);
6024 /* write recepient list */
6025 if (compose->to_list) {
6026 err |= (fprintf(fp, "R:<%s>", (gchar *)compose->to_list->data) < 0);
6027 for (cur = compose->to_list->next; cur != NULL;
6029 err |= (fprintf(fp, ",<%s>", (gchar *)cur->data) < 0);
6030 err |= (fprintf(fp, "\n") < 0);
6032 /* write newsgroup list */
6033 if (compose->newsgroup_list) {
6034 err |= (fprintf(fp, "NG:") < 0);
6035 err |= (fprintf(fp, "%s", (gchar *)compose->newsgroup_list->data) < 0);
6036 for (cur = compose->newsgroup_list->next; cur != NULL; cur = cur->next)
6037 err |= (fprintf(fp, ",%s", (gchar *)cur->data) < 0);
6038 err |= (fprintf(fp, "\n") < 0);
6040 /* Sylpheed account IDs */
6042 err |= (fprintf(fp, "MAID:%d\n", mailac->account_id) < 0);
6044 err |= (fprintf(fp, "NAID:%d\n", newsac->account_id) < 0);
6047 if (compose->privacy_system != NULL) {
6048 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
6049 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
6050 if (compose->use_encryption) {
6051 if (!compose_warn_encryption(compose)) {
6057 if (mailac && mailac->encrypt_to_self) {
6058 GSList *tmp_list = g_slist_copy(compose->to_list);
6059 tmp_list = g_slist_append(tmp_list, compose->account->address);
6060 compose->encdata = privacy_get_encrypt_data(compose->privacy_system, tmp_list);
6061 g_slist_free(tmp_list);
6063 compose->encdata = privacy_get_encrypt_data(compose->privacy_system, compose->to_list);
6065 if (compose->encdata != NULL) {
6066 if (strcmp(compose->encdata, "_DONT_ENCRYPT_")) {
6067 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
6068 err |= (fprintf(fp, "X-Claws-Encrypt-Data:%s\n",
6069 compose->encdata) < 0);
6070 } /* else we finally dont want to encrypt */
6072 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
6073 /* and if encdata was null, it means there's been a problem in
6076 g_warning("failed to write queue message");
6085 /* Save copy folder */
6086 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
6087 gchar *savefolderid;
6089 savefolderid = compose_get_save_to(compose);
6090 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
6091 g_free(savefolderid);
6093 /* Save copy folder */
6094 if (compose->return_receipt) {
6095 err |= (fprintf(fp, "RRCPT:1\n") < 0);
6097 /* Message-ID of message replying to */
6098 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
6099 gchar *folderid = NULL;
6101 if (compose->replyinfo->folder)
6102 folderid = folder_item_get_identifier(compose->replyinfo->folder);
6103 if (folderid == NULL)
6104 folderid = g_strdup("NULL");
6106 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
6109 /* Message-ID of message forwarding to */
6110 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
6111 gchar *folderid = NULL;
6113 if (compose->fwdinfo->folder)
6114 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
6115 if (folderid == NULL)
6116 folderid = g_strdup("NULL");
6118 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
6122 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
6123 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
6125 /* end of headers */
6126 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
6128 if (compose->redirect_filename != NULL) {
6129 if (compose_redirect_write_to_file(compose, fp) < 0) {
6137 if ((result = compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_SEND, TRUE)) < 0) {
6141 return result - 1; /* -2 for a generic error, -3 for signing error, -4 for encoding */
6145 g_warning("failed to write queue message");
6151 if (fclose(fp) == EOF) {
6152 FILE_OP_ERROR(tmp, "fclose");
6158 if (item && *item) {
6161 queue = account_get_special_folder(compose->account, F_QUEUE);
6164 g_warning("can't find queue folder");
6169 folder_item_scan(queue);
6170 if ((num = folder_item_add_msg(queue, tmp, NULL, FALSE)) < 0) {
6171 g_warning("can't queue the message");
6177 if (msgpath == NULL) {
6183 if (compose->mode == COMPOSE_REEDIT && remove_reedit_target) {
6184 compose_remove_reedit_target(compose, FALSE);
6187 if ((msgnum != NULL) && (item != NULL)) {
6195 static int compose_add_attachments(Compose *compose, MimeInfo *parent)
6198 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
6201 gchar *type, *subtype;
6202 GtkTreeModel *model;
6205 model = gtk_tree_view_get_model(tree_view);
6207 if (!gtk_tree_model_get_iter_first(model, &iter))
6210 gtk_tree_model_get(model, &iter,
6214 if (!is_file_exist(ainfo->file)) {
6215 gchar *msg = g_strdup_printf(_("Attachment %s doesn't exist anymore. Ignore?"), ainfo->file);
6216 AlertValue val = alertpanel_full(_("Warning"), msg, _("Cancel sending"), _("Ignore attachment"),
6217 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
6219 if (val == G_ALERTDEFAULT) {
6224 if (g_stat(ainfo->file, &statbuf) < 0)
6227 mimepart = procmime_mimeinfo_new();
6228 mimepart->content = MIMECONTENT_FILE;
6229 mimepart->data.filename = g_strdup(ainfo->file);
6230 mimepart->tmp = FALSE; /* or we destroy our attachment */
6231 mimepart->offset = 0;
6232 mimepart->length = statbuf.st_size;
6234 type = g_strdup(ainfo->content_type);
6236 if (!strchr(type, '/')) {
6238 type = g_strdup("application/octet-stream");
6241 subtype = strchr(type, '/') + 1;
6242 *(subtype - 1) = '\0';
6243 mimepart->type = procmime_get_media_type(type);
6244 mimepart->subtype = g_strdup(subtype);
6247 if (mimepart->type == MIMETYPE_MESSAGE &&
6248 !g_ascii_strcasecmp(mimepart->subtype, "rfc822")) {
6249 mimepart->disposition = DISPOSITIONTYPE_INLINE;
6250 } else if (mimepart->type == MIMETYPE_TEXT) {
6251 if (!ainfo->name && g_ascii_strcasecmp(mimepart->subtype, "plain")) {
6252 /* Text parts with no name come from multipart/alternative
6253 * forwards. Make sure the recipient won't look at the
6254 * original HTML part by mistake. */
6255 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6256 ainfo->name = g_strdup_printf(_("Original %s part"),
6260 g_hash_table_insert(mimepart->typeparameters,
6261 g_strdup("charset"), g_strdup(ainfo->charset));
6263 if (ainfo->name && mimepart->type != MIMETYPE_MESSAGE) {
6264 if (mimepart->type == MIMETYPE_APPLICATION &&
6265 !strcmp2(mimepart->subtype, "octet-stream"))
6266 g_hash_table_insert(mimepart->typeparameters,
6267 g_strdup("name"), g_strdup(ainfo->name));
6268 g_hash_table_insert(mimepart->dispositionparameters,
6269 g_strdup("filename"), g_strdup(ainfo->name));
6270 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6273 if (mimepart->type == MIMETYPE_MESSAGE
6274 || mimepart->type == MIMETYPE_MULTIPART)
6275 ainfo->encoding = ENC_BINARY;
6276 else if (compose->use_signing) {
6277 if (ainfo->encoding == ENC_7BIT)
6278 ainfo->encoding = ENC_QUOTED_PRINTABLE;
6279 else if (ainfo->encoding == ENC_8BIT)
6280 ainfo->encoding = ENC_BASE64;
6285 procmime_encode_content(mimepart, ainfo->encoding);
6287 g_node_append(parent->node, mimepart->node);
6288 } while (gtk_tree_model_iter_next(model, &iter));
6293 static gchar *compose_quote_list_of_addresses(gchar *str)
6295 GSList *list = NULL, *item = NULL;
6296 gchar *qname = NULL, *faddr = NULL, *result = NULL;
6298 list = address_list_append_with_comments(list, str);
6299 for (item = list; item != NULL; item = item->next) {
6300 gchar *spec = item->data;
6301 gchar *endofname = strstr(spec, " <");
6302 if (endofname != NULL) {
6305 QUOTE_IF_REQUIRED_NORMAL(qname, spec, return NULL);
6306 qqname = escape_internal_quotes(qname, '"');
6308 if (*qname != *spec || qqname != qname) { /* has been quoted, compute new */
6309 gchar *addr = g_strdup(endofname);
6310 gchar *name = (qqname != qname)? qqname: g_strdup(qname);
6311 faddr = g_strconcat(name, addr, NULL);
6314 debug_print("new auto-quoted address: '%s'\n", faddr);
6318 result = g_strdup((faddr != NULL)? faddr: spec);
6320 result = g_strconcat(result,
6322 (faddr != NULL)? faddr: spec,
6325 if (faddr != NULL) {
6330 slist_free_strings_full(list);
6335 #define IS_IN_CUSTOM_HEADER(header) \
6336 (compose->account->add_customhdr && \
6337 custom_header_find(compose->account->customhdr_list, header) != NULL)
6339 static void compose_add_headerfield_from_headerlist(Compose *compose,
6341 const gchar *fieldname,
6342 const gchar *seperator)
6344 gchar *str, *fieldname_w_colon;
6345 gboolean add_field = FALSE;
6347 ComposeHeaderEntry *headerentry;
6348 const gchar *headerentryname;
6349 const gchar *trans_fieldname;
6352 if (IS_IN_CUSTOM_HEADER(fieldname))
6355 debug_print("Adding %s-fields\n", fieldname);
6357 fieldstr = g_string_sized_new(64);
6359 fieldname_w_colon = g_strconcat(fieldname, ":", NULL);
6360 trans_fieldname = prefs_common_translated_header_name(fieldname_w_colon);
6362 for (list = compose->header_list; list; list = list->next) {
6363 headerentry = ((ComposeHeaderEntry *)list->data);
6364 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
6366 if (!g_utf8_collate(trans_fieldname, headerentryname)) {
6367 gchar * ustr = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
6369 str = compose_quote_list_of_addresses(ustr);
6371 if (str != NULL && str[0] != '\0') {
6373 g_string_append(fieldstr, seperator);
6374 g_string_append(fieldstr, str);
6383 buf = g_new0(gchar, fieldstr->len * 4 + 256);
6384 compose_convert_header
6385 (compose, buf, fieldstr->len * 4 + 256, fieldstr->str,
6386 strlen(fieldname) + 2, TRUE);
6387 g_string_append_printf(header, "%s: %s\n", fieldname, buf);
6391 g_free(fieldname_w_colon);
6392 g_string_free(fieldstr, TRUE);
6397 static gchar *compose_get_manual_headers_info(Compose *compose)
6399 GString *sh_header = g_string_new(" ");
6401 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6403 for (list = compose->header_list; list; list = list->next) {
6404 ComposeHeaderEntry *headerentry;
6407 gchar *headername_wcolon;
6408 const gchar *headername_trans;
6410 gboolean standard_header = FALSE;
6412 headerentry = ((ComposeHeaderEntry *)list->data);
6414 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6416 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6421 if (!strstr(tmp, ":")) {
6422 headername_wcolon = g_strconcat(tmp, ":", NULL);
6423 headername = g_strdup(tmp);
6425 headername_wcolon = g_strdup(tmp);
6426 headername = g_strdup(strtok(tmp, ":"));
6430 string = std_headers;
6431 while (*string != NULL) {
6432 headername_trans = prefs_common_translated_header_name(*string);
6433 if (!strcmp(headername_trans, headername_wcolon))
6434 standard_header = TRUE;
6437 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
6438 g_string_append_printf(sh_header, "%s ", headername);
6440 g_free(headername_wcolon);
6442 g_string_truncate(sh_header, strlen(sh_header->str) - 1); /* remove last space */
6443 return g_string_free(sh_header, FALSE);
6446 static gchar *compose_get_header(Compose *compose)
6448 gchar buf[BUFFSIZE];
6449 const gchar *entry_str;
6453 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6455 gchar *from_name = NULL, *from_address = NULL;
6458 cm_return_val_if_fail(compose->account != NULL, NULL);
6459 cm_return_val_if_fail(compose->account->address != NULL, NULL);
6461 header = g_string_sized_new(64);
6464 if (prefs_common.hide_timezone)
6465 get_rfc822_date_hide_tz(buf, sizeof(buf));
6467 get_rfc822_date(buf, sizeof(buf));
6468 g_string_append_printf(header, "Date: %s\n", buf);
6472 if (compose->account->name && *compose->account->name) {
6474 QUOTE_IF_REQUIRED(buf, compose->account->name);
6475 tmp = g_strdup_printf("%s <%s>",
6476 buf, compose->account->address);
6478 tmp = g_strdup_printf("%s",
6479 compose->account->address);
6481 if (!strcmp(gtk_entry_get_text(GTK_ENTRY(compose->from_name)), tmp)
6482 || strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) == 0) {
6484 from_name = compose->account->name ? g_strdup(compose->account->name):NULL;
6485 from_address = g_strdup(compose->account->address);
6487 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
6488 /* extract name and address */
6489 if (strstr(spec, " <") && strstr(spec, ">")) {
6490 from_address = g_strdup(strrchr(spec, '<')+1);
6491 *(strrchr(from_address, '>')) = '\0';
6492 from_name = g_strdup(spec);
6493 *(strrchr(from_name, '<')) = '\0';
6496 from_address = g_strdup(spec);
6503 if (from_name && *from_name) {
6505 compose_convert_header
6506 (compose, buf, sizeof(buf), from_name,
6507 strlen("From: "), TRUE);
6508 QUOTE_IF_REQUIRED(name, buf);
6509 qname = escape_internal_quotes(name, '"');
6511 g_string_append_printf(header, "From: %s <%s>\n",
6512 qname, from_address);
6513 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To") &&
6514 compose->return_receipt) {
6515 compose_convert_header(compose, buf, sizeof(buf), from_name,
6516 strlen("Disposition-Notification-To: "),
6518 g_string_append_printf(header, "Disposition-Notification-To: %s <%s>\n", buf, from_address);
6523 g_string_append_printf(header, "From: %s\n", from_address);
6524 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To") &&
6525 compose->return_receipt)
6526 g_string_append_printf(header, "Disposition-Notification-To: %s\n", from_address);
6530 g_free(from_address);
6533 compose_add_headerfield_from_headerlist(compose, header, "To", ", ");
6536 compose_add_headerfield_from_headerlist(compose, header, "Newsgroups", ",");
6539 compose_add_headerfield_from_headerlist(compose, header, "Cc", ", ");
6543 * If this account is a NNTP account remove Bcc header from
6544 * message body since it otherwise will be publicly shown
6546 if (compose->account->protocol != A_NNTP)
6547 compose_add_headerfield_from_headerlist(compose, header, "Bcc", ", ");
6550 str = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
6552 if (*str != '\0' && !IS_IN_CUSTOM_HEADER("Subject")) {
6555 compose_convert_header(compose, buf, sizeof(buf), str,
6556 strlen("Subject: "), FALSE);
6557 g_string_append_printf(header, "Subject: %s\n", buf);
6563 if (compose->msgid != NULL && strlen(compose->msgid) > 0) {
6564 g_string_append_printf(header, "Message-ID: <%s>\n",
6568 if (compose->remove_references == FALSE) {
6570 if (compose->inreplyto && compose->to_list)
6571 g_string_append_printf(header, "In-Reply-To: <%s>\n", compose->inreplyto);
6574 if (compose->references)
6575 g_string_append_printf(header, "References: %s\n", compose->references);
6579 compose_add_headerfield_from_headerlist(compose, header, "Followup-To", ",");
6582 compose_add_headerfield_from_headerlist(compose, header, "Reply-To", ", ");
6585 if (compose->account->organization &&
6586 strlen(compose->account->organization) &&
6587 !IS_IN_CUSTOM_HEADER("Organization")) {
6588 compose_convert_header(compose, buf, sizeof(buf),
6589 compose->account->organization,
6590 strlen("Organization: "), FALSE);
6591 g_string_append_printf(header, "Organization: %s\n", buf);
6594 /* Program version and system info */
6595 if (compose->account->gen_xmailer &&
6596 g_slist_length(compose->to_list) && !IS_IN_CUSTOM_HEADER("X-Mailer") &&
6597 !compose->newsgroup_list) {
6598 g_string_append_printf(header, "X-Mailer: %s (GTK+ %d.%d.%d; %s)\n",
6600 gtk_major_version, gtk_minor_version, gtk_micro_version,
6603 if (compose->account->gen_xmailer &&
6604 g_slist_length(compose->newsgroup_list) && !IS_IN_CUSTOM_HEADER("X-Newsreader")) {
6605 g_string_append_printf(header, "X-Newsreader: %s (GTK+ %d.%d.%d; %s)\n",
6607 gtk_major_version, gtk_minor_version, gtk_micro_version,
6611 /* custom headers */
6612 if (compose->account->add_customhdr) {
6615 for (cur = compose->account->customhdr_list; cur != NULL;
6617 CustomHeader *chdr = (CustomHeader *)cur->data;
6619 if (custom_header_is_allowed(chdr->name)
6620 && chdr->value != NULL
6621 && *(chdr->value) != '\0') {
6622 compose_convert_header
6623 (compose, buf, sizeof(buf),
6625 strlen(chdr->name) + 2, FALSE);
6626 g_string_append_printf(header, "%s: %s\n", chdr->name, buf);
6631 /* Automatic Faces and X-Faces */
6632 if (get_account_xface (buf, sizeof(buf), compose->account->account_name) == 0) {
6633 g_string_append_printf(header, "X-Face: %s\n", buf);
6635 else if (get_default_xface (buf, sizeof(buf)) == 0) {
6636 g_string_append_printf(header, "X-Face: %s\n", buf);
6638 if (get_account_face (buf, sizeof(buf), compose->account->account_name) == 0) {
6639 g_string_append_printf(header, "Face: %s\n", buf);
6641 else if (get_default_face (buf, sizeof(buf)) == 0) {
6642 g_string_append_printf(header, "Face: %s\n", buf);
6646 switch (compose->priority) {
6647 case PRIORITY_HIGHEST: g_string_append_printf(header, "Importance: high\n"
6648 "X-Priority: 1 (Highest)\n");
6650 case PRIORITY_HIGH: g_string_append_printf(header, "Importance: high\n"
6651 "X-Priority: 2 (High)\n");
6653 case PRIORITY_NORMAL: break;
6654 case PRIORITY_LOW: g_string_append_printf(header, "Importance: low\n"
6655 "X-Priority: 4 (Low)\n");
6657 case PRIORITY_LOWEST: g_string_append_printf(header, "Importance: low\n"
6658 "X-Priority: 5 (Lowest)\n");
6660 default: debug_print("compose: priority unknown : %d\n",
6664 /* get special headers */
6665 for (list = compose->header_list; list; list = list->next) {
6666 ComposeHeaderEntry *headerentry;
6669 gchar *headername_wcolon;
6670 const gchar *headername_trans;
6673 gboolean standard_header = FALSE;
6675 headerentry = ((ComposeHeaderEntry *)list->data);
6677 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6679 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6684 if (!strstr(tmp, ":")) {
6685 headername_wcolon = g_strconcat(tmp, ":", NULL);
6686 headername = g_strdup(tmp);
6688 headername_wcolon = g_strdup(tmp);
6689 headername = g_strdup(strtok(tmp, ":"));
6693 entry_str = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
6694 Xstrdup_a(headervalue, entry_str, return NULL);
6695 subst_char(headervalue, '\r', ' ');
6696 subst_char(headervalue, '\n', ' ');
6697 string = std_headers;
6698 while (*string != NULL) {
6699 headername_trans = prefs_common_translated_header_name(*string);
6700 if (!strcmp(headername_trans, headername_wcolon))
6701 standard_header = TRUE;
6704 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
6705 g_string_append_printf(header, "%s %s\n", headername_wcolon, headervalue);
6708 g_free(headername_wcolon);
6712 g_string_free(header, FALSE);
6717 #undef IS_IN_CUSTOM_HEADER
6719 static void compose_convert_header(Compose *compose, gchar *dest, gint len, gchar *src,
6720 gint header_len, gboolean addr_field)
6722 gchar *tmpstr = NULL;
6723 const gchar *out_codeset = NULL;
6725 cm_return_if_fail(src != NULL);
6726 cm_return_if_fail(dest != NULL);
6728 if (len < 1) return;
6730 tmpstr = g_strdup(src);
6732 subst_char(tmpstr, '\n', ' ');
6733 subst_char(tmpstr, '\r', ' ');
6736 if (!g_utf8_validate(tmpstr, -1, NULL)) {
6737 gchar *mybuf = g_malloc(strlen(tmpstr)*2 +1);
6738 conv_localetodisp(mybuf, strlen(tmpstr)*2 +1, tmpstr);
6743 codeconv_set_strict(TRUE);
6744 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6745 conv_get_charset_str(compose->out_encoding));
6746 codeconv_set_strict(FALSE);
6748 if (!dest || *dest == '\0') {
6749 gchar *test_conv_global_out = NULL;
6750 gchar *test_conv_reply = NULL;
6752 /* automatic mode. be automatic. */
6753 codeconv_set_strict(TRUE);
6755 out_codeset = conv_get_outgoing_charset_str();
6757 debug_print("trying to convert to %s\n", out_codeset);
6758 test_conv_global_out = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6761 if (!test_conv_global_out && compose->orig_charset
6762 && strcmp(compose->orig_charset, CS_US_ASCII)) {
6763 out_codeset = compose->orig_charset;
6764 debug_print("failure; trying to convert to %s\n", out_codeset);
6765 test_conv_reply = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6768 if (!test_conv_global_out && !test_conv_reply) {
6770 out_codeset = CS_INTERNAL;
6771 debug_print("finally using %s\n", out_codeset);
6773 g_free(test_conv_global_out);
6774 g_free(test_conv_reply);
6775 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6777 codeconv_set_strict(FALSE);
6782 static void compose_add_to_addressbook_cb(GtkMenuItem *menuitem, gpointer user_data)
6786 cm_return_if_fail(user_data != NULL);
6788 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(user_data)));
6789 g_strstrip(address);
6790 if (*address != '\0') {
6791 gchar *name = procheader_get_fromname(address);
6792 extract_address(address);
6793 #ifndef USE_ALT_ADDRBOOK
6794 addressbook_add_contact(name, address, NULL, NULL);
6796 debug_print("%s: %s\n", name, address);
6797 if (addressadd_selection(name, address, NULL, NULL)) {
6798 debug_print( "addressbook_add_contact - added\n" );
6805 static void compose_entry_popup_extend(GtkEntry *entry, GtkMenu *menu, gpointer user_data)
6807 GtkWidget *menuitem;
6810 cm_return_if_fail(menu != NULL);
6811 cm_return_if_fail(GTK_IS_MENU_SHELL(menu));
6813 menuitem = gtk_separator_menu_item_new();
6814 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6815 gtk_widget_show(menuitem);
6817 menuitem = gtk_menu_item_new_with_mnemonic(_("Add to address _book"));
6818 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6820 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
6821 g_strstrip(address);
6822 if (*address == '\0') {
6823 gtk_widget_set_sensitive(GTK_WIDGET(menuitem), FALSE);
6826 g_signal_connect(G_OBJECT(menuitem), "activate",
6827 G_CALLBACK(compose_add_to_addressbook_cb), entry);
6828 gtk_widget_show(menuitem);
6831 void compose_add_extra_header(gchar *header, GtkListStore *model)
6834 if (strcmp(header, "")) {
6835 COMBOBOX_ADD(model, header, COMPOSE_TO);
6839 void compose_add_extra_header_entries(GtkListStore *model)
6843 gchar buf[BUFFSIZE];
6846 if (extra_headers == NULL) {
6847 exhrc = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, "extraheaderrc", NULL);
6848 if ((exh = g_fopen(exhrc, "rb")) == NULL) {
6849 debug_print("extra headers file not found\n");
6850 goto extra_headers_done;
6852 while (fgets(buf, BUFFSIZE, exh) != NULL) {
6853 lastc = strlen(buf) - 1; /* remove trailing control chars */
6854 while (lastc >= 0 && buf[lastc] != ':')
6855 buf[lastc--] = '\0';
6856 if (lastc > 0 && buf[0] != '#' && buf[lastc] == ':') {
6857 buf[lastc] = '\0'; /* remove trailing : for comparison */
6858 if (custom_header_is_allowed(buf)) {
6860 extra_headers = g_slist_prepend(extra_headers, g_strdup(buf));
6863 g_message("disallowed extra header line: %s\n", buf);
6867 g_message("invalid extra header line: %s\n", buf);
6873 extra_headers = g_slist_prepend(extra_headers, g_strdup("")); /* end of list */
6874 extra_headers = g_slist_reverse(extra_headers);
6876 g_slist_foreach(extra_headers, (GFunc)compose_add_extra_header, (gpointer)model);
6879 static void compose_create_header_entry(Compose *compose)
6881 gchar *headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6888 const gchar *header = NULL;
6889 ComposeHeaderEntry *headerentry;
6890 gboolean standard_header = FALSE;
6891 GtkListStore *model;
6894 headerentry = g_new0(ComposeHeaderEntry, 1);
6896 /* Combo box model */
6897 model = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_BOOLEAN);
6898 #if !GTK_CHECK_VERSION(2, 24, 0)
6899 combo = gtk_combo_box_entry_new_with_model(GTK_TREE_MODEL(model), 0);
6901 COMBOBOX_ADD(model, prefs_common_translated_header_name("To:"),
6903 COMBOBOX_ADD(model, prefs_common_translated_header_name("Cc:"),
6905 COMBOBOX_ADD(model, prefs_common_translated_header_name("Bcc:"),
6907 COMBOBOX_ADD(model, prefs_common_translated_header_name("Newsgroups:"),
6908 COMPOSE_NEWSGROUPS);
6909 COMBOBOX_ADD(model, prefs_common_translated_header_name("Reply-To:"),
6911 COMBOBOX_ADD(model, prefs_common_translated_header_name("Followup-To:"),
6912 COMPOSE_FOLLOWUPTO);
6913 compose_add_extra_header_entries(model);
6916 #if GTK_CHECK_VERSION(2, 24, 0)
6917 combo = gtk_combo_box_new_with_model_and_entry(GTK_TREE_MODEL(model));
6918 GtkCellRenderer *cell = gtk_cell_renderer_text_new();
6919 gtk_cell_renderer_set_alignment(cell, 0.0, 0.5);
6920 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), cell, TRUE);
6921 gtk_combo_box_set_entry_text_column(GTK_COMBO_BOX(combo), 0);
6923 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
6924 g_signal_connect(G_OBJECT(gtk_bin_get_child(GTK_BIN(combo))), "grab_focus",
6925 G_CALLBACK(compose_grab_focus_cb), compose);
6926 gtk_widget_show(combo);
6928 /* Putting only the combobox child into focus chain of its parent causes
6929 * the parent to be skipped when changing focus via Tab or Shift+Tab.
6930 * This eliminates need to pres Tab twice in order to really get from the
6931 * combobox to next widget. */
6933 l = g_list_prepend(l, gtk_bin_get_child(GTK_BIN(combo)));
6934 gtk_container_set_focus_chain(GTK_CONTAINER(combo), l);
6937 gtk_table_attach(GTK_TABLE(compose->header_table), combo, 0, 1,
6938 compose->header_nextrow, compose->header_nextrow+1,
6939 GTK_SHRINK, GTK_FILL, 0, 0);
6940 if (compose->header_last && (compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN)) {
6941 const gchar *last_header_entry = gtk_entry_get_text(
6942 GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
6944 while (*string != NULL) {
6945 if (!strcmp(prefs_common_translated_header_name(*string), last_header_entry))
6946 standard_header = TRUE;
6949 if (standard_header)
6950 header = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
6952 if (!compose->header_last || !standard_header) {
6953 switch(compose->account->protocol) {
6955 header = prefs_common_translated_header_name("Newsgroups:");
6958 header = prefs_common_translated_header_name("To:");
6963 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((combo)))), header);
6965 gtk_editable_set_editable(
6966 GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((combo)))),
6967 prefs_common.type_any_header);
6969 g_signal_connect_after(G_OBJECT(gtk_bin_get_child(GTK_BIN((combo)))), "grab_focus",
6970 G_CALLBACK(compose_grab_focus_cb), compose);
6972 /* Entry field with cleanup button */
6973 button = gtk_button_new();
6974 gtk_button_set_image(GTK_BUTTON(button),
6975 gtk_image_new_from_stock(GTK_STOCK_CLEAR, GTK_ICON_SIZE_MENU));
6976 gtk_widget_show(button);
6977 CLAWS_SET_TIP(button,
6978 _("Delete entry contents"));
6979 entry = gtk_entry_new();
6980 gtk_widget_show(entry);
6981 CLAWS_SET_TIP(entry,
6982 _("Use <tab> to autocomplete from addressbook"));
6983 hbox = gtk_hbox_new (FALSE, 0);
6984 gtk_widget_show(hbox);
6985 gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, TRUE, 0);
6986 gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
6987 gtk_table_attach(GTK_TABLE(compose->header_table), hbox, 1, 2,
6988 compose->header_nextrow, compose->header_nextrow+1,
6989 GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
6991 g_signal_connect(G_OBJECT(entry), "key-press-event",
6992 G_CALLBACK(compose_headerentry_key_press_event_cb),
6994 g_signal_connect(G_OBJECT(entry), "changed",
6995 G_CALLBACK(compose_headerentry_changed_cb),
6997 g_signal_connect_after(G_OBJECT(entry), "grab_focus",
6998 G_CALLBACK(compose_grab_focus_cb), compose);
7000 g_signal_connect(G_OBJECT(button), "clicked",
7001 G_CALLBACK(compose_headerentry_button_clicked_cb),
7005 gtk_drag_dest_set(entry, GTK_DEST_DEFAULT_ALL, compose_mime_types,
7006 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7007 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7008 g_signal_connect(G_OBJECT(entry), "drag_data_received",
7009 G_CALLBACK(compose_header_drag_received_cb),
7011 g_signal_connect(G_OBJECT(entry), "drag-drop",
7012 G_CALLBACK(compose_drag_drop),
7014 g_signal_connect(G_OBJECT(entry), "populate-popup",
7015 G_CALLBACK(compose_entry_popup_extend),
7018 address_completion_register_entry(GTK_ENTRY(entry), TRUE);
7020 headerentry->compose = compose;
7021 headerentry->combo = combo;
7022 headerentry->entry = entry;
7023 headerentry->button = button;
7024 headerentry->hbox = hbox;
7025 headerentry->headernum = compose->header_nextrow;
7026 headerentry->type = PREF_NONE;
7028 compose->header_nextrow++;
7029 compose->header_last = headerentry;
7030 compose->header_list =
7031 g_slist_append(compose->header_list,
7035 static void compose_add_header_entry(Compose *compose, const gchar *header,
7036 gchar *text, ComposePrefType pref_type)
7038 ComposeHeaderEntry *last_header = compose->header_last;
7039 gchar *tmp = g_strdup(text), *email;
7040 gboolean replyto_hdr;
7042 replyto_hdr = (!strcasecmp(header,
7043 prefs_common_translated_header_name("Reply-To:")) ||
7045 prefs_common_translated_header_name("Followup-To:")) ||
7047 prefs_common_translated_header_name("In-Reply-To:")));
7049 extract_address(tmp);
7050 email = g_utf8_strdown(tmp, -1);
7052 if (replyto_hdr == FALSE &&
7053 g_hash_table_lookup(compose->email_hashtable, email) != NULL)
7055 debug_print("Ignoring duplicate address - %s %s, pref_type: %d\n",
7056 header, text, (gint) pref_type);
7062 if (!strcasecmp(header, prefs_common_translated_header_name("In-Reply-To:")))
7063 gtk_entry_set_text(GTK_ENTRY(
7064 gtk_bin_get_child(GTK_BIN(last_header->combo))), header);
7066 combobox_select_by_text(GTK_COMBO_BOX(last_header->combo), header);
7067 gtk_entry_set_text(GTK_ENTRY(last_header->entry), text);
7068 last_header->type = pref_type;
7070 if (replyto_hdr == FALSE)
7071 g_hash_table_insert(compose->email_hashtable, email,
7072 GUINT_TO_POINTER(1));
7079 static void compose_destroy_headerentry(Compose *compose,
7080 ComposeHeaderEntry *headerentry)
7082 gchar *text = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
7085 extract_address(text);
7086 email = g_utf8_strdown(text, -1);
7087 g_hash_table_remove(compose->email_hashtable, email);
7091 gtk_widget_destroy(headerentry->combo);
7092 gtk_widget_destroy(headerentry->entry);
7093 gtk_widget_destroy(headerentry->button);
7094 gtk_widget_destroy(headerentry->hbox);
7095 g_free(headerentry);
7098 static void compose_remove_header_entries(Compose *compose)
7101 for (list = compose->header_list; list; list = list->next)
7102 compose_destroy_headerentry(compose, (ComposeHeaderEntry *)list->data);
7104 compose->header_last = NULL;
7105 g_slist_free(compose->header_list);
7106 compose->header_list = NULL;
7107 compose->header_nextrow = 1;
7108 compose_create_header_entry(compose);
7111 static GtkWidget *compose_create_header(Compose *compose)
7113 GtkWidget *from_optmenu_hbox;
7114 GtkWidget *header_table_main;
7115 GtkWidget *header_scrolledwin;
7116 GtkWidget *header_table;
7118 /* parent with account selection and from header */
7119 header_table_main = gtk_table_new(2, 2, FALSE);
7120 gtk_widget_show(header_table_main);
7121 gtk_container_set_border_width(GTK_CONTAINER(header_table_main), BORDER_WIDTH);
7123 from_optmenu_hbox = compose_account_option_menu_create(compose);
7124 gtk_table_attach(GTK_TABLE(header_table_main), from_optmenu_hbox,
7125 0, 2, 0, 1, GTK_EXPAND | GTK_FILL, GTK_SHRINK, 0, 0);
7127 /* child with header labels and entries */
7128 header_scrolledwin = gtk_scrolled_window_new(NULL, NULL);
7129 gtk_widget_show(header_scrolledwin);
7130 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
7132 header_table = gtk_table_new(2, 2, FALSE);
7133 gtk_widget_show(header_table);
7134 gtk_container_set_border_width(GTK_CONTAINER(header_table), 0);
7135 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin), header_table);
7136 gtk_container_set_focus_vadjustment(GTK_CONTAINER(header_table),
7137 gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(header_scrolledwin)));
7138 gtk_viewport_set_shadow_type(GTK_VIEWPORT(gtk_bin_get_child(GTK_BIN(header_scrolledwin))), GTK_SHADOW_NONE);
7140 gtk_table_attach(GTK_TABLE(header_table_main), header_scrolledwin,
7141 0, 2, 1, 2, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 2);
7143 compose->header_table = header_table;
7144 compose->header_list = NULL;
7145 compose->header_nextrow = 0;
7147 compose_create_header_entry(compose);
7149 compose->table = NULL;
7151 return header_table_main;
7154 static gboolean popup_attach_button_pressed(GtkWidget *widget, gpointer data)
7156 Compose *compose = (Compose *)data;
7157 GdkEventButton event;
7160 event.time = gtk_get_current_event_time();
7162 return attach_button_pressed(compose->attach_clist, &event, compose);
7165 static GtkWidget *compose_create_attach(Compose *compose)
7167 GtkWidget *attach_scrwin;
7168 GtkWidget *attach_clist;
7170 GtkListStore *store;
7171 GtkCellRenderer *renderer;
7172 GtkTreeViewColumn *column;
7173 GtkTreeSelection *selection;
7175 /* attachment list */
7176 attach_scrwin = gtk_scrolled_window_new(NULL, NULL);
7177 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(attach_scrwin),
7178 GTK_POLICY_AUTOMATIC,
7179 GTK_POLICY_AUTOMATIC);
7180 gtk_widget_set_size_request(attach_scrwin, -1, 80);
7182 store = gtk_list_store_new(N_ATTACH_COLS,
7188 G_TYPE_AUTO_POINTER,
7190 attach_clist = GTK_WIDGET(gtk_tree_view_new_with_model
7191 (GTK_TREE_MODEL(store)));
7192 gtk_container_add(GTK_CONTAINER(attach_scrwin), attach_clist);
7193 g_object_unref(store);
7195 renderer = gtk_cell_renderer_text_new();
7196 column = gtk_tree_view_column_new_with_attributes
7197 (_("Mime type"), renderer, "text",
7198 COL_MIMETYPE, NULL);
7199 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7201 renderer = gtk_cell_renderer_text_new();
7202 column = gtk_tree_view_column_new_with_attributes
7203 (_("Size"), renderer, "text",
7205 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7207 renderer = gtk_cell_renderer_text_new();
7208 column = gtk_tree_view_column_new_with_attributes
7209 (_("Name"), renderer, "text",
7211 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7213 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(attach_clist),
7214 prefs_common.use_stripes_everywhere);
7215 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(attach_clist));
7216 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
7218 g_signal_connect(G_OBJECT(attach_clist), "row_activated",
7219 G_CALLBACK(attach_selected), compose);
7220 g_signal_connect(G_OBJECT(attach_clist), "button_press_event",
7221 G_CALLBACK(attach_button_pressed), compose);
7222 g_signal_connect(G_OBJECT(attach_clist), "popup-menu",
7223 G_CALLBACK(popup_attach_button_pressed), compose);
7224 g_signal_connect(G_OBJECT(attach_clist), "key_press_event",
7225 G_CALLBACK(attach_key_pressed), compose);
7228 gtk_drag_dest_set(attach_clist,
7229 GTK_DEST_DEFAULT_ALL, compose_mime_types,
7230 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7231 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7232 g_signal_connect(G_OBJECT(attach_clist), "drag_data_received",
7233 G_CALLBACK(compose_attach_drag_received_cb),
7235 g_signal_connect(G_OBJECT(attach_clist), "drag-drop",
7236 G_CALLBACK(compose_drag_drop),
7239 compose->attach_scrwin = attach_scrwin;
7240 compose->attach_clist = attach_clist;
7242 return attach_scrwin;
7245 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose);
7246 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose);
7248 static GtkWidget *compose_create_others(Compose *compose)
7251 GtkWidget *savemsg_checkbtn;
7252 GtkWidget *savemsg_combo;
7253 GtkWidget *savemsg_select;
7256 gchar *folderidentifier;
7258 /* Table for settings */
7259 table = gtk_table_new(3, 1, FALSE);
7260 gtk_container_set_border_width(GTK_CONTAINER(table), BORDER_WIDTH);
7261 gtk_widget_show(table);
7262 gtk_table_set_row_spacings(GTK_TABLE(table), VSPACING_NARROW);
7265 /* Save Message to folder */
7266 savemsg_checkbtn = gtk_check_button_new_with_label(_("Save Message to "));
7267 gtk_widget_show(savemsg_checkbtn);
7268 gtk_table_attach(GTK_TABLE(table), savemsg_checkbtn, 0, 1, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7269 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7270 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), prefs_common.savemsg);
7272 g_signal_connect(G_OBJECT(savemsg_checkbtn), "toggled",
7273 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
7275 #if !GTK_CHECK_VERSION(2, 24, 0)
7276 savemsg_combo = gtk_combo_box_entry_new_text();
7278 savemsg_combo = gtk_combo_box_text_new_with_entry();
7280 compose->savemsg_checkbtn = savemsg_checkbtn;
7281 compose->savemsg_combo = savemsg_combo;
7282 gtk_widget_show(savemsg_combo);
7284 if (prefs_common.compose_save_to_history)
7285 #if !GTK_CHECK_VERSION(2, 24, 0)
7286 combobox_set_popdown_strings(GTK_COMBO_BOX(savemsg_combo),
7287 prefs_common.compose_save_to_history);
7289 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(savemsg_combo),
7290 prefs_common.compose_save_to_history);
7292 gtk_table_attach(GTK_TABLE(table), savemsg_combo, 1, 2, rowcount, rowcount + 1, GTK_FILL|GTK_EXPAND, GTK_SHRINK, 0, 0);
7293 gtk_widget_set_sensitive(GTK_WIDGET(savemsg_combo), prefs_common.savemsg);
7294 g_signal_connect_after(G_OBJECT(savemsg_combo), "grab_focus",
7295 G_CALLBACK(compose_grab_focus_cb), compose);
7296 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7297 folderidentifier = folder_item_get_identifier(account_get_special_folder
7298 (compose->account, F_OUTBOX));
7299 compose_set_save_to(compose, folderidentifier);
7300 g_free(folderidentifier);
7303 savemsg_select = gtkut_get_browse_file_btn(_("_Browse"));
7304 gtk_widget_show(savemsg_select);
7305 gtk_table_attach(GTK_TABLE(table), savemsg_select, 2, 3, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7306 g_signal_connect(G_OBJECT(savemsg_select), "clicked",
7307 G_CALLBACK(compose_savemsg_select_cb),
7313 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose)
7315 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo),
7316 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn)));
7319 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose)
7324 dest = foldersel_folder_sel(NULL, FOLDER_SEL_COPY, NULL, FALSE);
7327 path = folder_item_get_identifier(dest);
7329 compose_set_save_to(compose, path);
7333 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry, gboolean wrap,
7334 GdkAtom clip, GtkTextIter *insert_place);
7337 static gboolean text_clicked(GtkWidget *text, GdkEventButton *event,
7341 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
7343 if (event->button == 3) {
7345 GtkTextIter sel_start, sel_end;
7346 gboolean stuff_selected;
7348 /* move the cursor to allow GtkAspell to check the word
7349 * under the mouse */
7350 if (event->x && event->y) {
7351 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7352 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7354 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7357 GtkTextMark *mark = gtk_text_buffer_get_insert(buffer);
7358 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7361 stuff_selected = gtk_text_buffer_get_selection_bounds(
7363 &sel_start, &sel_end);
7365 gtk_text_buffer_place_cursor (buffer, &iter);
7366 /* reselect stuff */
7368 && gtk_text_iter_in_range(&iter, &sel_start, &sel_end)) {
7369 gtk_text_buffer_select_range(buffer,
7370 &sel_start, &sel_end);
7372 return FALSE; /* pass the event so that the right-click goes through */
7375 if (event->button == 2) {
7380 /* get the middle-click position to paste at the correct place */
7381 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7382 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7384 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7387 entry_paste_clipboard(compose, text,
7388 prefs_common.linewrap_pastes,
7389 GDK_SELECTION_PRIMARY, &iter);
7397 static void compose_spell_menu_changed(void *data)
7399 Compose *compose = (Compose *)data;
7401 GtkWidget *menuitem;
7402 GtkWidget *parent_item;
7403 GtkMenu *menu = GTK_MENU(gtk_menu_new());
7406 if (compose->gtkaspell == NULL)
7409 parent_item = gtk_ui_manager_get_widget(compose->ui_manager,
7410 "/Menu/Spelling/Options");
7412 /* setting the submenu removes /Spelling/Options from the factory
7413 * so we need to save it */
7415 if (parent_item == NULL) {
7416 parent_item = compose->aspell_options_menu;
7417 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), NULL);
7419 compose->aspell_options_menu = parent_item;
7421 spell_menu = gtkaspell_make_config_menu(compose->gtkaspell);
7423 spell_menu = g_slist_reverse(spell_menu);
7424 for (items = spell_menu;
7425 items; items = items->next) {
7426 menuitem = GTK_WIDGET(GTK_MENU_ITEM(items->data));
7427 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), GTK_WIDGET(menuitem));
7428 gtk_widget_show(GTK_WIDGET(menuitem));
7430 g_slist_free(spell_menu);
7432 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), GTK_WIDGET(menu));
7433 gtk_widget_show(parent_item);
7436 static void compose_dict_changed(void *data)
7438 Compose *compose = (Compose *) data;
7440 if(!compose->gtkaspell)
7442 if(compose->gtkaspell->recheck_when_changing_dict == FALSE)
7445 gtkaspell_highlight_all(compose->gtkaspell);
7446 claws_spell_entry_recheck_all(CLAWS_SPELL_ENTRY(compose->subject_entry));
7450 static gboolean compose_popup_menu(GtkWidget *widget, gpointer data)
7452 Compose *compose = (Compose *)data;
7453 GdkEventButton event;
7456 event.time = gtk_get_current_event_time();
7460 return text_clicked(compose->text, &event, compose);
7463 static gboolean compose_force_window_origin = TRUE;
7464 static Compose *compose_create(PrefsAccount *account,
7473 GtkWidget *handlebox;
7475 GtkWidget *notebook;
7477 GtkWidget *attach_hbox;
7478 GtkWidget *attach_lab1;
7479 GtkWidget *attach_lab2;
7484 GtkWidget *subject_hbox;
7485 GtkWidget *subject_frame;
7486 GtkWidget *subject_entry;
7490 GtkWidget *edit_vbox;
7491 GtkWidget *ruler_hbox;
7493 GtkWidget *scrolledwin;
7495 GtkTextBuffer *buffer;
7496 GtkClipboard *clipboard;
7498 UndoMain *undostruct;
7500 GtkWidget *popupmenu;
7501 GtkWidget *tmpl_menu;
7502 GtkActionGroup *action_group = NULL;
7505 GtkAspell * gtkaspell = NULL;
7508 static GdkGeometry geometry;
7510 cm_return_val_if_fail(account != NULL, NULL);
7512 debug_print("Creating compose window...\n");
7513 compose = g_new0(Compose, 1);
7515 compose->batch = batch;
7516 compose->account = account;
7517 compose->folder = folder;
7519 compose->mutex = cm_mutex_new();
7520 compose->set_cursor_pos = -1;
7522 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose");
7524 gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
7525 gtk_widget_set_size_request(window, prefs_common.compose_width,
7526 prefs_common.compose_height);
7528 if (!geometry.max_width) {
7529 geometry.max_width = gdk_screen_width();
7530 geometry.max_height = gdk_screen_height();
7533 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7534 &geometry, GDK_HINT_MAX_SIZE);
7535 if (!geometry.min_width) {
7536 geometry.min_width = 600;
7537 geometry.min_height = 440;
7539 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7540 &geometry, GDK_HINT_MIN_SIZE);
7542 #ifndef GENERIC_UMPC
7543 if (compose_force_window_origin)
7544 gtk_window_move(GTK_WINDOW(window), prefs_common.compose_x,
7545 prefs_common.compose_y);
7547 g_signal_connect(G_OBJECT(window), "delete_event",
7548 G_CALLBACK(compose_delete_cb), compose);
7549 MANAGE_WINDOW_SIGNALS_CONNECT(window);
7550 gtk_widget_realize(window);
7552 gtkut_widget_set_composer_icon(window);
7554 vbox = gtk_vbox_new(FALSE, 0);
7555 gtk_container_add(GTK_CONTAINER(window), vbox);
7557 compose->ui_manager = gtk_ui_manager_new();
7558 action_group = cm_menu_create_action_group_full(compose->ui_manager,"Menu", compose_entries,
7559 G_N_ELEMENTS(compose_entries), (gpointer)compose);
7560 gtk_action_group_add_toggle_actions(action_group, compose_toggle_entries,
7561 G_N_ELEMENTS(compose_toggle_entries), (gpointer)compose);
7562 gtk_action_group_add_radio_actions(action_group, compose_radio_rm_entries,
7563 G_N_ELEMENTS(compose_radio_rm_entries), COMPOSE_REPLY, G_CALLBACK(compose_reply_change_mode_cb), (gpointer)compose);
7564 gtk_action_group_add_radio_actions(action_group, compose_radio_prio_entries,
7565 G_N_ELEMENTS(compose_radio_prio_entries), PRIORITY_NORMAL, G_CALLBACK(compose_set_priority_cb), (gpointer)compose);
7566 gtk_action_group_add_radio_actions(action_group, compose_radio_enc_entries,
7567 G_N_ELEMENTS(compose_radio_enc_entries), C_AUTO, G_CALLBACK(compose_set_encoding_cb), (gpointer)compose);
7569 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Menu", NULL, GTK_UI_MANAGER_MENUBAR)
7571 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Message", "Message", GTK_UI_MANAGER_MENU)
7572 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Edit", "Edit", GTK_UI_MANAGER_MENU)
7574 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Spelling", "Spelling", GTK_UI_MANAGER_MENU)
7576 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Options", "Options", GTK_UI_MANAGER_MENU)
7577 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Tools", "Tools", GTK_UI_MANAGER_MENU)
7578 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Help", "Help", GTK_UI_MANAGER_MENU)
7581 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Send", "Message/Send", GTK_UI_MANAGER_MENUITEM)
7582 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "SendLater", "Message/SendLater", GTK_UI_MANAGER_MENUITEM)
7583 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator1", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7584 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "AttachFile", "Message/AttachFile", GTK_UI_MANAGER_MENUITEM)
7585 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertFile", "Message/InsertFile", GTK_UI_MANAGER_MENUITEM)
7586 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertSig", "Message/InsertSig", GTK_UI_MANAGER_MENUITEM)
7587 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "ReplaceSig", "Message/ReplaceSig", GTK_UI_MANAGER_MENUITEM)
7588 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator2", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7589 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Save", "Message/Save", GTK_UI_MANAGER_MENUITEM)
7590 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator3", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7591 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Print", "Message/Print", GTK_UI_MANAGER_MENUITEM)
7592 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator4", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7593 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Close", "Message/Close", GTK_UI_MANAGER_MENUITEM)
7596 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Undo", "Edit/Undo", GTK_UI_MANAGER_MENUITEM)
7597 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Redo", "Edit/Redo", GTK_UI_MANAGER_MENUITEM)
7598 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator1", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7600 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Cut", "Edit/Cut", GTK_UI_MANAGER_MENUITEM)
7601 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Copy", "Edit/Copy", GTK_UI_MANAGER_MENUITEM)
7602 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Paste", "Edit/Paste", GTK_UI_MANAGER_MENUITEM)
7604 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SpecialPaste", "Edit/SpecialPaste", GTK_UI_MANAGER_MENU)
7605 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "AsQuotation", "Edit/SpecialPaste/AsQuotation", GTK_UI_MANAGER_MENUITEM)
7606 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Wrapped", "Edit/SpecialPaste/Wrapped", GTK_UI_MANAGER_MENUITEM)
7607 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Unwrapped", "Edit/SpecialPaste/Unwrapped", GTK_UI_MANAGER_MENUITEM)
7609 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SelectAll", "Edit/SelectAll", GTK_UI_MANAGER_MENUITEM)
7611 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Advanced", "Edit/Advanced", GTK_UI_MANAGER_MENU)
7612 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackChar", "Edit/Advanced/BackChar", GTK_UI_MANAGER_MENUITEM)
7613 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwChar", "Edit/Advanced/ForwChar", GTK_UI_MANAGER_MENUITEM)
7614 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackWord", "Edit/Advanced/BackWord", GTK_UI_MANAGER_MENUITEM)
7615 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwWord", "Edit/Advanced/ForwWord", GTK_UI_MANAGER_MENUITEM)
7616 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BegLine", "Edit/Advanced/BegLine", GTK_UI_MANAGER_MENUITEM)
7617 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "EndLine", "Edit/Advanced/EndLine", GTK_UI_MANAGER_MENUITEM)
7618 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "PrevLine", "Edit/Advanced/PrevLine", GTK_UI_MANAGER_MENUITEM)
7619 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "NextLine", "Edit/Advanced/NextLine", GTK_UI_MANAGER_MENUITEM)
7620 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackChar", "Edit/Advanced/DelBackChar", GTK_UI_MANAGER_MENUITEM)
7621 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwChar", "Edit/Advanced/DelForwChar", GTK_UI_MANAGER_MENUITEM)
7622 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackWord", "Edit/Advanced/DelBackWord", GTK_UI_MANAGER_MENUITEM)
7623 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwWord", "Edit/Advanced/DelForwWord", GTK_UI_MANAGER_MENUITEM)
7624 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelLine", "Edit/Advanced/DelLine", GTK_UI_MANAGER_MENUITEM)
7625 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelEndLine", "Edit/Advanced/DelEndLine", GTK_UI_MANAGER_MENUITEM)
7627 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator2", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7629 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Find", "Edit/Find", GTK_UI_MANAGER_MENUITEM)
7630 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapPara", "Edit/WrapPara", GTK_UI_MANAGER_MENUITEM)
7631 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapAllLines", "Edit/WrapAllLines", GTK_UI_MANAGER_MENUITEM)
7632 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoWrap", "Edit/AutoWrap", GTK_UI_MANAGER_MENUITEM)
7633 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoIndent", "Edit/AutoIndent", GTK_UI_MANAGER_MENUITEM)
7635 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator3", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7637 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "ExtEditor", "Edit/ExtEditor", GTK_UI_MANAGER_MENUITEM)
7641 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckAllSel", "Spelling/CheckAllSel", GTK_UI_MANAGER_MENUITEM)
7642 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "HighlightAll", "Spelling/HighlightAll", GTK_UI_MANAGER_MENUITEM)
7643 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckBackwards", "Spelling/CheckBackwards", GTK_UI_MANAGER_MENUITEM)
7644 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "ForwardNext", "Spelling/ForwardNext", GTK_UI_MANAGER_MENUITEM)
7645 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Separator1", "Spelling/---", GTK_UI_MANAGER_SEPARATOR)
7646 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Options", "Spelling/Options", GTK_UI_MANAGER_MENU)
7650 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "ReplyMode", "Options/ReplyMode", GTK_UI_MANAGER_MENU)
7651 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Normal", "Options/ReplyMode/Normal", GTK_UI_MANAGER_MENUITEM)
7652 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "All", "Options/ReplyMode/All", GTK_UI_MANAGER_MENUITEM)
7653 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Sender", "Options/ReplyMode/Sender", GTK_UI_MANAGER_MENUITEM)
7654 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "List", "Options/ReplyMode/List", GTK_UI_MANAGER_MENUITEM)
7656 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator1", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7657 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "PrivacySystem", "Options/PrivacySystem", GTK_UI_MANAGER_MENU)
7658 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/PrivacySystem", "PlaceHolder", "Options/PrivacySystem/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7659 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Sign", "Options/Sign", GTK_UI_MANAGER_MENUITEM)
7660 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encrypt", "Options/Encrypt", GTK_UI_MANAGER_MENUITEM)
7663 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator2", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7664 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Priority", "Options/Priority", GTK_UI_MANAGER_MENU)
7665 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Highest", "Options/Priority/Highest", GTK_UI_MANAGER_MENUITEM)
7666 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "High", "Options/Priority/High", GTK_UI_MANAGER_MENUITEM)
7667 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Normal", "Options/Priority/Normal", GTK_UI_MANAGER_MENUITEM)
7668 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Low", "Options/Priority/Low", GTK_UI_MANAGER_MENUITEM)
7669 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Lowest", "Options/Priority/Lowest", GTK_UI_MANAGER_MENUITEM)
7671 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator3", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7672 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RequestRetRcpt", "Options/RequestRetRcpt", GTK_UI_MANAGER_MENUITEM)
7673 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator4", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7674 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RemoveReferences", "Options/RemoveReferences", GTK_UI_MANAGER_MENUITEM)
7675 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator5", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7677 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encoding", "Options/Encoding", GTK_UI_MANAGER_MENU)
7679 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_AUTO, "Options/Encoding/"CS_AUTO, GTK_UI_MANAGER_MENUITEM)
7680 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator1", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7681 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_US_ASCII, "Options/Encoding/"CS_US_ASCII, GTK_UI_MANAGER_MENUITEM)
7682 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_UTF_8, "Options/Encoding/"CS_UTF_8, GTK_UI_MANAGER_MENUITEM)
7683 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator2", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7685 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Western", "Options/Encoding/Western", GTK_UI_MANAGER_MENU)
7686 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)
7687 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)
7688 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Western", CS_WINDOWS_1252, "Options/Encoding/Western/"CS_WINDOWS_1252, GTK_UI_MANAGER_MENUITEM)
7690 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_2, "Options/Encoding/"CS_ISO_8859_2, GTK_UI_MANAGER_MENUITEM)
7692 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Baltic", "Options/Encoding/Baltic", GTK_UI_MANAGER_MENU)
7693 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)
7694 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)
7696 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_7, "Options/Encoding/"CS_ISO_8859_7, GTK_UI_MANAGER_MENUITEM)
7698 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Hebrew", "Options/Encoding/Hebrew", GTK_UI_MANAGER_MENU)
7699 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)
7700 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Hebrew", CS_WINDOWS_1255, "Options/Encoding/Hebrew/"CS_WINDOWS_1255, GTK_UI_MANAGER_MENUITEM)
7702 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Arabic", "Options/Encoding/Arabic", GTK_UI_MANAGER_MENU)
7703 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)
7704 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Arabic", CS_WINDOWS_1256, "Options/Encoding/Arabic/"CS_WINDOWS_1256, GTK_UI_MANAGER_MENUITEM)
7706 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_9, "Options/Encoding/"CS_ISO_8859_9, GTK_UI_MANAGER_MENUITEM)
7708 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Cyrillic", "Options/Encoding/Cyrillic", GTK_UI_MANAGER_MENU)
7709 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)
7710 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_R, "Options/Encoding/Cyrillic/"CS_KOI8_R, GTK_UI_MANAGER_MENUITEM)
7711 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_MACCYR, "Options/Encoding/Cyrillic/"CS_MACCYR, GTK_UI_MANAGER_MENUITEM)
7712 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_U, "Options/Encoding/Cyrillic/"CS_KOI8_U, GTK_UI_MANAGER_MENUITEM)
7713 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_WINDOWS_1251, "Options/Encoding/Cyrillic/"CS_WINDOWS_1251, GTK_UI_MANAGER_MENUITEM)
7715 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Japanese", "Options/Encoding/Japanese", GTK_UI_MANAGER_MENU)
7716 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)
7717 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)
7718 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_EUC_JP, "Options/Encoding/Japanese/"CS_EUC_JP, GTK_UI_MANAGER_MENUITEM)
7719 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_SHIFT_JIS, "Options/Encoding/Japanese/"CS_SHIFT_JIS, GTK_UI_MANAGER_MENUITEM)
7721 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Chinese", "Options/Encoding/Chinese", GTK_UI_MANAGER_MENU)
7722 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB18030, "Options/Encoding/Chinese/"CS_GB18030, GTK_UI_MANAGER_MENUITEM)
7723 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB2312, "Options/Encoding/Chinese/"CS_GB2312, GTK_UI_MANAGER_MENUITEM)
7724 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GBK, "Options/Encoding/Chinese/"CS_GBK, GTK_UI_MANAGER_MENUITEM)
7725 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_BIG5, "Options/Encoding/Chinese/"CS_BIG5, GTK_UI_MANAGER_MENUITEM)
7726 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_EUC_TW, "Options/Encoding/Chinese/"CS_EUC_TW, GTK_UI_MANAGER_MENUITEM)
7728 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Korean", "Options/Encoding/Korean", GTK_UI_MANAGER_MENU)
7729 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Korean", CS_EUC_KR, "Options/Encoding/Korean/"CS_EUC_KR, GTK_UI_MANAGER_MENUITEM)
7730 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)
7732 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Thai", "Options/Encoding/Thai", GTK_UI_MANAGER_MENU)
7733 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_TIS_620, "Options/Encoding/Thai/"CS_TIS_620, GTK_UI_MANAGER_MENUITEM)
7734 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_WINDOWS_874, "Options/Encoding/Thai/"CS_WINDOWS_874, GTK_UI_MANAGER_MENUITEM)
7738 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "ShowRuler", "Tools/ShowRuler", GTK_UI_MANAGER_MENUITEM)
7739 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "AddressBook", "Tools/AddressBook", GTK_UI_MANAGER_MENUITEM)
7740 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Template", "Tools/Template", GTK_UI_MANAGER_MENU)
7741 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Template", "PlaceHolder", "Tools/Template/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7742 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Actions", "Tools/Actions", GTK_UI_MANAGER_MENU)
7743 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Actions", "PlaceHolder", "Tools/Actions/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7746 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Help", "About", "Help/About", GTK_UI_MANAGER_MENUITEM)
7748 menubar = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu");
7749 gtk_widget_show_all(menubar);
7751 gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(compose->ui_manager));
7752 gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, TRUE, 0);
7754 if (prefs_common.toolbar_detachable) {
7755 handlebox = gtk_handle_box_new();
7757 handlebox = gtk_hbox_new(FALSE, 0);
7759 gtk_box_pack_start(GTK_BOX(vbox), handlebox, FALSE, FALSE, 0);
7761 gtk_widget_realize(handlebox);
7762 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, handlebox,
7765 vbox2 = gtk_vbox_new(FALSE, 2);
7766 gtk_box_pack_start(GTK_BOX(vbox), vbox2, TRUE, TRUE, 0);
7767 gtk_container_set_border_width(GTK_CONTAINER(vbox2), 0);
7770 notebook = gtk_notebook_new();
7771 gtk_widget_show(notebook);
7773 /* header labels and entries */
7774 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7775 compose_create_header(compose),
7776 gtk_label_new_with_mnemonic(_("Hea_der")));
7777 /* attachment list */
7778 attach_hbox = gtk_hbox_new(FALSE, 0);
7779 gtk_widget_show(attach_hbox);
7781 attach_lab1 = gtk_label_new_with_mnemonic(_("_Attachments"));
7782 gtk_widget_show(attach_lab1);
7783 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab1, TRUE, TRUE, 0);
7785 attach_lab2 = gtk_label_new("");
7786 gtk_widget_show(attach_lab2);
7787 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab2, FALSE, FALSE, 0);
7789 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7790 compose_create_attach(compose),
7793 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7794 compose_create_others(compose),
7795 gtk_label_new_with_mnemonic(_("Othe_rs")));
7798 subject_hbox = gtk_hbox_new(FALSE, 0);
7799 gtk_widget_show(subject_hbox);
7801 subject_frame = gtk_frame_new(NULL);
7802 gtk_frame_set_shadow_type(GTK_FRAME(subject_frame), GTK_SHADOW_NONE);
7803 gtk_box_pack_start(GTK_BOX(subject_hbox), subject_frame, TRUE, TRUE, 0);
7804 gtk_widget_show(subject_frame);
7806 subject = gtk_hbox_new(FALSE, HSPACING_NARROW);
7807 gtk_container_set_border_width(GTK_CONTAINER(subject), 0);
7808 gtk_widget_show(subject);
7810 label = gtk_label_new_with_mnemonic(_("S_ubject:"));
7811 gtk_box_pack_start(GTK_BOX(subject), label, FALSE, FALSE, 0);
7812 gtk_widget_show(label);
7815 subject_entry = claws_spell_entry_new();
7817 subject_entry = gtk_entry_new();
7819 gtk_box_pack_start(GTK_BOX(subject), subject_entry, TRUE, TRUE, 0);
7820 g_signal_connect_after(G_OBJECT(subject_entry), "grab_focus",
7821 G_CALLBACK(compose_grab_focus_cb), compose);
7822 gtk_label_set_mnemonic_widget(GTK_LABEL(label), subject_entry);
7823 gtk_widget_show(subject_entry);
7824 compose->subject_entry = subject_entry;
7825 gtk_container_add(GTK_CONTAINER(subject_frame), subject);
7827 edit_vbox = gtk_vbox_new(FALSE, 0);
7829 gtk_box_pack_start(GTK_BOX(edit_vbox), subject_hbox, FALSE, FALSE, 0);
7832 ruler_hbox = gtk_hbox_new(FALSE, 0);
7833 gtk_box_pack_start(GTK_BOX(edit_vbox), ruler_hbox, FALSE, FALSE, 0);
7835 ruler = gtk_shruler_new(GTK_ORIENTATION_HORIZONTAL);
7836 gtk_shruler_set_range(GTK_SHRULER(ruler), 0.0, 100.0, 1.0);
7837 gtk_box_pack_start(GTK_BOX(ruler_hbox), ruler, TRUE, TRUE,
7841 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
7842 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
7843 GTK_POLICY_AUTOMATIC,
7844 GTK_POLICY_AUTOMATIC);
7845 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
7847 gtk_box_pack_start(GTK_BOX(edit_vbox), scrolledwin, TRUE, TRUE, 0);
7849 text = gtk_text_view_new();
7850 if (prefs_common.show_compose_margin) {
7851 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text), 6);
7852 gtk_text_view_set_right_margin(GTK_TEXT_VIEW(text), 6);
7854 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
7855 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
7856 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), TRUE);
7857 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
7858 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
7860 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
7861 g_signal_connect_after(G_OBJECT(text), "size_allocate",
7862 G_CALLBACK(compose_edit_size_alloc),
7864 g_signal_connect(G_OBJECT(buffer), "changed",
7865 G_CALLBACK(compose_changed_cb), compose);
7866 g_signal_connect(G_OBJECT(text), "grab_focus",
7867 G_CALLBACK(compose_grab_focus_cb), compose);
7868 g_signal_connect(G_OBJECT(buffer), "insert_text",
7869 G_CALLBACK(text_inserted), compose);
7870 g_signal_connect(G_OBJECT(text), "button_press_event",
7871 G_CALLBACK(text_clicked), compose);
7872 g_signal_connect(G_OBJECT(text), "popup-menu",
7873 G_CALLBACK(compose_popup_menu), compose);
7874 g_signal_connect(G_OBJECT(subject_entry), "changed",
7875 G_CALLBACK(compose_changed_cb), compose);
7876 g_signal_connect(G_OBJECT(subject_entry), "activate",
7877 G_CALLBACK(compose_subject_entry_activated), compose);
7880 gtk_drag_dest_set(text, GTK_DEST_DEFAULT_ALL, compose_mime_types,
7881 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7882 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7883 g_signal_connect(G_OBJECT(text), "drag_data_received",
7884 G_CALLBACK(compose_insert_drag_received_cb),
7886 g_signal_connect(G_OBJECT(text), "drag-drop",
7887 G_CALLBACK(compose_drag_drop),
7889 g_signal_connect(G_OBJECT(text), "key-press-event",
7890 G_CALLBACK(completion_set_focus_to_subject),
7892 gtk_widget_show_all(vbox);
7894 /* pane between attach clist and text */
7895 paned = gtk_vpaned_new();
7896 gtk_container_add(GTK_CONTAINER(vbox2), paned);
7897 gtk_paned_pack1(GTK_PANED(paned), notebook, FALSE, FALSE);
7898 gtk_paned_pack2(GTK_PANED(paned), edit_vbox, TRUE, FALSE);
7899 gtk_paned_set_position(GTK_PANED(paned), prefs_common.compose_notebook_height);
7900 g_signal_connect(G_OBJECT(notebook), "size_allocate",
7901 G_CALLBACK(compose_notebook_size_alloc), paned);
7903 gtk_widget_show_all(paned);
7906 if (prefs_common.textfont) {
7907 PangoFontDescription *font_desc;
7909 font_desc = pango_font_description_from_string
7910 (prefs_common.textfont);
7912 gtk_widget_modify_font(text, font_desc);
7913 pango_font_description_free(font_desc);
7917 gtk_action_group_add_actions(action_group, compose_popup_entries,
7918 G_N_ELEMENTS(compose_popup_entries), (gpointer)compose);
7919 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Popup", NULL, GTK_UI_MANAGER_MENUBAR)
7920 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup", "Compose", "Compose", GTK_UI_MANAGER_MENU)
7921 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Add", "Compose/Add", GTK_UI_MANAGER_MENUITEM)
7922 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Remove", "Compose/Remove", GTK_UI_MANAGER_MENUITEM)
7923 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Separator1", "Compose/---", GTK_UI_MANAGER_SEPARATOR)
7924 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Properties", "Compose/Properties", GTK_UI_MANAGER_MENUITEM)
7926 popupmenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(gtk_ui_manager_get_widget(compose->ui_manager, "/Popup/Compose")));
7928 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
7929 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
7930 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
7932 tmpl_menu = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu/Tools/Template");
7934 undostruct = undo_init(text);
7935 undo_set_change_state_func(undostruct, &compose_undo_state_changed,
7938 address_completion_start(window);
7940 compose->window = window;
7941 compose->vbox = vbox;
7942 compose->menubar = menubar;
7943 compose->handlebox = handlebox;
7945 compose->vbox2 = vbox2;
7947 compose->paned = paned;
7949 compose->attach_label = attach_lab2;
7951 compose->notebook = notebook;
7952 compose->edit_vbox = edit_vbox;
7953 compose->ruler_hbox = ruler_hbox;
7954 compose->ruler = ruler;
7955 compose->scrolledwin = scrolledwin;
7956 compose->text = text;
7958 compose->focused_editable = NULL;
7960 compose->popupmenu = popupmenu;
7962 compose->tmpl_menu = tmpl_menu;
7964 compose->mode = mode;
7965 compose->rmode = mode;
7967 compose->targetinfo = NULL;
7968 compose->replyinfo = NULL;
7969 compose->fwdinfo = NULL;
7971 compose->email_hashtable = g_hash_table_new_full(g_str_hash,
7972 g_str_equal, (GDestroyNotify) g_free, NULL);
7974 compose->replyto = NULL;
7976 compose->bcc = NULL;
7977 compose->followup_to = NULL;
7979 compose->ml_post = NULL;
7981 compose->inreplyto = NULL;
7982 compose->references = NULL;
7983 compose->msgid = NULL;
7984 compose->boundary = NULL;
7986 compose->autowrap = prefs_common.autowrap;
7987 compose->autoindent = prefs_common.auto_indent;
7988 compose->use_signing = FALSE;
7989 compose->use_encryption = FALSE;
7990 compose->privacy_system = NULL;
7991 compose->encdata = NULL;
7993 compose->modified = FALSE;
7995 compose->return_receipt = FALSE;
7997 compose->to_list = NULL;
7998 compose->newsgroup_list = NULL;
8000 compose->undostruct = undostruct;
8002 compose->sig_str = NULL;
8004 compose->exteditor_file = NULL;
8005 compose->exteditor_pid = -1;
8006 compose->exteditor_tag = -1;
8007 compose->exteditor_socket = NULL;
8008 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN; /* inhibit auto-drafting while loading */
8010 compose->folder_update_callback_id =
8011 hooks_register_hook(FOLDER_UPDATE_HOOKLIST,
8012 compose_update_folder_hook,
8013 (gpointer) compose);
8016 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
8017 if (mode != COMPOSE_REDIRECT) {
8018 if (prefs_common.enable_aspell && prefs_common.dictionary &&
8019 strcmp(prefs_common.dictionary, "")) {
8020 gtkaspell = gtkaspell_new(prefs_common.dictionary,
8021 prefs_common.alt_dictionary,
8022 conv_get_locale_charset_str(),
8023 prefs_common.misspelled_col,
8024 prefs_common.check_while_typing,
8025 prefs_common.recheck_when_changing_dict,
8026 prefs_common.use_alternate,
8027 prefs_common.use_both_dicts,
8028 GTK_TEXT_VIEW(text),
8029 GTK_WINDOW(compose->window),
8030 compose_dict_changed,
8031 compose_spell_menu_changed,
8034 alertpanel_error(_("Spell checker could not "
8036 gtkaspell_checkers_strerror());
8037 gtkaspell_checkers_reset_error();
8039 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", TRUE);
8043 compose->gtkaspell = gtkaspell;
8044 compose_spell_menu_changed(compose);
8045 claws_spell_entry_set_gtkaspell(CLAWS_SPELL_ENTRY(subject_entry), gtkaspell);
8048 compose_select_account(compose, account, TRUE);
8050 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", prefs_common.autowrap);
8051 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", prefs_common.auto_indent);
8053 if (account->set_autocc && account->auto_cc && mode != COMPOSE_REEDIT)
8054 compose_entry_append(compose, account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8056 if (account->set_autobcc && account->auto_bcc && mode != COMPOSE_REEDIT)
8057 compose_entry_append(compose, account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8059 if (account->set_autoreplyto && account->auto_replyto && mode != COMPOSE_REEDIT)
8060 compose_entry_append(compose, account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8062 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", compose->mode == COMPOSE_REPLY);
8063 if (account->protocol != A_NNTP)
8064 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
8065 prefs_common_translated_header_name("To:"));
8067 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
8068 prefs_common_translated_header_name("Newsgroups:"));
8070 #ifndef USE_ALT_ADDRBOOK
8071 addressbook_set_target_compose(compose);
8073 if (mode != COMPOSE_REDIRECT)
8074 compose_set_template_menu(compose);
8076 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Template", FALSE);
8079 compose_list = g_list_append(compose_list, compose);
8081 if (!prefs_common.show_ruler)
8082 gtk_widget_hide(ruler_hbox);
8084 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Tools/ShowRuler", prefs_common.show_ruler);
8087 compose->priority = PRIORITY_NORMAL;
8088 compose_update_priority_menu_item(compose);
8090 compose_set_out_encoding(compose);
8093 compose_update_actions_menu(compose);
8095 /* Privacy Systems menu */
8096 compose_update_privacy_systems_menu(compose);
8098 activate_privacy_system(compose, account, TRUE);
8099 toolbar_set_style(compose->toolbar->toolbar, compose->handlebox, prefs_common.toolbar_style);
8101 gtk_widget_realize(window);
8103 gtk_widget_show(window);
8109 static GtkWidget *compose_account_option_menu_create(Compose *compose)
8114 GtkWidget *optmenubox;
8115 GtkWidget *fromlabel;
8118 GtkWidget *from_name = NULL;
8120 gint num = 0, def_menu = 0;
8122 accounts = account_get_list();
8123 cm_return_val_if_fail(accounts != NULL, NULL);
8125 optmenubox = gtk_event_box_new();
8126 optmenu = gtkut_sc_combobox_create(optmenubox, FALSE);
8127 menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
8129 hbox = gtk_hbox_new(FALSE, 4);
8130 from_name = gtk_entry_new();
8132 g_signal_connect_after(G_OBJECT(from_name), "grab_focus",
8133 G_CALLBACK(compose_grab_focus_cb), compose);
8134 g_signal_connect_after(G_OBJECT(from_name), "activate",
8135 G_CALLBACK(from_name_activate_cb), optmenu);
8137 for (; accounts != NULL; accounts = accounts->next, num++) {
8138 PrefsAccount *ac = (PrefsAccount *)accounts->data;
8139 gchar *name, *from = NULL;
8141 if (ac == compose->account) def_menu = num;
8143 name = g_markup_printf_escaped("<i>%s</i>",
8146 if (ac == compose->account) {
8147 if (ac->name && *ac->name) {
8149 QUOTE_IF_REQUIRED_NORMAL(buf, ac->name, return NULL);
8150 from = g_strdup_printf("%s <%s>",
8152 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8154 from = g_strdup_printf("%s",
8156 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8159 COMBOBOX_ADD(menu, name, ac->account_id);
8164 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), def_menu);
8166 g_signal_connect(G_OBJECT(optmenu), "changed",
8167 G_CALLBACK(account_activated),
8169 g_signal_connect(G_OBJECT(from_name), "populate-popup",
8170 G_CALLBACK(compose_entry_popup_extend),
8173 fromlabel = gtk_label_new_with_mnemonic(_("_From:"));
8174 gtk_label_set_mnemonic_widget(GTK_LABEL(fromlabel), from_name);
8176 gtk_box_pack_start(GTK_BOX(hbox), fromlabel, FALSE, FALSE, 4);
8177 gtk_box_pack_start(GTK_BOX(hbox), optmenubox, FALSE, FALSE, 0);
8178 gtk_box_pack_start(GTK_BOX(hbox), from_name, TRUE, TRUE, 0);
8180 /* Putting only the GtkEntry into focus chain of parent hbox causes
8181 * the account selector combobox next to it to be unreachable when
8182 * navigating widgets in GtkTable with up/down arrow keys.
8183 * Note: gtk_widget_set_can_focus() was not enough. */
8185 l = g_list_prepend(l, from_name);
8186 gtk_container_set_focus_chain(GTK_CONTAINER(hbox), l);
8189 CLAWS_SET_TIP(optmenubox,
8190 _("Account to use for this email"));
8191 CLAWS_SET_TIP(from_name,
8192 _("Sender address to be used"));
8194 compose->account_combo = optmenu;
8195 compose->from_name = from_name;
8200 static void compose_set_priority_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8202 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8203 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8204 Compose *compose = (Compose *) data;
8206 compose->priority = value;
8210 static void compose_reply_change_mode(Compose *compose,
8213 gboolean was_modified = compose->modified;
8215 gboolean all = FALSE, ml = FALSE, sender = FALSE, followup = FALSE;
8217 cm_return_if_fail(compose->replyinfo != NULL);
8219 if (action == COMPOSE_REPLY && prefs_common.default_reply_list)
8221 if (action == COMPOSE_REPLY && compose->rmode == COMPOSE_FOLLOWUP_AND_REPLY_TO)
8223 if (action == COMPOSE_REPLY_TO_ALL)
8225 if (action == COMPOSE_REPLY_TO_SENDER)
8227 if (action == COMPOSE_REPLY_TO_LIST)
8230 compose_remove_header_entries(compose);
8231 compose_reply_set_entry(compose, compose->replyinfo, all, ml, sender, followup);
8232 if (compose->account->set_autocc && compose->account->auto_cc)
8233 compose_entry_append(compose, compose->account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8235 if (compose->account->set_autobcc && compose->account->auto_bcc)
8236 compose_entry_append(compose, compose->account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8238 if (compose->account->set_autoreplyto && compose->account->auto_replyto)
8239 compose_entry_append(compose, compose->account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8240 compose_show_first_last_header(compose, TRUE);
8241 compose->modified = was_modified;
8242 compose_set_title(compose);
8245 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8247 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8248 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8249 Compose *compose = (Compose *) data;
8252 compose_reply_change_mode(compose, value);
8255 static void compose_update_priority_menu_item(Compose * compose)
8257 GtkWidget *menuitem = NULL;
8258 switch (compose->priority) {
8259 case PRIORITY_HIGHEST:
8260 menuitem = gtk_ui_manager_get_widget
8261 (compose->ui_manager, "/Menu/Options/Priority/Highest");
8264 menuitem = gtk_ui_manager_get_widget
8265 (compose->ui_manager, "/Menu/Options/Priority/High");
8267 case PRIORITY_NORMAL:
8268 menuitem = gtk_ui_manager_get_widget
8269 (compose->ui_manager, "/Menu/Options/Priority/Normal");
8272 menuitem = gtk_ui_manager_get_widget
8273 (compose->ui_manager, "/Menu/Options/Priority/Low");
8275 case PRIORITY_LOWEST:
8276 menuitem = gtk_ui_manager_get_widget
8277 (compose->ui_manager, "/Menu/Options/Priority/Lowest");
8280 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8283 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data)
8285 Compose *compose = (Compose *) data;
8287 gboolean can_sign = FALSE, can_encrypt = FALSE;
8289 cm_return_if_fail(GTK_IS_CHECK_MENU_ITEM(widget));
8291 if (!gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget)))
8294 systemid = g_object_get_data(G_OBJECT(widget), "privacy_system");
8295 g_free(compose->privacy_system);
8296 compose->privacy_system = NULL;
8297 g_free(compose->encdata);
8298 compose->encdata = NULL;
8299 if (systemid != NULL) {
8300 compose->privacy_system = g_strdup(systemid);
8302 can_sign = privacy_system_can_sign(systemid);
8303 can_encrypt = privacy_system_can_encrypt(systemid);
8306 debug_print("activated privacy system: %s\n", systemid != NULL ? systemid : "None");
8308 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8309 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8312 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn)
8314 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8315 GtkWidget *menuitem = NULL;
8316 GList *children, *amenu;
8317 gboolean can_sign = FALSE, can_encrypt = FALSE;
8318 gboolean found = FALSE;
8320 if (compose->privacy_system != NULL) {
8322 menuitem = gtk_menu_item_get_submenu(GTK_MENU_ITEM(
8323 gtk_ui_manager_get_widget(compose->ui_manager, branch_path)));
8324 cm_return_if_fail(menuitem != NULL);
8326 children = gtk_container_get_children(GTK_CONTAINER(GTK_MENU_SHELL(menuitem)));
8329 while (amenu != NULL) {
8330 systemid = g_object_get_data(G_OBJECT(amenu->data), "privacy_system");
8331 if (systemid != NULL) {
8332 if (strcmp(systemid, compose->privacy_system) == 0 &&
8333 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8334 menuitem = GTK_WIDGET(amenu->data);
8336 can_sign = privacy_system_can_sign(systemid);
8337 can_encrypt = privacy_system_can_encrypt(systemid);
8341 } else if (strlen(compose->privacy_system) == 0 &&
8342 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8343 menuitem = GTK_WIDGET(amenu->data);
8346 can_encrypt = FALSE;
8351 amenu = amenu->next;
8353 g_list_free(children);
8354 if (menuitem != NULL)
8355 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8357 if (warn && !found && strlen(compose->privacy_system)) {
8358 alertpanel_warning(_("The privacy system '%s' cannot be loaded. You "
8359 "will not be able to sign or encrypt this message."),
8360 compose->privacy_system);
8364 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8365 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8368 static void compose_set_out_encoding(Compose *compose)
8370 CharSet out_encoding;
8371 const gchar *branch = NULL;
8372 out_encoding = conv_get_charset_from_str(prefs_common.outgoing_charset);
8374 switch(out_encoding) {
8375 case C_AUTO: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8376 case C_US_ASCII: branch = "Menu/Options/Encoding/" CS_US_ASCII; break;
8377 case C_UTF_8: branch = "Menu/Options/Encoding/" CS_UTF_8; break;
8378 case C_ISO_8859_2: branch = "Menu/Options/Encoding/" CS_ISO_8859_2; break;
8379 case C_ISO_8859_7: branch = "Menu/Options/Encoding/" CS_ISO_8859_7; break;
8380 case C_ISO_8859_9: branch = "Menu/Options/Encoding/" CS_ISO_8859_9; break;
8381 case C_ISO_8859_1: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_1; break;
8382 case C_ISO_8859_15: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_15; break;
8383 case C_WINDOWS_1252: branch = "Menu/Options/Encoding/Western/" CS_WINDOWS_1252; break;
8384 case C_ISO_8859_13: branch = "Menu/Options/Encoding/Baltic/" CS_ISO_8859_13; break;
8385 case C_ISO_8859_4: branch = "Menu/Options/Encoding/Baltic" CS_ISO_8859_4; break;
8386 case C_ISO_8859_8: branch = "Menu/Options/Encoding/Hebrew/" CS_ISO_8859_8; break;
8387 case C_WINDOWS_1255: branch = "Menu/Options/Encoding/Hebrew/" CS_WINDOWS_1255; break;
8388 case C_ISO_8859_6: branch = "Menu/Options/Encoding/Arabic/" CS_ISO_8859_6; break;
8389 case C_WINDOWS_1256: branch = "Menu/Options/Encoding/Arabic/" CS_WINDOWS_1256; break;
8390 case C_ISO_8859_5: branch = "Menu/Options/Encoding/Cyrillic/" CS_ISO_8859_5; break;
8391 case C_KOI8_R: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_R; break;
8392 case C_MACCYR: branch = "Menu/Options/Encoding/Cyrillic/" CS_MACCYR; break;
8393 case C_KOI8_U: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_U; break;
8394 case C_WINDOWS_1251: branch = "Menu/Options/Encoding/Cyrillic/" CS_WINDOWS_1251; break;
8395 case C_ISO_2022_JP: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP; break;
8396 case C_ISO_2022_JP_2: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP_2; break;
8397 case C_EUC_JP: branch = "Menu/Options/Encoding/Japanese/" CS_EUC_JP; break;
8398 case C_SHIFT_JIS: branch = "Menu/Options/Encoding/Japanese/" CS_SHIFT_JIS; break;
8399 case C_GB18030: branch = "Menu/Options/Encoding/Chinese/" CS_GB18030; break;
8400 case C_GB2312: branch = "Menu/Options/Encoding/Chinese/" CS_GB2312; break;
8401 case C_GBK: branch = "Menu/Options/Encoding/Chinese/" CS_GBK; break;
8402 case C_BIG5: branch = "Menu/Options/Encoding/Chinese/" CS_BIG5; break;
8403 case C_EUC_TW: branch = "Menu/Options/Encoding/Chinese/" CS_EUC_TW; break;
8404 case C_EUC_KR: branch = "Menu/Options/Encoding/Korean/" CS_EUC_KR; break;
8405 case C_ISO_2022_KR: branch = "Menu/Options/Encoding/Korean/" CS_ISO_2022_KR; break;
8406 case C_TIS_620: branch = "Menu/Options/Encoding/Thai/" CS_TIS_620; break;
8407 case C_WINDOWS_874: branch = "Menu/Options/Encoding/Thai/" CS_WINDOWS_874; break;
8408 default: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8410 cm_toggle_menu_set_active_full(compose->ui_manager, (gchar *)branch, TRUE);
8413 static void compose_set_template_menu(Compose *compose)
8415 GSList *tmpl_list, *cur;
8419 tmpl_list = template_get_config();
8421 menu = gtk_menu_new();
8423 gtk_menu_set_accel_group (GTK_MENU (menu),
8424 gtk_ui_manager_get_accel_group(compose->ui_manager));
8425 for (cur = tmpl_list; cur != NULL; cur = cur->next) {
8426 Template *tmpl = (Template *)cur->data;
8427 gchar *accel_path = NULL;
8428 item = gtk_menu_item_new_with_label(tmpl->name);
8429 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
8430 g_signal_connect(G_OBJECT(item), "activate",
8431 G_CALLBACK(compose_template_activate_cb),
8433 g_object_set_data(G_OBJECT(item), "template", tmpl);
8434 gtk_widget_show(item);
8435 accel_path = g_strconcat("<ComposeTemplates>" , "/", tmpl->name, NULL);
8436 gtk_menu_item_set_accel_path(GTK_MENU_ITEM(item), accel_path);
8440 gtk_widget_show(menu);
8441 gtk_menu_item_set_submenu(GTK_MENU_ITEM(compose->tmpl_menu), menu);
8444 void compose_update_actions_menu(Compose *compose)
8446 action_update_compose_menu(compose->ui_manager, "/Menu/Tools/Actions", compose);
8449 static void compose_update_privacy_systems_menu(Compose *compose)
8451 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8452 GSList *systems, *cur;
8454 GtkWidget *system_none;
8456 GtkWidget *privacy_menuitem = gtk_ui_manager_get_widget(compose->ui_manager, branch_path);
8457 GtkWidget *privacy_menu = gtk_menu_new();
8459 system_none = gtk_radio_menu_item_new_with_mnemonic(NULL, _("_None"));
8460 g_object_set_data_full(G_OBJECT(system_none), "privacy_system", NULL, NULL);
8462 g_signal_connect(G_OBJECT(system_none), "activate",
8463 G_CALLBACK(compose_set_privacy_system_cb), compose);
8465 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), system_none);
8466 gtk_widget_show(system_none);
8468 systems = privacy_get_system_ids();
8469 for (cur = systems; cur != NULL; cur = g_slist_next(cur)) {
8470 gchar *systemid = cur->data;
8472 group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(system_none));
8473 widget = gtk_radio_menu_item_new_with_label(group,
8474 privacy_system_get_name(systemid));
8475 g_object_set_data_full(G_OBJECT(widget), "privacy_system",
8476 g_strdup(systemid), g_free);
8477 g_signal_connect(G_OBJECT(widget), "activate",
8478 G_CALLBACK(compose_set_privacy_system_cb), compose);
8480 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), widget);
8481 gtk_widget_show(widget);
8484 g_slist_free(systems);
8485 gtk_menu_item_set_submenu(GTK_MENU_ITEM(privacy_menuitem), privacy_menu);
8486 gtk_widget_show_all(privacy_menu);
8487 gtk_widget_show_all(privacy_menuitem);
8490 void compose_reflect_prefs_all(void)
8495 for (cur = compose_list; cur != NULL; cur = cur->next) {
8496 compose = (Compose *)cur->data;
8497 compose_set_template_menu(compose);
8501 void compose_reflect_prefs_pixmap_theme(void)
8506 for (cur = compose_list; cur != NULL; cur = cur->next) {
8507 compose = (Compose *)cur->data;
8508 toolbar_update(TOOLBAR_COMPOSE, compose);
8512 static const gchar *compose_quote_char_from_context(Compose *compose)
8514 const gchar *qmark = NULL;
8516 cm_return_val_if_fail(compose != NULL, NULL);
8518 switch (compose->mode) {
8519 /* use forward-specific quote char */
8520 case COMPOSE_FORWARD:
8521 case COMPOSE_FORWARD_AS_ATTACH:
8522 case COMPOSE_FORWARD_INLINE:
8523 if (compose->folder && compose->folder->prefs &&
8524 compose->folder->prefs->forward_with_format)
8525 qmark = compose->folder->prefs->forward_quotemark;
8526 else if (compose->account->forward_with_format)
8527 qmark = compose->account->forward_quotemark;
8529 qmark = prefs_common.fw_quotemark;
8532 /* use reply-specific quote char in all other modes */
8534 if (compose->folder && compose->folder->prefs &&
8535 compose->folder->prefs->reply_with_format)
8536 qmark = compose->folder->prefs->reply_quotemark;
8537 else if (compose->account->reply_with_format)
8538 qmark = compose->account->reply_quotemark;
8540 qmark = prefs_common.quotemark;
8544 if (qmark == NULL || *qmark == '\0')
8550 static void compose_template_apply(Compose *compose, Template *tmpl,
8554 GtkTextBuffer *buffer;
8558 gchar *parsed_str = NULL;
8559 gint cursor_pos = 0;
8560 const gchar *err_msg = _("The body of the template has an error at line %d.");
8563 /* process the body */
8565 text = GTK_TEXT_VIEW(compose->text);
8566 buffer = gtk_text_view_get_buffer(text);
8569 qmark = compose_quote_char_from_context(compose);
8571 if (compose->replyinfo != NULL) {
8574 gtk_text_buffer_set_text(buffer, "", -1);
8575 mark = gtk_text_buffer_get_insert(buffer);
8576 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8578 parsed_str = compose_quote_fmt(compose, compose->replyinfo,
8579 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8581 } else if (compose->fwdinfo != NULL) {
8584 gtk_text_buffer_set_text(buffer, "", -1);
8585 mark = gtk_text_buffer_get_insert(buffer);
8586 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8588 parsed_str = compose_quote_fmt(compose, compose->fwdinfo,
8589 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8592 MsgInfo* dummyinfo = compose_msginfo_new_from_compose(compose);
8594 GtkTextIter start, end;
8597 gtk_text_buffer_get_start_iter(buffer, &start);
8598 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
8599 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
8601 /* clear the buffer now */
8603 gtk_text_buffer_set_text(buffer, "", -1);
8605 parsed_str = compose_quote_fmt(compose, dummyinfo,
8606 tmpl->value, qmark, tmp, FALSE, FALSE, err_msg);
8607 procmsg_msginfo_free( &dummyinfo );
8613 gtk_text_buffer_set_text(buffer, "", -1);
8614 mark = gtk_text_buffer_get_insert(buffer);
8615 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8618 if (replace && parsed_str && compose->account->auto_sig)
8619 compose_insert_sig(compose, FALSE);
8621 if (replace && parsed_str) {
8622 gtk_text_buffer_get_start_iter(buffer, &iter);
8623 gtk_text_buffer_place_cursor(buffer, &iter);
8627 cursor_pos = quote_fmt_get_cursor_pos();
8628 compose->set_cursor_pos = cursor_pos;
8629 if (cursor_pos == -1)
8631 gtk_text_buffer_get_start_iter(buffer, &iter);
8632 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
8633 gtk_text_buffer_place_cursor(buffer, &iter);
8636 /* process the other fields */
8638 compose_template_apply_fields(compose, tmpl);
8639 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
8640 quote_fmt_reset_vartable();
8641 compose_changed_cb(NULL, compose);
8644 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
8645 gtkaspell_highlight_all(compose->gtkaspell);
8649 static void compose_template_apply_fields(Compose *compose, Template *tmpl)
8651 MsgInfo* dummyinfo = NULL;
8652 MsgInfo *msginfo = NULL;
8655 if (compose->replyinfo != NULL)
8656 msginfo = compose->replyinfo;
8657 else if (compose->fwdinfo != NULL)
8658 msginfo = compose->fwdinfo;
8660 dummyinfo = compose_msginfo_new_from_compose(compose);
8661 msginfo = dummyinfo;
8664 if (tmpl->from && *tmpl->from != '\0') {
8666 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8667 compose->gtkaspell);
8669 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8671 quote_fmt_scan_string(tmpl->from);
8674 buf = quote_fmt_get_buffer();
8676 alertpanel_error(_("Template From format error."));
8678 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
8682 if (tmpl->to && *tmpl->to != '\0') {
8684 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8685 compose->gtkaspell);
8687 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8689 quote_fmt_scan_string(tmpl->to);
8692 buf = quote_fmt_get_buffer();
8694 alertpanel_error(_("Template To format error."));
8696 compose_entry_append(compose, buf, COMPOSE_TO, PREF_TEMPLATE);
8700 if (tmpl->cc && *tmpl->cc != '\0') {
8702 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8703 compose->gtkaspell);
8705 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8707 quote_fmt_scan_string(tmpl->cc);
8710 buf = quote_fmt_get_buffer();
8712 alertpanel_error(_("Template Cc format error."));
8714 compose_entry_append(compose, buf, COMPOSE_CC, PREF_TEMPLATE);
8718 if (tmpl->bcc && *tmpl->bcc != '\0') {
8720 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8721 compose->gtkaspell);
8723 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8725 quote_fmt_scan_string(tmpl->bcc);
8728 buf = quote_fmt_get_buffer();
8730 alertpanel_error(_("Template Bcc format error."));
8732 compose_entry_append(compose, buf, COMPOSE_BCC, PREF_TEMPLATE);
8736 if (tmpl->replyto && *tmpl->replyto != '\0') {
8738 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8739 compose->gtkaspell);
8741 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8743 quote_fmt_scan_string(tmpl->replyto);
8746 buf = quote_fmt_get_buffer();
8748 alertpanel_error(_("Template Reply-To format error."));
8750 compose_entry_append(compose, buf, COMPOSE_REPLYTO, PREF_TEMPLATE);
8754 /* process the subject */
8755 if (tmpl->subject && *tmpl->subject != '\0') {
8757 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8758 compose->gtkaspell);
8760 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8762 quote_fmt_scan_string(tmpl->subject);
8765 buf = quote_fmt_get_buffer();
8767 alertpanel_error(_("Template subject format error."));
8769 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
8773 procmsg_msginfo_free( &dummyinfo );
8776 static void compose_destroy(Compose *compose)
8778 GtkAllocation allocation;
8779 GtkTextBuffer *buffer;
8780 GtkClipboard *clipboard;
8782 compose_list = g_list_remove(compose_list, compose);
8784 if (compose->updating) {
8785 debug_print("danger, not destroying anything now\n");
8786 compose->deferred_destroy = TRUE;
8790 /* NOTE: address_completion_end() does nothing with the window
8791 * however this may change. */
8792 address_completion_end(compose->window);
8794 slist_free_strings_full(compose->to_list);
8795 slist_free_strings_full(compose->newsgroup_list);
8796 slist_free_strings_full(compose->header_list);
8798 slist_free_strings_full(extra_headers);
8799 extra_headers = NULL;
8801 compose->header_list = compose->newsgroup_list = compose->to_list = NULL;
8803 g_hash_table_destroy(compose->email_hashtable);
8805 hooks_unregister_hook(FOLDER_UPDATE_HOOKLIST,
8806 compose->folder_update_callback_id);
8808 procmsg_msginfo_free(&(compose->targetinfo));
8809 procmsg_msginfo_free(&(compose->replyinfo));
8810 procmsg_msginfo_free(&(compose->fwdinfo));
8812 g_free(compose->replyto);
8813 g_free(compose->cc);
8814 g_free(compose->bcc);
8815 g_free(compose->newsgroups);
8816 g_free(compose->followup_to);
8818 g_free(compose->ml_post);
8820 g_free(compose->inreplyto);
8821 g_free(compose->references);
8822 g_free(compose->msgid);
8823 g_free(compose->boundary);
8825 g_free(compose->redirect_filename);
8826 if (compose->undostruct)
8827 undo_destroy(compose->undostruct);
8829 g_free(compose->sig_str);
8831 g_free(compose->exteditor_file);
8833 g_free(compose->orig_charset);
8835 g_free(compose->privacy_system);
8836 g_free(compose->encdata);
8838 #ifndef USE_ALT_ADDRBOOK
8839 if (addressbook_get_target_compose() == compose)
8840 addressbook_set_target_compose(NULL);
8843 if (compose->gtkaspell) {
8844 gtkaspell_delete(compose->gtkaspell);
8845 compose->gtkaspell = NULL;
8849 if (!compose->batch) {
8850 gtk_widget_get_allocation(compose->window, &allocation);
8851 prefs_common.compose_width = allocation.width;
8852 prefs_common.compose_height = allocation.height;
8855 if (!gtk_widget_get_parent(compose->paned))
8856 gtk_widget_destroy(compose->paned);
8857 gtk_widget_destroy(compose->popupmenu);
8859 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
8860 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
8861 gtk_text_buffer_remove_selection_clipboard(buffer, clipboard);
8863 gtk_widget_destroy(compose->window);
8864 toolbar_destroy(compose->toolbar);
8865 g_free(compose->toolbar);
8866 cm_mutex_free(compose->mutex);
8870 static void compose_attach_info_free(AttachInfo *ainfo)
8872 g_free(ainfo->file);
8873 g_free(ainfo->content_type);
8874 g_free(ainfo->name);
8875 g_free(ainfo->charset);
8879 static void compose_attach_update_label(Compose *compose)
8884 GtkTreeModel *model;
8889 model = gtk_tree_view_get_model(GTK_TREE_VIEW(compose->attach_clist));
8890 if(!gtk_tree_model_get_iter_first(model, &iter)) {
8891 gtk_label_set_text(GTK_LABEL(compose->attach_label), "");
8895 while(gtk_tree_model_iter_next(model, &iter))
8898 text = g_strdup_printf("(%d)", i);
8899 gtk_label_set_text(GTK_LABEL(compose->attach_label), text);
8903 static void compose_attach_remove_selected(GtkAction *action, gpointer data)
8905 Compose *compose = (Compose *)data;
8906 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
8907 GtkTreeSelection *selection;
8909 GtkTreeModel *model;
8911 selection = gtk_tree_view_get_selection(tree_view);
8912 sel = gtk_tree_selection_get_selected_rows(selection, &model);
8917 for (cur = sel; cur != NULL; cur = cur->next) {
8918 GtkTreePath *path = cur->data;
8919 GtkTreeRowReference *ref = gtk_tree_row_reference_new
8922 gtk_tree_path_free(path);
8925 for (cur = sel; cur != NULL; cur = cur->next) {
8926 GtkTreeRowReference *ref = cur->data;
8927 GtkTreePath *path = gtk_tree_row_reference_get_path(ref);
8930 if (gtk_tree_model_get_iter(model, &iter, path))
8931 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
8933 gtk_tree_path_free(path);
8934 gtk_tree_row_reference_free(ref);
8938 compose_attach_update_label(compose);
8941 static struct _AttachProperty
8944 GtkWidget *mimetype_entry;
8945 GtkWidget *encoding_optmenu;
8946 GtkWidget *path_entry;
8947 GtkWidget *filename_entry;
8949 GtkWidget *cancel_btn;
8952 static void gtk_tree_path_free_(gpointer ptr, gpointer data)
8954 gtk_tree_path_free((GtkTreePath *)ptr);
8957 static void compose_attach_property(GtkAction *action, gpointer data)
8959 Compose *compose = (Compose *)data;
8960 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
8962 GtkComboBox *optmenu;
8963 GtkTreeSelection *selection;
8965 GtkTreeModel *model;
8968 static gboolean cancelled;
8970 /* only if one selected */
8971 selection = gtk_tree_view_get_selection(tree_view);
8972 if (gtk_tree_selection_count_selected_rows(selection) != 1)
8975 sel = gtk_tree_selection_get_selected_rows(selection, &model);
8979 path = (GtkTreePath *) sel->data;
8980 gtk_tree_model_get_iter(model, &iter, path);
8981 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
8984 g_list_foreach(sel, gtk_tree_path_free_, NULL);
8990 if (!attach_prop.window)
8991 compose_attach_property_create(&cancelled);
8992 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), TRUE);
8993 gtk_widget_grab_focus(attach_prop.ok_btn);
8994 gtk_widget_show(attach_prop.window);
8995 gtk_window_set_transient_for(GTK_WINDOW(attach_prop.window),
8996 GTK_WINDOW(compose->window));
8998 optmenu = GTK_COMBO_BOX(attach_prop.encoding_optmenu);
8999 if (ainfo->encoding == ENC_UNKNOWN)
9000 combobox_select_by_data(optmenu, ENC_BASE64);
9002 combobox_select_by_data(optmenu, ainfo->encoding);
9004 gtk_entry_set_text(GTK_ENTRY(attach_prop.mimetype_entry),
9005 ainfo->content_type ? ainfo->content_type : "");
9006 gtk_entry_set_text(GTK_ENTRY(attach_prop.path_entry),
9007 ainfo->file ? ainfo->file : "");
9008 gtk_entry_set_text(GTK_ENTRY(attach_prop.filename_entry),
9009 ainfo->name ? ainfo->name : "");
9012 const gchar *entry_text;
9014 gchar *cnttype = NULL;
9021 gtk_widget_hide(attach_prop.window);
9022 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), FALSE);
9027 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.mimetype_entry));
9028 if (*entry_text != '\0') {
9031 text = g_strstrip(g_strdup(entry_text));
9032 if ((p = strchr(text, '/')) && !strchr(p + 1, '/')) {
9033 cnttype = g_strdup(text);
9036 alertpanel_error(_("Invalid MIME type."));
9042 ainfo->encoding = combobox_get_active_data(optmenu);
9044 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.path_entry));
9045 if (*entry_text != '\0') {
9046 if (is_file_exist(entry_text) &&
9047 (size = get_file_size(entry_text)) > 0)
9048 file = g_strdup(entry_text);
9051 (_("File doesn't exist or is empty."));
9057 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.filename_entry));
9058 if (*entry_text != '\0') {
9059 g_free(ainfo->name);
9060 ainfo->name = g_strdup(entry_text);
9064 g_free(ainfo->content_type);
9065 ainfo->content_type = cnttype;
9068 g_free(ainfo->file);
9072 ainfo->size = (goffset)size;
9074 /* update tree store */
9075 text = to_human_readable(ainfo->size);
9076 gtk_tree_model_get_iter(model, &iter, path);
9077 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
9078 COL_MIMETYPE, ainfo->content_type,
9080 COL_NAME, ainfo->name,
9081 COL_CHARSET, ainfo->charset,
9087 gtk_tree_path_free(path);
9090 #define SET_LABEL_AND_ENTRY(str, entry, top) \
9092 label = gtk_label_new(str); \
9093 gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), \
9094 GTK_FILL, 0, 0, 0); \
9095 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); \
9097 entry = gtk_entry_new(); \
9098 gtk_table_attach(GTK_TABLE(table), entry, 1, 2, top, (top + 1), \
9099 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); \
9102 static void compose_attach_property_create(gboolean *cancelled)
9108 GtkWidget *mimetype_entry;
9111 GtkListStore *optmenu_menu;
9112 GtkWidget *path_entry;
9113 GtkWidget *filename_entry;
9116 GtkWidget *cancel_btn;
9117 GList *mime_type_list, *strlist;
9120 debug_print("Creating attach_property window...\n");
9122 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose_attach_property");
9123 gtk_widget_set_size_request(window, 480, -1);
9124 gtk_container_set_border_width(GTK_CONTAINER(window), 8);
9125 gtk_window_set_title(GTK_WINDOW(window), _("Properties"));
9126 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
9127 g_signal_connect(G_OBJECT(window), "delete_event",
9128 G_CALLBACK(attach_property_delete_event),
9130 g_signal_connect(G_OBJECT(window), "key_press_event",
9131 G_CALLBACK(attach_property_key_pressed),
9134 vbox = gtk_vbox_new(FALSE, 8);
9135 gtk_container_add(GTK_CONTAINER(window), vbox);
9137 table = gtk_table_new(4, 2, FALSE);
9138 gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
9139 gtk_table_set_row_spacings(GTK_TABLE(table), 8);
9140 gtk_table_set_col_spacings(GTK_TABLE(table), 8);
9142 label = gtk_label_new(_("MIME type"));
9143 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, (0 + 1),
9145 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
9146 #if !GTK_CHECK_VERSION(2, 24, 0)
9147 mimetype_entry = gtk_combo_box_entry_new_text();
9149 mimetype_entry = gtk_combo_box_text_new_with_entry();
9151 gtk_table_attach(GTK_TABLE(table), mimetype_entry, 1, 2, 0, (0 + 1),
9152 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
9154 /* stuff with list */
9155 mime_type_list = procmime_get_mime_type_list();
9157 for (; mime_type_list != NULL; mime_type_list = mime_type_list->next) {
9158 MimeType *type = (MimeType *) mime_type_list->data;
9161 tmp = g_strdup_printf("%s/%s", type->type, type->sub_type);
9163 if (g_list_find_custom(strlist, tmp, (GCompareFunc)strcmp2))
9166 strlist = g_list_insert_sorted(strlist, (gpointer)tmp,
9167 (GCompareFunc)strcmp2);
9170 for (mime_type_list = strlist; mime_type_list != NULL;
9171 mime_type_list = mime_type_list->next) {
9172 #if !GTK_CHECK_VERSION(2, 24, 0)
9173 gtk_combo_box_append_text(GTK_COMBO_BOX(mimetype_entry), mime_type_list->data);
9175 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(mimetype_entry), mime_type_list->data);
9177 g_free(mime_type_list->data);
9179 g_list_free(strlist);
9180 gtk_combo_box_set_active(GTK_COMBO_BOX(mimetype_entry), 0);
9181 mimetype_entry = gtk_bin_get_child(GTK_BIN((mimetype_entry)));
9183 label = gtk_label_new(_("Encoding"));
9184 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2,
9186 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
9188 hbox = gtk_hbox_new(FALSE, 0);
9189 gtk_table_attach(GTK_TABLE(table), hbox, 1, 2, 1, 2,
9190 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
9192 optmenu = gtkut_sc_combobox_create(NULL, TRUE);
9193 optmenu_menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
9195 COMBOBOX_ADD(optmenu_menu, "7bit", ENC_7BIT);
9196 COMBOBOX_ADD(optmenu_menu, "8bit", ENC_8BIT);
9197 COMBOBOX_ADD(optmenu_menu, "quoted-printable", ENC_QUOTED_PRINTABLE);
9198 COMBOBOX_ADD(optmenu_menu, "base64", ENC_BASE64);
9199 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), 0);
9201 gtk_box_pack_start(GTK_BOX(hbox), optmenu, TRUE, TRUE, 0);
9203 SET_LABEL_AND_ENTRY(_("Path"), path_entry, 2);
9204 SET_LABEL_AND_ENTRY(_("File name"), filename_entry, 3);
9206 gtkut_stock_button_set_create(&hbbox, &cancel_btn, GTK_STOCK_CANCEL,
9207 &ok_btn, GTK_STOCK_OK,
9209 gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
9210 gtk_widget_grab_default(ok_btn);
9212 g_signal_connect(G_OBJECT(ok_btn), "clicked",
9213 G_CALLBACK(attach_property_ok),
9215 g_signal_connect(G_OBJECT(cancel_btn), "clicked",
9216 G_CALLBACK(attach_property_cancel),
9219 gtk_widget_show_all(vbox);
9221 attach_prop.window = window;
9222 attach_prop.mimetype_entry = mimetype_entry;
9223 attach_prop.encoding_optmenu = optmenu;
9224 attach_prop.path_entry = path_entry;
9225 attach_prop.filename_entry = filename_entry;
9226 attach_prop.ok_btn = ok_btn;
9227 attach_prop.cancel_btn = cancel_btn;
9230 #undef SET_LABEL_AND_ENTRY
9232 static void attach_property_ok(GtkWidget *widget, gboolean *cancelled)
9238 static void attach_property_cancel(GtkWidget *widget, gboolean *cancelled)
9244 static gint attach_property_delete_event(GtkWidget *widget, GdkEventAny *event,
9245 gboolean *cancelled)
9253 static gboolean attach_property_key_pressed(GtkWidget *widget,
9255 gboolean *cancelled)
9257 if (event && event->keyval == GDK_KEY_Escape) {
9261 if (event && event->keyval == GDK_KEY_Return) {
9269 static void compose_exec_ext_editor(Compose *compose)
9274 GdkNativeWindow socket_wid = 0;
9278 tmp = g_strdup_printf("%s%ctmpmsg.%p", get_tmp_dir(),
9279 G_DIR_SEPARATOR, compose);
9281 if (compose_get_ext_editor_uses_socket()) {
9282 /* Only allow one socket */
9283 if (compose->exteditor_socket != NULL) {
9284 if (gtk_widget_is_focus(compose->exteditor_socket)) {
9285 /* Move the focus off of the socket */
9286 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9291 /* Create the receiving GtkSocket */
9292 socket = gtk_socket_new ();
9293 g_signal_connect (GTK_OBJECT(socket), "plug-removed",
9294 G_CALLBACK(compose_ext_editor_plug_removed_cb),
9296 gtk_box_pack_start(GTK_BOX(compose->edit_vbox), socket, TRUE, TRUE, 0);
9297 gtk_widget_set_size_request(socket, prefs_common.compose_width, -1);
9298 /* Realize the socket so that we can use its ID */
9299 gtk_widget_realize(socket);
9300 socket_wid = gtk_socket_get_id(GTK_SOCKET (socket));
9301 compose->exteditor_socket = socket;
9304 if (pipe(pipe_fds) < 0) {
9310 if ((pid = fork()) < 0) {
9317 /* close the write side of the pipe */
9320 compose->exteditor_file = g_strdup(tmp);
9321 compose->exteditor_pid = pid;
9323 compose_set_ext_editor_sensitive(compose, FALSE);
9326 compose->exteditor_ch = g_io_channel_unix_new(pipe_fds[0]);
9328 compose->exteditor_ch = g_io_channel_win32_new_fd(pipe_fds[0]);
9330 compose->exteditor_tag = g_io_add_watch(compose->exteditor_ch,
9334 } else { /* process-monitoring process */
9340 /* close the read side of the pipe */
9343 if (compose_write_body_to_file(compose, tmp) < 0) {
9344 fd_write_all(pipe_fds[1], "2\n", 2);
9348 pid_ed = compose_exec_ext_editor_real(tmp, socket_wid);
9350 fd_write_all(pipe_fds[1], "1\n", 2);
9354 /* wait until editor is terminated */
9355 waitpid(pid_ed, NULL, 0);
9357 fd_write_all(pipe_fds[1], "0\n", 2);
9364 #endif /* G_OS_UNIX */
9368 static gboolean compose_get_ext_editor_cmd_valid()
9370 gboolean has_s = FALSE;
9371 gboolean has_w = FALSE;
9372 const gchar *p = prefs_common_get_ext_editor_cmd();
9375 while ((p = strchr(p, '%'))) {
9381 } else if (*p == 'w') {
9392 static gint compose_exec_ext_editor_real(const gchar *file, GdkNativeWindow socket_wid)
9399 cm_return_val_if_fail(file != NULL, -1);
9401 if ((pid = fork()) < 0) {
9406 if (pid != 0) return pid;
9408 /* grandchild process */
9410 if (setpgid(0, getppid()))
9413 if (compose_get_ext_editor_cmd_valid()) {
9414 if (compose_get_ext_editor_uses_socket()) {
9415 p = g_strdup(prefs_common_get_ext_editor_cmd());
9416 s = strstr(p, "%w");
9418 if (strstr(p, "%s") < s)
9419 g_snprintf(buf, sizeof(buf), p, file, socket_wid);
9421 g_snprintf(buf, sizeof(buf), p, socket_wid, file);
9424 g_snprintf(buf, sizeof(buf),
9425 prefs_common_get_ext_editor_cmd(), file);
9428 if (prefs_common_get_ext_editor_cmd())
9429 g_warning("External editor command-line is invalid: '%s'",
9430 prefs_common_get_ext_editor_cmd());
9431 g_snprintf(buf, sizeof(buf), DEFAULT_EDITOR_CMD, file);
9434 cmdline = strsplit_with_quote(buf, " ", 1024);
9435 execvp(cmdline[0], cmdline);
9438 g_strfreev(cmdline);
9443 static gboolean compose_ext_editor_kill(Compose *compose)
9445 pid_t pgid = compose->exteditor_pid * -1;
9448 ret = kill(pgid, 0);
9450 if (ret == 0 || (ret == -1 && EPERM == errno)) {
9454 msg = g_strdup_printf
9455 (_("The external editor is still working.\n"
9456 "Force terminating the process?\n"
9457 "process group id: %d"), -pgid);
9458 val = alertpanel_full(_("Notice"), msg, GTK_STOCK_NO, GTK_STOCK_YES,
9459 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
9463 if (val == G_ALERTALTERNATE) {
9464 g_source_remove(compose->exteditor_tag);
9465 g_io_channel_shutdown(compose->exteditor_ch,
9467 g_io_channel_unref(compose->exteditor_ch);
9469 if (kill(pgid, SIGTERM) < 0) perror("kill");
9470 waitpid(compose->exteditor_pid, NULL, 0);
9472 g_warning("Terminated process group id: %d. "
9473 "Temporary file: %s", -pgid, compose->exteditor_file);
9475 compose_set_ext_editor_sensitive(compose, TRUE);
9477 g_free(compose->exteditor_file);
9478 compose->exteditor_file = NULL;
9479 compose->exteditor_pid = -1;
9480 compose->exteditor_ch = NULL;
9481 compose->exteditor_tag = -1;
9489 static gboolean compose_input_cb(GIOChannel *source, GIOCondition condition,
9493 Compose *compose = (Compose *)data;
9496 debug_print("Compose: input from monitoring process\n");
9498 if (g_io_channel_read_chars(source, buf, sizeof(buf), &bytes_read, NULL) != G_IO_STATUS_NORMAL) {
9503 g_io_channel_shutdown(source, FALSE, NULL);
9504 g_io_channel_unref(source);
9506 waitpid(compose->exteditor_pid, NULL, 0);
9508 if (buf[0] == '0') { /* success */
9509 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9510 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
9511 GtkTextIter start, end;
9514 gtk_text_buffer_set_text(buffer, "", -1);
9515 compose_insert_file(compose, compose->exteditor_file);
9516 compose_changed_cb(NULL, compose);
9517 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
9519 if (claws_unlink(compose->exteditor_file) < 0)
9520 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9522 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
9523 gtk_text_buffer_get_start_iter(buffer, &start);
9524 gtk_text_buffer_get_end_iter(buffer, &end);
9525 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
9526 if (chars && strlen(chars) > 0)
9527 compose->modified = TRUE;
9529 } else if (buf[0] == '1') { /* failed */
9530 g_warning("Couldn't exec external editor");
9531 if (claws_unlink(compose->exteditor_file) < 0)
9532 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9533 } else if (buf[0] == '2') {
9534 g_warning("Couldn't write to file");
9535 } else if (buf[0] == '3') {
9536 g_warning("Pipe read failed");
9539 compose_set_ext_editor_sensitive(compose, TRUE);
9541 g_free(compose->exteditor_file);
9542 compose->exteditor_file = NULL;
9543 compose->exteditor_pid = -1;
9544 compose->exteditor_ch = NULL;
9545 compose->exteditor_tag = -1;
9546 if (compose->exteditor_socket) {
9547 gtk_widget_destroy(compose->exteditor_socket);
9548 compose->exteditor_socket = NULL;
9555 static char *ext_editor_menu_entries[] = {
9556 "Menu/Message/Send",
9557 "Menu/Message/SendLater",
9558 "Menu/Message/InsertFile",
9559 "Menu/Message/InsertSig",
9560 "Menu/Message/ReplaceSig",
9561 "Menu/Message/Save",
9562 "Menu/Message/Print",
9567 "Menu/Tools/ShowRuler",
9568 "Menu/Tools/Actions",
9573 static void compose_set_ext_editor_sensitive(Compose *compose,
9578 for (i = 0; ext_editor_menu_entries[i]; ++i) {
9579 cm_menu_set_sensitive_full(compose->ui_manager,
9580 ext_editor_menu_entries[i], sensitive);
9583 if (compose_get_ext_editor_uses_socket()) {
9585 if (compose->exteditor_socket)
9586 gtk_widget_hide(compose->exteditor_socket);
9587 gtk_widget_show(compose->scrolledwin);
9588 if (prefs_common.show_ruler)
9589 gtk_widget_show(compose->ruler_hbox);
9590 /* Fix the focus, as it doesn't go anywhere when the
9591 * socket is hidden or destroyed */
9592 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9594 g_assert (compose->exteditor_socket != NULL);
9595 /* Fix the focus, as it doesn't go anywhere when the
9596 * edit box is hidden */
9597 if (gtk_widget_is_focus(compose->text))
9598 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9599 gtk_widget_hide(compose->scrolledwin);
9600 gtk_widget_hide(compose->ruler_hbox);
9601 gtk_widget_show(compose->exteditor_socket);
9604 gtk_widget_set_sensitive(compose->text, sensitive);
9606 if (compose->toolbar->send_btn)
9607 gtk_widget_set_sensitive(compose->toolbar->send_btn, sensitive);
9608 if (compose->toolbar->sendl_btn)
9609 gtk_widget_set_sensitive(compose->toolbar->sendl_btn, sensitive);
9610 if (compose->toolbar->draft_btn)
9611 gtk_widget_set_sensitive(compose->toolbar->draft_btn, sensitive);
9612 if (compose->toolbar->insert_btn)
9613 gtk_widget_set_sensitive(compose->toolbar->insert_btn, sensitive);
9614 if (compose->toolbar->sig_btn)
9615 gtk_widget_set_sensitive(compose->toolbar->sig_btn, sensitive);
9616 if (compose->toolbar->exteditor_btn)
9617 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, sensitive);
9618 if (compose->toolbar->linewrap_current_btn)
9619 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, sensitive);
9620 if (compose->toolbar->linewrap_all_btn)
9621 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, sensitive);
9624 static gboolean compose_get_ext_editor_uses_socket()
9626 return (prefs_common_get_ext_editor_cmd() &&
9627 strstr(prefs_common_get_ext_editor_cmd(), "%w"));
9630 static gboolean compose_ext_editor_plug_removed_cb(GtkSocket *socket, Compose *compose)
9632 compose->exteditor_socket = NULL;
9633 /* returning FALSE allows destruction of the socket */
9636 #endif /* G_OS_UNIX */
9639 * compose_undo_state_changed:
9641 * Change the sensivity of the menuentries undo and redo
9643 static void compose_undo_state_changed(UndoMain *undostruct, gint undo_state,
9644 gint redo_state, gpointer data)
9646 Compose *compose = (Compose *)data;
9648 switch (undo_state) {
9649 case UNDO_STATE_TRUE:
9650 if (!undostruct->undo_state) {
9651 undostruct->undo_state = TRUE;
9652 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", TRUE);
9655 case UNDO_STATE_FALSE:
9656 if (undostruct->undo_state) {
9657 undostruct->undo_state = FALSE;
9658 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
9661 case UNDO_STATE_UNCHANGED:
9663 case UNDO_STATE_REFRESH:
9664 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", undostruct->undo_state);
9667 g_warning("Undo state not recognized");
9671 switch (redo_state) {
9672 case UNDO_STATE_TRUE:
9673 if (!undostruct->redo_state) {
9674 undostruct->redo_state = TRUE;
9675 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", TRUE);
9678 case UNDO_STATE_FALSE:
9679 if (undostruct->redo_state) {
9680 undostruct->redo_state = FALSE;
9681 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
9684 case UNDO_STATE_UNCHANGED:
9686 case UNDO_STATE_REFRESH:
9687 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", undostruct->redo_state);
9690 g_warning("Redo state not recognized");
9695 /* callback functions */
9697 static void compose_notebook_size_alloc(GtkNotebook *notebook,
9698 GtkAllocation *allocation,
9701 prefs_common.compose_notebook_height = gtk_paned_get_position(paned);
9704 /* compose_edit_size_alloc() - called when resized. don't know whether Gtk
9705 * includes "non-client" (windows-izm) in calculation, so this calculation
9706 * may not be accurate.
9708 static gboolean compose_edit_size_alloc(GtkEditable *widget,
9709 GtkAllocation *allocation,
9710 GtkSHRuler *shruler)
9712 if (prefs_common.show_ruler) {
9713 gint char_width = 0, char_height = 0;
9714 gint line_width_in_chars;
9716 gtkut_get_font_size(GTK_WIDGET(widget),
9717 &char_width, &char_height);
9718 line_width_in_chars =
9719 (allocation->width - allocation->x) / char_width;
9721 /* got the maximum */
9722 gtk_shruler_set_range(GTK_SHRULER(shruler),
9723 0.0, line_width_in_chars, 0);
9732 ComposePrefType type;
9733 gboolean entry_marked;
9736 static void account_activated(GtkComboBox *optmenu, gpointer data)
9738 Compose *compose = (Compose *)data;
9741 gchar *folderidentifier;
9742 gint account_id = 0;
9745 GSList *list, *saved_list = NULL;
9746 HeaderEntryState *state;
9747 GtkRcStyle *style = NULL;
9748 #if !GTK_CHECK_VERSION(3, 0, 0)
9749 static GdkColor yellow;
9750 static gboolean color_set = FALSE;
9752 static GdkColor yellow = { (guint32)0, (guint32)0xf5, (guint32)0xf6, (guint32)0xbe };
9755 /* Get ID of active account in the combo box */
9756 menu = gtk_combo_box_get_model(optmenu);
9757 cm_return_if_fail(gtk_combo_box_get_active_iter(optmenu, &iter));
9758 gtk_tree_model_get(menu, &iter, 1, &account_id, -1);
9760 ac = account_find_from_id(account_id);
9761 cm_return_if_fail(ac != NULL);
9763 if (ac != compose->account) {
9764 compose_select_account(compose, ac, FALSE);
9766 for (list = compose->header_list; list; list = list->next) {
9767 ComposeHeaderEntry *hentry=(ComposeHeaderEntry *)list->data;
9769 if (hentry->type == PREF_ACCOUNT || !list->next) {
9770 compose_destroy_headerentry(compose, hentry);
9774 state = g_malloc0(sizeof(HeaderEntryState));
9775 state->header = gtk_editable_get_chars(GTK_EDITABLE(
9776 gtk_bin_get_child(GTK_BIN(hentry->combo))), 0, -1);
9777 state->entry = gtk_editable_get_chars(
9778 GTK_EDITABLE(hentry->entry), 0, -1);
9779 state->type = hentry->type;
9781 #if !GTK_CHECK_VERSION(3, 0, 0)
9783 gdk_color_parse("#f5f6be", &yellow);
9784 color_set = gdk_colormap_alloc_color(
9785 gdk_colormap_get_system(),
9786 &yellow, FALSE, TRUE);
9790 style = gtk_widget_get_modifier_style(hentry->entry);
9791 state->entry_marked = gdk_color_equal(&yellow,
9792 &style->base[GTK_STATE_NORMAL]);
9794 saved_list = g_slist_append(saved_list, state);
9795 compose_destroy_headerentry(compose, hentry);
9798 compose->header_last = NULL;
9799 g_slist_free(compose->header_list);
9800 compose->header_list = NULL;
9801 compose->header_nextrow = 1;
9802 compose_create_header_entry(compose);
9804 if (ac->set_autocc && ac->auto_cc)
9805 compose_entry_append(compose, ac->auto_cc,
9806 COMPOSE_CC, PREF_ACCOUNT);
9808 if (ac->set_autobcc && ac->auto_bcc)
9809 compose_entry_append(compose, ac->auto_bcc,
9810 COMPOSE_BCC, PREF_ACCOUNT);
9812 if (ac->set_autoreplyto && ac->auto_replyto)
9813 compose_entry_append(compose, ac->auto_replyto,
9814 COMPOSE_REPLYTO, PREF_ACCOUNT);
9816 for (list = saved_list; list; list = list->next) {
9817 state = (HeaderEntryState *) list->data;
9819 compose_add_header_entry(compose, state->header,
9820 state->entry, state->type);
9821 if (state->entry_marked)
9822 compose_entry_mark_default_to(compose, state->entry);
9824 g_free(state->header);
9825 g_free(state->entry);
9828 g_slist_free(saved_list);
9830 combobox_select_by_data(GTK_COMBO_BOX(compose->header_last->combo),
9831 (ac->protocol == A_NNTP) ?
9832 COMPOSE_NEWSGROUPS : COMPOSE_TO);
9835 /* Set message save folder */
9836 if (account_get_special_folder(compose->account, F_OUTBOX)) {
9837 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
9839 g_signal_connect(G_OBJECT(compose->savemsg_checkbtn), "toggled",
9840 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
9842 compose_set_save_to(compose, NULL);
9843 if (account_get_special_folder(compose->account, F_OUTBOX)) {
9844 folderidentifier = folder_item_get_identifier(account_get_special_folder
9845 (compose->account, F_OUTBOX));
9846 compose_set_save_to(compose, folderidentifier);
9847 g_free(folderidentifier);
9851 static void attach_selected(GtkTreeView *tree_view, GtkTreePath *tree_path,
9852 GtkTreeViewColumn *column, Compose *compose)
9854 compose_attach_property(NULL, compose);
9857 static gboolean attach_button_pressed(GtkWidget *widget, GdkEventButton *event,
9860 Compose *compose = (Compose *)data;
9861 GtkTreeSelection *attach_selection;
9862 gint attach_nr_selected;
9865 if (!event) return FALSE;
9867 if (event->button == 3) {
9868 attach_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
9869 attach_nr_selected = gtk_tree_selection_count_selected_rows(attach_selection);
9871 /* If no rows, or just one row is selected, right-click should
9872 * open menu relevant to the row being right-clicked on. We
9873 * achieve that by selecting the clicked row first. If more
9874 * than one row is selected, we shouldn't modify the selection,
9875 * as user may want to remove selected rows (attachments). */
9876 if (attach_nr_selected < 2) {
9877 gtk_tree_selection_unselect_all(attach_selection);
9878 attach_nr_selected = 0;
9879 gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget),
9880 event->x, event->y, &path, NULL, NULL, NULL);
9882 gtk_tree_selection_select_path(attach_selection, path);
9883 gtk_tree_path_free(path);
9884 attach_nr_selected++;
9888 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", (attach_nr_selected > 0));
9889 /* Properties menu item makes no sense with more than one row
9890 * selected, the properties dialog can only edit one attachment. */
9891 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", (attach_nr_selected == 1));
9893 gtk_menu_popup(GTK_MENU(compose->popupmenu), NULL, NULL,
9894 NULL, NULL, event->button, event->time);
9901 static gboolean attach_key_pressed(GtkWidget *widget, GdkEventKey *event,
9904 Compose *compose = (Compose *)data;
9906 if (!event) return FALSE;
9908 switch (event->keyval) {
9909 case GDK_KEY_Delete:
9910 compose_attach_remove_selected(NULL, compose);
9916 static void compose_allow_user_actions (Compose *compose, gboolean allow)
9918 toolbar_comp_set_sensitive(compose, allow);
9919 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message", allow);
9920 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", allow);
9922 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", allow);
9924 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", allow);
9925 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools", allow);
9926 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Help", allow);
9928 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), allow);
9932 static void compose_send_cb(GtkAction *action, gpointer data)
9934 Compose *compose = (Compose *)data;
9937 if (compose->exteditor_tag != -1) {
9938 debug_print("ignoring send: external editor still open\n");
9942 if (prefs_common.work_offline &&
9943 !inc_offline_should_override(TRUE,
9944 _("Claws Mail needs network access in order "
9945 "to send this email.")))
9948 if (compose->draft_timeout_tag >= 0) { /* CLAWS: disable draft timeout */
9949 g_source_remove(compose->draft_timeout_tag);
9950 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
9953 compose_send(compose);
9956 static void compose_send_later_cb(GtkAction *action, gpointer data)
9958 Compose *compose = (Compose *)data;
9962 compose_allow_user_actions(compose, FALSE);
9963 val = compose_queue_sub(compose, NULL, NULL, NULL, TRUE, TRUE);
9964 compose_allow_user_actions(compose, TRUE);
9968 compose_close(compose);
9969 } else if (val == -1) {
9970 alertpanel_error(_("Could not queue message."));
9971 } else if (val == -2) {
9972 alertpanel_error(_("Could not queue message:\n\n%s."), g_strerror(errno));
9973 } else if (val == -3) {
9974 if (privacy_peek_error())
9975 alertpanel_error(_("Could not queue message for sending:\n\n"
9976 "Signature failed: %s"), privacy_get_error());
9977 } else if (val == -4) {
9978 alertpanel_error(_("Could not queue message for sending:\n\n"
9979 "Charset conversion failed."));
9980 } else if (val == -5) {
9981 alertpanel_error(_("Could not queue message for sending:\n\n"
9982 "Couldn't get recipient encryption key."));
9983 } else if (val == -6) {
9986 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
9989 #define DRAFTED_AT_EXIT "drafted_at_exit"
9990 static void compose_register_draft(MsgInfo *info)
9992 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
9993 DRAFTED_AT_EXIT, NULL);
9994 FILE *fp = g_fopen(filepath, "ab");
9997 fprintf(fp, "%s\t%d\n", folder_item_get_identifier(info->folder),
10005 gboolean compose_draft (gpointer data, guint action)
10007 Compose *compose = (Compose *)data;
10012 MsgFlags flag = {0, 0};
10013 static gboolean lock = FALSE;
10014 MsgInfo *newmsginfo;
10016 gboolean target_locked = FALSE;
10017 gboolean err = FALSE;
10019 if (lock) return FALSE;
10021 if (compose->sending)
10024 draft = account_get_special_folder(compose->account, F_DRAFT);
10025 cm_return_val_if_fail(draft != NULL, FALSE);
10027 if (!g_mutex_trylock(compose->mutex)) {
10028 /* we don't want to lock the mutex once it's available,
10029 * because as the only other part of compose.c locking
10030 * it is compose_close - which means once unlocked,
10031 * the compose struct will be freed */
10032 debug_print("couldn't lock mutex, probably sending\n");
10038 tmp = g_strdup_printf("%s%cdraft.%p", get_tmp_dir(),
10039 G_DIR_SEPARATOR, compose);
10040 if ((fp = g_fopen(tmp, "wb")) == NULL) {
10041 FILE_OP_ERROR(tmp, "fopen");
10045 /* chmod for security */
10046 if (change_file_mode_rw(fp, tmp) < 0) {
10047 FILE_OP_ERROR(tmp, "chmod");
10048 g_warning("can't change file mode");
10051 /* Save draft infos */
10052 err |= (fprintf(fp, "X-Claws-Account-Id:%d\n", compose->account->account_id) < 0);
10053 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
10055 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
10056 gchar *savefolderid;
10058 savefolderid = compose_get_save_to(compose);
10059 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
10060 g_free(savefolderid);
10062 if (compose->return_receipt) {
10063 err |= (fprintf(fp, "RRCPT:1\n") < 0);
10065 if (compose->privacy_system) {
10066 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
10067 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
10068 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
10071 /* Message-ID of message replying to */
10072 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
10073 gchar *folderid = NULL;
10075 if (compose->replyinfo->folder)
10076 folderid = folder_item_get_identifier(compose->replyinfo->folder);
10077 if (folderid == NULL)
10078 folderid = g_strdup("NULL");
10080 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
10083 /* Message-ID of message forwarding to */
10084 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
10085 gchar *folderid = NULL;
10087 if (compose->fwdinfo->folder)
10088 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
10089 if (folderid == NULL)
10090 folderid = g_strdup("NULL");
10092 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
10096 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
10097 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
10099 sheaders = compose_get_manual_headers_info(compose);
10100 err |= (fprintf(fp, "X-Claws-Manual-Headers:%s\n", sheaders) < 0);
10103 /* end of headers */
10104 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
10111 if (compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_STORE, action != COMPOSE_AUTO_SAVE) < 0) {
10115 if (fclose(fp) == EOF) {
10119 flag.perm_flags = MSG_NEW|MSG_UNREAD;
10120 if (compose->targetinfo) {
10121 target_locked = MSG_IS_LOCKED(compose->targetinfo->flags);
10123 flag.perm_flags |= MSG_LOCKED;
10125 flag.tmp_flags = MSG_DRAFT;
10127 folder_item_scan(draft);
10128 if ((msgnum = folder_item_add_msg(draft, tmp, &flag, TRUE)) < 0) {
10129 MsgInfo *tmpinfo = NULL;
10130 debug_print("didn't get msgnum after adding draft [%s]\n", compose->msgid?compose->msgid:"no msgid");
10131 if (compose->msgid) {
10132 tmpinfo = folder_item_get_msginfo_by_msgid(draft, compose->msgid);
10135 msgnum = tmpinfo->msgnum;
10136 procmsg_msginfo_free(&tmpinfo);
10137 debug_print("got draft msgnum %d from scanning\n", msgnum);
10139 debug_print("didn't get draft msgnum after scanning\n");
10142 debug_print("got draft msgnum %d from adding\n", msgnum);
10148 if (action != COMPOSE_AUTO_SAVE) {
10149 if (action != COMPOSE_DRAFT_FOR_EXIT)
10150 alertpanel_error(_("Could not save draft."));
10153 gtkut_window_popup(compose->window);
10154 val = alertpanel_full(_("Could not save draft"),
10155 _("Could not save draft.\n"
10156 "Do you want to cancel exit or discard this email?"),
10157 _("_Cancel exit"), _("_Discard email"), NULL,
10158 FALSE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
10159 if (val == G_ALERTALTERNATE) {
10161 g_mutex_unlock(compose->mutex); /* must be done before closing */
10162 compose_close(compose);
10166 g_mutex_unlock(compose->mutex); /* must be done before closing */
10175 if (compose->mode == COMPOSE_REEDIT) {
10176 compose_remove_reedit_target(compose, TRUE);
10179 newmsginfo = folder_item_get_msginfo(draft, msgnum);
10182 procmsg_msginfo_unset_flags(newmsginfo, ~0, ~0);
10184 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD|MSG_LOCKED, MSG_DRAFT);
10186 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD, MSG_DRAFT);
10187 if (compose_use_attach(compose) && action != COMPOSE_AUTO_SAVE)
10188 procmsg_msginfo_set_flags(newmsginfo, 0,
10189 MSG_HAS_ATTACHMENT);
10191 if (action == COMPOSE_DRAFT_FOR_EXIT) {
10192 compose_register_draft(newmsginfo);
10194 procmsg_msginfo_free(&newmsginfo);
10197 folder_item_scan(draft);
10199 if (action == COMPOSE_QUIT_EDITING || action == COMPOSE_DRAFT_FOR_EXIT) {
10201 g_mutex_unlock(compose->mutex); /* must be done before closing */
10202 compose_close(compose);
10208 path = folder_item_fetch_msg(draft, msgnum);
10209 if (path == NULL) {
10210 debug_print("can't fetch %s:%d\n", draft->path, msgnum);
10213 if (g_stat(path, &s) < 0) {
10214 FILE_OP_ERROR(path, "stat");
10220 procmsg_msginfo_free(&(compose->targetinfo));
10221 compose->targetinfo = procmsg_msginfo_new();
10222 compose->targetinfo->msgnum = msgnum;
10223 compose->targetinfo->size = (goffset)s.st_size;
10224 compose->targetinfo->mtime = s.st_mtime;
10225 compose->targetinfo->folder = draft;
10227 procmsg_msginfo_set_flags(compose->targetinfo, MSG_LOCKED, 0);
10228 compose->mode = COMPOSE_REEDIT;
10230 if (action == COMPOSE_AUTO_SAVE) {
10231 compose->autosaved_draft = compose->targetinfo;
10233 compose->modified = FALSE;
10234 compose_set_title(compose);
10238 g_mutex_unlock(compose->mutex);
10242 void compose_clear_exit_drafts(void)
10244 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10245 DRAFTED_AT_EXIT, NULL);
10246 if (is_file_exist(filepath))
10247 claws_unlink(filepath);
10252 void compose_reopen_exit_drafts(void)
10254 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10255 DRAFTED_AT_EXIT, NULL);
10256 FILE *fp = g_fopen(filepath, "rb");
10260 while (fgets(buf, sizeof(buf), fp)) {
10261 gchar **parts = g_strsplit(buf, "\t", 2);
10262 const gchar *folder = parts[0];
10263 int msgnum = parts[1] ? atoi(parts[1]):-1;
10265 if (folder && *folder && msgnum > -1) {
10266 FolderItem *item = folder_find_item_from_identifier(folder);
10267 MsgInfo *info = folder_item_get_msginfo(item, msgnum);
10269 compose_reedit(info, FALSE);
10276 compose_clear_exit_drafts();
10279 static void compose_save_cb(GtkAction *action, gpointer data)
10281 Compose *compose = (Compose *)data;
10282 compose_draft(compose, COMPOSE_KEEP_EDITING);
10283 compose->rmode = COMPOSE_REEDIT;
10286 void compose_attach_from_list(Compose *compose, GList *file_list, gboolean free_data)
10288 if (compose && file_list) {
10291 for ( tmp = file_list; tmp; tmp = tmp->next) {
10292 gchar *file = (gchar *) tmp->data;
10293 gchar *utf8_filename = conv_filename_to_utf8(file);
10294 compose_attach_append(compose, file, utf8_filename, NULL, NULL);
10295 compose_changed_cb(NULL, compose);
10300 g_free(utf8_filename);
10305 static void compose_attach_cb(GtkAction *action, gpointer data)
10307 Compose *compose = (Compose *)data;
10310 if (compose->redirect_filename != NULL)
10313 /* Set focus_window properly, in case we were called via popup menu,
10314 * which unsets it (via focus_out_event callback on compose window). */
10315 manage_window_focus_in(compose->window, NULL, NULL);
10317 file_list = filesel_select_multiple_files_open(_("Select file"), NULL);
10320 compose_attach_from_list(compose, file_list, TRUE);
10321 g_list_free(file_list);
10325 static void compose_insert_file_cb(GtkAction *action, gpointer data)
10327 Compose *compose = (Compose *)data;
10329 gint files_inserted = 0;
10331 file_list = filesel_select_multiple_files_open(_("Select file"), NULL);
10336 for ( tmp = file_list; tmp; tmp = tmp->next) {
10337 gchar *file = (gchar *) tmp->data;
10338 gchar *filedup = g_strdup(file);
10339 gchar *shortfile = g_path_get_basename(filedup);
10340 ComposeInsertResult res;
10341 /* insert the file if the file is short or if the user confirmed that
10342 he/she wants to insert the large file */
10343 res = compose_insert_file(compose, file);
10344 if (res == COMPOSE_INSERT_READ_ERROR) {
10345 alertpanel_error(_("File '%s' could not be read."), shortfile);
10346 } else if (res == COMPOSE_INSERT_INVALID_CHARACTER) {
10347 alertpanel_error(_("File '%s' contained invalid characters\n"
10348 "for the current encoding, insertion may be incorrect."),
10350 } else if (res == COMPOSE_INSERT_SUCCESS)
10357 g_list_free(file_list);
10361 if (files_inserted > 0 && compose->gtkaspell &&
10362 compose->gtkaspell->check_while_typing)
10363 gtkaspell_highlight_all(compose->gtkaspell);
10367 static void compose_insert_sig_cb(GtkAction *action, gpointer data)
10369 Compose *compose = (Compose *)data;
10371 compose_insert_sig(compose, FALSE);
10374 static void compose_replace_sig_cb(GtkAction *action, gpointer data)
10376 Compose *compose = (Compose *)data;
10378 compose_insert_sig(compose, TRUE);
10381 static gint compose_delete_cb(GtkWidget *widget, GdkEventAny *event,
10385 Compose *compose = (Compose *)data;
10387 gtkut_widget_get_uposition(widget, &x, &y);
10388 if (!compose->batch) {
10389 prefs_common.compose_x = x;
10390 prefs_common.compose_y = y;
10392 if (compose->sending || compose->updating)
10394 compose_close_cb(NULL, compose);
10398 void compose_close_toolbar(Compose *compose)
10400 compose_close_cb(NULL, compose);
10403 static gboolean compose_can_autosave(Compose *compose)
10405 if (compose->privacy_system && compose->use_encryption)
10406 return prefs_common.autosave && prefs_common.autosave_encrypted;
10408 return prefs_common.autosave;
10411 static void compose_close_cb(GtkAction *action, gpointer data)
10413 Compose *compose = (Compose *)data;
10417 if (compose->exteditor_tag != -1) {
10418 if (!compose_ext_editor_kill(compose))
10423 if (compose->modified) {
10424 gboolean reedit = (compose->rmode == COMPOSE_REEDIT);
10425 if (!g_mutex_trylock(compose->mutex)) {
10426 /* we don't want to lock the mutex once it's available,
10427 * because as the only other part of compose.c locking
10428 * it is compose_close - which means once unlocked,
10429 * the compose struct will be freed */
10430 debug_print("couldn't lock mutex, probably sending\n");
10434 val = alertpanel(_("Discard message"),
10435 _("This message has been modified. Discard it?"),
10436 _("_Discard"), _("_Save to Drafts"), GTK_STOCK_CANCEL);
10438 val = alertpanel(_("Save changes"),
10439 _("This message has been modified. Save the latest changes?"),
10440 _("_Don't save"), g_strconcat("+", _("_Save to Drafts"), NULL),
10443 g_mutex_unlock(compose->mutex);
10445 case G_ALERTDEFAULT:
10446 if (compose_can_autosave(compose) && !reedit)
10447 compose_remove_draft(compose);
10449 case G_ALERTALTERNATE:
10450 compose_draft(data, COMPOSE_QUIT_EDITING);
10457 compose_close(compose);
10460 static void compose_print_cb(GtkAction *action, gpointer data)
10462 Compose *compose = (Compose *) data;
10464 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
10465 if (compose->targetinfo)
10466 messageview_print(compose->targetinfo, FALSE, -1, -1, 0);
10469 static void compose_set_encoding_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
10471 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
10472 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
10473 Compose *compose = (Compose *) data;
10476 compose->out_encoding = (CharSet)value;
10479 static void compose_address_cb(GtkAction *action, gpointer data)
10481 Compose *compose = (Compose *)data;
10483 #ifndef USE_ALT_ADDRBOOK
10484 addressbook_open(compose);
10486 GError* error = NULL;
10487 addressbook_connect_signals(compose);
10488 addressbook_dbus_open(TRUE, &error);
10490 g_warning("%s", error->message);
10491 g_error_free(error);
10496 static void about_show_cb(GtkAction *action, gpointer data)
10501 static void compose_template_activate_cb(GtkWidget *widget, gpointer data)
10503 Compose *compose = (Compose *)data;
10508 tmpl = g_object_get_data(G_OBJECT(widget), "template");
10509 cm_return_if_fail(tmpl != NULL);
10511 msg = g_strdup_printf(_("Do you want to apply the template '%s'?"),
10513 val = alertpanel(_("Apply template"), msg,
10514 _("_Replace"), _("_Insert"), GTK_STOCK_CANCEL);
10517 if (val == G_ALERTDEFAULT)
10518 compose_template_apply(compose, tmpl, TRUE);
10519 else if (val == G_ALERTALTERNATE)
10520 compose_template_apply(compose, tmpl, FALSE);
10523 static void compose_ext_editor_cb(GtkAction *action, gpointer data)
10525 Compose *compose = (Compose *)data;
10528 if (compose->exteditor_tag != -1) {
10529 debug_print("ignoring open external editor: external editor still open\n");
10533 compose_exec_ext_editor(compose);
10536 static void compose_undo_cb(GtkAction *action, gpointer data)
10538 Compose *compose = (Compose *)data;
10539 gboolean prev_autowrap = compose->autowrap;
10541 compose->autowrap = FALSE;
10542 undo_undo(compose->undostruct);
10543 compose->autowrap = prev_autowrap;
10546 static void compose_redo_cb(GtkAction *action, gpointer data)
10548 Compose *compose = (Compose *)data;
10549 gboolean prev_autowrap = compose->autowrap;
10551 compose->autowrap = FALSE;
10552 undo_redo(compose->undostruct);
10553 compose->autowrap = prev_autowrap;
10556 static void entry_cut_clipboard(GtkWidget *entry)
10558 if (GTK_IS_EDITABLE(entry))
10559 gtk_editable_cut_clipboard (GTK_EDITABLE(entry));
10560 else if (GTK_IS_TEXT_VIEW(entry))
10561 gtk_text_buffer_cut_clipboard(
10562 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10563 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
10567 static void entry_copy_clipboard(GtkWidget *entry)
10569 if (GTK_IS_EDITABLE(entry))
10570 gtk_editable_copy_clipboard (GTK_EDITABLE(entry));
10571 else if (GTK_IS_TEXT_VIEW(entry))
10572 gtk_text_buffer_copy_clipboard(
10573 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10574 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
10577 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry,
10578 gboolean wrap, GdkAtom clip, GtkTextIter *insert_place)
10580 if (GTK_IS_TEXT_VIEW(entry)) {
10581 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10582 GtkTextMark *mark_start = gtk_text_buffer_get_insert(buffer);
10583 GtkTextIter start_iter, end_iter;
10585 gchar *contents = gtk_clipboard_wait_for_text(gtk_clipboard_get(clip));
10587 if (contents == NULL)
10590 /* we shouldn't delete the selection when middle-click-pasting, or we
10591 * can't mid-click-paste our own selection */
10592 if (clip != GDK_SELECTION_PRIMARY) {
10593 undo_paste_clipboard(GTK_TEXT_VIEW(compose->text), compose->undostruct);
10594 gtk_text_buffer_delete_selection(buffer, FALSE, TRUE);
10597 if (insert_place == NULL) {
10598 /* if insert_place isn't specified, insert at the cursor.
10599 * used for Ctrl-V pasting */
10600 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10601 start = gtk_text_iter_get_offset(&start_iter);
10602 gtk_text_buffer_insert(buffer, &start_iter, contents, strlen(contents));
10604 /* if insert_place is specified, paste here.
10605 * used for mid-click-pasting */
10606 start = gtk_text_iter_get_offset(insert_place);
10607 gtk_text_buffer_insert(buffer, insert_place, contents, strlen(contents));
10608 if (prefs_common.primary_paste_unselects)
10609 gtk_text_buffer_select_range(buffer, insert_place, insert_place);
10613 /* paste unwrapped: mark the paste so it's not wrapped later */
10614 end = start + strlen(contents);
10615 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, start);
10616 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, end);
10617 gtk_text_buffer_apply_tag_by_name(buffer, "no_wrap", &start_iter, &end_iter);
10618 } else if (wrap && clip == GDK_SELECTION_PRIMARY) {
10619 /* rewrap paragraph now (after a mid-click-paste) */
10620 mark_start = gtk_text_buffer_get_insert(buffer);
10621 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10622 gtk_text_iter_backward_char(&start_iter);
10623 compose_beautify_paragraph(compose, &start_iter, TRUE);
10625 } else if (GTK_IS_EDITABLE(entry))
10626 gtk_editable_paste_clipboard (GTK_EDITABLE(entry));
10628 compose->modified = TRUE;
10631 static void entry_allsel(GtkWidget *entry)
10633 if (GTK_IS_EDITABLE(entry))
10634 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
10635 else if (GTK_IS_TEXT_VIEW(entry)) {
10636 GtkTextIter startiter, enditer;
10637 GtkTextBuffer *textbuf;
10639 textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10640 gtk_text_buffer_get_start_iter(textbuf, &startiter);
10641 gtk_text_buffer_get_end_iter(textbuf, &enditer);
10643 gtk_text_buffer_move_mark_by_name(textbuf,
10644 "selection_bound", &startiter);
10645 gtk_text_buffer_move_mark_by_name(textbuf,
10646 "insert", &enditer);
10650 static void compose_cut_cb(GtkAction *action, gpointer data)
10652 Compose *compose = (Compose *)data;
10653 if (compose->focused_editable
10654 #ifndef GENERIC_UMPC
10655 && gtk_widget_has_focus(compose->focused_editable)
10658 entry_cut_clipboard(compose->focused_editable);
10661 static void compose_copy_cb(GtkAction *action, gpointer data)
10663 Compose *compose = (Compose *)data;
10664 if (compose->focused_editable
10665 #ifndef GENERIC_UMPC
10666 && gtk_widget_has_focus(compose->focused_editable)
10669 entry_copy_clipboard(compose->focused_editable);
10672 static void compose_paste_cb(GtkAction *action, gpointer data)
10674 Compose *compose = (Compose *)data;
10675 gint prev_autowrap;
10676 GtkTextBuffer *buffer;
10678 if (compose->focused_editable &&
10679 #ifndef GENERIC_UMPC
10680 gtk_widget_has_focus(compose->focused_editable)
10683 entry_paste_clipboard(compose, compose->focused_editable,
10684 prefs_common.linewrap_pastes,
10685 GDK_SELECTION_CLIPBOARD, NULL);
10690 #ifndef GENERIC_UMPC
10691 gtk_widget_has_focus(compose->text) &&
10693 compose->gtkaspell &&
10694 compose->gtkaspell->check_while_typing)
10695 gtkaspell_highlight_all(compose->gtkaspell);
10699 static void compose_paste_as_quote_cb(GtkAction *action, gpointer data)
10701 Compose *compose = (Compose *)data;
10702 gint wrap_quote = prefs_common.linewrap_quote;
10703 if (compose->focused_editable
10704 #ifndef GENERIC_UMPC
10705 && gtk_widget_has_focus(compose->focused_editable)
10708 /* let text_insert() (called directly or at a later time
10709 * after the gtk_editable_paste_clipboard) know that
10710 * text is to be inserted as a quotation. implemented
10711 * by using a simple refcount... */
10712 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data(
10713 G_OBJECT(compose->focused_editable),
10714 "paste_as_quotation"));
10715 g_object_set_data(G_OBJECT(compose->focused_editable),
10716 "paste_as_quotation",
10717 GINT_TO_POINTER(paste_as_quotation + 1));
10718 prefs_common.linewrap_quote = prefs_common.linewrap_pastes;
10719 entry_paste_clipboard(compose, compose->focused_editable,
10720 prefs_common.linewrap_pastes,
10721 GDK_SELECTION_CLIPBOARD, NULL);
10722 prefs_common.linewrap_quote = wrap_quote;
10726 static void compose_paste_no_wrap_cb(GtkAction *action, gpointer data)
10728 Compose *compose = (Compose *)data;
10729 gint prev_autowrap;
10730 GtkTextBuffer *buffer;
10732 if (compose->focused_editable
10733 #ifndef GENERIC_UMPC
10734 && gtk_widget_has_focus(compose->focused_editable)
10737 entry_paste_clipboard(compose, compose->focused_editable, FALSE,
10738 GDK_SELECTION_CLIPBOARD, NULL);
10743 #ifndef GENERIC_UMPC
10744 gtk_widget_has_focus(compose->text) &&
10746 compose->gtkaspell &&
10747 compose->gtkaspell->check_while_typing)
10748 gtkaspell_highlight_all(compose->gtkaspell);
10752 static void compose_paste_wrap_cb(GtkAction *action, gpointer data)
10754 Compose *compose = (Compose *)data;
10755 gint prev_autowrap;
10756 GtkTextBuffer *buffer;
10758 if (compose->focused_editable
10759 #ifndef GENERIC_UMPC
10760 && gtk_widget_has_focus(compose->focused_editable)
10763 entry_paste_clipboard(compose, compose->focused_editable, TRUE,
10764 GDK_SELECTION_CLIPBOARD, NULL);
10769 #ifndef GENERIC_UMPC
10770 gtk_widget_has_focus(compose->text) &&
10772 compose->gtkaspell &&
10773 compose->gtkaspell->check_while_typing)
10774 gtkaspell_highlight_all(compose->gtkaspell);
10778 static void compose_allsel_cb(GtkAction *action, gpointer data)
10780 Compose *compose = (Compose *)data;
10781 if (compose->focused_editable
10782 #ifndef GENERIC_UMPC
10783 && gtk_widget_has_focus(compose->focused_editable)
10786 entry_allsel(compose->focused_editable);
10789 static void textview_move_beginning_of_line (GtkTextView *text)
10791 GtkTextBuffer *buffer;
10795 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10797 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10798 mark = gtk_text_buffer_get_insert(buffer);
10799 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10800 gtk_text_iter_set_line_offset(&ins, 0);
10801 gtk_text_buffer_place_cursor(buffer, &ins);
10804 static void textview_move_forward_character (GtkTextView *text)
10806 GtkTextBuffer *buffer;
10810 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10812 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10813 mark = gtk_text_buffer_get_insert(buffer);
10814 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10815 if (gtk_text_iter_forward_cursor_position(&ins))
10816 gtk_text_buffer_place_cursor(buffer, &ins);
10819 static void textview_move_backward_character (GtkTextView *text)
10821 GtkTextBuffer *buffer;
10825 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10827 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10828 mark = gtk_text_buffer_get_insert(buffer);
10829 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10830 if (gtk_text_iter_backward_cursor_position(&ins))
10831 gtk_text_buffer_place_cursor(buffer, &ins);
10834 static void textview_move_forward_word (GtkTextView *text)
10836 GtkTextBuffer *buffer;
10841 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10843 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10844 mark = gtk_text_buffer_get_insert(buffer);
10845 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10846 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
10847 if (gtk_text_iter_forward_word_ends(&ins, count)) {
10848 gtk_text_iter_backward_word_start(&ins);
10849 gtk_text_buffer_place_cursor(buffer, &ins);
10853 static void textview_move_backward_word (GtkTextView *text)
10855 GtkTextBuffer *buffer;
10859 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10861 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10862 mark = gtk_text_buffer_get_insert(buffer);
10863 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10864 if (gtk_text_iter_backward_word_starts(&ins, 1))
10865 gtk_text_buffer_place_cursor(buffer, &ins);
10868 static void textview_move_end_of_line (GtkTextView *text)
10870 GtkTextBuffer *buffer;
10874 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10876 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10877 mark = gtk_text_buffer_get_insert(buffer);
10878 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10879 if (gtk_text_iter_forward_to_line_end(&ins))
10880 gtk_text_buffer_place_cursor(buffer, &ins);
10883 static void textview_move_next_line (GtkTextView *text)
10885 GtkTextBuffer *buffer;
10890 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10892 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10893 mark = gtk_text_buffer_get_insert(buffer);
10894 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10895 offset = gtk_text_iter_get_line_offset(&ins);
10896 if (gtk_text_iter_forward_line(&ins)) {
10897 gtk_text_iter_set_line_offset(&ins, offset);
10898 gtk_text_buffer_place_cursor(buffer, &ins);
10902 static void textview_move_previous_line (GtkTextView *text)
10904 GtkTextBuffer *buffer;
10909 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10911 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10912 mark = gtk_text_buffer_get_insert(buffer);
10913 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10914 offset = gtk_text_iter_get_line_offset(&ins);
10915 if (gtk_text_iter_backward_line(&ins)) {
10916 gtk_text_iter_set_line_offset(&ins, offset);
10917 gtk_text_buffer_place_cursor(buffer, &ins);
10921 static void textview_delete_forward_character (GtkTextView *text)
10923 GtkTextBuffer *buffer;
10925 GtkTextIter ins, end_iter;
10927 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10929 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10930 mark = gtk_text_buffer_get_insert(buffer);
10931 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10933 if (gtk_text_iter_forward_char(&end_iter)) {
10934 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10938 static void textview_delete_backward_character (GtkTextView *text)
10940 GtkTextBuffer *buffer;
10942 GtkTextIter ins, end_iter;
10944 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10946 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10947 mark = gtk_text_buffer_get_insert(buffer);
10948 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10950 if (gtk_text_iter_backward_char(&end_iter)) {
10951 gtk_text_buffer_delete(buffer, &end_iter, &ins);
10955 static void textview_delete_forward_word (GtkTextView *text)
10957 GtkTextBuffer *buffer;
10959 GtkTextIter ins, end_iter;
10961 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10963 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10964 mark = gtk_text_buffer_get_insert(buffer);
10965 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10967 if (gtk_text_iter_forward_word_end(&end_iter)) {
10968 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10972 static void textview_delete_backward_word (GtkTextView *text)
10974 GtkTextBuffer *buffer;
10976 GtkTextIter ins, end_iter;
10978 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10980 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10981 mark = gtk_text_buffer_get_insert(buffer);
10982 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10984 if (gtk_text_iter_backward_word_start(&end_iter)) {
10985 gtk_text_buffer_delete(buffer, &end_iter, &ins);
10989 static void textview_delete_line (GtkTextView *text)
10991 GtkTextBuffer *buffer;
10993 GtkTextIter ins, start_iter, end_iter;
10995 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10997 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10998 mark = gtk_text_buffer_get_insert(buffer);
10999 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11002 gtk_text_iter_set_line_offset(&start_iter, 0);
11005 if (gtk_text_iter_ends_line(&end_iter)){
11006 if (!gtk_text_iter_forward_char(&end_iter))
11007 gtk_text_iter_backward_char(&start_iter);
11010 gtk_text_iter_forward_to_line_end(&end_iter);
11011 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
11014 static void textview_delete_to_line_end (GtkTextView *text)
11016 GtkTextBuffer *buffer;
11018 GtkTextIter ins, end_iter;
11020 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11022 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11023 mark = gtk_text_buffer_get_insert(buffer);
11024 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11026 if (gtk_text_iter_ends_line(&end_iter))
11027 gtk_text_iter_forward_char(&end_iter);
11029 gtk_text_iter_forward_to_line_end(&end_iter);
11030 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11033 #define DO_ACTION(name, act) { \
11034 if(!strcmp(name, a_name)) { \
11038 static ComposeCallAdvancedAction compose_call_advanced_action_from_path(GtkAction *action)
11040 const gchar *a_name = gtk_action_get_name(action);
11041 DO_ACTION("Edit/Advanced/BackChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER);
11042 DO_ACTION("Edit/Advanced/ForwChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER);
11043 DO_ACTION("Edit/Advanced/BackWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD);
11044 DO_ACTION("Edit/Advanced/ForwWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD);
11045 DO_ACTION("Edit/Advanced/BegLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE);
11046 DO_ACTION("Edit/Advanced/EndLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE);
11047 DO_ACTION("Edit/Advanced/PrevLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE);
11048 DO_ACTION("Edit/Advanced/NextLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE);
11049 DO_ACTION("Edit/Advanced/DelBackChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER);
11050 DO_ACTION("Edit/Advanced/DelForwChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER);
11051 DO_ACTION("Edit/Advanced/DelBackWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD);
11052 DO_ACTION("Edit/Advanced/DelForwWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD);
11053 DO_ACTION("Edit/Advanced/DelLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE);
11054 DO_ACTION("Edit/Advanced/DelEndLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END);
11058 static void compose_advanced_action_cb(GtkAction *gaction, gpointer data)
11060 Compose *compose = (Compose *)data;
11061 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11062 ComposeCallAdvancedAction action = -1;
11064 action = compose_call_advanced_action_from_path(gaction);
11067 void (*do_action) (GtkTextView *text);
11068 } action_table[] = {
11069 {textview_move_beginning_of_line},
11070 {textview_move_forward_character},
11071 {textview_move_backward_character},
11072 {textview_move_forward_word},
11073 {textview_move_backward_word},
11074 {textview_move_end_of_line},
11075 {textview_move_next_line},
11076 {textview_move_previous_line},
11077 {textview_delete_forward_character},
11078 {textview_delete_backward_character},
11079 {textview_delete_forward_word},
11080 {textview_delete_backward_word},
11081 {textview_delete_line},
11082 {textview_delete_to_line_end}
11085 if (!gtk_widget_has_focus(GTK_WIDGET(text))) return;
11087 if (action >= COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE &&
11088 action <= COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END) {
11089 if (action_table[action].do_action)
11090 action_table[action].do_action(text);
11092 g_warning("Not implemented yet.");
11096 static void compose_grab_focus_cb(GtkWidget *widget, Compose *compose)
11098 GtkAllocation allocation;
11102 if (GTK_IS_EDITABLE(widget)) {
11103 str = gtk_editable_get_chars(GTK_EDITABLE(widget), 0, -1);
11104 gtk_editable_set_position(GTK_EDITABLE(widget),
11107 if ((parent = gtk_widget_get_parent(widget))
11108 && (parent = gtk_widget_get_parent(parent))
11109 && (parent = gtk_widget_get_parent(parent))) {
11110 if (GTK_IS_SCROLLED_WINDOW(parent)) {
11111 gtk_widget_get_allocation(widget, &allocation);
11112 gint y = allocation.y;
11113 gint height = allocation.height;
11114 GtkAdjustment *shown = gtk_scrolled_window_get_vadjustment
11115 (GTK_SCROLLED_WINDOW(parent));
11117 gfloat value = gtk_adjustment_get_value(shown);
11118 gfloat upper = gtk_adjustment_get_upper(shown);
11119 gfloat page_size = gtk_adjustment_get_page_size(shown);
11120 if (y < (int)value) {
11121 gtk_adjustment_set_value(shown, y - 1);
11123 if ((y + height) > ((int)value + (int)page_size)) {
11124 if ((y - height - 1) < ((int)upper - (int)page_size)) {
11125 gtk_adjustment_set_value(shown,
11126 y + height - (int)page_size - 1);
11128 gtk_adjustment_set_value(shown,
11129 (int)upper - (int)page_size - 1);
11136 if (GTK_IS_EDITABLE(widget) || GTK_IS_TEXT_VIEW(widget))
11137 compose->focused_editable = widget;
11139 #ifdef GENERIC_UMPC
11140 if (GTK_IS_TEXT_VIEW(widget)
11141 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->edit_vbox) {
11142 g_object_ref(compose->notebook);
11143 g_object_ref(compose->edit_vbox);
11144 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
11145 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
11146 gtk_paned_add1(GTK_PANED(compose->paned), compose->edit_vbox);
11147 gtk_paned_add2(GTK_PANED(compose->paned), compose->notebook);
11148 g_object_unref(compose->notebook);
11149 g_object_unref(compose->edit_vbox);
11150 g_signal_handlers_block_by_func(G_OBJECT(widget),
11151 G_CALLBACK(compose_grab_focus_cb),
11153 gtk_widget_grab_focus(widget);
11154 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
11155 G_CALLBACK(compose_grab_focus_cb),
11157 } else if (!GTK_IS_TEXT_VIEW(widget)
11158 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->notebook) {
11159 g_object_ref(compose->notebook);
11160 g_object_ref(compose->edit_vbox);
11161 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
11162 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
11163 gtk_paned_add1(GTK_PANED(compose->paned), compose->notebook);
11164 gtk_paned_add2(GTK_PANED(compose->paned), compose->edit_vbox);
11165 g_object_unref(compose->notebook);
11166 g_object_unref(compose->edit_vbox);
11167 g_signal_handlers_block_by_func(G_OBJECT(widget),
11168 G_CALLBACK(compose_grab_focus_cb),
11170 gtk_widget_grab_focus(widget);
11171 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
11172 G_CALLBACK(compose_grab_focus_cb),
11178 static void compose_changed_cb(GtkTextBuffer *textbuf, Compose *compose)
11180 compose->modified = TRUE;
11181 // compose_beautify_paragraph(compose, NULL, TRUE);
11182 #ifndef GENERIC_UMPC
11183 compose_set_title(compose);
11187 static void compose_wrap_cb(GtkAction *action, gpointer data)
11189 Compose *compose = (Compose *)data;
11190 compose_beautify_paragraph(compose, NULL, TRUE);
11193 static void compose_wrap_all_cb(GtkAction *action, gpointer data)
11195 Compose *compose = (Compose *)data;
11196 compose_wrap_all_full(compose, TRUE);
11199 static void compose_find_cb(GtkAction *action, gpointer data)
11201 Compose *compose = (Compose *)data;
11203 message_search_compose(compose);
11206 static void compose_toggle_autowrap_cb(GtkToggleAction *action,
11209 Compose *compose = (Compose *)data;
11210 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11211 if (compose->autowrap)
11212 compose_wrap_all_full(compose, TRUE);
11213 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11216 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
11219 Compose *compose = (Compose *)data;
11220 compose->autoindent = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11223 static void compose_toggle_sign_cb(GtkToggleAction *action, gpointer data)
11225 Compose *compose = (Compose *)data;
11227 compose->use_signing = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11230 static void compose_toggle_encrypt_cb(GtkToggleAction *action, gpointer data)
11232 Compose *compose = (Compose *)data;
11234 compose->use_encryption = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11237 static void activate_privacy_system(Compose *compose, PrefsAccount *account, gboolean warn)
11239 g_free(compose->privacy_system);
11240 g_free(compose->encdata);
11242 compose->privacy_system = g_strdup(account->default_privacy_system);
11243 compose_update_privacy_system_menu_item(compose, warn);
11246 static void compose_toggle_ruler_cb(GtkToggleAction *action, gpointer data)
11248 Compose *compose = (Compose *)data;
11250 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action))) {
11251 gtk_widget_show(compose->ruler_hbox);
11252 prefs_common.show_ruler = TRUE;
11254 gtk_widget_hide(compose->ruler_hbox);
11255 gtk_widget_queue_resize(compose->edit_vbox);
11256 prefs_common.show_ruler = FALSE;
11260 static void compose_attach_drag_received_cb (GtkWidget *widget,
11261 GdkDragContext *context,
11264 GtkSelectionData *data,
11267 gpointer user_data)
11269 Compose *compose = (Compose *)user_data;
11273 type = gtk_selection_data_get_data_type(data);
11274 if ((gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list"))
11275 && gtk_drag_get_source_widget(context) !=
11276 summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
11277 list = uri_list_extract_filenames(
11278 (const gchar *)gtk_selection_data_get_data(data));
11279 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11280 gchar *utf8_filename = conv_filename_to_utf8((const gchar *)tmp->data);
11281 compose_attach_append
11282 (compose, (const gchar *)tmp->data,
11283 utf8_filename, NULL, NULL);
11284 g_free(utf8_filename);
11286 if (list) compose_changed_cb(NULL, compose);
11287 list_free_strings(list);
11289 } else if (gtk_drag_get_source_widget(context)
11290 == summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
11291 /* comes from our summaryview */
11292 SummaryView * summaryview = NULL;
11293 GSList * list = NULL, *cur = NULL;
11295 if (mainwindow_get_mainwindow())
11296 summaryview = mainwindow_get_mainwindow()->summaryview;
11299 list = summary_get_selected_msg_list(summaryview);
11301 for (cur = list; cur; cur = cur->next) {
11302 MsgInfo *msginfo = (MsgInfo *)cur->data;
11303 gchar *file = NULL;
11305 file = procmsg_get_message_file_full(msginfo,
11308 compose_attach_append(compose, (const gchar *)file,
11309 (const gchar *)file, "message/rfc822", NULL);
11313 g_slist_free(list);
11317 static gboolean compose_drag_drop(GtkWidget *widget,
11318 GdkDragContext *drag_context,
11320 guint time, gpointer user_data)
11322 /* not handling this signal makes compose_insert_drag_received_cb
11327 static gboolean completion_set_focus_to_subject
11328 (GtkWidget *widget,
11329 GdkEventKey *event,
11332 cm_return_val_if_fail(compose != NULL, FALSE);
11334 /* make backtab move to subject field */
11335 if(event->keyval == GDK_KEY_ISO_Left_Tab) {
11336 gtk_widget_grab_focus(compose->subject_entry);
11342 static void compose_insert_drag_received_cb (GtkWidget *widget,
11343 GdkDragContext *drag_context,
11346 GtkSelectionData *data,
11349 gpointer user_data)
11351 Compose *compose = (Compose *)user_data;
11357 /* strangely, testing data->type == gdk_atom_intern("text/uri-list", TRUE)
11359 type = gtk_selection_data_get_data_type(data);
11360 if (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list")) {
11361 AlertValue val = G_ALERTDEFAULT;
11362 const gchar* ddata = (const gchar *)gtk_selection_data_get_data(data);
11364 list = uri_list_extract_filenames(ddata);
11365 num_files = g_list_length(list);
11366 if (list == NULL && strstr(ddata, "://")) {
11367 /* Assume a list of no files, and data has ://, is a remote link */
11368 gchar *tmpdata = g_strstrip(g_strdup(ddata));
11369 gchar *tmpfile = get_tmp_file();
11370 str_write_to_file(tmpdata, tmpfile);
11372 compose_insert_file(compose, tmpfile);
11373 claws_unlink(tmpfile);
11375 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11376 compose_beautify_paragraph(compose, NULL, TRUE);
11379 switch (prefs_common.compose_dnd_mode) {
11380 case COMPOSE_DND_ASK:
11381 msg = g_strdup_printf(
11383 "Do you want to insert the contents of the file "
11384 "into the message body, or attach it to the email?",
11385 "Do you want to insert the contents of the %d files "
11386 "into the message body, or attach them to the email?",
11389 val = alertpanel_full(_("Insert or attach?"), msg,
11390 GTK_STOCK_CANCEL, g_strconcat("+", _("_Insert"), NULL), _("_Attach"),
11391 TRUE, NULL, ALERT_QUESTION, G_ALERTALTERNATE);
11394 case COMPOSE_DND_INSERT:
11395 val = G_ALERTALTERNATE;
11397 case COMPOSE_DND_ATTACH:
11398 val = G_ALERTOTHER;
11401 /* unexpected case */
11402 g_warning("error: unexpected compose_dnd_mode option value in compose_insert_drag_received_cb()");
11405 if (val & G_ALERTDISABLE) {
11406 val &= ~G_ALERTDISABLE;
11407 /* remember what action to perform by default, only if we don't click Cancel */
11408 if (val == G_ALERTALTERNATE)
11409 prefs_common.compose_dnd_mode = COMPOSE_DND_INSERT;
11410 else if (val == G_ALERTOTHER)
11411 prefs_common.compose_dnd_mode = COMPOSE_DND_ATTACH;
11414 if (val == G_ALERTDEFAULT || val == G_ALERTCANCEL) {
11415 gtk_drag_finish(drag_context, FALSE, FALSE, time);
11416 list_free_strings(list);
11419 } else if (val == G_ALERTOTHER) {
11420 compose_attach_drag_received_cb(widget, drag_context, x, y, data, info, time, user_data);
11421 list_free_strings(list);
11426 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11427 compose_insert_file(compose, (const gchar *)tmp->data);
11429 list_free_strings(list);
11431 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11436 static void compose_header_drag_received_cb (GtkWidget *widget,
11437 GdkDragContext *drag_context,
11440 GtkSelectionData *data,
11443 gpointer user_data)
11445 GtkEditable *entry = (GtkEditable *)user_data;
11446 const gchar *email = (const gchar *)gtk_selection_data_get_data(data);
11448 /* strangely, testing data->type == gdk_atom_intern("text/plain", TRUE)
11451 if (!strncmp(email, "mailto:", strlen("mailto:"))) {
11452 gchar *decoded=g_new(gchar, strlen(email));
11455 decode_uri(decoded, email + strlen("mailto:")); /* will fit */
11456 gtk_editable_delete_text(entry, 0, -1);
11457 gtk_editable_insert_text(entry, decoded, strlen(decoded), &start);
11458 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11462 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11465 static void compose_toggle_return_receipt_cb(GtkToggleAction *action, gpointer data)
11467 Compose *compose = (Compose *)data;
11469 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11470 compose->return_receipt = TRUE;
11472 compose->return_receipt = FALSE;
11475 static void compose_toggle_remove_refs_cb(GtkToggleAction *action, gpointer data)
11477 Compose *compose = (Compose *)data;
11479 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11480 compose->remove_references = TRUE;
11482 compose->remove_references = FALSE;
11485 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
11486 ComposeHeaderEntry *headerentry)
11488 gtk_entry_set_text(GTK_ENTRY(headerentry->entry), "");
11492 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
11493 GdkEventKey *event,
11494 ComposeHeaderEntry *headerentry)
11496 if ((g_slist_length(headerentry->compose->header_list) > 0) &&
11497 ((headerentry->headernum + 1) != headerentry->compose->header_nextrow) &&
11498 !(event->state & GDK_MODIFIER_MASK) &&
11499 (event->keyval == GDK_KEY_BackSpace) &&
11500 (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) == 0)) {
11501 gtk_container_remove
11502 (GTK_CONTAINER(headerentry->compose->header_table),
11503 headerentry->combo);
11504 gtk_container_remove
11505 (GTK_CONTAINER(headerentry->compose->header_table),
11506 headerentry->entry);
11507 headerentry->compose->header_list =
11508 g_slist_remove(headerentry->compose->header_list,
11510 g_free(headerentry);
11511 } else if (event->keyval == GDK_KEY_Tab) {
11512 if (headerentry->compose->header_last == headerentry) {
11513 /* Override default next focus, and give it to subject_entry
11514 * instead of notebook tabs
11516 g_signal_stop_emission_by_name(G_OBJECT(entry), "key-press-event");
11517 gtk_widget_grab_focus(headerentry->compose->subject_entry);
11524 static gboolean scroll_postpone(gpointer data)
11526 Compose *compose = (Compose *)data;
11528 if (compose->batch)
11531 GTK_EVENTS_FLUSH();
11532 compose_show_first_last_header(compose, FALSE);
11536 static void compose_headerentry_changed_cb(GtkWidget *entry,
11537 ComposeHeaderEntry *headerentry)
11539 if (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) != 0) {
11540 compose_create_header_entry(headerentry->compose);
11541 g_signal_handlers_disconnect_matched
11542 (G_OBJECT(entry), G_SIGNAL_MATCH_DATA,
11543 0, 0, NULL, NULL, headerentry);
11545 if (!headerentry->compose->batch)
11546 g_timeout_add(0, scroll_postpone, headerentry->compose);
11550 static gboolean compose_defer_auto_save_draft(Compose *compose)
11552 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
11553 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
11557 static void compose_show_first_last_header(Compose *compose, gboolean show_first)
11559 GtkAdjustment *vadj;
11561 cm_return_if_fail(compose);
11566 cm_return_if_fail(GTK_IS_WIDGET(compose->header_table));
11567 cm_return_if_fail(GTK_IS_VIEWPORT(gtk_widget_get_parent(compose->header_table)));
11568 vadj = gtk_viewport_get_vadjustment(GTK_VIEWPORT(
11569 gtk_widget_get_parent(compose->header_table)));
11570 gtk_adjustment_set_value(vadj, (show_first ?
11571 gtk_adjustment_get_lower(vadj) :
11572 (gtk_adjustment_get_upper(vadj) -
11573 gtk_adjustment_get_page_size(vadj))));
11574 gtk_adjustment_changed(vadj);
11577 static void text_inserted(GtkTextBuffer *buffer, GtkTextIter *iter,
11578 const gchar *text, gint len, Compose *compose)
11580 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data
11581 (G_OBJECT(compose->text), "paste_as_quotation"));
11584 cm_return_if_fail(text != NULL);
11586 g_signal_handlers_block_by_func(G_OBJECT(buffer),
11587 G_CALLBACK(text_inserted),
11589 if (paste_as_quotation) {
11591 const gchar *qmark;
11593 GtkTextIter start_iter;
11596 len = strlen(text);
11598 new_text = g_strndup(text, len);
11600 qmark = compose_quote_char_from_context(compose);
11602 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11603 gtk_text_buffer_place_cursor(buffer, iter);
11605 pos = gtk_text_iter_get_offset(iter);
11607 compose_quote_fmt(compose, NULL, "%Q", qmark, new_text, TRUE, FALSE,
11608 _("Quote format error at line %d."));
11609 quote_fmt_reset_vartable();
11611 g_object_set_data(G_OBJECT(compose->text), "paste_as_quotation",
11612 GINT_TO_POINTER(paste_as_quotation - 1));
11614 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11615 gtk_text_buffer_place_cursor(buffer, iter);
11616 gtk_text_buffer_delete_mark(buffer, mark);
11618 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, pos);
11619 mark = gtk_text_buffer_create_mark(buffer, NULL, &start_iter, FALSE);
11620 compose_beautify_paragraph(compose, &start_iter, FALSE);
11621 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark);
11622 gtk_text_buffer_delete_mark(buffer, mark);
11624 if (strcmp(text, "\n") || compose->automatic_break
11625 || gtk_text_iter_starts_line(iter)) {
11626 GtkTextIter before_ins;
11627 gtk_text_buffer_insert(buffer, iter, text, len);
11628 if (!strstr(text, "\n") && gtk_text_iter_has_tag(iter, compose->no_join_tag)) {
11629 before_ins = *iter;
11630 gtk_text_iter_backward_chars(&before_ins, len);
11631 gtk_text_buffer_remove_tag_by_name(buffer, "no_join", &before_ins, iter);
11634 /* check if the preceding is just whitespace or quote */
11635 GtkTextIter start_line;
11636 gchar *tmp = NULL, *quote = NULL;
11637 gint quote_len = 0, is_normal = 0;
11638 start_line = *iter;
11639 gtk_text_iter_set_line_offset(&start_line, 0);
11640 tmp = gtk_text_buffer_get_text(buffer, &start_line, iter, FALSE);
11643 if (*tmp == '\0') {
11646 quote = compose_get_quote_str(buffer, &start_line, "e_len);
11654 gtk_text_buffer_insert(buffer, iter, text, len);
11656 gtk_text_buffer_insert_with_tags_by_name(buffer,
11657 iter, text, len, "no_join", NULL);
11662 if (!paste_as_quotation) {
11663 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11664 compose_beautify_paragraph(compose, iter, FALSE);
11665 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11666 gtk_text_buffer_delete_mark(buffer, mark);
11669 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
11670 G_CALLBACK(text_inserted),
11672 g_signal_stop_emission_by_name(G_OBJECT(buffer), "insert-text");
11674 if (compose_can_autosave(compose) &&
11675 gtk_text_buffer_get_char_count(buffer) % prefs_common.autosave_length == 0 &&
11676 compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN /* disabled while loading */)
11677 compose->draft_timeout_tag = g_timeout_add
11678 (500, (GSourceFunc) compose_defer_auto_save_draft, compose);
11682 static void compose_check_all(GtkAction *action, gpointer data)
11684 Compose *compose = (Compose *)data;
11685 if (!compose->gtkaspell)
11688 if (gtk_widget_has_focus(compose->subject_entry))
11689 claws_spell_entry_check_all(
11690 CLAWS_SPELL_ENTRY(compose->subject_entry));
11692 gtkaspell_check_all(compose->gtkaspell);
11695 static void compose_highlight_all(GtkAction *action, gpointer data)
11697 Compose *compose = (Compose *)data;
11698 if (compose->gtkaspell) {
11699 claws_spell_entry_recheck_all(
11700 CLAWS_SPELL_ENTRY(compose->subject_entry));
11701 gtkaspell_highlight_all(compose->gtkaspell);
11705 static void compose_check_backwards(GtkAction *action, gpointer data)
11707 Compose *compose = (Compose *)data;
11708 if (!compose->gtkaspell) {
11709 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
11713 if (gtk_widget_has_focus(compose->subject_entry))
11714 claws_spell_entry_check_backwards(
11715 CLAWS_SPELL_ENTRY(compose->subject_entry));
11717 gtkaspell_check_backwards(compose->gtkaspell);
11720 static void compose_check_forwards_go(GtkAction *action, gpointer data)
11722 Compose *compose = (Compose *)data;
11723 if (!compose->gtkaspell) {
11724 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
11728 if (gtk_widget_has_focus(compose->subject_entry))
11729 claws_spell_entry_check_forwards_go(
11730 CLAWS_SPELL_ENTRY(compose->subject_entry));
11732 gtkaspell_check_forwards_go(compose->gtkaspell);
11737 *\brief Guess originating forward account from MsgInfo and several
11738 * "common preference" settings. Return NULL if no guess.
11740 static PrefsAccount *compose_guess_forward_account_from_msginfo(MsgInfo *msginfo)
11742 PrefsAccount *account = NULL;
11744 cm_return_val_if_fail(msginfo, NULL);
11745 cm_return_val_if_fail(msginfo->folder, NULL);
11746 cm_return_val_if_fail(msginfo->folder->prefs, NULL);
11748 if (msginfo->folder->prefs->enable_default_account)
11749 account = account_find_from_id(msginfo->folder->prefs->default_account);
11751 if (!account && msginfo->to && prefs_common.forward_account_autosel) {
11753 Xstrdup_a(to, msginfo->to, return NULL);
11754 extract_address(to);
11755 account = account_find_from_address(to, FALSE);
11758 if (!account && prefs_common.forward_account_autosel) {
11759 gchar cc[BUFFSIZE];
11760 if (!procheader_get_header_from_msginfo
11761 (msginfo, cc,sizeof cc , "Cc:")) {
11762 gchar *buf = cc + strlen("Cc:");
11763 extract_address(buf);
11764 account = account_find_from_address(buf, FALSE);
11768 if (!account && prefs_common.forward_account_autosel) {
11769 gchar deliveredto[BUFFSIZE];
11770 if (!procheader_get_header_from_msginfo
11771 (msginfo, deliveredto,sizeof deliveredto , "Delivered-To:")) {
11772 gchar *buf = deliveredto + strlen("Delivered-To:");
11773 extract_address(buf);
11774 account = account_find_from_address(buf, FALSE);
11779 account = msginfo->folder->folder->account;
11784 gboolean compose_close(Compose *compose)
11788 cm_return_val_if_fail(compose, FALSE);
11790 if (!g_mutex_trylock(compose->mutex)) {
11791 /* we have to wait for the (possibly deferred by auto-save)
11792 * drafting to be done, before destroying the compose under
11794 debug_print("waiting for drafting to finish...\n");
11795 compose_allow_user_actions(compose, FALSE);
11796 if (compose->close_timeout_tag == 0) {
11797 compose->close_timeout_tag =
11798 g_timeout_add (500, (GSourceFunc) compose_close,
11804 if (compose->draft_timeout_tag >= 0) {
11805 g_source_remove(compose->draft_timeout_tag);
11806 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN;
11809 gtkut_widget_get_uposition(compose->window, &x, &y);
11810 if (!compose->batch) {
11811 prefs_common.compose_x = x;
11812 prefs_common.compose_y = y;
11814 g_mutex_unlock(compose->mutex);
11815 compose_destroy(compose);
11820 * Add entry field for each address in list.
11821 * \param compose E-Mail composition object.
11822 * \param listAddress List of (formatted) E-Mail addresses.
11824 static void compose_add_field_list( Compose *compose, GList *listAddress ) {
11827 node = listAddress;
11829 addr = ( gchar * ) node->data;
11830 compose_entry_append( compose, addr, COMPOSE_TO, PREF_NONE );
11831 node = g_list_next( node );
11835 static void compose_reply_from_messageview_real(MessageView *msgview, GSList *msginfo_list,
11836 guint action, gboolean opening_multiple)
11838 gchar *body = NULL;
11839 GSList *new_msglist = NULL;
11840 MsgInfo *tmp_msginfo = NULL;
11841 gboolean originally_enc = FALSE;
11842 gboolean originally_sig = FALSE;
11843 Compose *compose = NULL;
11844 gchar *s_system = NULL;
11846 cm_return_if_fail(msgview != NULL);
11848 cm_return_if_fail(msginfo_list != NULL);
11850 if (g_slist_length(msginfo_list) == 1 && !opening_multiple) {
11851 MimeInfo *mimeinfo = messageview_get_selected_mime_part(msgview);
11852 MsgInfo *orig_msginfo = (MsgInfo *)msginfo_list->data;
11854 if (mimeinfo != NULL && mimeinfo->type == MIMETYPE_MESSAGE &&
11855 !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
11856 tmp_msginfo = procmsg_msginfo_new_from_mimeinfo(
11857 orig_msginfo, mimeinfo);
11858 if (tmp_msginfo != NULL) {
11859 new_msglist = g_slist_append(NULL, tmp_msginfo);
11861 originally_enc = MSG_IS_ENCRYPTED(orig_msginfo->flags);
11862 privacy_msginfo_get_signed_state(orig_msginfo, &s_system);
11863 originally_sig = MSG_IS_SIGNED(orig_msginfo->flags);
11865 tmp_msginfo->folder = orig_msginfo->folder;
11866 tmp_msginfo->msgnum = orig_msginfo->msgnum;
11867 if (orig_msginfo->tags) {
11868 tmp_msginfo->tags = g_slist_copy(orig_msginfo->tags);
11869 tmp_msginfo->folder->tags_dirty = TRUE;
11875 if (!opening_multiple)
11876 body = messageview_get_selection(msgview);
11879 compose = compose_reply_mode((ComposeMode)action, new_msglist, body);
11880 procmsg_msginfo_free(&tmp_msginfo);
11881 g_slist_free(new_msglist);
11883 compose = compose_reply_mode((ComposeMode)action, msginfo_list, body);
11885 if (compose && originally_enc) {
11886 compose_force_encryption(compose, compose->account, FALSE, s_system);
11889 if (compose && originally_sig && compose->account->default_sign_reply) {
11890 compose_force_signing(compose, compose->account, s_system);
11894 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
11897 void compose_reply_from_messageview(MessageView *msgview, GSList *msginfo_list,
11900 if ((!prefs_common.forward_as_attachment || action != COMPOSE_FORWARD)
11901 && action != COMPOSE_FORWARD_AS_ATTACH && g_slist_length(msginfo_list) > 1) {
11902 GSList *cur = msginfo_list;
11903 gchar *msg = g_strdup_printf(_("You are about to reply to %d "
11904 "messages. Opening the windows "
11905 "could take some time. Do you "
11906 "want to continue?"),
11907 g_slist_length(msginfo_list));
11908 if (g_slist_length(msginfo_list) > 9
11909 && alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, "+" GTK_STOCK_YES, NULL)
11910 != G_ALERTALTERNATE) {
11915 /* We'll open multiple compose windows */
11916 /* let the WM place the next windows */
11917 compose_force_window_origin = FALSE;
11918 for (; cur; cur = cur->next) {
11920 tmplist.data = cur->data;
11921 tmplist.next = NULL;
11922 compose_reply_from_messageview_real(msgview, &tmplist, action, TRUE);
11924 compose_force_window_origin = TRUE;
11926 /* forwarding multiple mails as attachments is done via a
11927 * single compose window */
11928 compose_reply_from_messageview_real(msgview, msginfo_list, action, FALSE);
11932 void compose_check_for_email_account(Compose *compose)
11934 PrefsAccount *ac = NULL, *curr = NULL;
11940 if (compose->account && compose->account->protocol == A_NNTP) {
11941 ac = account_get_cur_account();
11942 if (ac->protocol == A_NNTP) {
11943 list = account_get_list();
11945 for( ; list != NULL ; list = g_list_next(list)) {
11946 curr = (PrefsAccount *) list->data;
11947 if (curr->protocol != A_NNTP) {
11953 combobox_select_by_data(GTK_COMBO_BOX(compose->account_combo),
11958 void compose_reply_to_address(MessageView *msgview, MsgInfo *msginfo,
11959 const gchar *address)
11961 GSList *msginfo_list = NULL;
11962 gchar *body = messageview_get_selection(msgview);
11965 msginfo_list = g_slist_prepend(msginfo_list, msginfo);
11967 compose = compose_reply_mode(COMPOSE_REPLY_TO_ADDRESS, msginfo_list, body);
11968 compose_check_for_email_account(compose);
11969 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
11970 compose_entry_append(compose, address, COMPOSE_TO, PREF_NONE);
11971 compose_reply_set_subject(compose, msginfo);
11974 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
11977 void compose_set_position(Compose *compose, gint pos)
11979 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11981 gtkut_text_view_set_position(text, pos);
11984 gboolean compose_search_string(Compose *compose,
11985 const gchar *str, gboolean case_sens)
11987 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11989 return gtkut_text_view_search_string(text, str, case_sens);
11992 gboolean compose_search_string_backward(Compose *compose,
11993 const gchar *str, gboolean case_sens)
11995 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11997 return gtkut_text_view_search_string_backward(text, str, case_sens);
12000 /* allocate a msginfo structure and populate its data from a compose data structure */
12001 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose)
12003 MsgInfo *newmsginfo;
12005 gchar buf[BUFFSIZE];
12007 cm_return_val_if_fail( compose != NULL, NULL );
12009 newmsginfo = procmsg_msginfo_new();
12012 get_rfc822_date(buf, sizeof(buf));
12013 newmsginfo->date = g_strdup(buf);
12016 if (compose->from_name) {
12017 newmsginfo->from = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
12018 newmsginfo->fromname = procheader_get_fromname(newmsginfo->from);
12022 if (compose->subject_entry)
12023 newmsginfo->subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
12025 /* to, cc, reply-to, newsgroups */
12026 for (list = compose->header_list; list; list = list->next) {
12027 gchar *header = gtk_editable_get_chars(
12029 gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
12030 gchar *entry = gtk_editable_get_chars(
12031 GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
12033 if ( strcasecmp(header, prefs_common_translated_header_name("To:")) == 0 ) {
12034 if ( newmsginfo->to == NULL ) {
12035 newmsginfo->to = g_strdup(entry);
12036 } else if (entry && *entry) {
12037 gchar *tmp = g_strconcat(newmsginfo->to, ", ", entry, NULL);
12038 g_free(newmsginfo->to);
12039 newmsginfo->to = tmp;
12042 if ( strcasecmp(header, prefs_common_translated_header_name("Cc:")) == 0 ) {
12043 if ( newmsginfo->cc == NULL ) {
12044 newmsginfo->cc = g_strdup(entry);
12045 } else if (entry && *entry) {
12046 gchar *tmp = g_strconcat(newmsginfo->cc, ", ", entry, NULL);
12047 g_free(newmsginfo->cc);
12048 newmsginfo->cc = tmp;
12051 if ( strcasecmp(header,
12052 prefs_common_translated_header_name("Newsgroups:")) == 0 ) {
12053 if ( newmsginfo->newsgroups == NULL ) {
12054 newmsginfo->newsgroups = g_strdup(entry);
12055 } else if (entry && *entry) {
12056 gchar *tmp = g_strconcat(newmsginfo->newsgroups, ", ", entry, NULL);
12057 g_free(newmsginfo->newsgroups);
12058 newmsginfo->newsgroups = tmp;
12066 /* other data is unset */
12072 /* update compose's dictionaries from folder dict settings */
12073 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
12074 FolderItem *folder_item)
12076 cm_return_if_fail(compose != NULL);
12078 if (compose->gtkaspell && folder_item && folder_item->prefs) {
12079 FolderItemPrefs *prefs = folder_item->prefs;
12081 if (prefs->enable_default_dictionary)
12082 gtkaspell_change_dict(compose->gtkaspell,
12083 prefs->default_dictionary, FALSE);
12084 if (folder_item->prefs->enable_default_alt_dictionary)
12085 gtkaspell_change_alt_dict(compose->gtkaspell,
12086 prefs->default_alt_dictionary);
12087 if (prefs->enable_default_dictionary
12088 || prefs->enable_default_alt_dictionary)
12089 compose_spell_menu_changed(compose);
12094 static void compose_subject_entry_activated(GtkWidget *widget, gpointer data)
12096 Compose *compose = (Compose *)data;
12098 cm_return_if_fail(compose != NULL);
12100 gtk_widget_grab_focus(compose->text);
12103 static void from_name_activate_cb(GtkWidget *widget, gpointer data)
12105 gtk_combo_box_popup(GTK_COMBO_BOX(data));