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->references != NULL) {
2979 GString *refs = g_string_new(NULL);
2980 GSList *r = msginfo->references;
2982 g_string_append_printf(refs, "<%s>%s", (gchar *)r->data,
2983 (g_slist_next(r) ? "\n\t" : ""));
2984 r = g_slist_next(r);
2988 if (msginfo->msgid && *msginfo->msgid)
2989 compose->inreplyto = g_strdup(msginfo->msgid);
2991 if (!compose->references) {
2992 if (msginfo->msgid && *msginfo->msgid) {
2993 if (msginfo->inreplyto && *msginfo->inreplyto)
2994 compose->references =
2995 g_strdup_printf("<%s>\n\t<%s>",
2999 compose->references =
3000 g_strconcat("<", msginfo->msgid, ">",
3002 } else if (msginfo->inreplyto && *msginfo->inreplyto) {
3003 compose->references =
3004 g_strconcat("<", msginfo->inreplyto, ">",
3013 static gint compose_parse_manual_headers(Compose *compose, MsgInfo *msginfo, HeaderEntry *entries)
3018 cm_return_val_if_fail(msginfo != NULL, -1);
3020 if ((fp = procmsg_open_message(msginfo)) == NULL) return -1;
3021 procheader_get_header_fields(fp, entries);
3025 while (he != NULL && he->name != NULL) {
3027 GtkListStore *model = NULL;
3029 debug_print("Adding manual header: %s with value %s\n", he->name, he->body);
3030 model = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(compose->header_last->combo)));
3031 COMBOBOX_ADD(model, he->name, COMPOSE_TO);
3032 gtk_combo_box_set_active_iter(GTK_COMBO_BOX(compose->header_last->combo), &iter);
3033 gtk_entry_set_text(GTK_ENTRY(compose->header_last->entry), he->body);
3040 static gchar *compose_parse_references(const gchar *ref, const gchar *msgid)
3042 GSList *ref_id_list, *cur;
3046 ref_id_list = references_list_append(NULL, ref);
3047 if (!ref_id_list) return NULL;
3048 if (msgid && *msgid)
3049 ref_id_list = g_slist_append(ref_id_list, g_strdup(msgid));
3054 for (cur = ref_id_list; cur != NULL; cur = cur->next)
3055 /* "<" + Message-ID + ">" + CR+LF+TAB */
3056 len += strlen((gchar *)cur->data) + 5;
3058 if (len > MAX_REFERENCES_LEN) {
3059 /* remove second message-ID */
3060 if (ref_id_list && ref_id_list->next &&
3061 ref_id_list->next->next) {
3062 g_free(ref_id_list->next->data);
3063 ref_id_list = g_slist_remove
3064 (ref_id_list, ref_id_list->next->data);
3066 slist_free_strings_full(ref_id_list);
3073 new_ref = g_string_new("");
3074 for (cur = ref_id_list; cur != NULL; cur = cur->next) {
3075 if (new_ref->len > 0)
3076 g_string_append(new_ref, "\n\t");
3077 g_string_append_printf(new_ref, "<%s>", (gchar *)cur->data);
3080 slist_free_strings_full(ref_id_list);
3082 new_ref_str = new_ref->str;
3083 g_string_free(new_ref, FALSE);
3088 static gchar *compose_quote_fmt(Compose *compose, MsgInfo *msginfo,
3089 const gchar *fmt, const gchar *qmark,
3090 const gchar *body, gboolean rewrap,
3091 gboolean need_unescape,
3092 const gchar *err_msg)
3094 MsgInfo* dummyinfo = NULL;
3095 gchar *quote_str = NULL;
3097 gboolean prev_autowrap;
3098 const gchar *trimmed_body = body;
3099 gint cursor_pos = -1;
3100 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3101 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3106 SIGNAL_BLOCK(buffer);
3109 dummyinfo = compose_msginfo_new_from_compose(compose);
3110 msginfo = dummyinfo;
3113 if (qmark != NULL) {
3115 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
3116 compose->gtkaspell);
3118 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
3120 quote_fmt_scan_string(qmark);
3123 buf = quote_fmt_get_buffer();
3125 alertpanel_error(_("The \"Quotation mark\" of the template is invalid."));
3127 Xstrdup_a(quote_str, buf, goto error)
3130 if (fmt && *fmt != '\0') {
3133 while (*trimmed_body == '\n')
3137 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE,
3138 compose->gtkaspell);
3140 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE);
3142 if (need_unescape) {
3145 /* decode \-escape sequences in the internal representation of the quote format */
3146 tmp = g_malloc(strlen(fmt)+1);
3147 pref_get_unescaped_pref(tmp, fmt);
3148 quote_fmt_scan_string(tmp);
3152 quote_fmt_scan_string(fmt);
3156 buf = quote_fmt_get_buffer();
3158 gint line = quote_fmt_get_line();
3159 alertpanel_error(err_msg, line);
3165 prev_autowrap = compose->autowrap;
3166 compose->autowrap = FALSE;
3168 mark = gtk_text_buffer_get_insert(buffer);
3169 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3170 if (g_utf8_validate(buf, -1, NULL)) {
3171 gtk_text_buffer_insert(buffer, &iter, buf, -1);
3173 gchar *tmpout = NULL;
3174 tmpout = conv_codeset_strdup
3175 (buf, conv_get_locale_charset_str_no_utf8(),
3177 if (!tmpout || !g_utf8_validate(tmpout, -1, NULL)) {
3179 tmpout = g_malloc(strlen(buf)*2+1);
3180 conv_localetodisp(tmpout, strlen(buf)*2+1, buf);
3182 gtk_text_buffer_insert(buffer, &iter, tmpout, -1);
3186 cursor_pos = quote_fmt_get_cursor_pos();
3187 if (cursor_pos == -1)
3188 cursor_pos = gtk_text_iter_get_offset(&iter);
3189 compose->set_cursor_pos = cursor_pos;
3191 gtk_text_buffer_get_start_iter(buffer, &iter);
3192 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
3193 gtk_text_buffer_place_cursor(buffer, &iter);
3195 compose->autowrap = prev_autowrap;
3196 if (compose->autowrap && rewrap)
3197 compose_wrap_all(compose);
3204 SIGNAL_UNBLOCK(buffer);
3206 procmsg_msginfo_free( &dummyinfo );
3211 /* if ml_post is of type addr@host and from is of type
3212 * addr-anything@host, return TRUE
3214 static gboolean is_subscription(const gchar *ml_post, const gchar *from)
3216 gchar *left_ml = NULL;
3217 gchar *right_ml = NULL;
3218 gchar *left_from = NULL;
3219 gchar *right_from = NULL;
3220 gboolean result = FALSE;
3222 if (!ml_post || !from)
3225 left_ml = g_strdup(ml_post);
3226 if (strstr(left_ml, "@")) {
3227 right_ml = strstr(left_ml, "@")+1;
3228 *(strstr(left_ml, "@")) = '\0';
3231 left_from = g_strdup(from);
3232 if (strstr(left_from, "@")) {
3233 right_from = strstr(left_from, "@")+1;
3234 *(strstr(left_from, "@")) = '\0';
3237 if (right_ml && right_from
3238 && !strncmp(left_from, left_ml, strlen(left_ml))
3239 && !strcmp(right_from, right_ml)) {
3248 static void compose_set_folder_prefs(Compose *compose, FolderItem *folder,
3249 gboolean respect_default_to)
3253 if (!folder || !folder->prefs)
3256 if (respect_default_to && folder->prefs->enable_default_to) {
3257 compose_entry_append(compose, folder->prefs->default_to,
3258 COMPOSE_TO, PREF_FOLDER);
3259 compose_entry_mark_default_to(compose, folder->prefs->default_to);
3261 if (folder->prefs->enable_default_cc)
3262 compose_entry_append(compose, folder->prefs->default_cc,
3263 COMPOSE_CC, PREF_FOLDER);
3264 if (folder->prefs->enable_default_bcc)
3265 compose_entry_append(compose, folder->prefs->default_bcc,
3266 COMPOSE_BCC, PREF_FOLDER);
3267 if (folder->prefs->enable_default_replyto)
3268 compose_entry_append(compose, folder->prefs->default_replyto,
3269 COMPOSE_REPLYTO, PREF_FOLDER);
3272 static void compose_reply_set_subject(Compose *compose, MsgInfo *msginfo)
3277 if (!compose || !msginfo)
3280 if (msginfo->subject && *msginfo->subject) {
3281 buf = p = g_strdup(msginfo->subject);
3282 p += subject_get_prefix_length(p);
3283 memmove(buf, p, strlen(p) + 1);
3285 buf2 = g_strdup_printf("Re: %s", buf);
3286 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
3291 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), "Re: ");
3294 static void compose_reply_set_entry(Compose *compose, MsgInfo *msginfo,
3295 gboolean to_all, gboolean to_ml,
3297 gboolean followup_and_reply_to)
3299 GSList *cc_list = NULL;
3302 gchar *replyto = NULL;
3303 gchar *ac_email = NULL;
3305 gboolean reply_to_ml = FALSE;
3306 gboolean default_reply_to = FALSE;
3308 cm_return_if_fail(compose->account != NULL);
3309 cm_return_if_fail(msginfo != NULL);
3311 reply_to_ml = to_ml && compose->ml_post;
3313 default_reply_to = msginfo->folder &&
3314 msginfo->folder->prefs->enable_default_reply_to;
3316 if (compose->account->protocol != A_NNTP) {
3317 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
3319 if (reply_to_ml && !default_reply_to) {
3321 gboolean is_subscr = is_subscription(compose->ml_post,
3324 /* normal answer to ml post with a reply-to */
3325 compose_entry_append(compose,
3327 COMPOSE_TO, PREF_ML);
3328 if (compose->replyto)
3329 compose_entry_append(compose,
3331 COMPOSE_CC, PREF_ML);
3333 /* answer to subscription confirmation */
3334 if (compose->replyto)
3335 compose_entry_append(compose,
3337 COMPOSE_TO, PREF_ML);
3338 else if (msginfo->from)
3339 compose_entry_append(compose,
3341 COMPOSE_TO, PREF_ML);
3344 else if (!(to_all || to_sender) && default_reply_to) {
3345 compose_entry_append(compose,
3346 msginfo->folder->prefs->default_reply_to,
3347 COMPOSE_TO, PREF_FOLDER);
3348 compose_entry_mark_default_to(compose,
3349 msginfo->folder->prefs->default_reply_to);
3355 compose_entry_append(compose, msginfo->from,
3356 COMPOSE_TO, PREF_NONE);
3358 Xstrdup_a(tmp1, msginfo->from, return);
3359 extract_address(tmp1);
3360 compose_entry_append(compose,
3361 (!account_find_from_address(tmp1, FALSE))
3364 COMPOSE_TO, PREF_NONE);
3365 if (compose->replyto)
3366 compose_entry_append(compose,
3368 COMPOSE_CC, PREF_NONE);
3370 if (!folder_has_parent_of_type(msginfo->folder, F_QUEUE) &&
3371 !folder_has_parent_of_type(msginfo->folder, F_OUTBOX) &&
3372 !folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
3373 if (compose->replyto) {
3374 compose_entry_append(compose,
3376 COMPOSE_TO, PREF_NONE);
3378 compose_entry_append(compose,
3379 msginfo->from ? msginfo->from : "",
3380 COMPOSE_TO, PREF_NONE);
3383 /* replying to own mail, use original recp */
3384 compose_entry_append(compose,
3385 msginfo->to ? msginfo->to : "",
3386 COMPOSE_TO, PREF_NONE);
3387 compose_entry_append(compose,
3388 msginfo->cc ? msginfo->cc : "",
3389 COMPOSE_CC, PREF_NONE);
3394 if (to_sender || (compose->followup_to &&
3395 !strncmp(compose->followup_to, "poster", 6)))
3396 compose_entry_append
3398 (compose->replyto ? compose->replyto :
3399 msginfo->from ? msginfo->from : ""),
3400 COMPOSE_TO, PREF_NONE);
3402 else if (followup_and_reply_to || to_all) {
3403 compose_entry_append
3405 (compose->replyto ? compose->replyto :
3406 msginfo->from ? msginfo->from : ""),
3407 COMPOSE_TO, PREF_NONE);
3409 compose_entry_append
3411 compose->followup_to ? compose->followup_to :
3412 compose->newsgroups ? compose->newsgroups : "",
3413 COMPOSE_NEWSGROUPS, PREF_NONE);
3415 compose_entry_append
3417 msginfo->cc ? msginfo->cc : "",
3418 COMPOSE_CC, PREF_NONE);
3421 compose_entry_append
3423 compose->followup_to ? compose->followup_to :
3424 compose->newsgroups ? compose->newsgroups : "",
3425 COMPOSE_NEWSGROUPS, PREF_NONE);
3427 compose_reply_set_subject(compose, msginfo);
3429 if (to_ml && compose->ml_post) return;
3430 if (!to_all || compose->account->protocol == A_NNTP) return;
3432 if (compose->replyto) {
3433 Xstrdup_a(replyto, compose->replyto, return);
3434 extract_address(replyto);
3436 if (msginfo->from) {
3437 Xstrdup_a(from, msginfo->from, return);
3438 extract_address(from);
3441 if (replyto && from)
3442 cc_list = address_list_append_with_comments(cc_list, from);
3443 if (to_all && msginfo->folder &&
3444 msginfo->folder->prefs->enable_default_reply_to)
3445 cc_list = address_list_append_with_comments(cc_list,
3446 msginfo->folder->prefs->default_reply_to);
3447 cc_list = address_list_append_with_comments(cc_list, msginfo->to);
3448 cc_list = address_list_append_with_comments(cc_list, compose->cc);
3450 ac_email = g_utf8_strdown(compose->account->address, -1);
3453 for (cur = cc_list; cur != NULL; cur = cur->next) {
3454 gchar *addr = g_utf8_strdown(cur->data, -1);
3455 extract_address(addr);
3457 if (strcmp(ac_email, addr))
3458 compose_entry_append(compose, (gchar *)cur->data,
3459 COMPOSE_CC, PREF_NONE);
3461 debug_print("Cc address same as compose account's, ignoring\n");
3466 slist_free_strings_full(cc_list);
3472 #define SET_ENTRY(entry, str) \
3475 gtk_entry_set_text(GTK_ENTRY(compose->entry), str); \
3478 #define SET_ADDRESS(type, str) \
3481 compose_entry_append(compose, str, type, PREF_NONE); \
3484 static void compose_reedit_set_entry(Compose *compose, MsgInfo *msginfo)
3486 cm_return_if_fail(msginfo != NULL);
3488 SET_ENTRY(subject_entry, msginfo->subject);
3489 SET_ENTRY(from_name, msginfo->from);
3490 SET_ADDRESS(COMPOSE_TO, msginfo->to);
3491 SET_ADDRESS(COMPOSE_CC, compose->cc);
3492 SET_ADDRESS(COMPOSE_BCC, compose->bcc);
3493 SET_ADDRESS(COMPOSE_REPLYTO, compose->replyto);
3494 SET_ADDRESS(COMPOSE_NEWSGROUPS, compose->newsgroups);
3495 SET_ADDRESS(COMPOSE_FOLLOWUPTO, compose->followup_to);
3497 compose_update_priority_menu_item(compose);
3498 compose_update_privacy_system_menu_item(compose, FALSE);
3499 compose_show_first_last_header(compose, TRUE);
3505 static void compose_insert_sig(Compose *compose, gboolean replace)
3507 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3508 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3510 GtkTextIter iter, iter_end;
3511 gint cur_pos, ins_pos;
3512 gboolean prev_autowrap;
3513 gboolean found = FALSE;
3514 gboolean exists = FALSE;
3516 cm_return_if_fail(compose->account != NULL);
3520 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3521 G_CALLBACK(compose_changed_cb),
3524 mark = gtk_text_buffer_get_insert(buffer);
3525 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3526 cur_pos = gtk_text_iter_get_offset (&iter);
3529 gtk_text_buffer_get_end_iter(buffer, &iter);
3531 exists = (compose->sig_str != NULL);
3534 GtkTextIter first_iter, start_iter, end_iter;
3536 gtk_text_buffer_get_start_iter(buffer, &first_iter);
3538 if (!exists || compose->sig_str[0] == '\0')
3541 found = gtk_text_iter_forward_to_tag_toggle(&first_iter,
3542 compose->signature_tag);
3545 /* include previous \n\n */
3546 gtk_text_iter_backward_chars(&first_iter, 1);
3547 start_iter = first_iter;
3548 end_iter = first_iter;
3550 found = gtk_text_iter_forward_to_tag_toggle(&end_iter,
3551 compose->signature_tag);
3552 found &= gtk_text_iter_forward_to_tag_toggle(&end_iter,
3553 compose->signature_tag);
3555 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
3561 g_free(compose->sig_str);
3562 compose->sig_str = account_get_signature_str(compose->account);
3564 cur_pos = gtk_text_iter_get_offset(&iter);
3566 if (!compose->sig_str || (replace && !compose->account->auto_sig)) {
3567 g_free(compose->sig_str);
3568 compose->sig_str = NULL;
3570 if (compose->sig_inserted == FALSE)
3571 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
3572 compose->sig_inserted = TRUE;
3574 cur_pos = gtk_text_iter_get_offset(&iter);
3575 gtk_text_buffer_insert(buffer, &iter, compose->sig_str, -1);
3577 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cur_pos);
3578 gtk_text_iter_forward_chars(&iter, 1);
3579 gtk_text_buffer_get_end_iter(buffer, &iter_end);
3580 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &iter_end);
3582 if (cur_pos > gtk_text_buffer_get_char_count (buffer))
3583 cur_pos = gtk_text_buffer_get_char_count (buffer);
3586 /* put the cursor where it should be
3587 * either where the quote_fmt says, either where it was */
3588 if (compose->set_cursor_pos < 0)
3589 gtk_text_buffer_get_iter_at_offset(buffer, &iter, ins_pos);
3591 gtk_text_buffer_get_iter_at_offset(buffer, &iter,
3592 compose->set_cursor_pos);
3594 compose->set_cursor_pos = -1;
3595 gtk_text_buffer_place_cursor(buffer, &iter);
3596 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3597 G_CALLBACK(compose_changed_cb),
3603 static ComposeInsertResult compose_insert_file(Compose *compose, const gchar *file)
3606 GtkTextBuffer *buffer;
3609 const gchar *cur_encoding;
3610 gchar buf[BUFFSIZE];
3613 gboolean prev_autowrap;
3616 GString *file_contents = NULL;
3617 ComposeInsertResult result = COMPOSE_INSERT_SUCCESS;
3619 cm_return_val_if_fail(file != NULL, COMPOSE_INSERT_NO_FILE);
3621 /* get the size of the file we are about to insert */
3622 ret = g_stat(file, &file_stat);
3624 gchar *shortfile = g_path_get_basename(file);
3625 alertpanel_error(_("Could not get size of file '%s'."), shortfile);
3627 return COMPOSE_INSERT_NO_FILE;
3628 } else if (prefs_common.warn_large_insert == TRUE) {
3630 /* ask user for confirmation if the file is large */
3631 if (prefs_common.warn_large_insert_size < 0 ||
3632 file_stat.st_size > (prefs_common.warn_large_insert_size * 1024)) {
3636 msg = g_strdup_printf(_("You are about to insert a file of %s "
3637 "in the message body. Are you sure you want to do that?"),
3638 to_human_readable(file_stat.st_size));
3639 aval = alertpanel_full(_("Are you sure?"), msg, GTK_STOCK_CANCEL,
3640 g_strconcat("+", _("_Insert"), NULL), NULL, TRUE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
3643 /* do we ask for confirmation next time? */
3644 if (aval & G_ALERTDISABLE) {
3645 /* no confirmation next time, disable feature in preferences */
3646 aval &= ~G_ALERTDISABLE;
3647 prefs_common.warn_large_insert = FALSE;
3650 /* abort file insertion if user canceled action */
3651 if (aval != G_ALERTALTERNATE) {
3652 return COMPOSE_INSERT_NO_FILE;
3658 if ((fp = g_fopen(file, "rb")) == NULL) {
3659 FILE_OP_ERROR(file, "fopen");
3660 return COMPOSE_INSERT_READ_ERROR;
3663 prev_autowrap = compose->autowrap;
3664 compose->autowrap = FALSE;
3666 text = GTK_TEXT_VIEW(compose->text);
3667 buffer = gtk_text_view_get_buffer(text);
3668 mark = gtk_text_buffer_get_insert(buffer);
3669 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3671 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3672 G_CALLBACK(text_inserted),
3675 cur_encoding = conv_get_locale_charset_str_no_utf8();
3677 file_contents = g_string_new("");
3678 while (fgets(buf, sizeof(buf), fp) != NULL) {
3681 if (g_utf8_validate(buf, -1, NULL) == TRUE)
3682 str = g_strdup(buf);
3684 codeconv_set_strict(TRUE);
3685 str = conv_codeset_strdup
3686 (buf, cur_encoding, CS_INTERNAL);
3687 codeconv_set_strict(FALSE);
3690 result = COMPOSE_INSERT_INVALID_CHARACTER;
3696 /* strip <CR> if DOS/Windows file,
3697 replace <CR> with <LF> if Macintosh file. */
3700 if (len > 0 && str[len - 1] != '\n') {
3702 if (str[len] == '\r') str[len] = '\n';
3705 file_contents = g_string_append(file_contents, str);
3709 if (result == COMPOSE_INSERT_SUCCESS) {
3710 gtk_text_buffer_insert(buffer, &iter, file_contents->str, -1);
3712 compose_changed_cb(NULL, compose);
3713 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3714 G_CALLBACK(text_inserted),
3716 compose->autowrap = prev_autowrap;
3717 if (compose->autowrap)
3718 compose_wrap_all(compose);
3721 g_string_free(file_contents, TRUE);
3727 static gboolean compose_attach_append(Compose *compose, const gchar *file,
3728 const gchar *filename,
3729 const gchar *content_type,
3730 const gchar *charset)
3738 GtkListStore *store;
3740 gboolean has_binary = FALSE;
3742 if (!is_file_exist(file)) {
3743 gchar *file_from_uri = g_filename_from_uri(file, NULL, NULL);
3744 gboolean result = FALSE;
3745 if (file_from_uri && is_file_exist(file_from_uri)) {
3746 result = compose_attach_append(
3747 compose, file_from_uri,
3748 filename, content_type,
3751 g_free(file_from_uri);
3754 alertpanel_error("File %s doesn't exist\n", filename);
3757 if ((size = get_file_size(file)) < 0) {
3758 alertpanel_error("Can't get file size of %s\n", filename);
3762 /* In batch mode, we allow 0-length files to be attached no questions asked */
3763 if (size == 0 && !compose->batch) {
3764 gchar * msg = g_strdup_printf(_("File %s is empty."), filename);
3765 AlertValue aval = alertpanel_full(_("Empty file"), msg,
3766 GTK_STOCK_CANCEL, g_strconcat("+", _("_Attach anyway"), NULL), NULL, FALSE,
3767 NULL, ALERT_WARNING, G_ALERTDEFAULT);
3770 if (aval != G_ALERTALTERNATE) {
3774 if ((fp = g_fopen(file, "rb")) == NULL) {
3775 alertpanel_error(_("Can't read %s."), filename);
3780 ainfo = g_new0(AttachInfo, 1);
3781 auto_ainfo = g_auto_pointer_new_with_free
3782 (ainfo, (GFreeFunc) compose_attach_info_free);
3783 ainfo->file = g_strdup(file);
3786 ainfo->content_type = g_strdup(content_type);
3787 if (!g_ascii_strcasecmp(content_type, "message/rfc822")) {
3789 MsgFlags flags = {0, 0};
3791 if (procmime_get_encoding_for_text_file(file, &has_binary) == ENC_7BIT)
3792 ainfo->encoding = ENC_7BIT;
3794 ainfo->encoding = ENC_8BIT;
3796 msginfo = procheader_parse_file(file, flags, FALSE, FALSE);
3797 if (msginfo && msginfo->subject)
3798 name = g_strdup(msginfo->subject);
3800 name = g_path_get_basename(filename ? filename : file);
3802 ainfo->name = g_strdup_printf(_("Message: %s"), name);
3804 procmsg_msginfo_free(&msginfo);
3806 if (!g_ascii_strncasecmp(content_type, "text/", 5)) {
3807 ainfo->charset = g_strdup(charset);
3808 ainfo->encoding = procmime_get_encoding_for_text_file(file, &has_binary);
3810 ainfo->encoding = ENC_BASE64;
3812 name = g_path_get_basename(filename ? filename : file);
3813 ainfo->name = g_strdup(name);
3817 ainfo->content_type = procmime_get_mime_type(file);
3818 if (!ainfo->content_type) {
3819 ainfo->content_type =
3820 g_strdup("application/octet-stream");
3821 ainfo->encoding = ENC_BASE64;
3822 } else if (!g_ascii_strncasecmp(ainfo->content_type, "text/", 5))
3824 procmime_get_encoding_for_text_file(file, &has_binary);
3826 ainfo->encoding = ENC_BASE64;
3827 name = g_path_get_basename(filename ? filename : file);
3828 ainfo->name = g_strdup(name);
3832 if (ainfo->name != NULL
3833 && !strcmp(ainfo->name, ".")) {
3834 g_free(ainfo->name);
3838 if (!strcmp(ainfo->content_type, "unknown") || has_binary) {
3839 g_free(ainfo->content_type);
3840 ainfo->content_type = g_strdup("application/octet-stream");
3841 g_free(ainfo->charset);
3842 ainfo->charset = NULL;
3845 ainfo->size = (goffset)size;
3846 size_text = to_human_readable((goffset)size);
3848 store = GTK_LIST_STORE(gtk_tree_view_get_model
3849 (GTK_TREE_VIEW(compose->attach_clist)));
3851 gtk_list_store_append(store, &iter);
3852 gtk_list_store_set(store, &iter,
3853 COL_MIMETYPE, ainfo->content_type,
3854 COL_SIZE, size_text,
3855 COL_NAME, ainfo->name,
3856 COL_CHARSET, ainfo->charset,
3858 COL_AUTODATA, auto_ainfo,
3861 g_auto_pointer_free(auto_ainfo);
3862 compose_attach_update_label(compose);
3866 static void compose_use_signing(Compose *compose, gboolean use_signing)
3868 compose->use_signing = use_signing;
3869 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", use_signing);
3872 static void compose_use_encryption(Compose *compose, gboolean use_encryption)
3874 compose->use_encryption = use_encryption;
3875 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", use_encryption);
3878 #define NEXT_PART_NOT_CHILD(info) \
3880 node = info->node; \
3881 while (node->children) \
3882 node = g_node_last_child(node); \
3883 info = procmime_mimeinfo_next((MimeInfo *)node->data); \
3886 static void compose_attach_parts(Compose *compose, MsgInfo *msginfo)
3890 MimeInfo *firsttext = NULL;
3891 MimeInfo *encrypted = NULL;
3894 const gchar *partname = NULL;
3896 mimeinfo = procmime_scan_message(msginfo);
3897 if (!mimeinfo) return;
3899 if (mimeinfo->node->children == NULL) {
3900 procmime_mimeinfo_free_all(&mimeinfo);
3904 /* find first content part */
3905 child = (MimeInfo *) mimeinfo->node->children->data;
3906 while (child && child->node->children && (child->type == MIMETYPE_MULTIPART))
3907 child = (MimeInfo *)child->node->children->data;
3910 if (child->type == MIMETYPE_TEXT) {
3912 debug_print("First text part found\n");
3913 } else if (compose->mode == COMPOSE_REEDIT &&
3914 child->type == MIMETYPE_APPLICATION &&
3915 !g_ascii_strcasecmp(child->subtype, "pgp-encrypted")) {
3916 encrypted = (MimeInfo *)child->node->parent->data;
3919 child = (MimeInfo *) mimeinfo->node->children->data;
3920 while (child != NULL) {
3923 if (child == encrypted) {
3924 /* skip this part of tree */
3925 NEXT_PART_NOT_CHILD(child);
3929 if (child->type == MIMETYPE_MULTIPART) {
3930 /* get the actual content */
3931 child = procmime_mimeinfo_next(child);
3935 if (child == firsttext) {
3936 child = procmime_mimeinfo_next(child);
3940 outfile = procmime_get_tmp_file_name(child);
3941 if ((err = procmime_get_part(outfile, child)) < 0)
3942 g_warning("Can't get the part of multipart message. (%s)", g_strerror(-err));
3944 gchar *content_type;
3946 content_type = procmime_get_content_type_str(child->type, child->subtype);
3948 /* if we meet a pgp signature, we don't attach it, but
3949 * we force signing. */
3950 if ((strcmp(content_type, "application/pgp-signature") &&
3951 strcmp(content_type, "application/pkcs7-signature") &&
3952 strcmp(content_type, "application/x-pkcs7-signature"))
3953 || compose->mode == COMPOSE_REDIRECT) {
3954 partname = procmime_mimeinfo_get_parameter(child, "filename");
3955 if (partname == NULL)
3956 partname = procmime_mimeinfo_get_parameter(child, "name");
3957 if (partname == NULL)
3959 compose_attach_append(compose, outfile,
3960 partname, content_type,
3961 procmime_mimeinfo_get_parameter(child, "charset"));
3963 compose_force_signing(compose, compose->account, NULL);
3965 g_free(content_type);
3968 NEXT_PART_NOT_CHILD(child);
3970 procmime_mimeinfo_free_all(&mimeinfo);
3973 #undef NEXT_PART_NOT_CHILD
3978 WAIT_FOR_INDENT_CHAR,
3979 WAIT_FOR_INDENT_CHAR_OR_SPACE,
3982 /* return indent length, we allow:
3983 indent characters followed by indent characters or spaces/tabs,
3984 alphabets and numbers immediately followed by indent characters,
3985 and the repeating sequences of the above
3986 If quote ends with multiple spaces, only the first one is included. */
3987 static gchar *compose_get_quote_str(GtkTextBuffer *buffer,
3988 const GtkTextIter *start, gint *len)
3990 GtkTextIter iter = *start;
3994 IndentState state = WAIT_FOR_INDENT_CHAR;
3997 gint alnum_count = 0;
3998 gint space_count = 0;
4001 if (prefs_common.quote_chars == NULL) {
4005 while (!gtk_text_iter_ends_line(&iter)) {
4006 wc = gtk_text_iter_get_char(&iter);
4007 if (g_unichar_iswide(wc))
4009 clen = g_unichar_to_utf8(wc, ch);
4013 is_indent = strchr(prefs_common.quote_chars, ch[0]) ? TRUE : FALSE;
4014 is_space = g_unichar_isspace(wc);
4016 if (state == WAIT_FOR_INDENT_CHAR) {
4017 if (!is_indent && !g_unichar_isalnum(wc))
4020 quote_len += alnum_count + space_count + 1;
4021 alnum_count = space_count = 0;
4022 state = WAIT_FOR_INDENT_CHAR_OR_SPACE;
4025 } else if (state == WAIT_FOR_INDENT_CHAR_OR_SPACE) {
4026 if (!is_indent && !is_space && !g_unichar_isalnum(wc))
4030 else if (is_indent) {
4031 quote_len += alnum_count + space_count + 1;
4032 alnum_count = space_count = 0;
4035 state = WAIT_FOR_INDENT_CHAR;
4039 gtk_text_iter_forward_char(&iter);
4042 if (quote_len > 0 && space_count > 0)
4048 if (quote_len > 0) {
4050 gtk_text_iter_forward_chars(&iter, quote_len);
4051 return gtk_text_buffer_get_text(buffer, start, &iter, FALSE);
4057 /* return >0 if the line is itemized */
4058 static int compose_itemized_length(GtkTextBuffer *buffer,
4059 const GtkTextIter *start)
4061 GtkTextIter iter = *start;
4066 if (gtk_text_iter_ends_line(&iter))
4071 wc = gtk_text_iter_get_char(&iter);
4072 if (!g_unichar_isspace(wc))
4074 gtk_text_iter_forward_char(&iter);
4075 if (gtk_text_iter_ends_line(&iter))
4079 clen = g_unichar_to_utf8(wc, ch);
4083 if (!strchr("*-+", ch[0]))
4086 gtk_text_iter_forward_char(&iter);
4087 if (gtk_text_iter_ends_line(&iter))
4089 wc = gtk_text_iter_get_char(&iter);
4090 if (g_unichar_isspace(wc)) {
4096 /* return the string at the start of the itemization */
4097 static gchar * compose_get_itemized_chars(GtkTextBuffer *buffer,
4098 const GtkTextIter *start)
4100 GtkTextIter iter = *start;
4103 GString *item_chars = g_string_new("");
4106 if (gtk_text_iter_ends_line(&iter))
4111 wc = gtk_text_iter_get_char(&iter);
4112 if (!g_unichar_isspace(wc))
4114 gtk_text_iter_forward_char(&iter);
4115 if (gtk_text_iter_ends_line(&iter))
4117 g_string_append_unichar(item_chars, wc);
4120 str = item_chars->str;
4121 g_string_free(item_chars, FALSE);
4125 /* return the number of spaces at a line's start */
4126 static int compose_left_offset_length(GtkTextBuffer *buffer,
4127 const GtkTextIter *start)
4129 GtkTextIter iter = *start;
4132 if (gtk_text_iter_ends_line(&iter))
4136 wc = gtk_text_iter_get_char(&iter);
4137 if (!g_unichar_isspace(wc))
4140 gtk_text_iter_forward_char(&iter);
4141 if (gtk_text_iter_ends_line(&iter))
4145 gtk_text_iter_forward_char(&iter);
4146 if (gtk_text_iter_ends_line(&iter))
4151 static gboolean compose_get_line_break_pos(GtkTextBuffer *buffer,
4152 const GtkTextIter *start,
4153 GtkTextIter *break_pos,
4157 GtkTextIter iter = *start, line_end = *start;
4158 PangoLogAttr *attrs;
4165 gboolean can_break = FALSE;
4166 gboolean do_break = FALSE;
4167 gboolean was_white = FALSE;
4168 gboolean prev_dont_break = FALSE;
4170 gtk_text_iter_forward_to_line_end(&line_end);
4171 str = gtk_text_buffer_get_text(buffer, &iter, &line_end, FALSE);
4172 len = g_utf8_strlen(str, -1);
4176 g_warning("compose_get_line_break_pos: len = 0!");
4180 /* g_print("breaking line: %d: %s (len = %d)\n",
4181 gtk_text_iter_get_line(&iter), str, len); */
4183 attrs = g_new(PangoLogAttr, len + 1);
4185 pango_default_break(str, -1, NULL, attrs, len + 1);
4189 /* skip quote and leading spaces */
4190 for (i = 0; *p != '\0' && i < len; i++) {
4193 wc = g_utf8_get_char(p);
4194 if (i >= quote_len && !g_unichar_isspace(wc))
4196 if (g_unichar_iswide(wc))
4198 else if (*p == '\t')
4202 p = g_utf8_next_char(p);
4205 for (; *p != '\0' && i < len; i++) {
4206 PangoLogAttr *attr = attrs + i;
4210 if (attr->is_line_break && can_break && was_white && !prev_dont_break)
4213 was_white = attr->is_white;
4215 /* don't wrap URI */
4216 if ((uri_len = get_uri_len(p)) > 0) {
4218 if (pos > 0 && col > max_col) {
4228 wc = g_utf8_get_char(p);
4229 if (g_unichar_iswide(wc)) {
4231 if (prev_dont_break && can_break && attr->is_line_break)
4233 } else if (*p == '\t')
4237 if (pos > 0 && col > max_col) {
4242 if (*p == '-' || *p == '/')
4243 prev_dont_break = TRUE;
4245 prev_dont_break = FALSE;
4247 p = g_utf8_next_char(p);
4251 // debug_print("compose_get_line_break_pos(): do_break = %d, pos = %d, col = %d\n", do_break, pos, col);
4256 *break_pos = *start;
4257 gtk_text_iter_set_line_offset(break_pos, pos);
4262 static gboolean compose_join_next_line(Compose *compose,
4263 GtkTextBuffer *buffer,
4265 const gchar *quote_str)
4267 GtkTextIter iter_ = *iter, cur, prev, next, end;
4268 PangoLogAttr attrs[3];
4270 gchar *next_quote_str;
4273 gboolean keep_cursor = FALSE;
4275 if (!gtk_text_iter_forward_line(&iter_) ||
4276 gtk_text_iter_ends_line(&iter_)) {
4279 next_quote_str = compose_get_quote_str(buffer, &iter_, "e_len);
4281 if ((quote_str || next_quote_str) &&
4282 strcmp2(quote_str, next_quote_str) != 0) {
4283 g_free(next_quote_str);
4286 g_free(next_quote_str);
4289 if (quote_len > 0) {
4290 gtk_text_iter_forward_chars(&end, quote_len);
4291 if (gtk_text_iter_ends_line(&end)) {
4296 /* don't join itemized lines */
4297 if (compose_itemized_length(buffer, &end) > 0) {
4301 /* don't join signature separator */
4302 if (compose_is_sig_separator(compose, buffer, &iter_)) {
4305 /* delete quote str */
4307 gtk_text_buffer_delete(buffer, &iter_, &end);
4309 /* don't join line breaks put by the user */
4311 gtk_text_iter_backward_char(&cur);
4312 if (gtk_text_iter_has_tag(&cur, compose->no_join_tag)) {
4313 gtk_text_iter_forward_char(&cur);
4317 gtk_text_iter_forward_char(&cur);
4318 /* delete linebreak and extra spaces */
4319 while (gtk_text_iter_backward_char(&cur)) {
4320 wc1 = gtk_text_iter_get_char(&cur);
4321 if (!g_unichar_isspace(wc1))
4326 while (!gtk_text_iter_ends_line(&cur)) {
4327 wc1 = gtk_text_iter_get_char(&cur);
4328 if (!g_unichar_isspace(wc1))
4330 gtk_text_iter_forward_char(&cur);
4333 if (!gtk_text_iter_equal(&prev, &next)) {
4336 mark = gtk_text_buffer_get_insert(buffer);
4337 gtk_text_buffer_get_iter_at_mark(buffer, &cur, mark);
4338 if (gtk_text_iter_equal(&prev, &cur))
4340 gtk_text_buffer_delete(buffer, &prev, &next);
4344 /* insert space if required */
4345 gtk_text_iter_backward_char(&prev);
4346 wc1 = gtk_text_iter_get_char(&prev);
4347 wc2 = gtk_text_iter_get_char(&next);
4348 gtk_text_iter_forward_char(&next);
4349 str = gtk_text_buffer_get_text(buffer, &prev, &next, FALSE);
4350 pango_default_break(str, -1, NULL, attrs, 3);
4351 if (!attrs[1].is_line_break ||
4352 (!g_unichar_iswide(wc1) || !g_unichar_iswide(wc2))) {
4353 gtk_text_buffer_insert(buffer, &iter_, " ", 1);
4355 gtk_text_iter_backward_char(&iter_);
4356 gtk_text_buffer_place_cursor(buffer, &iter_);
4365 #define ADD_TXT_POS(bp_, ep_, pti_) \
4366 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
4367 last = last->next; \
4368 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
4369 last->next = NULL; \
4371 g_warning("alloc error scanning URIs"); \
4374 static gboolean compose_beautify_paragraph(Compose *compose, GtkTextIter *par_iter, gboolean force)
4376 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4377 GtkTextBuffer *buffer;
4378 GtkTextIter iter, break_pos, end_of_line;
4379 gchar *quote_str = NULL;
4381 gboolean wrap_quote = force || prefs_common.linewrap_quote;
4382 gboolean prev_autowrap = compose->autowrap;
4383 gint startq_offset = -1, noq_offset = -1;
4384 gint uri_start = -1, uri_stop = -1;
4385 gint nouri_start = -1, nouri_stop = -1;
4386 gint num_blocks = 0;
4387 gint quotelevel = -1;
4388 gboolean modified = force;
4389 gboolean removed = FALSE;
4390 gboolean modified_before_remove = FALSE;
4392 gboolean start = TRUE;
4393 gint itemized_len = 0, rem_item_len = 0;
4394 gchar *itemized_chars = NULL;
4395 gboolean item_continuation = FALSE;
4400 if (compose->draft_timeout_tag == COMPOSE_DRAFT_TIMEOUT_FORBIDDEN) {
4404 compose->autowrap = FALSE;
4406 buffer = gtk_text_view_get_buffer(text);
4407 undo_wrapping(compose->undostruct, TRUE);
4412 mark = gtk_text_buffer_get_insert(buffer);
4413 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
4417 if (compose->draft_timeout_tag == COMPOSE_DRAFT_TIMEOUT_FORBIDDEN) {
4418 if (gtk_text_iter_ends_line(&iter)) {
4419 while (gtk_text_iter_ends_line(&iter) &&
4420 gtk_text_iter_forward_line(&iter))
4423 while (gtk_text_iter_backward_line(&iter)) {
4424 if (gtk_text_iter_ends_line(&iter)) {
4425 gtk_text_iter_forward_line(&iter);
4431 /* move to line start */
4432 gtk_text_iter_set_line_offset(&iter, 0);
4435 itemized_len = compose_itemized_length(buffer, &iter);
4437 if (!itemized_len) {
4438 itemized_len = compose_left_offset_length(buffer, &iter);
4439 item_continuation = TRUE;
4443 itemized_chars = compose_get_itemized_chars(buffer, &iter);
4445 /* go until paragraph end (empty line) */
4446 while (start || !gtk_text_iter_ends_line(&iter)) {
4447 gchar *scanpos = NULL;
4448 /* parse table - in order of priority */
4450 const gchar *needle; /* token */
4452 /* token search function */
4453 gchar *(*search) (const gchar *haystack,
4454 const gchar *needle);
4455 /* part parsing function */
4456 gboolean (*parse) (const gchar *start,
4457 const gchar *scanpos,
4461 /* part to URI function */
4462 gchar *(*build_uri) (const gchar *bp,
4466 static struct table parser[] = {
4467 {"http://", strcasestr, get_uri_part, make_uri_string},
4468 {"https://", strcasestr, get_uri_part, make_uri_string},
4469 {"ftp://", strcasestr, get_uri_part, make_uri_string},
4470 {"sftp://", strcasestr, get_uri_part, make_uri_string},
4471 {"gopher://",strcasestr, get_uri_part, make_uri_string},
4472 {"www.", strcasestr, get_uri_part, make_http_string},
4473 {"mailto:", strcasestr, get_uri_part, make_uri_string},
4474 {"@", strcasestr, get_email_part, make_email_string}
4476 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
4477 gint last_index = PARSE_ELEMS;
4479 gchar *o_walk = NULL, *walk = NULL, *bp = NULL, *ep = NULL;
4483 if (!prev_autowrap && num_blocks == 0) {
4485 g_signal_handlers_block_by_func(G_OBJECT(buffer),
4486 G_CALLBACK(text_inserted),
4489 if (gtk_text_iter_has_tag(&iter, compose->no_wrap_tag) && !force)
4492 uri_start = uri_stop = -1;
4494 quote_str = compose_get_quote_str(buffer, &iter, "e_len);
4497 // debug_print("compose_beautify_paragraph(): quote_str = '%s'\n", quote_str);
4498 if (startq_offset == -1)
4499 startq_offset = gtk_text_iter_get_offset(&iter);
4500 quotelevel = get_quote_level(quote_str, prefs_common.quote_chars);
4501 if (quotelevel > 2) {
4502 /* recycle colors */
4503 if (prefs_common.recycle_quote_colors)
4512 if (startq_offset == -1)
4513 noq_offset = gtk_text_iter_get_offset(&iter);
4517 if (prev_autowrap == FALSE && !force && !wrap_quote) {
4520 if (gtk_text_iter_ends_line(&iter)) {
4522 } else if (compose_get_line_break_pos(buffer, &iter, &break_pos,
4523 prefs_common.linewrap_len,
4525 GtkTextIter prev, next, cur;
4526 if (prev_autowrap != FALSE || force) {
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);
4536 } else if (quote_str && wrap_quote) {
4537 compose->automatic_break = TRUE;
4539 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4540 compose->automatic_break = FALSE;
4541 if (itemized_len && compose->autoindent) {
4542 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4543 if (!item_continuation)
4544 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4548 /* remove trailing spaces */
4550 rem_item_len = itemized_len;
4551 while (compose->autoindent && rem_item_len-- > 0)
4552 gtk_text_iter_backward_char(&cur);
4553 gtk_text_iter_backward_char(&cur);
4556 while (!gtk_text_iter_starts_line(&cur)) {
4559 gtk_text_iter_backward_char(&cur);
4560 wc = gtk_text_iter_get_char(&cur);
4561 if (!g_unichar_isspace(wc))
4565 if (!gtk_text_iter_equal(&prev, &next)) {
4566 gtk_text_buffer_delete(buffer, &prev, &next);
4568 gtk_text_iter_forward_char(&break_pos);
4572 gtk_text_buffer_insert(buffer, &break_pos,
4576 modified |= compose_join_next_line(compose, buffer, &iter, quote_str);
4578 /* move iter to current line start */
4579 gtk_text_iter_set_line_offset(&iter, 0);
4586 /* move iter to next line start */
4592 if (!prev_autowrap && num_blocks > 0) {
4594 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
4595 G_CALLBACK(text_inserted),
4599 while (!gtk_text_iter_ends_line(&end_of_line)) {
4600 gtk_text_iter_forward_char(&end_of_line);
4602 o_walk = walk = gtk_text_buffer_get_text(buffer, &iter, &end_of_line, FALSE);
4604 nouri_start = gtk_text_iter_get_offset(&iter);
4605 nouri_stop = gtk_text_iter_get_offset(&end_of_line);
4607 walk_pos = gtk_text_iter_get_offset(&iter);
4608 /* FIXME: this looks phony. scanning for anything in the parse table */
4609 for (n = 0; n < PARSE_ELEMS; n++) {
4612 tmp = parser[n].search(walk, parser[n].needle);
4614 if (scanpos == NULL || tmp < scanpos) {
4623 /* check if URI can be parsed */
4624 if (parser[last_index].parse(walk, scanpos, (const gchar **)&bp,
4625 (const gchar **)&ep, FALSE)
4626 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
4630 strlen(parser[last_index].needle);
4633 uri_start = walk_pos + (bp - o_walk);
4634 uri_stop = walk_pos + (ep - o_walk);
4638 gtk_text_iter_forward_line(&iter);
4641 if (startq_offset != -1) {
4642 GtkTextIter startquote, endquote;
4643 gtk_text_buffer_get_iter_at_offset(
4644 buffer, &startquote, startq_offset);
4647 switch (quotelevel) {
4649 if (!gtk_text_iter_has_tag(&startquote, compose->quote0_tag) ||
4650 !gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) {
4651 gtk_text_buffer_apply_tag_by_name(
4652 buffer, "quote0", &startquote, &endquote);
4653 gtk_text_buffer_remove_tag_by_name(
4654 buffer, "quote1", &startquote, &endquote);
4655 gtk_text_buffer_remove_tag_by_name(
4656 buffer, "quote2", &startquote, &endquote);
4661 if (!gtk_text_iter_has_tag(&startquote, compose->quote1_tag) ||
4662 !gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) {
4663 gtk_text_buffer_apply_tag_by_name(
4664 buffer, "quote1", &startquote, &endquote);
4665 gtk_text_buffer_remove_tag_by_name(
4666 buffer, "quote0", &startquote, &endquote);
4667 gtk_text_buffer_remove_tag_by_name(
4668 buffer, "quote2", &startquote, &endquote);
4673 if (!gtk_text_iter_has_tag(&startquote, compose->quote2_tag) ||
4674 !gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag)) {
4675 gtk_text_buffer_apply_tag_by_name(
4676 buffer, "quote2", &startquote, &endquote);
4677 gtk_text_buffer_remove_tag_by_name(
4678 buffer, "quote0", &startquote, &endquote);
4679 gtk_text_buffer_remove_tag_by_name(
4680 buffer, "quote1", &startquote, &endquote);
4686 } else if (noq_offset != -1) {
4687 GtkTextIter startnoquote, endnoquote;
4688 gtk_text_buffer_get_iter_at_offset(
4689 buffer, &startnoquote, noq_offset);
4692 if ((gtk_text_iter_has_tag(&startnoquote, compose->quote0_tag)
4693 && gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) ||
4694 (gtk_text_iter_has_tag(&startnoquote, compose->quote1_tag)
4695 && gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) ||
4696 (gtk_text_iter_has_tag(&startnoquote, compose->quote2_tag)
4697 && gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag))) {
4698 gtk_text_buffer_remove_tag_by_name(
4699 buffer, "quote0", &startnoquote, &endnoquote);
4700 gtk_text_buffer_remove_tag_by_name(
4701 buffer, "quote1", &startnoquote, &endnoquote);
4702 gtk_text_buffer_remove_tag_by_name(
4703 buffer, "quote2", &startnoquote, &endnoquote);
4709 if (uri_start != nouri_start && uri_stop != nouri_stop) {
4710 GtkTextIter nouri_start_iter, nouri_end_iter;
4711 gtk_text_buffer_get_iter_at_offset(
4712 buffer, &nouri_start_iter, nouri_start);
4713 gtk_text_buffer_get_iter_at_offset(
4714 buffer, &nouri_end_iter, nouri_stop);
4715 if (gtk_text_iter_has_tag(&nouri_start_iter, compose->uri_tag) &&
4716 gtk_text_iter_has_tag(&nouri_end_iter, compose->uri_tag)) {
4717 gtk_text_buffer_remove_tag_by_name(
4718 buffer, "link", &nouri_start_iter, &nouri_end_iter);
4719 modified_before_remove = modified;
4724 if (uri_start >= 0 && uri_stop > 0) {
4725 GtkTextIter uri_start_iter, uri_end_iter, back;
4726 gtk_text_buffer_get_iter_at_offset(
4727 buffer, &uri_start_iter, uri_start);
4728 gtk_text_buffer_get_iter_at_offset(
4729 buffer, &uri_end_iter, uri_stop);
4730 back = uri_end_iter;
4731 gtk_text_iter_backward_char(&back);
4732 if (!gtk_text_iter_has_tag(&uri_start_iter, compose->uri_tag) ||
4733 !gtk_text_iter_has_tag(&back, compose->uri_tag)) {
4734 gtk_text_buffer_apply_tag_by_name(
4735 buffer, "link", &uri_start_iter, &uri_end_iter);
4737 if (removed && !modified_before_remove) {
4743 // debug_print("not modified, out after %d lines\n", lines);
4747 // debug_print("modified, out after %d lines\n", lines);
4749 g_free(itemized_chars);
4752 undo_wrapping(compose->undostruct, FALSE);
4753 compose->autowrap = prev_autowrap;
4758 void compose_action_cb(void *data)
4760 Compose *compose = (Compose *)data;
4761 compose_wrap_all(compose);
4764 static void compose_wrap_all(Compose *compose)
4766 compose_wrap_all_full(compose, FALSE);
4769 static void compose_wrap_all_full(Compose *compose, gboolean force)
4771 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4772 GtkTextBuffer *buffer;
4774 gboolean modified = TRUE;
4776 buffer = gtk_text_view_get_buffer(text);
4778 gtk_text_buffer_get_start_iter(buffer, &iter);
4780 undo_wrapping(compose->undostruct, TRUE);
4782 while (!gtk_text_iter_is_end(&iter) && modified)
4783 modified = compose_beautify_paragraph(compose, &iter, force);
4785 undo_wrapping(compose->undostruct, FALSE);
4789 static void compose_set_title(Compose *compose)
4795 edited = compose->modified ? _(" [Edited]") : "";
4797 subject = gtk_editable_get_chars(
4798 GTK_EDITABLE(compose->subject_entry), 0, -1);
4800 #ifndef GENERIC_UMPC
4801 if (subject && strlen(subject))
4802 str = g_strdup_printf(_("%s - Compose message%s"),
4805 str = g_strdup_printf(_("[no subject] - Compose message%s"), edited);
4807 str = g_strdup(_("Compose message"));
4810 gtk_window_set_title(GTK_WINDOW(compose->window), str);
4816 * compose_current_mail_account:
4818 * Find a current mail account (the currently selected account, or the
4819 * default account, if a news account is currently selected). If a
4820 * mail account cannot be found, display an error message.
4822 * Return value: Mail account, or NULL if not found.
4824 static PrefsAccount *
4825 compose_current_mail_account(void)
4829 if (cur_account && cur_account->protocol != A_NNTP)
4832 ac = account_get_default();
4833 if (!ac || ac->protocol == A_NNTP) {
4834 alertpanel_error(_("Account for sending mail is not specified.\n"
4835 "Please select a mail account before sending."));
4842 #define QUOTE_IF_REQUIRED(out, str) \
4844 if (*str != '"' && strpbrk(str, ",.:;[]<>()@\\\"")) { \
4848 len = strlen(str) + 3; \
4849 if ((__tmp = alloca(len)) == NULL) { \
4850 g_warning("can't allocate memory"); \
4851 g_string_free(header, TRUE); \
4854 g_snprintf(__tmp, len, "\"%s\"", str); \
4859 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4860 g_warning("can't allocate memory"); \
4861 g_string_free(header, TRUE); \
4864 strcpy(__tmp, str); \
4870 #define QUOTE_IF_REQUIRED_NORMAL(out, str, errret) \
4872 if (*str != '"' && strpbrk(str, ",.:;[]<>()@\\\"")) { \
4876 len = strlen(str) + 3; \
4877 if ((__tmp = alloca(len)) == NULL) { \
4878 g_warning("can't allocate memory"); \
4881 g_snprintf(__tmp, len, "\"%s\"", str); \
4886 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4887 g_warning("can't allocate memory"); \
4890 strcpy(__tmp, str); \
4896 static void compose_select_account(Compose *compose, PrefsAccount *account,
4899 gchar *from = NULL, *header = NULL;
4900 ComposeHeaderEntry *header_entry;
4901 #if GTK_CHECK_VERSION(2, 24, 0)
4905 cm_return_if_fail(account != NULL);
4907 compose->account = account;
4908 if (account->name && *account->name) {
4910 QUOTE_IF_REQUIRED_NORMAL(buf, account->name, return);
4911 qbuf = escape_internal_quotes(buf, '"');
4912 from = g_strdup_printf("%s <%s>",
4913 qbuf, account->address);
4916 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4918 from = g_strdup_printf("<%s>",
4920 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4925 compose_set_title(compose);
4927 if (account->default_sign && compose->mode != COMPOSE_REDIRECT)
4928 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", TRUE);
4930 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", FALSE);
4931 if (account->default_encrypt && compose->mode != COMPOSE_REDIRECT)
4932 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", TRUE);
4934 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", FALSE);
4936 activate_privacy_system(compose, account, FALSE);
4938 if (!init && compose->mode != COMPOSE_REDIRECT) {
4939 undo_block(compose->undostruct);
4940 compose_insert_sig(compose, TRUE);
4941 undo_unblock(compose->undostruct);
4944 header_entry = (ComposeHeaderEntry *) compose->header_list->data;
4945 #if !GTK_CHECK_VERSION(2, 24, 0)
4946 header = gtk_combo_box_get_active_text(GTK_COMBO_BOX(header_entry->combo));
4948 if (gtk_combo_box_get_active_iter(GTK_COMBO_BOX(header_entry->combo), &iter))
4949 gtk_tree_model_get(gtk_combo_box_get_model(GTK_COMBO_BOX(
4950 header_entry->combo)), &iter, COMBOBOX_TEXT, &header, -1);
4953 if (header && !strlen(gtk_entry_get_text(GTK_ENTRY(header_entry->entry)))) {
4954 if (account->protocol == A_NNTP) {
4955 if (!strcmp(header, _("To:")))
4956 combobox_select_by_text(
4957 GTK_COMBO_BOX(header_entry->combo),
4960 if (!strcmp(header, _("Newsgroups:")))
4961 combobox_select_by_text(
4962 GTK_COMBO_BOX(header_entry->combo),
4970 /* use account's dict info if set */
4971 if (compose->gtkaspell) {
4972 if (account->enable_default_dictionary)
4973 gtkaspell_change_dict(compose->gtkaspell,
4974 account->default_dictionary, FALSE);
4975 if (account->enable_default_alt_dictionary)
4976 gtkaspell_change_alt_dict(compose->gtkaspell,
4977 account->default_alt_dictionary);
4978 if (account->enable_default_dictionary
4979 || account->enable_default_alt_dictionary)
4980 compose_spell_menu_changed(compose);
4985 gboolean compose_check_for_valid_recipient(Compose *compose) {
4986 gchar *recipient_headers_mail[] = {"To:", "Cc:", "Bcc:", NULL};
4987 gchar *recipient_headers_news[] = {"Newsgroups:", NULL};
4988 gboolean recipient_found = FALSE;
4992 /* free to and newsgroup list */
4993 slist_free_strings_full(compose->to_list);
4994 compose->to_list = NULL;
4996 slist_free_strings_full(compose->newsgroup_list);
4997 compose->newsgroup_list = NULL;
4999 /* search header entries for to and newsgroup entries */
5000 for (list = compose->header_list; list; list = list->next) {
5003 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5004 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5007 if (entry[0] != '\0') {
5008 for (strptr = recipient_headers_mail; *strptr != NULL; strptr++) {
5009 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
5010 compose->to_list = address_list_append(compose->to_list, entry);
5011 recipient_found = TRUE;
5014 for (strptr = recipient_headers_news; *strptr != NULL; strptr++) {
5015 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
5016 compose->newsgroup_list = newsgroup_list_append(compose->newsgroup_list, entry);
5017 recipient_found = TRUE;
5024 return recipient_found;
5027 static gboolean compose_check_for_set_recipients(Compose *compose)
5029 if (compose->account->set_autocc && compose->account->auto_cc) {
5030 gboolean found_other = FALSE;
5032 /* search header entries for to and newsgroup entries */
5033 for (list = compose->header_list; list; list = list->next) {
5036 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5037 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5040 if (strcmp(entry, compose->account->auto_cc)
5041 || strcmp(header, prefs_common_translated_header_name("Cc:"))) {
5051 if (compose->batch) {
5052 gtk_widget_show_all(compose->window);
5054 aval = alertpanel(_("Send"),
5055 _("The only recipient is the default CC address. Send anyway?"),
5056 GTK_STOCK_CANCEL, g_strconcat("+", _("_Send"), NULL), NULL);
5057 if (aval != G_ALERTALTERNATE)
5061 if (compose->account->set_autobcc && compose->account->auto_bcc) {
5062 gboolean found_other = FALSE;
5064 /* search header entries for to and newsgroup entries */
5065 for (list = compose->header_list; list; list = list->next) {
5068 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5069 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5072 if (strcmp(entry, compose->account->auto_bcc)
5073 || strcmp(header, prefs_common_translated_header_name("Bcc:"))) {
5083 if (compose->batch) {
5084 gtk_widget_show_all(compose->window);
5086 aval = alertpanel(_("Send"),
5087 _("The only recipient is the default BCC address. Send anyway?"),
5088 GTK_STOCK_CANCEL, g_strconcat("+", _("_Send"), NULL), NULL);
5089 if (aval != G_ALERTALTERNATE)
5096 static gboolean compose_check_entries(Compose *compose, gboolean check_everything)
5100 if (compose_check_for_valid_recipient(compose) == FALSE) {
5101 if (compose->batch) {
5102 gtk_widget_show_all(compose->window);
5104 alertpanel_error(_("Recipient is not specified."));
5108 if (compose_check_for_set_recipients(compose) == FALSE) {
5112 if (!compose->batch && prefs_common.warn_empty_subj == TRUE) {
5113 str = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5114 if (*str == '\0' && check_everything == TRUE &&
5115 compose->mode != COMPOSE_REDIRECT) {
5117 gchar *button_label;
5120 if (compose->sending)
5121 button_label = g_strconcat("+", _("_Send"), NULL);
5123 button_label = g_strconcat("+", _("_Queue"), NULL);
5124 message = g_strdup_printf(_("Subject is empty. %s"),
5125 compose->sending?_("Send it anyway?"):
5126 _("Queue it anyway?"));
5128 aval = alertpanel_full(compose->sending?_("Send"):_("Send later"), message,
5129 GTK_STOCK_CANCEL, button_label, NULL, TRUE, NULL,
5130 ALERT_QUESTION, G_ALERTDEFAULT);
5132 if (aval & G_ALERTDISABLE) {
5133 aval &= ~G_ALERTDISABLE;
5134 prefs_common.warn_empty_subj = FALSE;
5136 if (aval != G_ALERTALTERNATE)
5141 if (check_everything && hooks_invoke(COMPOSE_CHECK_BEFORE_SEND_HOOKLIST, compose))
5147 gint compose_send(Compose *compose)
5150 FolderItem *folder = NULL;
5152 gchar *msgpath = NULL;
5153 gboolean discard_window = FALSE;
5154 gchar *errstr = NULL;
5155 gchar *tmsgid = NULL;
5156 MainWindow *mainwin = mainwindow_get_mainwindow();
5157 gboolean queued_removed = FALSE;
5159 if (prefs_common.send_dialog_invisible
5160 || compose->batch == TRUE)
5161 discard_window = TRUE;
5163 compose_allow_user_actions (compose, FALSE);
5164 compose->sending = TRUE;
5166 if (compose_check_entries(compose, TRUE) == FALSE) {
5167 if (compose->batch) {
5168 gtk_widget_show_all(compose->window);
5174 val = compose_queue(compose, &msgnum, &folder, &msgpath, TRUE);
5177 if (compose->batch) {
5178 gtk_widget_show_all(compose->window);
5181 alertpanel_error(_("Could not queue message for sending:\n\n"
5182 "Charset conversion failed."));
5183 } else if (val == -5) {
5184 alertpanel_error(_("Could not queue message for sending:\n\n"
5185 "Couldn't get recipient encryption key."));
5186 } else if (val == -6) {
5188 } else if (val == -3) {
5189 if (privacy_peek_error())
5190 alertpanel_error(_("Could not queue message for sending:\n\n"
5191 "Signature failed: %s"), privacy_get_error());
5192 } else if (val == -2 && errno != 0) {
5193 alertpanel_error(_("Could not queue message for sending:\n\n%s."), g_strerror(errno));
5195 alertpanel_error(_("Could not queue message for sending."));
5200 tmsgid = compose->msgid ? g_strdup(compose->msgid) : NULL;
5201 if (discard_window) {
5202 compose->sending = FALSE;
5203 compose_close(compose);
5204 /* No more compose access in the normal codepath
5205 * after this point! */
5210 alertpanel_error(_("The message was queued but could not be "
5211 "sent.\nUse \"Send queued messages\" from "
5212 "the main window to retry."));
5213 if (!discard_window) {
5220 if (msgpath == NULL) {
5221 msgpath = folder_item_fetch_msg(folder, msgnum);
5222 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5225 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5226 claws_unlink(msgpath);
5229 if (!discard_window) {
5231 if (!queued_removed)
5232 folder_item_remove_msg(folder, msgnum);
5233 folder_item_scan(folder);
5235 /* make sure we delete that */
5236 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5238 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5239 folder_item_remove_msg(folder, tmp->msgnum);
5240 procmsg_msginfo_free(&tmp);
5247 if (!queued_removed)
5248 folder_item_remove_msg(folder, msgnum);
5249 folder_item_scan(folder);
5251 /* make sure we delete that */
5252 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5254 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5255 folder_item_remove_msg(folder, tmp->msgnum);
5256 procmsg_msginfo_free(&tmp);
5259 if (!discard_window) {
5260 compose->sending = FALSE;
5261 compose_allow_user_actions (compose, TRUE);
5262 compose_close(compose);
5266 alertpanel_error_log(_("%s\nUse \"Send queued messages\" from "
5267 "the main window to retry."), errstr);
5270 alertpanel_error_log(_("The message was queued but could not be "
5271 "sent.\nUse \"Send queued messages\" from "
5272 "the main window to retry."));
5274 if (!discard_window) {
5283 toolbar_main_set_sensitive(mainwin);
5284 main_window_set_menu_sensitive(mainwin);
5290 compose_allow_user_actions (compose, TRUE);
5291 compose->sending = FALSE;
5292 compose->modified = TRUE;
5293 toolbar_main_set_sensitive(mainwin);
5294 main_window_set_menu_sensitive(mainwin);
5299 static gboolean compose_use_attach(Compose *compose)
5301 GtkTreeModel *model = gtk_tree_view_get_model
5302 (GTK_TREE_VIEW(compose->attach_clist));
5303 return gtk_tree_model_iter_n_children(model, NULL) > 0;
5306 static gint compose_redirect_write_headers_from_headerlist(Compose *compose,
5309 gchar buf[BUFFSIZE];
5311 gboolean first_to_address;
5312 gboolean first_cc_address;
5314 ComposeHeaderEntry *headerentry;
5315 const gchar *headerentryname;
5316 const gchar *cc_hdr;
5317 const gchar *to_hdr;
5318 gboolean err = FALSE;
5320 debug_print("Writing redirect header\n");
5322 cc_hdr = prefs_common_translated_header_name("Cc:");
5323 to_hdr = prefs_common_translated_header_name("To:");
5325 first_to_address = TRUE;
5326 for (list = compose->header_list; list; list = list->next) {
5327 headerentry = ((ComposeHeaderEntry *)list->data);
5328 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5330 if (g_utf8_collate(headerentryname, to_hdr) == 0) {
5331 const gchar *entstr = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5332 Xstrdup_a(str, entstr, return -1);
5334 if (str[0] != '\0') {
5335 compose_convert_header
5336 (compose, buf, sizeof(buf), str,
5337 strlen("Resent-To") + 2, TRUE);
5339 if (first_to_address) {
5340 err |= (fprintf(fp, "Resent-To: ") < 0);
5341 first_to_address = FALSE;
5343 err |= (fprintf(fp, ",") < 0);
5345 err |= (fprintf(fp, "%s", buf) < 0);
5349 if (!first_to_address) {
5350 err |= (fprintf(fp, "\n") < 0);
5353 first_cc_address = TRUE;
5354 for (list = compose->header_list; list; list = list->next) {
5355 headerentry = ((ComposeHeaderEntry *)list->data);
5356 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5358 if (g_utf8_collate(headerentryname, cc_hdr) == 0) {
5359 const gchar *strg = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5360 Xstrdup_a(str, strg, return -1);
5362 if (str[0] != '\0') {
5363 compose_convert_header
5364 (compose, buf, sizeof(buf), str,
5365 strlen("Resent-Cc") + 2, TRUE);
5367 if (first_cc_address) {
5368 err |= (fprintf(fp, "Resent-Cc: ") < 0);
5369 first_cc_address = FALSE;
5371 err |= (fprintf(fp, ",") < 0);
5373 err |= (fprintf(fp, "%s", buf) < 0);
5377 if (!first_cc_address) {
5378 err |= (fprintf(fp, "\n") < 0);
5381 return (err ? -1:0);
5384 static gint compose_redirect_write_headers(Compose *compose, FILE *fp)
5386 gchar buf[BUFFSIZE];
5388 const gchar *entstr;
5389 /* struct utsname utsbuf; */
5390 gboolean err = FALSE;
5392 cm_return_val_if_fail(fp != NULL, -1);
5393 cm_return_val_if_fail(compose->account != NULL, -1);
5394 cm_return_val_if_fail(compose->account->address != NULL, -1);
5397 if (prefs_common.hide_timezone)
5398 get_rfc822_date_hide_tz(buf, sizeof(buf));
5400 get_rfc822_date(buf, sizeof(buf));
5401 err |= (fprintf(fp, "Resent-Date: %s\n", buf) < 0);
5404 if (compose->account->name && *compose->account->name) {
5405 compose_convert_header
5406 (compose, buf, sizeof(buf), compose->account->name,
5407 strlen("From: "), TRUE);
5408 err |= (fprintf(fp, "Resent-From: %s <%s>\n",
5409 buf, compose->account->address) < 0);
5411 err |= (fprintf(fp, "Resent-From: %s\n", compose->account->address) < 0);
5414 entstr = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5415 if (*entstr != '\0') {
5416 Xstrdup_a(str, entstr, return -1);
5419 compose_convert_header(compose, buf, sizeof(buf), str,
5420 strlen("Subject: "), FALSE);
5421 err |= (fprintf(fp, "Subject: %s\n", buf) < 0);
5425 /* Resent-Message-ID */
5426 if (compose->account->gen_msgid) {
5427 gchar *addr = prefs_account_generate_msgid(compose->account);
5428 err |= (fprintf(fp, "Resent-Message-ID: <%s>\n", addr) < 0);
5430 g_free(compose->msgid);
5431 compose->msgid = addr;
5433 compose->msgid = NULL;
5436 if (compose_redirect_write_headers_from_headerlist(compose, fp))
5439 /* separator between header and body */
5440 err |= (fputs("\n", fp) == EOF);
5442 return (err ? -1:0);
5445 static gint compose_redirect_write_to_file(Compose *compose, FILE *fdest)
5449 gchar buf[BUFFSIZE];
5451 gboolean skip = FALSE;
5452 gboolean err = FALSE;
5453 gchar *not_included[]={
5454 "Return-Path:", "Delivered-To:", "Received:",
5455 "Subject:", "X-UIDL:", "AF:",
5456 "NF:", "PS:", "SRH:",
5457 "SFN:", "DSR:", "MID:",
5458 "CFG:", "PT:", "S:",
5459 "RQ:", "SSV:", "NSV:",
5460 "SSH:", "R:", "MAID:",
5461 "NAID:", "RMID:", "FMID:",
5462 "SCF:", "RRCPT:", "NG:",
5463 "X-Claws-Privacy", "X-Claws-Sign:", "X-Claws-Encrypt",
5464 "X-Claws-End-Special-Headers:", "X-Claws-Account-Id:",
5465 "X-Sylpheed-Privacy", "X-Sylpheed-Sign:", "X-Sylpheed-Encrypt",
5466 "X-Sylpheed-End-Special-Headers:", "X-Sylpheed-Account-Id:",
5467 "X-Claws-Auto-Wrapping:", "X-Claws-Auto-Indent:",
5470 if ((fp = g_fopen(compose->redirect_filename, "rb")) == NULL) {
5471 FILE_OP_ERROR(compose->redirect_filename, "fopen");
5475 while (procheader_get_one_field_asis(buf, sizeof(buf), fp) != -1) {
5477 for (i = 0; not_included[i] != NULL; i++) {
5478 if (g_ascii_strncasecmp(buf, not_included[i],
5479 strlen(not_included[i])) == 0) {
5486 if (fputs(buf, fdest) == -1)
5489 if (!prefs_common.redirect_keep_from) {
5490 if (g_ascii_strncasecmp(buf, "From:",
5491 strlen("From:")) == 0) {
5492 err |= (fputs(" (by way of ", fdest) == EOF);
5493 if (compose->account->name
5494 && *compose->account->name) {
5495 compose_convert_header
5496 (compose, buf, sizeof(buf),
5497 compose->account->name,
5500 err |= (fprintf(fdest, "%s <%s>",
5502 compose->account->address) < 0);
5504 err |= (fprintf(fdest, "%s",
5505 compose->account->address) < 0);
5506 err |= (fputs(")", fdest) == EOF);
5510 if (fputs("\n", fdest) == -1)
5517 if (compose_redirect_write_headers(compose, fdest))
5520 while ((len = fread(buf, sizeof(gchar), sizeof(buf), fp)) > 0) {
5521 if (fwrite(buf, sizeof(gchar), len, fdest) != len)
5534 static gint compose_write_to_file(Compose *compose, FILE *fp, gint action, gboolean attach_parts)
5536 GtkTextBuffer *buffer;
5537 GtkTextIter start, end;
5538 gchar *chars, *tmp_enc_file, *content;
5540 const gchar *out_codeset;
5541 EncodingType encoding = ENC_UNKNOWN;
5542 MimeInfo *mimemsg, *mimetext;
5544 const gchar *src_codeset = CS_INTERNAL;
5545 gchar *from_addr = NULL;
5546 gchar *from_name = NULL;
5549 if (action == COMPOSE_WRITE_FOR_SEND) {
5550 attach_parts = TRUE;
5552 /* We're sending the message, generate a Message-ID
5554 if (compose->msgid == NULL &&
5555 compose->account->gen_msgid) {
5556 compose->msgid = prefs_account_generate_msgid(compose->account);
5560 /* create message MimeInfo */
5561 mimemsg = procmime_mimeinfo_new();
5562 mimemsg->type = MIMETYPE_MESSAGE;
5563 mimemsg->subtype = g_strdup("rfc822");
5564 mimemsg->content = MIMECONTENT_MEM;
5565 mimemsg->tmp = TRUE; /* must free content later */
5566 mimemsg->data.mem = compose_get_header(compose);
5568 debug_print(mimemsg->data.mem);
5570 /* Create text part MimeInfo */
5571 /* get all composed text */
5572 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5573 gtk_text_buffer_get_start_iter(buffer, &start);
5574 gtk_text_buffer_get_end_iter(buffer, &end);
5575 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5577 out_codeset = conv_get_charset_str(compose->out_encoding);
5579 if (!out_codeset && is_ascii_str(chars)) {
5580 out_codeset = CS_US_ASCII;
5581 } else if (prefs_common.outgoing_fallback_to_ascii &&
5582 is_ascii_str(chars)) {
5583 out_codeset = CS_US_ASCII;
5584 encoding = ENC_7BIT;
5588 gchar *test_conv_global_out = NULL;
5589 gchar *test_conv_reply = NULL;
5591 /* automatic mode. be automatic. */
5592 codeconv_set_strict(TRUE);
5594 out_codeset = conv_get_outgoing_charset_str();
5596 debug_print("trying to convert to %s\n", out_codeset);
5597 test_conv_global_out = conv_codeset_strdup(chars, src_codeset, out_codeset);
5600 if (!test_conv_global_out && compose->orig_charset
5601 && strcmp(compose->orig_charset, CS_US_ASCII)) {
5602 out_codeset = compose->orig_charset;
5603 debug_print("failure; trying to convert to %s\n", out_codeset);
5604 test_conv_reply = conv_codeset_strdup(chars, src_codeset, out_codeset);
5607 if (!test_conv_global_out && !test_conv_reply) {
5609 out_codeset = CS_INTERNAL;
5610 debug_print("failure; finally using %s\n", out_codeset);
5612 g_free(test_conv_global_out);
5613 g_free(test_conv_reply);
5614 codeconv_set_strict(FALSE);
5617 if (encoding == ENC_UNKNOWN) {
5618 if (prefs_common.encoding_method == CTE_BASE64)
5619 encoding = ENC_BASE64;
5620 else if (prefs_common.encoding_method == CTE_QUOTED_PRINTABLE)
5621 encoding = ENC_QUOTED_PRINTABLE;
5622 else if (prefs_common.encoding_method == CTE_8BIT)
5623 encoding = ENC_8BIT;
5625 encoding = procmime_get_encoding_for_charset(out_codeset);
5628 debug_print("src encoding = %s, out encoding = %s, transfer encoding = %s\n",
5629 src_codeset, out_codeset, procmime_get_encoding_str(encoding));
5631 if (action == COMPOSE_WRITE_FOR_SEND) {
5632 codeconv_set_strict(TRUE);
5633 buf = conv_codeset_strdup(chars, src_codeset, out_codeset);
5634 codeconv_set_strict(FALSE);
5639 msg = g_strdup_printf(_("Can't convert the character encoding of the message \n"
5640 "to the specified %s charset.\n"
5641 "Send it as %s?"), out_codeset, src_codeset);
5642 aval = alertpanel_full(_("Error"), msg, GTK_STOCK_CANCEL,
5643 g_strconcat("+", _("_Send"), NULL), NULL, FALSE,
5644 NULL, ALERT_ERROR, G_ALERTDEFAULT);
5647 if (aval != G_ALERTALTERNATE) {
5652 out_codeset = src_codeset;
5658 out_codeset = src_codeset;
5663 if (prefs_common.rewrite_first_from && (encoding == ENC_8BIT || encoding == ENC_7BIT)) {
5664 if (!strncmp(buf, "From ", sizeof("From ")-1) ||
5665 strstr(buf, "\nFrom ") != NULL) {
5666 encoding = ENC_QUOTED_PRINTABLE;
5670 mimetext = procmime_mimeinfo_new();
5671 mimetext->content = MIMECONTENT_MEM;
5672 mimetext->tmp = TRUE; /* must free content later */
5673 /* dup'ed because procmime_encode_content can turn it into a tmpfile
5674 * and free the data, which we need later. */
5675 mimetext->data.mem = g_strdup(buf);
5676 mimetext->type = MIMETYPE_TEXT;
5677 mimetext->subtype = g_strdup("plain");
5678 g_hash_table_insert(mimetext->typeparameters, g_strdup("charset"),
5679 g_strdup(out_codeset));
5681 /* protect trailing spaces when signing message */
5682 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5683 privacy_system_can_sign(compose->privacy_system)) {
5684 encoding = ENC_QUOTED_PRINTABLE;
5687 debug_print("main text: %zd bytes encoded as %s in %d\n",
5688 strlen(buf), out_codeset, encoding);
5690 /* check for line length limit */
5691 if (action == COMPOSE_WRITE_FOR_SEND &&
5692 encoding != ENC_QUOTED_PRINTABLE && encoding != ENC_BASE64 &&
5693 check_line_length(buf, 1000, &line) < 0) {
5696 msg = g_strdup_printf
5697 (_("Line %d exceeds the line length limit (998 bytes).\n"
5698 "The contents of the message might be broken on the way to the delivery.\n"
5700 "Send it anyway?"), line + 1);
5701 aval = alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, GTK_STOCK_OK, NULL);
5703 if (aval != G_ALERTALTERNATE) {
5709 if (encoding != ENC_UNKNOWN)
5710 procmime_encode_content(mimetext, encoding);
5712 /* append attachment parts */
5713 if (compose_use_attach(compose) && attach_parts) {
5714 MimeInfo *mimempart;
5715 gchar *boundary = NULL;
5716 mimempart = procmime_mimeinfo_new();
5717 mimempart->content = MIMECONTENT_EMPTY;
5718 mimempart->type = MIMETYPE_MULTIPART;
5719 mimempart->subtype = g_strdup("mixed");
5723 boundary = generate_mime_boundary(NULL);
5724 } while (strstr(buf, boundary) != NULL);
5726 g_hash_table_insert(mimempart->typeparameters, g_strdup("boundary"),
5729 mimetext->disposition = DISPOSITIONTYPE_INLINE;
5731 g_node_append(mimempart->node, mimetext->node);
5732 g_node_append(mimemsg->node, mimempart->node);
5734 if (compose_add_attachments(compose, mimempart) < 0)
5737 g_node_append(mimemsg->node, mimetext->node);
5741 if (strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) != 0) {
5742 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
5743 /* extract name and address */
5744 if (strstr(spec, " <") && strstr(spec, ">")) {
5745 from_addr = g_strdup(strrchr(spec, '<')+1);
5746 *(strrchr(from_addr, '>')) = '\0';
5747 from_name = g_strdup(spec);
5748 *(strrchr(from_name, '<')) = '\0';
5755 /* sign message if sending */
5756 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5757 privacy_system_can_sign(compose->privacy_system))
5758 if (!privacy_sign(compose->privacy_system, mimemsg,
5759 compose->account, from_addr)) {
5767 if (compose->use_encryption) {
5768 if (compose->encdata != NULL &&
5769 strcmp(compose->encdata, "_DONT_ENCRYPT_")) {
5771 /* First, write an unencrypted copy and save it to outbox, if
5772 * user wants that. */
5773 if (compose->account->save_encrypted_as_clear_text) {
5774 debug_print("saving sent message unencrypted...\n");
5775 FILE *tmpfp = get_tmpfile_in_dir(get_mime_tmp_dir(), &tmp_enc_file);
5779 /* fp now points to a file with headers written,
5780 * let's make a copy. */
5782 content = file_read_stream_to_str(fp);
5784 str_write_to_file(content, tmp_enc_file);
5787 /* Now write the unencrypted body. */
5788 if ((tmpfp = g_fopen(tmp_enc_file, "a")) != NULL) {
5789 procmime_write_mimeinfo(mimemsg, tmpfp);
5792 outbox = folder_find_item_from_identifier(compose_get_save_to(compose));
5794 outbox = folder_get_default_outbox();
5796 procmsg_save_to_outbox(outbox, tmp_enc_file, TRUE);
5797 claws_unlink(tmp_enc_file);
5799 g_warning("Can't open file '%s'", tmp_enc_file);
5802 g_warning("couldn't get tempfile");
5805 if (!privacy_encrypt(compose->privacy_system, mimemsg, compose->encdata)) {
5806 debug_print("Couldn't encrypt mime structure: %s.\n",
5807 privacy_get_error());
5808 alertpanel_error(_("Couldn't encrypt the email: %s"),
5809 privacy_get_error());
5814 procmime_write_mimeinfo(mimemsg, fp);
5816 procmime_mimeinfo_free_all(&mimemsg);
5821 static gint compose_write_body_to_file(Compose *compose, const gchar *file)
5823 GtkTextBuffer *buffer;
5824 GtkTextIter start, end;
5829 if ((fp = g_fopen(file, "wb")) == NULL) {
5830 FILE_OP_ERROR(file, "fopen");
5834 /* chmod for security */
5835 if (change_file_mode_rw(fp, file) < 0) {
5836 FILE_OP_ERROR(file, "chmod");
5837 g_warning("can't change file mode");
5840 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5841 gtk_text_buffer_get_start_iter(buffer, &start);
5842 gtk_text_buffer_get_end_iter(buffer, &end);
5843 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5845 chars = conv_codeset_strdup
5846 (tmp, CS_INTERNAL, conv_get_locale_charset_str());
5855 len = strlen(chars);
5856 if (fwrite(chars, sizeof(gchar), len, fp) != len) {
5857 FILE_OP_ERROR(file, "fwrite");
5866 if (fclose(fp) == EOF) {
5867 FILE_OP_ERROR(file, "fclose");
5874 static gint compose_remove_reedit_target(Compose *compose, gboolean force)
5877 MsgInfo *msginfo = compose->targetinfo;
5879 cm_return_val_if_fail(compose->mode == COMPOSE_REEDIT, -1);
5880 if (!msginfo) return -1;
5882 if (!force && MSG_IS_LOCKED(msginfo->flags))
5885 item = msginfo->folder;
5886 cm_return_val_if_fail(item != NULL, -1);
5888 if (procmsg_msg_exist(msginfo) &&
5889 (folder_has_parent_of_type(item, F_QUEUE) ||
5890 folder_has_parent_of_type(item, F_DRAFT)
5891 || msginfo == compose->autosaved_draft)) {
5892 if (folder_item_remove_msg(item, msginfo->msgnum) < 0) {
5893 g_warning("can't remove the old message");
5896 debug_print("removed reedit target %d\n", msginfo->msgnum);
5903 static void compose_remove_draft(Compose *compose)
5906 MsgInfo *msginfo = compose->targetinfo;
5907 drafts = account_get_special_folder(compose->account, F_DRAFT);
5909 if (procmsg_msg_exist(msginfo)) {
5910 folder_item_remove_msg(drafts, msginfo->msgnum);
5915 gint compose_queue(Compose *compose, gint *msgnum, FolderItem **item, gchar **msgpath,
5916 gboolean remove_reedit_target)
5918 return compose_queue_sub (compose, msgnum, item, msgpath, FALSE, remove_reedit_target);
5921 static gboolean compose_warn_encryption(Compose *compose)
5923 const gchar *warning = privacy_get_encrypt_warning(compose->privacy_system);
5924 AlertValue val = G_ALERTALTERNATE;
5926 if (warning == NULL)
5929 val = alertpanel_full(_("Encryption warning"), warning,
5930 GTK_STOCK_CANCEL, g_strconcat("+", _("C_ontinue"), NULL), NULL,
5931 TRUE, NULL, ALERT_WARNING, G_ALERTALTERNATE);
5932 if (val & G_ALERTDISABLE) {
5933 val &= ~G_ALERTDISABLE;
5934 if (val == G_ALERTALTERNATE)
5935 privacy_inhibit_encrypt_warning(compose->privacy_system,
5939 if (val == G_ALERTALTERNATE) {
5946 static gint compose_queue_sub(Compose *compose, gint *msgnum, FolderItem **item,
5947 gchar **msgpath, gboolean check_subject,
5948 gboolean remove_reedit_target)
5955 PrefsAccount *mailac = NULL, *newsac = NULL;
5956 gboolean err = FALSE;
5958 debug_print("queueing message...\n");
5959 cm_return_val_if_fail(compose->account != NULL, -1);
5961 if (compose_check_entries(compose, check_subject) == FALSE) {
5962 if (compose->batch) {
5963 gtk_widget_show_all(compose->window);
5968 if (!compose->to_list && !compose->newsgroup_list) {
5969 g_warning("can't get recipient list.");
5973 if (compose->to_list) {
5974 if (compose->account->protocol != A_NNTP)
5975 mailac = compose->account;
5976 else if (cur_account && cur_account->protocol != A_NNTP)
5977 mailac = cur_account;
5978 else if (!(mailac = compose_current_mail_account())) {
5979 alertpanel_error(_("No account for sending mails available!"));
5984 if (compose->newsgroup_list) {
5985 if (compose->account->protocol == A_NNTP)
5986 newsac = compose->account;
5988 alertpanel_error(_("Selected account isn't NNTP: Posting is impossible."));
5993 /* write queue header */
5994 tmp = g_strdup_printf("%s%cqueue.%p%08x", get_tmp_dir(),
5995 G_DIR_SEPARATOR, compose, (guint) rand());
5996 debug_print("queuing to %s\n", tmp);
5997 if ((fp = g_fopen(tmp, "w+b")) == NULL) {
5998 FILE_OP_ERROR(tmp, "fopen");
6003 if (change_file_mode_rw(fp, tmp) < 0) {
6004 FILE_OP_ERROR(tmp, "chmod");
6005 g_warning("can't change file mode");
6008 /* queueing variables */
6009 err |= (fprintf(fp, "AF:\n") < 0);
6010 err |= (fprintf(fp, "NF:0\n") < 0);
6011 err |= (fprintf(fp, "PS:10\n") < 0);
6012 err |= (fprintf(fp, "SRH:1\n") < 0);
6013 err |= (fprintf(fp, "SFN:\n") < 0);
6014 err |= (fprintf(fp, "DSR:\n") < 0);
6016 err |= (fprintf(fp, "MID:<%s>\n", compose->msgid) < 0);
6018 err |= (fprintf(fp, "MID:\n") < 0);
6019 err |= (fprintf(fp, "CFG:\n") < 0);
6020 err |= (fprintf(fp, "PT:0\n") < 0);
6021 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
6022 err |= (fprintf(fp, "RQ:\n") < 0);
6024 err |= (fprintf(fp, "SSV:%s\n", mailac->smtp_server) < 0);
6026 err |= (fprintf(fp, "SSV:\n") < 0);
6028 err |= (fprintf(fp, "NSV:%s\n", newsac->nntp_server) < 0);
6030 err |= (fprintf(fp, "NSV:\n") < 0);
6031 err |= (fprintf(fp, "SSH:\n") < 0);
6032 /* write recepient list */
6033 if (compose->to_list) {
6034 err |= (fprintf(fp, "R:<%s>", (gchar *)compose->to_list->data) < 0);
6035 for (cur = compose->to_list->next; cur != NULL;
6037 err |= (fprintf(fp, ",<%s>", (gchar *)cur->data) < 0);
6038 err |= (fprintf(fp, "\n") < 0);
6040 /* write newsgroup list */
6041 if (compose->newsgroup_list) {
6042 err |= (fprintf(fp, "NG:") < 0);
6043 err |= (fprintf(fp, "%s", (gchar *)compose->newsgroup_list->data) < 0);
6044 for (cur = compose->newsgroup_list->next; cur != NULL; cur = cur->next)
6045 err |= (fprintf(fp, ",%s", (gchar *)cur->data) < 0);
6046 err |= (fprintf(fp, "\n") < 0);
6048 /* Sylpheed account IDs */
6050 err |= (fprintf(fp, "MAID:%d\n", mailac->account_id) < 0);
6052 err |= (fprintf(fp, "NAID:%d\n", newsac->account_id) < 0);
6055 if (compose->privacy_system != NULL) {
6056 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
6057 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
6058 if (compose->use_encryption) {
6059 if (!compose_warn_encryption(compose)) {
6065 if (mailac && mailac->encrypt_to_self) {
6066 GSList *tmp_list = g_slist_copy(compose->to_list);
6067 tmp_list = g_slist_append(tmp_list, compose->account->address);
6068 compose->encdata = privacy_get_encrypt_data(compose->privacy_system, tmp_list);
6069 g_slist_free(tmp_list);
6071 compose->encdata = privacy_get_encrypt_data(compose->privacy_system, compose->to_list);
6073 if (compose->encdata != NULL) {
6074 if (strcmp(compose->encdata, "_DONT_ENCRYPT_")) {
6075 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
6076 err |= (fprintf(fp, "X-Claws-Encrypt-Data:%s\n",
6077 compose->encdata) < 0);
6078 } /* else we finally dont want to encrypt */
6080 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
6081 /* and if encdata was null, it means there's been a problem in
6084 g_warning("failed to write queue message");
6093 /* Save copy folder */
6094 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
6095 gchar *savefolderid;
6097 savefolderid = compose_get_save_to(compose);
6098 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
6099 g_free(savefolderid);
6101 /* Save copy folder */
6102 if (compose->return_receipt) {
6103 err |= (fprintf(fp, "RRCPT:1\n") < 0);
6105 /* Message-ID of message replying to */
6106 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
6107 gchar *folderid = NULL;
6109 if (compose->replyinfo->folder)
6110 folderid = folder_item_get_identifier(compose->replyinfo->folder);
6111 if (folderid == NULL)
6112 folderid = g_strdup("NULL");
6114 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
6117 /* Message-ID of message forwarding to */
6118 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
6119 gchar *folderid = NULL;
6121 if (compose->fwdinfo->folder)
6122 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
6123 if (folderid == NULL)
6124 folderid = g_strdup("NULL");
6126 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
6130 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
6131 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
6133 /* end of headers */
6134 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
6136 if (compose->redirect_filename != NULL) {
6137 if (compose_redirect_write_to_file(compose, fp) < 0) {
6145 if ((result = compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_SEND, TRUE)) < 0) {
6149 return result - 1; /* -2 for a generic error, -3 for signing error, -4 for encoding */
6153 g_warning("failed to write queue message");
6159 if (fclose(fp) == EOF) {
6160 FILE_OP_ERROR(tmp, "fclose");
6166 if (item && *item) {
6169 queue = account_get_special_folder(compose->account, F_QUEUE);
6172 g_warning("can't find queue folder");
6177 folder_item_scan(queue);
6178 if ((num = folder_item_add_msg(queue, tmp, NULL, FALSE)) < 0) {
6179 g_warning("can't queue the message");
6185 if (msgpath == NULL) {
6191 if (compose->mode == COMPOSE_REEDIT && remove_reedit_target) {
6192 compose_remove_reedit_target(compose, FALSE);
6195 if ((msgnum != NULL) && (item != NULL)) {
6203 static int compose_add_attachments(Compose *compose, MimeInfo *parent)
6206 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
6209 gchar *type, *subtype;
6210 GtkTreeModel *model;
6213 model = gtk_tree_view_get_model(tree_view);
6215 if (!gtk_tree_model_get_iter_first(model, &iter))
6218 gtk_tree_model_get(model, &iter,
6222 if (!is_file_exist(ainfo->file)) {
6223 gchar *msg = g_strdup_printf(_("Attachment %s doesn't exist anymore. Ignore?"), ainfo->file);
6224 AlertValue val = alertpanel_full(_("Warning"), msg, _("Cancel sending"), _("Ignore attachment"),
6225 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
6227 if (val == G_ALERTDEFAULT) {
6232 if (g_stat(ainfo->file, &statbuf) < 0)
6235 mimepart = procmime_mimeinfo_new();
6236 mimepart->content = MIMECONTENT_FILE;
6237 mimepart->data.filename = g_strdup(ainfo->file);
6238 mimepart->tmp = FALSE; /* or we destroy our attachment */
6239 mimepart->offset = 0;
6240 mimepart->length = statbuf.st_size;
6242 type = g_strdup(ainfo->content_type);
6244 if (!strchr(type, '/')) {
6246 type = g_strdup("application/octet-stream");
6249 subtype = strchr(type, '/') + 1;
6250 *(subtype - 1) = '\0';
6251 mimepart->type = procmime_get_media_type(type);
6252 mimepart->subtype = g_strdup(subtype);
6255 if (mimepart->type == MIMETYPE_MESSAGE &&
6256 !g_ascii_strcasecmp(mimepart->subtype, "rfc822")) {
6257 mimepart->disposition = DISPOSITIONTYPE_INLINE;
6258 } else if (mimepart->type == MIMETYPE_TEXT) {
6259 if (!ainfo->name && g_ascii_strcasecmp(mimepart->subtype, "plain")) {
6260 /* Text parts with no name come from multipart/alternative
6261 * forwards. Make sure the recipient won't look at the
6262 * original HTML part by mistake. */
6263 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6264 ainfo->name = g_strdup_printf(_("Original %s part"),
6268 g_hash_table_insert(mimepart->typeparameters,
6269 g_strdup("charset"), g_strdup(ainfo->charset));
6271 if (ainfo->name && mimepart->type != MIMETYPE_MESSAGE) {
6272 if (mimepart->type == MIMETYPE_APPLICATION &&
6273 !strcmp2(mimepart->subtype, "octet-stream"))
6274 g_hash_table_insert(mimepart->typeparameters,
6275 g_strdup("name"), g_strdup(ainfo->name));
6276 g_hash_table_insert(mimepart->dispositionparameters,
6277 g_strdup("filename"), g_strdup(ainfo->name));
6278 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6281 if (mimepart->type == MIMETYPE_MESSAGE
6282 || mimepart->type == MIMETYPE_MULTIPART)
6283 ainfo->encoding = ENC_BINARY;
6284 else if (compose->use_signing) {
6285 if (ainfo->encoding == ENC_7BIT)
6286 ainfo->encoding = ENC_QUOTED_PRINTABLE;
6287 else if (ainfo->encoding == ENC_8BIT)
6288 ainfo->encoding = ENC_BASE64;
6293 procmime_encode_content(mimepart, ainfo->encoding);
6295 g_node_append(parent->node, mimepart->node);
6296 } while (gtk_tree_model_iter_next(model, &iter));
6301 static gchar *compose_quote_list_of_addresses(gchar *str)
6303 GSList *list = NULL, *item = NULL;
6304 gchar *qname = NULL, *faddr = NULL, *result = NULL;
6306 list = address_list_append_with_comments(list, str);
6307 for (item = list; item != NULL; item = item->next) {
6308 gchar *spec = item->data;
6309 gchar *endofname = strstr(spec, " <");
6310 if (endofname != NULL) {
6313 QUOTE_IF_REQUIRED_NORMAL(qname, spec, return NULL);
6314 qqname = escape_internal_quotes(qname, '"');
6316 if (*qname != *spec || qqname != qname) { /* has been quoted, compute new */
6317 gchar *addr = g_strdup(endofname);
6318 gchar *name = (qqname != qname)? qqname: g_strdup(qname);
6319 faddr = g_strconcat(name, addr, NULL);
6322 debug_print("new auto-quoted address: '%s'\n", faddr);
6326 result = g_strdup((faddr != NULL)? faddr: spec);
6328 result = g_strconcat(result,
6330 (faddr != NULL)? faddr: spec,
6333 if (faddr != NULL) {
6338 slist_free_strings_full(list);
6343 #define IS_IN_CUSTOM_HEADER(header) \
6344 (compose->account->add_customhdr && \
6345 custom_header_find(compose->account->customhdr_list, header) != NULL)
6347 static void compose_add_headerfield_from_headerlist(Compose *compose,
6349 const gchar *fieldname,
6350 const gchar *seperator)
6352 gchar *str, *fieldname_w_colon;
6353 gboolean add_field = FALSE;
6355 ComposeHeaderEntry *headerentry;
6356 const gchar *headerentryname;
6357 const gchar *trans_fieldname;
6360 if (IS_IN_CUSTOM_HEADER(fieldname))
6363 debug_print("Adding %s-fields\n", fieldname);
6365 fieldstr = g_string_sized_new(64);
6367 fieldname_w_colon = g_strconcat(fieldname, ":", NULL);
6368 trans_fieldname = prefs_common_translated_header_name(fieldname_w_colon);
6370 for (list = compose->header_list; list; list = list->next) {
6371 headerentry = ((ComposeHeaderEntry *)list->data);
6372 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
6374 if (!g_utf8_collate(trans_fieldname, headerentryname)) {
6375 gchar * ustr = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
6377 str = compose_quote_list_of_addresses(ustr);
6379 if (str != NULL && str[0] != '\0') {
6381 g_string_append(fieldstr, seperator);
6382 g_string_append(fieldstr, str);
6391 buf = g_new0(gchar, fieldstr->len * 4 + 256);
6392 compose_convert_header
6393 (compose, buf, fieldstr->len * 4 + 256, fieldstr->str,
6394 strlen(fieldname) + 2, TRUE);
6395 g_string_append_printf(header, "%s: %s\n", fieldname, buf);
6399 g_free(fieldname_w_colon);
6400 g_string_free(fieldstr, TRUE);
6405 static gchar *compose_get_manual_headers_info(Compose *compose)
6407 GString *sh_header = g_string_new(" ");
6409 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6411 for (list = compose->header_list; list; list = list->next) {
6412 ComposeHeaderEntry *headerentry;
6415 gchar *headername_wcolon;
6416 const gchar *headername_trans;
6418 gboolean standard_header = FALSE;
6420 headerentry = ((ComposeHeaderEntry *)list->data);
6422 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6424 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6429 if (!strstr(tmp, ":")) {
6430 headername_wcolon = g_strconcat(tmp, ":", NULL);
6431 headername = g_strdup(tmp);
6433 headername_wcolon = g_strdup(tmp);
6434 headername = g_strdup(strtok(tmp, ":"));
6438 string = std_headers;
6439 while (*string != NULL) {
6440 headername_trans = prefs_common_translated_header_name(*string);
6441 if (!strcmp(headername_trans, headername_wcolon))
6442 standard_header = TRUE;
6445 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
6446 g_string_append_printf(sh_header, "%s ", headername);
6448 g_free(headername_wcolon);
6450 g_string_truncate(sh_header, strlen(sh_header->str) - 1); /* remove last space */
6451 return g_string_free(sh_header, FALSE);
6454 static gchar *compose_get_header(Compose *compose)
6456 gchar buf[BUFFSIZE];
6457 const gchar *entry_str;
6461 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6463 gchar *from_name = NULL, *from_address = NULL;
6466 cm_return_val_if_fail(compose->account != NULL, NULL);
6467 cm_return_val_if_fail(compose->account->address != NULL, NULL);
6469 header = g_string_sized_new(64);
6472 if (prefs_common.hide_timezone)
6473 get_rfc822_date_hide_tz(buf, sizeof(buf));
6475 get_rfc822_date(buf, sizeof(buf));
6476 g_string_append_printf(header, "Date: %s\n", buf);
6480 if (compose->account->name && *compose->account->name) {
6482 QUOTE_IF_REQUIRED(buf, compose->account->name);
6483 tmp = g_strdup_printf("%s <%s>",
6484 buf, compose->account->address);
6486 tmp = g_strdup_printf("%s",
6487 compose->account->address);
6489 if (!strcmp(gtk_entry_get_text(GTK_ENTRY(compose->from_name)), tmp)
6490 || strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) == 0) {
6492 from_name = compose->account->name ? g_strdup(compose->account->name):NULL;
6493 from_address = g_strdup(compose->account->address);
6495 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
6496 /* extract name and address */
6497 if (strstr(spec, " <") && strstr(spec, ">")) {
6498 from_address = g_strdup(strrchr(spec, '<')+1);
6499 *(strrchr(from_address, '>')) = '\0';
6500 from_name = g_strdup(spec);
6501 *(strrchr(from_name, '<')) = '\0';
6504 from_address = g_strdup(spec);
6511 if (from_name && *from_name) {
6513 compose_convert_header
6514 (compose, buf, sizeof(buf), from_name,
6515 strlen("From: "), TRUE);
6516 QUOTE_IF_REQUIRED(name, buf);
6517 qname = escape_internal_quotes(name, '"');
6519 g_string_append_printf(header, "From: %s <%s>\n",
6520 qname, from_address);
6521 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To") &&
6522 compose->return_receipt) {
6523 compose_convert_header(compose, buf, sizeof(buf), from_name,
6524 strlen("Disposition-Notification-To: "),
6526 g_string_append_printf(header, "Disposition-Notification-To: %s <%s>\n", buf, from_address);
6531 g_string_append_printf(header, "From: %s\n", from_address);
6532 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To") &&
6533 compose->return_receipt)
6534 g_string_append_printf(header, "Disposition-Notification-To: %s\n", from_address);
6538 g_free(from_address);
6541 compose_add_headerfield_from_headerlist(compose, header, "To", ", ");
6544 compose_add_headerfield_from_headerlist(compose, header, "Newsgroups", ",");
6547 compose_add_headerfield_from_headerlist(compose, header, "Cc", ", ");
6551 * If this account is a NNTP account remove Bcc header from
6552 * message body since it otherwise will be publicly shown
6554 if (compose->account->protocol != A_NNTP)
6555 compose_add_headerfield_from_headerlist(compose, header, "Bcc", ", ");
6558 str = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
6560 if (*str != '\0' && !IS_IN_CUSTOM_HEADER("Subject")) {
6563 compose_convert_header(compose, buf, sizeof(buf), str,
6564 strlen("Subject: "), FALSE);
6565 g_string_append_printf(header, "Subject: %s\n", buf);
6571 if (compose->msgid != NULL && strlen(compose->msgid) > 0) {
6572 g_string_append_printf(header, "Message-ID: <%s>\n",
6576 if (compose->remove_references == FALSE) {
6578 if (compose->inreplyto && compose->to_list)
6579 g_string_append_printf(header, "In-Reply-To: <%s>\n", compose->inreplyto);
6582 if (compose->references)
6583 g_string_append_printf(header, "References: %s\n", compose->references);
6587 compose_add_headerfield_from_headerlist(compose, header, "Followup-To", ",");
6590 compose_add_headerfield_from_headerlist(compose, header, "Reply-To", ", ");
6593 if (compose->account->organization &&
6594 strlen(compose->account->organization) &&
6595 !IS_IN_CUSTOM_HEADER("Organization")) {
6596 compose_convert_header(compose, buf, sizeof(buf),
6597 compose->account->organization,
6598 strlen("Organization: "), FALSE);
6599 g_string_append_printf(header, "Organization: %s\n", buf);
6602 /* Program version and system info */
6603 if (compose->account->gen_xmailer &&
6604 g_slist_length(compose->to_list) && !IS_IN_CUSTOM_HEADER("X-Mailer") &&
6605 !compose->newsgroup_list) {
6606 g_string_append_printf(header, "X-Mailer: %s (GTK+ %d.%d.%d; %s)\n",
6608 gtk_major_version, gtk_minor_version, gtk_micro_version,
6611 if (compose->account->gen_xmailer &&
6612 g_slist_length(compose->newsgroup_list) && !IS_IN_CUSTOM_HEADER("X-Newsreader")) {
6613 g_string_append_printf(header, "X-Newsreader: %s (GTK+ %d.%d.%d; %s)\n",
6615 gtk_major_version, gtk_minor_version, gtk_micro_version,
6619 /* custom headers */
6620 if (compose->account->add_customhdr) {
6623 for (cur = compose->account->customhdr_list; cur != NULL;
6625 CustomHeader *chdr = (CustomHeader *)cur->data;
6627 if (custom_header_is_allowed(chdr->name)
6628 && chdr->value != NULL
6629 && *(chdr->value) != '\0') {
6630 compose_convert_header
6631 (compose, buf, sizeof(buf),
6633 strlen(chdr->name) + 2, FALSE);
6634 g_string_append_printf(header, "%s: %s\n", chdr->name, buf);
6639 /* Automatic Faces and X-Faces */
6640 if (get_account_xface (buf, sizeof(buf), compose->account->account_name) == 0) {
6641 g_string_append_printf(header, "X-Face: %s\n", buf);
6643 else if (get_default_xface (buf, sizeof(buf)) == 0) {
6644 g_string_append_printf(header, "X-Face: %s\n", buf);
6646 if (get_account_face (buf, sizeof(buf), compose->account->account_name) == 0) {
6647 g_string_append_printf(header, "Face: %s\n", buf);
6649 else if (get_default_face (buf, sizeof(buf)) == 0) {
6650 g_string_append_printf(header, "Face: %s\n", buf);
6654 switch (compose->priority) {
6655 case PRIORITY_HIGHEST: g_string_append_printf(header, "Importance: high\n"
6656 "X-Priority: 1 (Highest)\n");
6658 case PRIORITY_HIGH: g_string_append_printf(header, "Importance: high\n"
6659 "X-Priority: 2 (High)\n");
6661 case PRIORITY_NORMAL: break;
6662 case PRIORITY_LOW: g_string_append_printf(header, "Importance: low\n"
6663 "X-Priority: 4 (Low)\n");
6665 case PRIORITY_LOWEST: g_string_append_printf(header, "Importance: low\n"
6666 "X-Priority: 5 (Lowest)\n");
6668 default: debug_print("compose: priority unknown : %d\n",
6672 /* get special headers */
6673 for (list = compose->header_list; list; list = list->next) {
6674 ComposeHeaderEntry *headerentry;
6677 gchar *headername_wcolon;
6678 const gchar *headername_trans;
6681 gboolean standard_header = FALSE;
6683 headerentry = ((ComposeHeaderEntry *)list->data);
6685 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6687 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6692 if (!strstr(tmp, ":")) {
6693 headername_wcolon = g_strconcat(tmp, ":", NULL);
6694 headername = g_strdup(tmp);
6696 headername_wcolon = g_strdup(tmp);
6697 headername = g_strdup(strtok(tmp, ":"));
6701 entry_str = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
6702 Xstrdup_a(headervalue, entry_str, return NULL);
6703 subst_char(headervalue, '\r', ' ');
6704 subst_char(headervalue, '\n', ' ');
6705 string = std_headers;
6706 while (*string != NULL) {
6707 headername_trans = prefs_common_translated_header_name(*string);
6708 if (!strcmp(headername_trans, headername_wcolon))
6709 standard_header = TRUE;
6712 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
6713 g_string_append_printf(header, "%s %s\n", headername_wcolon, headervalue);
6716 g_free(headername_wcolon);
6720 g_string_free(header, FALSE);
6725 #undef IS_IN_CUSTOM_HEADER
6727 static void compose_convert_header(Compose *compose, gchar *dest, gint len, gchar *src,
6728 gint header_len, gboolean addr_field)
6730 gchar *tmpstr = NULL;
6731 const gchar *out_codeset = NULL;
6733 cm_return_if_fail(src != NULL);
6734 cm_return_if_fail(dest != NULL);
6736 if (len < 1) return;
6738 tmpstr = g_strdup(src);
6740 subst_char(tmpstr, '\n', ' ');
6741 subst_char(tmpstr, '\r', ' ');
6744 if (!g_utf8_validate(tmpstr, -1, NULL)) {
6745 gchar *mybuf = g_malloc(strlen(tmpstr)*2 +1);
6746 conv_localetodisp(mybuf, strlen(tmpstr)*2 +1, tmpstr);
6751 codeconv_set_strict(TRUE);
6752 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6753 conv_get_charset_str(compose->out_encoding));
6754 codeconv_set_strict(FALSE);
6756 if (!dest || *dest == '\0') {
6757 gchar *test_conv_global_out = NULL;
6758 gchar *test_conv_reply = NULL;
6760 /* automatic mode. be automatic. */
6761 codeconv_set_strict(TRUE);
6763 out_codeset = conv_get_outgoing_charset_str();
6765 debug_print("trying to convert to %s\n", out_codeset);
6766 test_conv_global_out = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6769 if (!test_conv_global_out && compose->orig_charset
6770 && strcmp(compose->orig_charset, CS_US_ASCII)) {
6771 out_codeset = compose->orig_charset;
6772 debug_print("failure; trying to convert to %s\n", out_codeset);
6773 test_conv_reply = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6776 if (!test_conv_global_out && !test_conv_reply) {
6778 out_codeset = CS_INTERNAL;
6779 debug_print("finally using %s\n", out_codeset);
6781 g_free(test_conv_global_out);
6782 g_free(test_conv_reply);
6783 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6785 codeconv_set_strict(FALSE);
6790 static void compose_add_to_addressbook_cb(GtkMenuItem *menuitem, gpointer user_data)
6794 cm_return_if_fail(user_data != NULL);
6796 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(user_data)));
6797 g_strstrip(address);
6798 if (*address != '\0') {
6799 gchar *name = procheader_get_fromname(address);
6800 extract_address(address);
6801 #ifndef USE_ALT_ADDRBOOK
6802 addressbook_add_contact(name, address, NULL, NULL);
6804 debug_print("%s: %s\n", name, address);
6805 if (addressadd_selection(name, address, NULL, NULL)) {
6806 debug_print( "addressbook_add_contact - added\n" );
6813 static void compose_entry_popup_extend(GtkEntry *entry, GtkMenu *menu, gpointer user_data)
6815 GtkWidget *menuitem;
6818 cm_return_if_fail(menu != NULL);
6819 cm_return_if_fail(GTK_IS_MENU_SHELL(menu));
6821 menuitem = gtk_separator_menu_item_new();
6822 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6823 gtk_widget_show(menuitem);
6825 menuitem = gtk_menu_item_new_with_mnemonic(_("Add to address _book"));
6826 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6828 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
6829 g_strstrip(address);
6830 if (*address == '\0') {
6831 gtk_widget_set_sensitive(GTK_WIDGET(menuitem), FALSE);
6834 g_signal_connect(G_OBJECT(menuitem), "activate",
6835 G_CALLBACK(compose_add_to_addressbook_cb), entry);
6836 gtk_widget_show(menuitem);
6839 void compose_add_extra_header(gchar *header, GtkListStore *model)
6842 if (strcmp(header, "")) {
6843 COMBOBOX_ADD(model, header, COMPOSE_TO);
6847 void compose_add_extra_header_entries(GtkListStore *model)
6851 gchar buf[BUFFSIZE];
6854 if (extra_headers == NULL) {
6855 exhrc = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, "extraheaderrc", NULL);
6856 if ((exh = g_fopen(exhrc, "rb")) == NULL) {
6857 debug_print("extra headers file not found\n");
6858 goto extra_headers_done;
6860 while (fgets(buf, BUFFSIZE, exh) != NULL) {
6861 lastc = strlen(buf) - 1; /* remove trailing control chars */
6862 while (lastc >= 0 && buf[lastc] != ':')
6863 buf[lastc--] = '\0';
6864 if (lastc > 0 && buf[0] != '#' && buf[lastc] == ':') {
6865 buf[lastc] = '\0'; /* remove trailing : for comparison */
6866 if (custom_header_is_allowed(buf)) {
6868 extra_headers = g_slist_prepend(extra_headers, g_strdup(buf));
6871 g_message("disallowed extra header line: %s\n", buf);
6875 g_message("invalid extra header line: %s\n", buf);
6881 extra_headers = g_slist_prepend(extra_headers, g_strdup("")); /* end of list */
6882 extra_headers = g_slist_reverse(extra_headers);
6884 g_slist_foreach(extra_headers, (GFunc)compose_add_extra_header, (gpointer)model);
6887 static void compose_create_header_entry(Compose *compose)
6889 gchar *headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6896 const gchar *header = NULL;
6897 ComposeHeaderEntry *headerentry;
6898 gboolean standard_header = FALSE;
6899 GtkListStore *model;
6902 headerentry = g_new0(ComposeHeaderEntry, 1);
6904 /* Combo box model */
6905 model = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_BOOLEAN);
6906 #if !GTK_CHECK_VERSION(2, 24, 0)
6907 combo = gtk_combo_box_entry_new_with_model(GTK_TREE_MODEL(model), 0);
6909 COMBOBOX_ADD(model, prefs_common_translated_header_name("To:"),
6911 COMBOBOX_ADD(model, prefs_common_translated_header_name("Cc:"),
6913 COMBOBOX_ADD(model, prefs_common_translated_header_name("Bcc:"),
6915 COMBOBOX_ADD(model, prefs_common_translated_header_name("Newsgroups:"),
6916 COMPOSE_NEWSGROUPS);
6917 COMBOBOX_ADD(model, prefs_common_translated_header_name("Reply-To:"),
6919 COMBOBOX_ADD(model, prefs_common_translated_header_name("Followup-To:"),
6920 COMPOSE_FOLLOWUPTO);
6921 compose_add_extra_header_entries(model);
6924 #if GTK_CHECK_VERSION(2, 24, 0)
6925 combo = gtk_combo_box_new_with_model_and_entry(GTK_TREE_MODEL(model));
6926 GtkCellRenderer *cell = gtk_cell_renderer_text_new();
6927 gtk_cell_renderer_set_alignment(cell, 0.0, 0.5);
6928 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), cell, TRUE);
6929 gtk_combo_box_set_entry_text_column(GTK_COMBO_BOX(combo), 0);
6931 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
6932 g_signal_connect(G_OBJECT(gtk_bin_get_child(GTK_BIN(combo))), "grab_focus",
6933 G_CALLBACK(compose_grab_focus_cb), compose);
6934 gtk_widget_show(combo);
6936 /* Putting only the combobox child into focus chain of its parent causes
6937 * the parent to be skipped when changing focus via Tab or Shift+Tab.
6938 * This eliminates need to pres Tab twice in order to really get from the
6939 * combobox to next widget. */
6941 l = g_list_prepend(l, gtk_bin_get_child(GTK_BIN(combo)));
6942 gtk_container_set_focus_chain(GTK_CONTAINER(combo), l);
6945 gtk_table_attach(GTK_TABLE(compose->header_table), combo, 0, 1,
6946 compose->header_nextrow, compose->header_nextrow+1,
6947 GTK_SHRINK, GTK_FILL, 0, 0);
6948 if (compose->header_last && (compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN)) {
6949 const gchar *last_header_entry = gtk_entry_get_text(
6950 GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
6952 while (*string != NULL) {
6953 if (!strcmp(prefs_common_translated_header_name(*string), last_header_entry))
6954 standard_header = TRUE;
6957 if (standard_header)
6958 header = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
6960 if (!compose->header_last || !standard_header) {
6961 switch(compose->account->protocol) {
6963 header = prefs_common_translated_header_name("Newsgroups:");
6966 header = prefs_common_translated_header_name("To:");
6971 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((combo)))), header);
6973 gtk_editable_set_editable(
6974 GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((combo)))),
6975 prefs_common.type_any_header);
6977 g_signal_connect_after(G_OBJECT(gtk_bin_get_child(GTK_BIN((combo)))), "grab_focus",
6978 G_CALLBACK(compose_grab_focus_cb), compose);
6980 /* Entry field with cleanup button */
6981 button = gtk_button_new();
6982 gtk_button_set_image(GTK_BUTTON(button),
6983 gtk_image_new_from_stock(GTK_STOCK_CLEAR, GTK_ICON_SIZE_MENU));
6984 gtk_widget_show(button);
6985 CLAWS_SET_TIP(button,
6986 _("Delete entry contents"));
6987 entry = gtk_entry_new();
6988 gtk_widget_show(entry);
6989 CLAWS_SET_TIP(entry,
6990 _("Use <tab> to autocomplete from addressbook"));
6991 hbox = gtk_hbox_new (FALSE, 0);
6992 gtk_widget_show(hbox);
6993 gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, TRUE, 0);
6994 gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
6995 gtk_table_attach(GTK_TABLE(compose->header_table), hbox, 1, 2,
6996 compose->header_nextrow, compose->header_nextrow+1,
6997 GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
6999 g_signal_connect(G_OBJECT(entry), "key-press-event",
7000 G_CALLBACK(compose_headerentry_key_press_event_cb),
7002 g_signal_connect(G_OBJECT(entry), "changed",
7003 G_CALLBACK(compose_headerentry_changed_cb),
7005 g_signal_connect_after(G_OBJECT(entry), "grab_focus",
7006 G_CALLBACK(compose_grab_focus_cb), compose);
7008 g_signal_connect(G_OBJECT(button), "clicked",
7009 G_CALLBACK(compose_headerentry_button_clicked_cb),
7013 gtk_drag_dest_set(entry, GTK_DEST_DEFAULT_ALL, compose_mime_types,
7014 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7015 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7016 g_signal_connect(G_OBJECT(entry), "drag_data_received",
7017 G_CALLBACK(compose_header_drag_received_cb),
7019 g_signal_connect(G_OBJECT(entry), "drag-drop",
7020 G_CALLBACK(compose_drag_drop),
7022 g_signal_connect(G_OBJECT(entry), "populate-popup",
7023 G_CALLBACK(compose_entry_popup_extend),
7026 address_completion_register_entry(GTK_ENTRY(entry), TRUE);
7028 headerentry->compose = compose;
7029 headerentry->combo = combo;
7030 headerentry->entry = entry;
7031 headerentry->button = button;
7032 headerentry->hbox = hbox;
7033 headerentry->headernum = compose->header_nextrow;
7034 headerentry->type = PREF_NONE;
7036 compose->header_nextrow++;
7037 compose->header_last = headerentry;
7038 compose->header_list =
7039 g_slist_append(compose->header_list,
7043 static void compose_add_header_entry(Compose *compose, const gchar *header,
7044 gchar *text, ComposePrefType pref_type)
7046 ComposeHeaderEntry *last_header = compose->header_last;
7047 gchar *tmp = g_strdup(text), *email;
7048 gboolean replyto_hdr;
7050 replyto_hdr = (!strcasecmp(header,
7051 prefs_common_translated_header_name("Reply-To:")) ||
7053 prefs_common_translated_header_name("Followup-To:")) ||
7055 prefs_common_translated_header_name("In-Reply-To:")));
7057 extract_address(tmp);
7058 email = g_utf8_strdown(tmp, -1);
7060 if (replyto_hdr == FALSE &&
7061 g_hash_table_lookup(compose->email_hashtable, email) != NULL)
7063 debug_print("Ignoring duplicate address - %s %s, pref_type: %d\n",
7064 header, text, (gint) pref_type);
7070 if (!strcasecmp(header, prefs_common_translated_header_name("In-Reply-To:")))
7071 gtk_entry_set_text(GTK_ENTRY(
7072 gtk_bin_get_child(GTK_BIN(last_header->combo))), header);
7074 combobox_select_by_text(GTK_COMBO_BOX(last_header->combo), header);
7075 gtk_entry_set_text(GTK_ENTRY(last_header->entry), text);
7076 last_header->type = pref_type;
7078 if (replyto_hdr == FALSE)
7079 g_hash_table_insert(compose->email_hashtable, email,
7080 GUINT_TO_POINTER(1));
7087 static void compose_destroy_headerentry(Compose *compose,
7088 ComposeHeaderEntry *headerentry)
7090 gchar *text = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
7093 extract_address(text);
7094 email = g_utf8_strdown(text, -1);
7095 g_hash_table_remove(compose->email_hashtable, email);
7099 gtk_widget_destroy(headerentry->combo);
7100 gtk_widget_destroy(headerentry->entry);
7101 gtk_widget_destroy(headerentry->button);
7102 gtk_widget_destroy(headerentry->hbox);
7103 g_free(headerentry);
7106 static void compose_remove_header_entries(Compose *compose)
7109 for (list = compose->header_list; list; list = list->next)
7110 compose_destroy_headerentry(compose, (ComposeHeaderEntry *)list->data);
7112 compose->header_last = NULL;
7113 g_slist_free(compose->header_list);
7114 compose->header_list = NULL;
7115 compose->header_nextrow = 1;
7116 compose_create_header_entry(compose);
7119 static GtkWidget *compose_create_header(Compose *compose)
7121 GtkWidget *from_optmenu_hbox;
7122 GtkWidget *header_table_main;
7123 GtkWidget *header_scrolledwin;
7124 GtkWidget *header_table;
7126 /* parent with account selection and from header */
7127 header_table_main = gtk_table_new(2, 2, FALSE);
7128 gtk_widget_show(header_table_main);
7129 gtk_container_set_border_width(GTK_CONTAINER(header_table_main), BORDER_WIDTH);
7131 from_optmenu_hbox = compose_account_option_menu_create(compose);
7132 gtk_table_attach(GTK_TABLE(header_table_main), from_optmenu_hbox,
7133 0, 2, 0, 1, GTK_EXPAND | GTK_FILL, GTK_SHRINK, 0, 0);
7135 /* child with header labels and entries */
7136 header_scrolledwin = gtk_scrolled_window_new(NULL, NULL);
7137 gtk_widget_show(header_scrolledwin);
7138 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
7140 header_table = gtk_table_new(2, 2, FALSE);
7141 gtk_widget_show(header_table);
7142 gtk_container_set_border_width(GTK_CONTAINER(header_table), 0);
7143 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin), header_table);
7144 gtk_container_set_focus_vadjustment(GTK_CONTAINER(header_table),
7145 gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(header_scrolledwin)));
7146 gtk_viewport_set_shadow_type(GTK_VIEWPORT(gtk_bin_get_child(GTK_BIN(header_scrolledwin))), GTK_SHADOW_NONE);
7148 gtk_table_attach(GTK_TABLE(header_table_main), header_scrolledwin,
7149 0, 2, 1, 2, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 2);
7151 compose->header_table = header_table;
7152 compose->header_list = NULL;
7153 compose->header_nextrow = 0;
7155 compose_create_header_entry(compose);
7157 compose->table = NULL;
7159 return header_table_main;
7162 static gboolean popup_attach_button_pressed(GtkWidget *widget, gpointer data)
7164 Compose *compose = (Compose *)data;
7165 GdkEventButton event;
7168 event.time = gtk_get_current_event_time();
7170 return attach_button_pressed(compose->attach_clist, &event, compose);
7173 static GtkWidget *compose_create_attach(Compose *compose)
7175 GtkWidget *attach_scrwin;
7176 GtkWidget *attach_clist;
7178 GtkListStore *store;
7179 GtkCellRenderer *renderer;
7180 GtkTreeViewColumn *column;
7181 GtkTreeSelection *selection;
7183 /* attachment list */
7184 attach_scrwin = gtk_scrolled_window_new(NULL, NULL);
7185 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(attach_scrwin),
7186 GTK_POLICY_AUTOMATIC,
7187 GTK_POLICY_AUTOMATIC);
7188 gtk_widget_set_size_request(attach_scrwin, -1, 80);
7190 store = gtk_list_store_new(N_ATTACH_COLS,
7196 G_TYPE_AUTO_POINTER,
7198 attach_clist = GTK_WIDGET(gtk_tree_view_new_with_model
7199 (GTK_TREE_MODEL(store)));
7200 gtk_container_add(GTK_CONTAINER(attach_scrwin), attach_clist);
7201 g_object_unref(store);
7203 renderer = gtk_cell_renderer_text_new();
7204 column = gtk_tree_view_column_new_with_attributes
7205 (_("Mime type"), renderer, "text",
7206 COL_MIMETYPE, NULL);
7207 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7209 renderer = gtk_cell_renderer_text_new();
7210 column = gtk_tree_view_column_new_with_attributes
7211 (_("Size"), renderer, "text",
7213 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7215 renderer = gtk_cell_renderer_text_new();
7216 column = gtk_tree_view_column_new_with_attributes
7217 (_("Name"), renderer, "text",
7219 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7221 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(attach_clist),
7222 prefs_common.use_stripes_everywhere);
7223 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(attach_clist));
7224 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
7226 g_signal_connect(G_OBJECT(attach_clist), "row_activated",
7227 G_CALLBACK(attach_selected), compose);
7228 g_signal_connect(G_OBJECT(attach_clist), "button_press_event",
7229 G_CALLBACK(attach_button_pressed), compose);
7230 g_signal_connect(G_OBJECT(attach_clist), "popup-menu",
7231 G_CALLBACK(popup_attach_button_pressed), compose);
7232 g_signal_connect(G_OBJECT(attach_clist), "key_press_event",
7233 G_CALLBACK(attach_key_pressed), compose);
7236 gtk_drag_dest_set(attach_clist,
7237 GTK_DEST_DEFAULT_ALL, compose_mime_types,
7238 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7239 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7240 g_signal_connect(G_OBJECT(attach_clist), "drag_data_received",
7241 G_CALLBACK(compose_attach_drag_received_cb),
7243 g_signal_connect(G_OBJECT(attach_clist), "drag-drop",
7244 G_CALLBACK(compose_drag_drop),
7247 compose->attach_scrwin = attach_scrwin;
7248 compose->attach_clist = attach_clist;
7250 return attach_scrwin;
7253 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose);
7254 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose);
7256 static GtkWidget *compose_create_others(Compose *compose)
7259 GtkWidget *savemsg_checkbtn;
7260 GtkWidget *savemsg_combo;
7261 GtkWidget *savemsg_select;
7264 gchar *folderidentifier;
7266 /* Table for settings */
7267 table = gtk_table_new(3, 1, FALSE);
7268 gtk_container_set_border_width(GTK_CONTAINER(table), BORDER_WIDTH);
7269 gtk_widget_show(table);
7270 gtk_table_set_row_spacings(GTK_TABLE(table), VSPACING_NARROW);
7273 /* Save Message to folder */
7274 savemsg_checkbtn = gtk_check_button_new_with_label(_("Save Message to "));
7275 gtk_widget_show(savemsg_checkbtn);
7276 gtk_table_attach(GTK_TABLE(table), savemsg_checkbtn, 0, 1, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7277 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7278 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), prefs_common.savemsg);
7280 g_signal_connect(G_OBJECT(savemsg_checkbtn), "toggled",
7281 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
7283 #if !GTK_CHECK_VERSION(2, 24, 0)
7284 savemsg_combo = gtk_combo_box_entry_new_text();
7286 savemsg_combo = gtk_combo_box_text_new_with_entry();
7288 compose->savemsg_checkbtn = savemsg_checkbtn;
7289 compose->savemsg_combo = savemsg_combo;
7290 gtk_widget_show(savemsg_combo);
7292 if (prefs_common.compose_save_to_history)
7293 #if !GTK_CHECK_VERSION(2, 24, 0)
7294 combobox_set_popdown_strings(GTK_COMBO_BOX(savemsg_combo),
7295 prefs_common.compose_save_to_history);
7297 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(savemsg_combo),
7298 prefs_common.compose_save_to_history);
7300 gtk_table_attach(GTK_TABLE(table), savemsg_combo, 1, 2, rowcount, rowcount + 1, GTK_FILL|GTK_EXPAND, GTK_SHRINK, 0, 0);
7301 gtk_widget_set_sensitive(GTK_WIDGET(savemsg_combo), prefs_common.savemsg);
7302 g_signal_connect_after(G_OBJECT(savemsg_combo), "grab_focus",
7303 G_CALLBACK(compose_grab_focus_cb), compose);
7304 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7305 folderidentifier = folder_item_get_identifier(account_get_special_folder
7306 (compose->account, F_OUTBOX));
7307 compose_set_save_to(compose, folderidentifier);
7308 g_free(folderidentifier);
7311 savemsg_select = gtkut_get_browse_file_btn(_("_Browse"));
7312 gtk_widget_show(savemsg_select);
7313 gtk_table_attach(GTK_TABLE(table), savemsg_select, 2, 3, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7314 g_signal_connect(G_OBJECT(savemsg_select), "clicked",
7315 G_CALLBACK(compose_savemsg_select_cb),
7321 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose)
7323 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo),
7324 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn)));
7327 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose)
7332 dest = foldersel_folder_sel(NULL, FOLDER_SEL_COPY, NULL, FALSE);
7335 path = folder_item_get_identifier(dest);
7337 compose_set_save_to(compose, path);
7341 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry, gboolean wrap,
7342 GdkAtom clip, GtkTextIter *insert_place);
7345 static gboolean text_clicked(GtkWidget *text, GdkEventButton *event,
7349 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
7351 if (event->button == 3) {
7353 GtkTextIter sel_start, sel_end;
7354 gboolean stuff_selected;
7356 /* move the cursor to allow GtkAspell to check the word
7357 * under the mouse */
7358 if (event->x && event->y) {
7359 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7360 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7362 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7365 GtkTextMark *mark = gtk_text_buffer_get_insert(buffer);
7366 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7369 stuff_selected = gtk_text_buffer_get_selection_bounds(
7371 &sel_start, &sel_end);
7373 gtk_text_buffer_place_cursor (buffer, &iter);
7374 /* reselect stuff */
7376 && gtk_text_iter_in_range(&iter, &sel_start, &sel_end)) {
7377 gtk_text_buffer_select_range(buffer,
7378 &sel_start, &sel_end);
7380 return FALSE; /* pass the event so that the right-click goes through */
7383 if (event->button == 2) {
7388 /* get the middle-click position to paste at the correct place */
7389 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7390 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7392 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7395 entry_paste_clipboard(compose, text,
7396 prefs_common.linewrap_pastes,
7397 GDK_SELECTION_PRIMARY, &iter);
7405 static void compose_spell_menu_changed(void *data)
7407 Compose *compose = (Compose *)data;
7409 GtkWidget *menuitem;
7410 GtkWidget *parent_item;
7411 GtkMenu *menu = GTK_MENU(gtk_menu_new());
7414 if (compose->gtkaspell == NULL)
7417 parent_item = gtk_ui_manager_get_widget(compose->ui_manager,
7418 "/Menu/Spelling/Options");
7420 /* setting the submenu removes /Spelling/Options from the factory
7421 * so we need to save it */
7423 if (parent_item == NULL) {
7424 parent_item = compose->aspell_options_menu;
7425 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), NULL);
7427 compose->aspell_options_menu = parent_item;
7429 spell_menu = gtkaspell_make_config_menu(compose->gtkaspell);
7431 spell_menu = g_slist_reverse(spell_menu);
7432 for (items = spell_menu;
7433 items; items = items->next) {
7434 menuitem = GTK_WIDGET(GTK_MENU_ITEM(items->data));
7435 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), GTK_WIDGET(menuitem));
7436 gtk_widget_show(GTK_WIDGET(menuitem));
7438 g_slist_free(spell_menu);
7440 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), GTK_WIDGET(menu));
7441 gtk_widget_show(parent_item);
7444 static void compose_dict_changed(void *data)
7446 Compose *compose = (Compose *) data;
7448 if(!compose->gtkaspell)
7450 if(compose->gtkaspell->recheck_when_changing_dict == FALSE)
7453 gtkaspell_highlight_all(compose->gtkaspell);
7454 claws_spell_entry_recheck_all(CLAWS_SPELL_ENTRY(compose->subject_entry));
7458 static gboolean compose_popup_menu(GtkWidget *widget, gpointer data)
7460 Compose *compose = (Compose *)data;
7461 GdkEventButton event;
7464 event.time = gtk_get_current_event_time();
7468 return text_clicked(compose->text, &event, compose);
7471 static gboolean compose_force_window_origin = TRUE;
7472 static Compose *compose_create(PrefsAccount *account,
7481 GtkWidget *handlebox;
7483 GtkWidget *notebook;
7485 GtkWidget *attach_hbox;
7486 GtkWidget *attach_lab1;
7487 GtkWidget *attach_lab2;
7492 GtkWidget *subject_hbox;
7493 GtkWidget *subject_frame;
7494 GtkWidget *subject_entry;
7498 GtkWidget *edit_vbox;
7499 GtkWidget *ruler_hbox;
7501 GtkWidget *scrolledwin;
7503 GtkTextBuffer *buffer;
7504 GtkClipboard *clipboard;
7506 UndoMain *undostruct;
7508 GtkWidget *popupmenu;
7509 GtkWidget *tmpl_menu;
7510 GtkActionGroup *action_group = NULL;
7513 GtkAspell * gtkaspell = NULL;
7516 static GdkGeometry geometry;
7518 cm_return_val_if_fail(account != NULL, NULL);
7520 debug_print("Creating compose window...\n");
7521 compose = g_new0(Compose, 1);
7523 compose->batch = batch;
7524 compose->account = account;
7525 compose->folder = folder;
7527 compose->mutex = cm_mutex_new();
7528 compose->set_cursor_pos = -1;
7530 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose");
7532 gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
7533 gtk_widget_set_size_request(window, prefs_common.compose_width,
7534 prefs_common.compose_height);
7536 if (!geometry.max_width) {
7537 geometry.max_width = gdk_screen_width();
7538 geometry.max_height = gdk_screen_height();
7541 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7542 &geometry, GDK_HINT_MAX_SIZE);
7543 if (!geometry.min_width) {
7544 geometry.min_width = 600;
7545 geometry.min_height = 440;
7547 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7548 &geometry, GDK_HINT_MIN_SIZE);
7550 #ifndef GENERIC_UMPC
7551 if (compose_force_window_origin)
7552 gtk_window_move(GTK_WINDOW(window), prefs_common.compose_x,
7553 prefs_common.compose_y);
7555 g_signal_connect(G_OBJECT(window), "delete_event",
7556 G_CALLBACK(compose_delete_cb), compose);
7557 MANAGE_WINDOW_SIGNALS_CONNECT(window);
7558 gtk_widget_realize(window);
7560 gtkut_widget_set_composer_icon(window);
7562 vbox = gtk_vbox_new(FALSE, 0);
7563 gtk_container_add(GTK_CONTAINER(window), vbox);
7565 compose->ui_manager = gtk_ui_manager_new();
7566 action_group = cm_menu_create_action_group_full(compose->ui_manager,"Menu", compose_entries,
7567 G_N_ELEMENTS(compose_entries), (gpointer)compose);
7568 gtk_action_group_add_toggle_actions(action_group, compose_toggle_entries,
7569 G_N_ELEMENTS(compose_toggle_entries), (gpointer)compose);
7570 gtk_action_group_add_radio_actions(action_group, compose_radio_rm_entries,
7571 G_N_ELEMENTS(compose_radio_rm_entries), COMPOSE_REPLY, G_CALLBACK(compose_reply_change_mode_cb), (gpointer)compose);
7572 gtk_action_group_add_radio_actions(action_group, compose_radio_prio_entries,
7573 G_N_ELEMENTS(compose_radio_prio_entries), PRIORITY_NORMAL, G_CALLBACK(compose_set_priority_cb), (gpointer)compose);
7574 gtk_action_group_add_radio_actions(action_group, compose_radio_enc_entries,
7575 G_N_ELEMENTS(compose_radio_enc_entries), C_AUTO, G_CALLBACK(compose_set_encoding_cb), (gpointer)compose);
7577 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Menu", NULL, GTK_UI_MANAGER_MENUBAR)
7579 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Message", "Message", GTK_UI_MANAGER_MENU)
7580 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Edit", "Edit", GTK_UI_MANAGER_MENU)
7582 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Spelling", "Spelling", GTK_UI_MANAGER_MENU)
7584 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Options", "Options", GTK_UI_MANAGER_MENU)
7585 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Tools", "Tools", GTK_UI_MANAGER_MENU)
7586 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Help", "Help", GTK_UI_MANAGER_MENU)
7589 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Send", "Message/Send", GTK_UI_MANAGER_MENUITEM)
7590 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "SendLater", "Message/SendLater", GTK_UI_MANAGER_MENUITEM)
7591 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator1", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7592 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "AttachFile", "Message/AttachFile", GTK_UI_MANAGER_MENUITEM)
7593 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertFile", "Message/InsertFile", GTK_UI_MANAGER_MENUITEM)
7594 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertSig", "Message/InsertSig", GTK_UI_MANAGER_MENUITEM)
7595 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "ReplaceSig", "Message/ReplaceSig", GTK_UI_MANAGER_MENUITEM)
7596 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator2", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7597 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Save", "Message/Save", GTK_UI_MANAGER_MENUITEM)
7598 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator3", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7599 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Print", "Message/Print", GTK_UI_MANAGER_MENUITEM)
7600 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator4", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7601 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Close", "Message/Close", GTK_UI_MANAGER_MENUITEM)
7604 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Undo", "Edit/Undo", GTK_UI_MANAGER_MENUITEM)
7605 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Redo", "Edit/Redo", GTK_UI_MANAGER_MENUITEM)
7606 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator1", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7608 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Cut", "Edit/Cut", GTK_UI_MANAGER_MENUITEM)
7609 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Copy", "Edit/Copy", GTK_UI_MANAGER_MENUITEM)
7610 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Paste", "Edit/Paste", GTK_UI_MANAGER_MENUITEM)
7612 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SpecialPaste", "Edit/SpecialPaste", GTK_UI_MANAGER_MENU)
7613 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "AsQuotation", "Edit/SpecialPaste/AsQuotation", GTK_UI_MANAGER_MENUITEM)
7614 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Wrapped", "Edit/SpecialPaste/Wrapped", GTK_UI_MANAGER_MENUITEM)
7615 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Unwrapped", "Edit/SpecialPaste/Unwrapped", GTK_UI_MANAGER_MENUITEM)
7617 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SelectAll", "Edit/SelectAll", GTK_UI_MANAGER_MENUITEM)
7619 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Advanced", "Edit/Advanced", GTK_UI_MANAGER_MENU)
7620 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackChar", "Edit/Advanced/BackChar", GTK_UI_MANAGER_MENUITEM)
7621 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwChar", "Edit/Advanced/ForwChar", GTK_UI_MANAGER_MENUITEM)
7622 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackWord", "Edit/Advanced/BackWord", GTK_UI_MANAGER_MENUITEM)
7623 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwWord", "Edit/Advanced/ForwWord", GTK_UI_MANAGER_MENUITEM)
7624 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BegLine", "Edit/Advanced/BegLine", GTK_UI_MANAGER_MENUITEM)
7625 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "EndLine", "Edit/Advanced/EndLine", GTK_UI_MANAGER_MENUITEM)
7626 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "PrevLine", "Edit/Advanced/PrevLine", GTK_UI_MANAGER_MENUITEM)
7627 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "NextLine", "Edit/Advanced/NextLine", GTK_UI_MANAGER_MENUITEM)
7628 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackChar", "Edit/Advanced/DelBackChar", GTK_UI_MANAGER_MENUITEM)
7629 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwChar", "Edit/Advanced/DelForwChar", GTK_UI_MANAGER_MENUITEM)
7630 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackWord", "Edit/Advanced/DelBackWord", GTK_UI_MANAGER_MENUITEM)
7631 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwWord", "Edit/Advanced/DelForwWord", GTK_UI_MANAGER_MENUITEM)
7632 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelLine", "Edit/Advanced/DelLine", GTK_UI_MANAGER_MENUITEM)
7633 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelEndLine", "Edit/Advanced/DelEndLine", GTK_UI_MANAGER_MENUITEM)
7635 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator2", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7637 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Find", "Edit/Find", GTK_UI_MANAGER_MENUITEM)
7638 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapPara", "Edit/WrapPara", GTK_UI_MANAGER_MENUITEM)
7639 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapAllLines", "Edit/WrapAllLines", GTK_UI_MANAGER_MENUITEM)
7640 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoWrap", "Edit/AutoWrap", GTK_UI_MANAGER_MENUITEM)
7641 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoIndent", "Edit/AutoIndent", GTK_UI_MANAGER_MENUITEM)
7643 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator3", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7645 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "ExtEditor", "Edit/ExtEditor", GTK_UI_MANAGER_MENUITEM)
7649 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckAllSel", "Spelling/CheckAllSel", GTK_UI_MANAGER_MENUITEM)
7650 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "HighlightAll", "Spelling/HighlightAll", GTK_UI_MANAGER_MENUITEM)
7651 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckBackwards", "Spelling/CheckBackwards", GTK_UI_MANAGER_MENUITEM)
7652 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "ForwardNext", "Spelling/ForwardNext", GTK_UI_MANAGER_MENUITEM)
7653 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Separator1", "Spelling/---", GTK_UI_MANAGER_SEPARATOR)
7654 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Options", "Spelling/Options", GTK_UI_MANAGER_MENU)
7658 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "ReplyMode", "Options/ReplyMode", GTK_UI_MANAGER_MENU)
7659 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Normal", "Options/ReplyMode/Normal", GTK_UI_MANAGER_MENUITEM)
7660 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "All", "Options/ReplyMode/All", GTK_UI_MANAGER_MENUITEM)
7661 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Sender", "Options/ReplyMode/Sender", GTK_UI_MANAGER_MENUITEM)
7662 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "List", "Options/ReplyMode/List", GTK_UI_MANAGER_MENUITEM)
7664 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator1", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7665 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "PrivacySystem", "Options/PrivacySystem", GTK_UI_MANAGER_MENU)
7666 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/PrivacySystem", "PlaceHolder", "Options/PrivacySystem/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7667 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Sign", "Options/Sign", GTK_UI_MANAGER_MENUITEM)
7668 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encrypt", "Options/Encrypt", GTK_UI_MANAGER_MENUITEM)
7671 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator2", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7672 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Priority", "Options/Priority", GTK_UI_MANAGER_MENU)
7673 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Highest", "Options/Priority/Highest", GTK_UI_MANAGER_MENUITEM)
7674 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "High", "Options/Priority/High", GTK_UI_MANAGER_MENUITEM)
7675 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Normal", "Options/Priority/Normal", GTK_UI_MANAGER_MENUITEM)
7676 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Low", "Options/Priority/Low", GTK_UI_MANAGER_MENUITEM)
7677 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Lowest", "Options/Priority/Lowest", GTK_UI_MANAGER_MENUITEM)
7679 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator3", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7680 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RequestRetRcpt", "Options/RequestRetRcpt", GTK_UI_MANAGER_MENUITEM)
7681 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator4", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7682 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RemoveReferences", "Options/RemoveReferences", GTK_UI_MANAGER_MENUITEM)
7683 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator5", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7685 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encoding", "Options/Encoding", GTK_UI_MANAGER_MENU)
7687 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_AUTO, "Options/Encoding/"CS_AUTO, GTK_UI_MANAGER_MENUITEM)
7688 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator1", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7689 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_US_ASCII, "Options/Encoding/"CS_US_ASCII, GTK_UI_MANAGER_MENUITEM)
7690 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_UTF_8, "Options/Encoding/"CS_UTF_8, GTK_UI_MANAGER_MENUITEM)
7691 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator2", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7693 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Western", "Options/Encoding/Western", GTK_UI_MANAGER_MENU)
7694 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Western", CS_ISO_8859_1, "Options/Encoding/Western/"CS_ISO_8859_1, GTK_UI_MANAGER_MENUITEM)
7695 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Western", CS_ISO_8859_15, "Options/Encoding/Western/"CS_ISO_8859_15, GTK_UI_MANAGER_MENUITEM)
7696 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Western", CS_WINDOWS_1252, "Options/Encoding/Western/"CS_WINDOWS_1252, GTK_UI_MANAGER_MENUITEM)
7698 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_2, "Options/Encoding/"CS_ISO_8859_2, GTK_UI_MANAGER_MENUITEM)
7700 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Baltic", "Options/Encoding/Baltic", GTK_UI_MANAGER_MENU)
7701 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Baltic", CS_ISO_8859_13, "Options/Encoding/Baltic/"CS_ISO_8859_13, GTK_UI_MANAGER_MENUITEM)
7702 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Baltic", CS_ISO_8859_4, "Options/Encoding/Baltic/"CS_ISO_8859_4, GTK_UI_MANAGER_MENUITEM)
7704 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_7, "Options/Encoding/"CS_ISO_8859_7, GTK_UI_MANAGER_MENUITEM)
7706 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Hebrew", "Options/Encoding/Hebrew", GTK_UI_MANAGER_MENU)
7707 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Hebrew", CS_ISO_8859_8, "Options/Encoding/Hebrew/"CS_ISO_8859_8, GTK_UI_MANAGER_MENUITEM)
7708 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Hebrew", CS_WINDOWS_1255, "Options/Encoding/Hebrew/"CS_WINDOWS_1255, GTK_UI_MANAGER_MENUITEM)
7710 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Arabic", "Options/Encoding/Arabic", GTK_UI_MANAGER_MENU)
7711 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Arabic", CS_ISO_8859_6, "Options/Encoding/Arabic/"CS_ISO_8859_6, GTK_UI_MANAGER_MENUITEM)
7712 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Arabic", CS_WINDOWS_1256, "Options/Encoding/Arabic/"CS_WINDOWS_1256, GTK_UI_MANAGER_MENUITEM)
7714 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_9, "Options/Encoding/"CS_ISO_8859_9, GTK_UI_MANAGER_MENUITEM)
7716 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Cyrillic", "Options/Encoding/Cyrillic", GTK_UI_MANAGER_MENU)
7717 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_ISO_8859_5, "Options/Encoding/Cyrillic/"CS_ISO_8859_5, GTK_UI_MANAGER_MENUITEM)
7718 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_R, "Options/Encoding/Cyrillic/"CS_KOI8_R, GTK_UI_MANAGER_MENUITEM)
7719 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_MACCYR, "Options/Encoding/Cyrillic/"CS_MACCYR, GTK_UI_MANAGER_MENUITEM)
7720 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_U, "Options/Encoding/Cyrillic/"CS_KOI8_U, GTK_UI_MANAGER_MENUITEM)
7721 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_WINDOWS_1251, "Options/Encoding/Cyrillic/"CS_WINDOWS_1251, GTK_UI_MANAGER_MENUITEM)
7723 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Japanese", "Options/Encoding/Japanese", GTK_UI_MANAGER_MENU)
7724 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_ISO_2022_JP, "Options/Encoding/Japanese/"CS_ISO_2022_JP, GTK_UI_MANAGER_MENUITEM)
7725 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_ISO_2022_JP_2, "Options/Encoding/Japanese/"CS_ISO_2022_JP_2, GTK_UI_MANAGER_MENUITEM)
7726 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_EUC_JP, "Options/Encoding/Japanese/"CS_EUC_JP, GTK_UI_MANAGER_MENUITEM)
7727 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_SHIFT_JIS, "Options/Encoding/Japanese/"CS_SHIFT_JIS, GTK_UI_MANAGER_MENUITEM)
7729 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Chinese", "Options/Encoding/Chinese", GTK_UI_MANAGER_MENU)
7730 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB18030, "Options/Encoding/Chinese/"CS_GB18030, GTK_UI_MANAGER_MENUITEM)
7731 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB2312, "Options/Encoding/Chinese/"CS_GB2312, GTK_UI_MANAGER_MENUITEM)
7732 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GBK, "Options/Encoding/Chinese/"CS_GBK, GTK_UI_MANAGER_MENUITEM)
7733 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_BIG5, "Options/Encoding/Chinese/"CS_BIG5, GTK_UI_MANAGER_MENUITEM)
7734 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_EUC_TW, "Options/Encoding/Chinese/"CS_EUC_TW, GTK_UI_MANAGER_MENUITEM)
7736 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Korean", "Options/Encoding/Korean", GTK_UI_MANAGER_MENU)
7737 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Korean", CS_EUC_KR, "Options/Encoding/Korean/"CS_EUC_KR, GTK_UI_MANAGER_MENUITEM)
7738 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Korean", CS_ISO_2022_KR, "Options/Encoding/Korean/"CS_ISO_2022_KR, GTK_UI_MANAGER_MENUITEM)
7740 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Thai", "Options/Encoding/Thai", GTK_UI_MANAGER_MENU)
7741 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_TIS_620, "Options/Encoding/Thai/"CS_TIS_620, GTK_UI_MANAGER_MENUITEM)
7742 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_WINDOWS_874, "Options/Encoding/Thai/"CS_WINDOWS_874, GTK_UI_MANAGER_MENUITEM)
7746 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "ShowRuler", "Tools/ShowRuler", GTK_UI_MANAGER_MENUITEM)
7747 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "AddressBook", "Tools/AddressBook", GTK_UI_MANAGER_MENUITEM)
7748 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Template", "Tools/Template", GTK_UI_MANAGER_MENU)
7749 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Template", "PlaceHolder", "Tools/Template/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7750 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Actions", "Tools/Actions", GTK_UI_MANAGER_MENU)
7751 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Actions", "PlaceHolder", "Tools/Actions/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7754 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Help", "About", "Help/About", GTK_UI_MANAGER_MENUITEM)
7756 menubar = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu");
7757 gtk_widget_show_all(menubar);
7759 gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(compose->ui_manager));
7760 gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, TRUE, 0);
7762 if (prefs_common.toolbar_detachable) {
7763 handlebox = gtk_handle_box_new();
7765 handlebox = gtk_hbox_new(FALSE, 0);
7767 gtk_box_pack_start(GTK_BOX(vbox), handlebox, FALSE, FALSE, 0);
7769 gtk_widget_realize(handlebox);
7770 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, handlebox,
7773 vbox2 = gtk_vbox_new(FALSE, 2);
7774 gtk_box_pack_start(GTK_BOX(vbox), vbox2, TRUE, TRUE, 0);
7775 gtk_container_set_border_width(GTK_CONTAINER(vbox2), 0);
7778 notebook = gtk_notebook_new();
7779 gtk_widget_show(notebook);
7781 /* header labels and entries */
7782 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7783 compose_create_header(compose),
7784 gtk_label_new_with_mnemonic(_("Hea_der")));
7785 /* attachment list */
7786 attach_hbox = gtk_hbox_new(FALSE, 0);
7787 gtk_widget_show(attach_hbox);
7789 attach_lab1 = gtk_label_new_with_mnemonic(_("_Attachments"));
7790 gtk_widget_show(attach_lab1);
7791 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab1, TRUE, TRUE, 0);
7793 attach_lab2 = gtk_label_new("");
7794 gtk_widget_show(attach_lab2);
7795 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab2, FALSE, FALSE, 0);
7797 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7798 compose_create_attach(compose),
7801 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7802 compose_create_others(compose),
7803 gtk_label_new_with_mnemonic(_("Othe_rs")));
7806 subject_hbox = gtk_hbox_new(FALSE, 0);
7807 gtk_widget_show(subject_hbox);
7809 subject_frame = gtk_frame_new(NULL);
7810 gtk_frame_set_shadow_type(GTK_FRAME(subject_frame), GTK_SHADOW_NONE);
7811 gtk_box_pack_start(GTK_BOX(subject_hbox), subject_frame, TRUE, TRUE, 0);
7812 gtk_widget_show(subject_frame);
7814 subject = gtk_hbox_new(FALSE, HSPACING_NARROW);
7815 gtk_container_set_border_width(GTK_CONTAINER(subject), 0);
7816 gtk_widget_show(subject);
7818 label = gtk_label_new_with_mnemonic(_("S_ubject:"));
7819 gtk_box_pack_start(GTK_BOX(subject), label, FALSE, FALSE, 0);
7820 gtk_widget_show(label);
7823 subject_entry = claws_spell_entry_new();
7825 subject_entry = gtk_entry_new();
7827 gtk_box_pack_start(GTK_BOX(subject), subject_entry, TRUE, TRUE, 0);
7828 g_signal_connect_after(G_OBJECT(subject_entry), "grab_focus",
7829 G_CALLBACK(compose_grab_focus_cb), compose);
7830 gtk_label_set_mnemonic_widget(GTK_LABEL(label), subject_entry);
7831 gtk_widget_show(subject_entry);
7832 compose->subject_entry = subject_entry;
7833 gtk_container_add(GTK_CONTAINER(subject_frame), subject);
7835 edit_vbox = gtk_vbox_new(FALSE, 0);
7837 gtk_box_pack_start(GTK_BOX(edit_vbox), subject_hbox, FALSE, FALSE, 0);
7840 ruler_hbox = gtk_hbox_new(FALSE, 0);
7841 gtk_box_pack_start(GTK_BOX(edit_vbox), ruler_hbox, FALSE, FALSE, 0);
7843 ruler = gtk_shruler_new(GTK_ORIENTATION_HORIZONTAL);
7844 gtk_shruler_set_range(GTK_SHRULER(ruler), 0.0, 100.0, 1.0);
7845 gtk_box_pack_start(GTK_BOX(ruler_hbox), ruler, TRUE, TRUE,
7849 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
7850 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
7851 GTK_POLICY_AUTOMATIC,
7852 GTK_POLICY_AUTOMATIC);
7853 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
7855 gtk_box_pack_start(GTK_BOX(edit_vbox), scrolledwin, TRUE, TRUE, 0);
7857 text = gtk_text_view_new();
7858 if (prefs_common.show_compose_margin) {
7859 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text), 6);
7860 gtk_text_view_set_right_margin(GTK_TEXT_VIEW(text), 6);
7862 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
7863 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
7864 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), TRUE);
7865 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
7866 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
7868 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
7869 g_signal_connect_after(G_OBJECT(text), "size_allocate",
7870 G_CALLBACK(compose_edit_size_alloc),
7872 g_signal_connect(G_OBJECT(buffer), "changed",
7873 G_CALLBACK(compose_changed_cb), compose);
7874 g_signal_connect(G_OBJECT(text), "grab_focus",
7875 G_CALLBACK(compose_grab_focus_cb), compose);
7876 g_signal_connect(G_OBJECT(buffer), "insert_text",
7877 G_CALLBACK(text_inserted), compose);
7878 g_signal_connect(G_OBJECT(text), "button_press_event",
7879 G_CALLBACK(text_clicked), compose);
7880 g_signal_connect(G_OBJECT(text), "popup-menu",
7881 G_CALLBACK(compose_popup_menu), compose);
7882 g_signal_connect(G_OBJECT(subject_entry), "changed",
7883 G_CALLBACK(compose_changed_cb), compose);
7884 g_signal_connect(G_OBJECT(subject_entry), "activate",
7885 G_CALLBACK(compose_subject_entry_activated), compose);
7888 gtk_drag_dest_set(text, GTK_DEST_DEFAULT_ALL, compose_mime_types,
7889 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7890 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7891 g_signal_connect(G_OBJECT(text), "drag_data_received",
7892 G_CALLBACK(compose_insert_drag_received_cb),
7894 g_signal_connect(G_OBJECT(text), "drag-drop",
7895 G_CALLBACK(compose_drag_drop),
7897 g_signal_connect(G_OBJECT(text), "key-press-event",
7898 G_CALLBACK(completion_set_focus_to_subject),
7900 gtk_widget_show_all(vbox);
7902 /* pane between attach clist and text */
7903 paned = gtk_vpaned_new();
7904 gtk_container_add(GTK_CONTAINER(vbox2), paned);
7905 gtk_paned_pack1(GTK_PANED(paned), notebook, FALSE, FALSE);
7906 gtk_paned_pack2(GTK_PANED(paned), edit_vbox, TRUE, FALSE);
7907 gtk_paned_set_position(GTK_PANED(paned), prefs_common.compose_notebook_height);
7908 g_signal_connect(G_OBJECT(notebook), "size_allocate",
7909 G_CALLBACK(compose_notebook_size_alloc), paned);
7911 gtk_widget_show_all(paned);
7914 if (prefs_common.textfont) {
7915 PangoFontDescription *font_desc;
7917 font_desc = pango_font_description_from_string
7918 (prefs_common.textfont);
7920 gtk_widget_modify_font(text, font_desc);
7921 pango_font_description_free(font_desc);
7925 gtk_action_group_add_actions(action_group, compose_popup_entries,
7926 G_N_ELEMENTS(compose_popup_entries), (gpointer)compose);
7927 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Popup", NULL, GTK_UI_MANAGER_MENUBAR)
7928 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup", "Compose", "Compose", GTK_UI_MANAGER_MENU)
7929 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Add", "Compose/Add", GTK_UI_MANAGER_MENUITEM)
7930 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Remove", "Compose/Remove", GTK_UI_MANAGER_MENUITEM)
7931 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Separator1", "Compose/---", GTK_UI_MANAGER_SEPARATOR)
7932 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Properties", "Compose/Properties", GTK_UI_MANAGER_MENUITEM)
7934 popupmenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(gtk_ui_manager_get_widget(compose->ui_manager, "/Popup/Compose")));
7936 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
7937 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
7938 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
7940 tmpl_menu = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu/Tools/Template");
7942 undostruct = undo_init(text);
7943 undo_set_change_state_func(undostruct, &compose_undo_state_changed,
7946 address_completion_start(window);
7948 compose->window = window;
7949 compose->vbox = vbox;
7950 compose->menubar = menubar;
7951 compose->handlebox = handlebox;
7953 compose->vbox2 = vbox2;
7955 compose->paned = paned;
7957 compose->attach_label = attach_lab2;
7959 compose->notebook = notebook;
7960 compose->edit_vbox = edit_vbox;
7961 compose->ruler_hbox = ruler_hbox;
7962 compose->ruler = ruler;
7963 compose->scrolledwin = scrolledwin;
7964 compose->text = text;
7966 compose->focused_editable = NULL;
7968 compose->popupmenu = popupmenu;
7970 compose->tmpl_menu = tmpl_menu;
7972 compose->mode = mode;
7973 compose->rmode = mode;
7975 compose->targetinfo = NULL;
7976 compose->replyinfo = NULL;
7977 compose->fwdinfo = NULL;
7979 compose->email_hashtable = g_hash_table_new_full(g_str_hash,
7980 g_str_equal, (GDestroyNotify) g_free, NULL);
7982 compose->replyto = NULL;
7984 compose->bcc = NULL;
7985 compose->followup_to = NULL;
7987 compose->ml_post = NULL;
7989 compose->inreplyto = NULL;
7990 compose->references = NULL;
7991 compose->msgid = NULL;
7992 compose->boundary = NULL;
7994 compose->autowrap = prefs_common.autowrap;
7995 compose->autoindent = prefs_common.auto_indent;
7996 compose->use_signing = FALSE;
7997 compose->use_encryption = FALSE;
7998 compose->privacy_system = NULL;
7999 compose->encdata = NULL;
8001 compose->modified = FALSE;
8003 compose->return_receipt = FALSE;
8005 compose->to_list = NULL;
8006 compose->newsgroup_list = NULL;
8008 compose->undostruct = undostruct;
8010 compose->sig_str = NULL;
8012 compose->exteditor_file = NULL;
8013 compose->exteditor_pid = -1;
8014 compose->exteditor_tag = -1;
8015 compose->exteditor_socket = NULL;
8016 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN; /* inhibit auto-drafting while loading */
8018 compose->folder_update_callback_id =
8019 hooks_register_hook(FOLDER_UPDATE_HOOKLIST,
8020 compose_update_folder_hook,
8021 (gpointer) compose);
8024 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
8025 if (mode != COMPOSE_REDIRECT) {
8026 if (prefs_common.enable_aspell && prefs_common.dictionary &&
8027 strcmp(prefs_common.dictionary, "")) {
8028 gtkaspell = gtkaspell_new(prefs_common.dictionary,
8029 prefs_common.alt_dictionary,
8030 conv_get_locale_charset_str(),
8031 prefs_common.misspelled_col,
8032 prefs_common.check_while_typing,
8033 prefs_common.recheck_when_changing_dict,
8034 prefs_common.use_alternate,
8035 prefs_common.use_both_dicts,
8036 GTK_TEXT_VIEW(text),
8037 GTK_WINDOW(compose->window),
8038 compose_dict_changed,
8039 compose_spell_menu_changed,
8042 alertpanel_error(_("Spell checker could not "
8044 gtkaspell_checkers_strerror());
8045 gtkaspell_checkers_reset_error();
8047 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", TRUE);
8051 compose->gtkaspell = gtkaspell;
8052 compose_spell_menu_changed(compose);
8053 claws_spell_entry_set_gtkaspell(CLAWS_SPELL_ENTRY(subject_entry), gtkaspell);
8056 compose_select_account(compose, account, TRUE);
8058 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", prefs_common.autowrap);
8059 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", prefs_common.auto_indent);
8061 if (account->set_autocc && account->auto_cc && mode != COMPOSE_REEDIT)
8062 compose_entry_append(compose, account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8064 if (account->set_autobcc && account->auto_bcc && mode != COMPOSE_REEDIT)
8065 compose_entry_append(compose, account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8067 if (account->set_autoreplyto && account->auto_replyto && mode != COMPOSE_REEDIT)
8068 compose_entry_append(compose, account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8070 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", compose->mode == COMPOSE_REPLY);
8071 if (account->protocol != A_NNTP)
8072 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
8073 prefs_common_translated_header_name("To:"));
8075 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
8076 prefs_common_translated_header_name("Newsgroups:"));
8078 #ifndef USE_ALT_ADDRBOOK
8079 addressbook_set_target_compose(compose);
8081 if (mode != COMPOSE_REDIRECT)
8082 compose_set_template_menu(compose);
8084 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Template", FALSE);
8087 compose_list = g_list_append(compose_list, compose);
8089 if (!prefs_common.show_ruler)
8090 gtk_widget_hide(ruler_hbox);
8092 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Tools/ShowRuler", prefs_common.show_ruler);
8095 compose->priority = PRIORITY_NORMAL;
8096 compose_update_priority_menu_item(compose);
8098 compose_set_out_encoding(compose);
8101 compose_update_actions_menu(compose);
8103 /* Privacy Systems menu */
8104 compose_update_privacy_systems_menu(compose);
8106 activate_privacy_system(compose, account, TRUE);
8107 toolbar_set_style(compose->toolbar->toolbar, compose->handlebox, prefs_common.toolbar_style);
8109 gtk_widget_realize(window);
8111 gtk_widget_show(window);
8117 static GtkWidget *compose_account_option_menu_create(Compose *compose)
8122 GtkWidget *optmenubox;
8123 GtkWidget *fromlabel;
8126 GtkWidget *from_name = NULL;
8128 gint num = 0, def_menu = 0;
8130 accounts = account_get_list();
8131 cm_return_val_if_fail(accounts != NULL, NULL);
8133 optmenubox = gtk_event_box_new();
8134 optmenu = gtkut_sc_combobox_create(optmenubox, FALSE);
8135 menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
8137 hbox = gtk_hbox_new(FALSE, 4);
8138 from_name = gtk_entry_new();
8140 g_signal_connect_after(G_OBJECT(from_name), "grab_focus",
8141 G_CALLBACK(compose_grab_focus_cb), compose);
8142 g_signal_connect_after(G_OBJECT(from_name), "activate",
8143 G_CALLBACK(from_name_activate_cb), optmenu);
8145 for (; accounts != NULL; accounts = accounts->next, num++) {
8146 PrefsAccount *ac = (PrefsAccount *)accounts->data;
8147 gchar *name, *from = NULL;
8149 if (ac == compose->account) def_menu = num;
8151 name = g_markup_printf_escaped("<i>%s</i>",
8154 if (ac == compose->account) {
8155 if (ac->name && *ac->name) {
8157 QUOTE_IF_REQUIRED_NORMAL(buf, ac->name, return NULL);
8158 from = g_strdup_printf("%s <%s>",
8160 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8162 from = g_strdup_printf("%s",
8164 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8167 COMBOBOX_ADD(menu, name, ac->account_id);
8172 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), def_menu);
8174 g_signal_connect(G_OBJECT(optmenu), "changed",
8175 G_CALLBACK(account_activated),
8177 g_signal_connect(G_OBJECT(from_name), "populate-popup",
8178 G_CALLBACK(compose_entry_popup_extend),
8181 fromlabel = gtk_label_new_with_mnemonic(_("_From:"));
8182 gtk_label_set_mnemonic_widget(GTK_LABEL(fromlabel), from_name);
8184 gtk_box_pack_start(GTK_BOX(hbox), fromlabel, FALSE, FALSE, 4);
8185 gtk_box_pack_start(GTK_BOX(hbox), optmenubox, FALSE, FALSE, 0);
8186 gtk_box_pack_start(GTK_BOX(hbox), from_name, TRUE, TRUE, 0);
8188 /* Putting only the GtkEntry into focus chain of parent hbox causes
8189 * the account selector combobox next to it to be unreachable when
8190 * navigating widgets in GtkTable with up/down arrow keys.
8191 * Note: gtk_widget_set_can_focus() was not enough. */
8193 l = g_list_prepend(l, from_name);
8194 gtk_container_set_focus_chain(GTK_CONTAINER(hbox), l);
8197 CLAWS_SET_TIP(optmenubox,
8198 _("Account to use for this email"));
8199 CLAWS_SET_TIP(from_name,
8200 _("Sender address to be used"));
8202 compose->account_combo = optmenu;
8203 compose->from_name = from_name;
8208 static void compose_set_priority_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8210 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8211 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8212 Compose *compose = (Compose *) data;
8214 compose->priority = value;
8218 static void compose_reply_change_mode(Compose *compose,
8221 gboolean was_modified = compose->modified;
8223 gboolean all = FALSE, ml = FALSE, sender = FALSE, followup = FALSE;
8225 cm_return_if_fail(compose->replyinfo != NULL);
8227 if (action == COMPOSE_REPLY && prefs_common.default_reply_list)
8229 if (action == COMPOSE_REPLY && compose->rmode == COMPOSE_FOLLOWUP_AND_REPLY_TO)
8231 if (action == COMPOSE_REPLY_TO_ALL)
8233 if (action == COMPOSE_REPLY_TO_SENDER)
8235 if (action == COMPOSE_REPLY_TO_LIST)
8238 compose_remove_header_entries(compose);
8239 compose_reply_set_entry(compose, compose->replyinfo, all, ml, sender, followup);
8240 if (compose->account->set_autocc && compose->account->auto_cc)
8241 compose_entry_append(compose, compose->account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8243 if (compose->account->set_autobcc && compose->account->auto_bcc)
8244 compose_entry_append(compose, compose->account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8246 if (compose->account->set_autoreplyto && compose->account->auto_replyto)
8247 compose_entry_append(compose, compose->account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8248 compose_show_first_last_header(compose, TRUE);
8249 compose->modified = was_modified;
8250 compose_set_title(compose);
8253 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8255 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8256 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8257 Compose *compose = (Compose *) data;
8260 compose_reply_change_mode(compose, value);
8263 static void compose_update_priority_menu_item(Compose * compose)
8265 GtkWidget *menuitem = NULL;
8266 switch (compose->priority) {
8267 case PRIORITY_HIGHEST:
8268 menuitem = gtk_ui_manager_get_widget
8269 (compose->ui_manager, "/Menu/Options/Priority/Highest");
8272 menuitem = gtk_ui_manager_get_widget
8273 (compose->ui_manager, "/Menu/Options/Priority/High");
8275 case PRIORITY_NORMAL:
8276 menuitem = gtk_ui_manager_get_widget
8277 (compose->ui_manager, "/Menu/Options/Priority/Normal");
8280 menuitem = gtk_ui_manager_get_widget
8281 (compose->ui_manager, "/Menu/Options/Priority/Low");
8283 case PRIORITY_LOWEST:
8284 menuitem = gtk_ui_manager_get_widget
8285 (compose->ui_manager, "/Menu/Options/Priority/Lowest");
8288 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8291 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data)
8293 Compose *compose = (Compose *) data;
8295 gboolean can_sign = FALSE, can_encrypt = FALSE;
8297 cm_return_if_fail(GTK_IS_CHECK_MENU_ITEM(widget));
8299 if (!gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget)))
8302 systemid = g_object_get_data(G_OBJECT(widget), "privacy_system");
8303 g_free(compose->privacy_system);
8304 compose->privacy_system = NULL;
8305 g_free(compose->encdata);
8306 compose->encdata = NULL;
8307 if (systemid != NULL) {
8308 compose->privacy_system = g_strdup(systemid);
8310 can_sign = privacy_system_can_sign(systemid);
8311 can_encrypt = privacy_system_can_encrypt(systemid);
8314 debug_print("activated privacy system: %s\n", systemid != NULL ? systemid : "None");
8316 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8317 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8320 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn)
8322 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8323 GtkWidget *menuitem = NULL;
8324 GList *children, *amenu;
8325 gboolean can_sign = FALSE, can_encrypt = FALSE;
8326 gboolean found = FALSE;
8328 if (compose->privacy_system != NULL) {
8330 menuitem = gtk_menu_item_get_submenu(GTK_MENU_ITEM(
8331 gtk_ui_manager_get_widget(compose->ui_manager, branch_path)));
8332 cm_return_if_fail(menuitem != NULL);
8334 children = gtk_container_get_children(GTK_CONTAINER(GTK_MENU_SHELL(menuitem)));
8337 while (amenu != NULL) {
8338 systemid = g_object_get_data(G_OBJECT(amenu->data), "privacy_system");
8339 if (systemid != NULL) {
8340 if (strcmp(systemid, compose->privacy_system) == 0 &&
8341 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8342 menuitem = GTK_WIDGET(amenu->data);
8344 can_sign = privacy_system_can_sign(systemid);
8345 can_encrypt = privacy_system_can_encrypt(systemid);
8349 } else if (strlen(compose->privacy_system) == 0 &&
8350 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8351 menuitem = GTK_WIDGET(amenu->data);
8354 can_encrypt = FALSE;
8359 amenu = amenu->next;
8361 g_list_free(children);
8362 if (menuitem != NULL)
8363 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8365 if (warn && !found && strlen(compose->privacy_system)) {
8366 alertpanel_warning(_("The privacy system '%s' cannot be loaded. You "
8367 "will not be able to sign or encrypt this message."),
8368 compose->privacy_system);
8372 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8373 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8376 static void compose_set_out_encoding(Compose *compose)
8378 CharSet out_encoding;
8379 const gchar *branch = NULL;
8380 out_encoding = conv_get_charset_from_str(prefs_common.outgoing_charset);
8382 switch(out_encoding) {
8383 case C_AUTO: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8384 case C_US_ASCII: branch = "Menu/Options/Encoding/" CS_US_ASCII; break;
8385 case C_UTF_8: branch = "Menu/Options/Encoding/" CS_UTF_8; break;
8386 case C_ISO_8859_2: branch = "Menu/Options/Encoding/" CS_ISO_8859_2; break;
8387 case C_ISO_8859_7: branch = "Menu/Options/Encoding/" CS_ISO_8859_7; break;
8388 case C_ISO_8859_9: branch = "Menu/Options/Encoding/" CS_ISO_8859_9; break;
8389 case C_ISO_8859_1: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_1; break;
8390 case C_ISO_8859_15: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_15; break;
8391 case C_WINDOWS_1252: branch = "Menu/Options/Encoding/Western/" CS_WINDOWS_1252; break;
8392 case C_ISO_8859_13: branch = "Menu/Options/Encoding/Baltic/" CS_ISO_8859_13; break;
8393 case C_ISO_8859_4: branch = "Menu/Options/Encoding/Baltic" CS_ISO_8859_4; break;
8394 case C_ISO_8859_8: branch = "Menu/Options/Encoding/Hebrew/" CS_ISO_8859_8; break;
8395 case C_WINDOWS_1255: branch = "Menu/Options/Encoding/Hebrew/" CS_WINDOWS_1255; break;
8396 case C_ISO_8859_6: branch = "Menu/Options/Encoding/Arabic/" CS_ISO_8859_6; break;
8397 case C_WINDOWS_1256: branch = "Menu/Options/Encoding/Arabic/" CS_WINDOWS_1256; break;
8398 case C_ISO_8859_5: branch = "Menu/Options/Encoding/Cyrillic/" CS_ISO_8859_5; break;
8399 case C_KOI8_R: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_R; break;
8400 case C_MACCYR: branch = "Menu/Options/Encoding/Cyrillic/" CS_MACCYR; break;
8401 case C_KOI8_U: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_U; break;
8402 case C_WINDOWS_1251: branch = "Menu/Options/Encoding/Cyrillic/" CS_WINDOWS_1251; break;
8403 case C_ISO_2022_JP: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP; break;
8404 case C_ISO_2022_JP_2: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP_2; break;
8405 case C_EUC_JP: branch = "Menu/Options/Encoding/Japanese/" CS_EUC_JP; break;
8406 case C_SHIFT_JIS: branch = "Menu/Options/Encoding/Japanese/" CS_SHIFT_JIS; break;
8407 case C_GB18030: branch = "Menu/Options/Encoding/Chinese/" CS_GB18030; break;
8408 case C_GB2312: branch = "Menu/Options/Encoding/Chinese/" CS_GB2312; break;
8409 case C_GBK: branch = "Menu/Options/Encoding/Chinese/" CS_GBK; break;
8410 case C_BIG5: branch = "Menu/Options/Encoding/Chinese/" CS_BIG5; break;
8411 case C_EUC_TW: branch = "Menu/Options/Encoding/Chinese/" CS_EUC_TW; break;
8412 case C_EUC_KR: branch = "Menu/Options/Encoding/Korean/" CS_EUC_KR; break;
8413 case C_ISO_2022_KR: branch = "Menu/Options/Encoding/Korean/" CS_ISO_2022_KR; break;
8414 case C_TIS_620: branch = "Menu/Options/Encoding/Thai/" CS_TIS_620; break;
8415 case C_WINDOWS_874: branch = "Menu/Options/Encoding/Thai/" CS_WINDOWS_874; break;
8416 default: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8418 cm_toggle_menu_set_active_full(compose->ui_manager, (gchar *)branch, TRUE);
8421 static void compose_set_template_menu(Compose *compose)
8423 GSList *tmpl_list, *cur;
8427 tmpl_list = template_get_config();
8429 menu = gtk_menu_new();
8431 gtk_menu_set_accel_group (GTK_MENU (menu),
8432 gtk_ui_manager_get_accel_group(compose->ui_manager));
8433 for (cur = tmpl_list; cur != NULL; cur = cur->next) {
8434 Template *tmpl = (Template *)cur->data;
8435 gchar *accel_path = NULL;
8436 item = gtk_menu_item_new_with_label(tmpl->name);
8437 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
8438 g_signal_connect(G_OBJECT(item), "activate",
8439 G_CALLBACK(compose_template_activate_cb),
8441 g_object_set_data(G_OBJECT(item), "template", tmpl);
8442 gtk_widget_show(item);
8443 accel_path = g_strconcat("<ComposeTemplates>" , "/", tmpl->name, NULL);
8444 gtk_menu_item_set_accel_path(GTK_MENU_ITEM(item), accel_path);
8448 gtk_widget_show(menu);
8449 gtk_menu_item_set_submenu(GTK_MENU_ITEM(compose->tmpl_menu), menu);
8452 void compose_update_actions_menu(Compose *compose)
8454 action_update_compose_menu(compose->ui_manager, "/Menu/Tools/Actions", compose);
8457 static void compose_update_privacy_systems_menu(Compose *compose)
8459 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8460 GSList *systems, *cur;
8462 GtkWidget *system_none;
8464 GtkWidget *privacy_menuitem = gtk_ui_manager_get_widget(compose->ui_manager, branch_path);
8465 GtkWidget *privacy_menu = gtk_menu_new();
8467 system_none = gtk_radio_menu_item_new_with_mnemonic(NULL, _("_None"));
8468 g_object_set_data_full(G_OBJECT(system_none), "privacy_system", NULL, NULL);
8470 g_signal_connect(G_OBJECT(system_none), "activate",
8471 G_CALLBACK(compose_set_privacy_system_cb), compose);
8473 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), system_none);
8474 gtk_widget_show(system_none);
8476 systems = privacy_get_system_ids();
8477 for (cur = systems; cur != NULL; cur = g_slist_next(cur)) {
8478 gchar *systemid = cur->data;
8480 group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(system_none));
8481 widget = gtk_radio_menu_item_new_with_label(group,
8482 privacy_system_get_name(systemid));
8483 g_object_set_data_full(G_OBJECT(widget), "privacy_system",
8484 g_strdup(systemid), g_free);
8485 g_signal_connect(G_OBJECT(widget), "activate",
8486 G_CALLBACK(compose_set_privacy_system_cb), compose);
8488 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), widget);
8489 gtk_widget_show(widget);
8492 g_slist_free(systems);
8493 gtk_menu_item_set_submenu(GTK_MENU_ITEM(privacy_menuitem), privacy_menu);
8494 gtk_widget_show_all(privacy_menu);
8495 gtk_widget_show_all(privacy_menuitem);
8498 void compose_reflect_prefs_all(void)
8503 for (cur = compose_list; cur != NULL; cur = cur->next) {
8504 compose = (Compose *)cur->data;
8505 compose_set_template_menu(compose);
8509 void compose_reflect_prefs_pixmap_theme(void)
8514 for (cur = compose_list; cur != NULL; cur = cur->next) {
8515 compose = (Compose *)cur->data;
8516 toolbar_update(TOOLBAR_COMPOSE, compose);
8520 static const gchar *compose_quote_char_from_context(Compose *compose)
8522 const gchar *qmark = NULL;
8524 cm_return_val_if_fail(compose != NULL, NULL);
8526 switch (compose->mode) {
8527 /* use forward-specific quote char */
8528 case COMPOSE_FORWARD:
8529 case COMPOSE_FORWARD_AS_ATTACH:
8530 case COMPOSE_FORWARD_INLINE:
8531 if (compose->folder && compose->folder->prefs &&
8532 compose->folder->prefs->forward_with_format)
8533 qmark = compose->folder->prefs->forward_quotemark;
8534 else if (compose->account->forward_with_format)
8535 qmark = compose->account->forward_quotemark;
8537 qmark = prefs_common.fw_quotemark;
8540 /* use reply-specific quote char in all other modes */
8542 if (compose->folder && compose->folder->prefs &&
8543 compose->folder->prefs->reply_with_format)
8544 qmark = compose->folder->prefs->reply_quotemark;
8545 else if (compose->account->reply_with_format)
8546 qmark = compose->account->reply_quotemark;
8548 qmark = prefs_common.quotemark;
8552 if (qmark == NULL || *qmark == '\0')
8558 static void compose_template_apply(Compose *compose, Template *tmpl,
8562 GtkTextBuffer *buffer;
8566 gchar *parsed_str = NULL;
8567 gint cursor_pos = 0;
8568 const gchar *err_msg = _("The body of the template has an error at line %d.");
8571 /* process the body */
8573 text = GTK_TEXT_VIEW(compose->text);
8574 buffer = gtk_text_view_get_buffer(text);
8577 qmark = compose_quote_char_from_context(compose);
8579 if (compose->replyinfo != NULL) {
8582 gtk_text_buffer_set_text(buffer, "", -1);
8583 mark = gtk_text_buffer_get_insert(buffer);
8584 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8586 parsed_str = compose_quote_fmt(compose, compose->replyinfo,
8587 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8589 } else if (compose->fwdinfo != NULL) {
8592 gtk_text_buffer_set_text(buffer, "", -1);
8593 mark = gtk_text_buffer_get_insert(buffer);
8594 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8596 parsed_str = compose_quote_fmt(compose, compose->fwdinfo,
8597 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8600 MsgInfo* dummyinfo = compose_msginfo_new_from_compose(compose);
8602 GtkTextIter start, end;
8605 gtk_text_buffer_get_start_iter(buffer, &start);
8606 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
8607 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
8609 /* clear the buffer now */
8611 gtk_text_buffer_set_text(buffer, "", -1);
8613 parsed_str = compose_quote_fmt(compose, dummyinfo,
8614 tmpl->value, qmark, tmp, FALSE, FALSE, err_msg);
8615 procmsg_msginfo_free( &dummyinfo );
8621 gtk_text_buffer_set_text(buffer, "", -1);
8622 mark = gtk_text_buffer_get_insert(buffer);
8623 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8626 if (replace && parsed_str && compose->account->auto_sig)
8627 compose_insert_sig(compose, FALSE);
8629 if (replace && parsed_str) {
8630 gtk_text_buffer_get_start_iter(buffer, &iter);
8631 gtk_text_buffer_place_cursor(buffer, &iter);
8635 cursor_pos = quote_fmt_get_cursor_pos();
8636 compose->set_cursor_pos = cursor_pos;
8637 if (cursor_pos == -1)
8639 gtk_text_buffer_get_start_iter(buffer, &iter);
8640 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
8641 gtk_text_buffer_place_cursor(buffer, &iter);
8644 /* process the other fields */
8646 compose_template_apply_fields(compose, tmpl);
8647 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
8648 quote_fmt_reset_vartable();
8649 compose_changed_cb(NULL, compose);
8652 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
8653 gtkaspell_highlight_all(compose->gtkaspell);
8657 static void compose_template_apply_fields(Compose *compose, Template *tmpl)
8659 MsgInfo* dummyinfo = NULL;
8660 MsgInfo *msginfo = NULL;
8663 if (compose->replyinfo != NULL)
8664 msginfo = compose->replyinfo;
8665 else if (compose->fwdinfo != NULL)
8666 msginfo = compose->fwdinfo;
8668 dummyinfo = compose_msginfo_new_from_compose(compose);
8669 msginfo = dummyinfo;
8672 if (tmpl->from && *tmpl->from != '\0') {
8674 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8675 compose->gtkaspell);
8677 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8679 quote_fmt_scan_string(tmpl->from);
8682 buf = quote_fmt_get_buffer();
8684 alertpanel_error(_("Template From format error."));
8686 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
8690 if (tmpl->to && *tmpl->to != '\0') {
8692 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8693 compose->gtkaspell);
8695 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8697 quote_fmt_scan_string(tmpl->to);
8700 buf = quote_fmt_get_buffer();
8702 alertpanel_error(_("Template To format error."));
8704 compose_entry_append(compose, buf, COMPOSE_TO, PREF_TEMPLATE);
8708 if (tmpl->cc && *tmpl->cc != '\0') {
8710 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8711 compose->gtkaspell);
8713 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8715 quote_fmt_scan_string(tmpl->cc);
8718 buf = quote_fmt_get_buffer();
8720 alertpanel_error(_("Template Cc format error."));
8722 compose_entry_append(compose, buf, COMPOSE_CC, PREF_TEMPLATE);
8726 if (tmpl->bcc && *tmpl->bcc != '\0') {
8728 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8729 compose->gtkaspell);
8731 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8733 quote_fmt_scan_string(tmpl->bcc);
8736 buf = quote_fmt_get_buffer();
8738 alertpanel_error(_("Template Bcc format error."));
8740 compose_entry_append(compose, buf, COMPOSE_BCC, PREF_TEMPLATE);
8744 if (tmpl->replyto && *tmpl->replyto != '\0') {
8746 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8747 compose->gtkaspell);
8749 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8751 quote_fmt_scan_string(tmpl->replyto);
8754 buf = quote_fmt_get_buffer();
8756 alertpanel_error(_("Template Reply-To format error."));
8758 compose_entry_append(compose, buf, COMPOSE_REPLYTO, PREF_TEMPLATE);
8762 /* process the subject */
8763 if (tmpl->subject && *tmpl->subject != '\0') {
8765 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8766 compose->gtkaspell);
8768 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8770 quote_fmt_scan_string(tmpl->subject);
8773 buf = quote_fmt_get_buffer();
8775 alertpanel_error(_("Template subject format error."));
8777 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
8781 procmsg_msginfo_free( &dummyinfo );
8784 static void compose_destroy(Compose *compose)
8786 GtkAllocation allocation;
8787 GtkTextBuffer *buffer;
8788 GtkClipboard *clipboard;
8790 compose_list = g_list_remove(compose_list, compose);
8792 if (compose->updating) {
8793 debug_print("danger, not destroying anything now\n");
8794 compose->deferred_destroy = TRUE;
8798 /* NOTE: address_completion_end() does nothing with the window
8799 * however this may change. */
8800 address_completion_end(compose->window);
8802 slist_free_strings_full(compose->to_list);
8803 slist_free_strings_full(compose->newsgroup_list);
8804 slist_free_strings_full(compose->header_list);
8806 slist_free_strings_full(extra_headers);
8807 extra_headers = NULL;
8809 compose->header_list = compose->newsgroup_list = compose->to_list = NULL;
8811 g_hash_table_destroy(compose->email_hashtable);
8813 hooks_unregister_hook(FOLDER_UPDATE_HOOKLIST,
8814 compose->folder_update_callback_id);
8816 procmsg_msginfo_free(&(compose->targetinfo));
8817 procmsg_msginfo_free(&(compose->replyinfo));
8818 procmsg_msginfo_free(&(compose->fwdinfo));
8820 g_free(compose->replyto);
8821 g_free(compose->cc);
8822 g_free(compose->bcc);
8823 g_free(compose->newsgroups);
8824 g_free(compose->followup_to);
8826 g_free(compose->ml_post);
8828 g_free(compose->inreplyto);
8829 g_free(compose->references);
8830 g_free(compose->msgid);
8831 g_free(compose->boundary);
8833 g_free(compose->redirect_filename);
8834 if (compose->undostruct)
8835 undo_destroy(compose->undostruct);
8837 g_free(compose->sig_str);
8839 g_free(compose->exteditor_file);
8841 g_free(compose->orig_charset);
8843 g_free(compose->privacy_system);
8844 g_free(compose->encdata);
8846 #ifndef USE_ALT_ADDRBOOK
8847 if (addressbook_get_target_compose() == compose)
8848 addressbook_set_target_compose(NULL);
8851 if (compose->gtkaspell) {
8852 gtkaspell_delete(compose->gtkaspell);
8853 compose->gtkaspell = NULL;
8857 if (!compose->batch) {
8858 gtk_widget_get_allocation(compose->window, &allocation);
8859 prefs_common.compose_width = allocation.width;
8860 prefs_common.compose_height = allocation.height;
8863 if (!gtk_widget_get_parent(compose->paned))
8864 gtk_widget_destroy(compose->paned);
8865 gtk_widget_destroy(compose->popupmenu);
8867 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
8868 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
8869 gtk_text_buffer_remove_selection_clipboard(buffer, clipboard);
8871 gtk_widget_destroy(compose->window);
8872 toolbar_destroy(compose->toolbar);
8873 g_free(compose->toolbar);
8874 cm_mutex_free(compose->mutex);
8878 static void compose_attach_info_free(AttachInfo *ainfo)
8880 g_free(ainfo->file);
8881 g_free(ainfo->content_type);
8882 g_free(ainfo->name);
8883 g_free(ainfo->charset);
8887 static void compose_attach_update_label(Compose *compose)
8892 GtkTreeModel *model;
8897 model = gtk_tree_view_get_model(GTK_TREE_VIEW(compose->attach_clist));
8898 if(!gtk_tree_model_get_iter_first(model, &iter)) {
8899 gtk_label_set_text(GTK_LABEL(compose->attach_label), "");
8903 while(gtk_tree_model_iter_next(model, &iter))
8906 text = g_strdup_printf("(%d)", i);
8907 gtk_label_set_text(GTK_LABEL(compose->attach_label), text);
8911 static void compose_attach_remove_selected(GtkAction *action, gpointer data)
8913 Compose *compose = (Compose *)data;
8914 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
8915 GtkTreeSelection *selection;
8917 GtkTreeModel *model;
8919 selection = gtk_tree_view_get_selection(tree_view);
8920 sel = gtk_tree_selection_get_selected_rows(selection, &model);
8925 for (cur = sel; cur != NULL; cur = cur->next) {
8926 GtkTreePath *path = cur->data;
8927 GtkTreeRowReference *ref = gtk_tree_row_reference_new
8930 gtk_tree_path_free(path);
8933 for (cur = sel; cur != NULL; cur = cur->next) {
8934 GtkTreeRowReference *ref = cur->data;
8935 GtkTreePath *path = gtk_tree_row_reference_get_path(ref);
8938 if (gtk_tree_model_get_iter(model, &iter, path))
8939 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
8941 gtk_tree_path_free(path);
8942 gtk_tree_row_reference_free(ref);
8946 compose_attach_update_label(compose);
8949 static struct _AttachProperty
8952 GtkWidget *mimetype_entry;
8953 GtkWidget *encoding_optmenu;
8954 GtkWidget *path_entry;
8955 GtkWidget *filename_entry;
8957 GtkWidget *cancel_btn;
8960 static void gtk_tree_path_free_(gpointer ptr, gpointer data)
8962 gtk_tree_path_free((GtkTreePath *)ptr);
8965 static void compose_attach_property(GtkAction *action, gpointer data)
8967 Compose *compose = (Compose *)data;
8968 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
8970 GtkComboBox *optmenu;
8971 GtkTreeSelection *selection;
8973 GtkTreeModel *model;
8976 static gboolean cancelled;
8978 /* only if one selected */
8979 selection = gtk_tree_view_get_selection(tree_view);
8980 if (gtk_tree_selection_count_selected_rows(selection) != 1)
8983 sel = gtk_tree_selection_get_selected_rows(selection, &model);
8987 path = (GtkTreePath *) sel->data;
8988 gtk_tree_model_get_iter(model, &iter, path);
8989 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
8992 g_list_foreach(sel, gtk_tree_path_free_, NULL);
8998 if (!attach_prop.window)
8999 compose_attach_property_create(&cancelled);
9000 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), TRUE);
9001 gtk_widget_grab_focus(attach_prop.ok_btn);
9002 gtk_widget_show(attach_prop.window);
9003 gtk_window_set_transient_for(GTK_WINDOW(attach_prop.window),
9004 GTK_WINDOW(compose->window));
9006 optmenu = GTK_COMBO_BOX(attach_prop.encoding_optmenu);
9007 if (ainfo->encoding == ENC_UNKNOWN)
9008 combobox_select_by_data(optmenu, ENC_BASE64);
9010 combobox_select_by_data(optmenu, ainfo->encoding);
9012 gtk_entry_set_text(GTK_ENTRY(attach_prop.mimetype_entry),
9013 ainfo->content_type ? ainfo->content_type : "");
9014 gtk_entry_set_text(GTK_ENTRY(attach_prop.path_entry),
9015 ainfo->file ? ainfo->file : "");
9016 gtk_entry_set_text(GTK_ENTRY(attach_prop.filename_entry),
9017 ainfo->name ? ainfo->name : "");
9020 const gchar *entry_text;
9022 gchar *cnttype = NULL;
9029 gtk_widget_hide(attach_prop.window);
9030 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), FALSE);
9035 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.mimetype_entry));
9036 if (*entry_text != '\0') {
9039 text = g_strstrip(g_strdup(entry_text));
9040 if ((p = strchr(text, '/')) && !strchr(p + 1, '/')) {
9041 cnttype = g_strdup(text);
9044 alertpanel_error(_("Invalid MIME type."));
9050 ainfo->encoding = combobox_get_active_data(optmenu);
9052 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.path_entry));
9053 if (*entry_text != '\0') {
9054 if (is_file_exist(entry_text) &&
9055 (size = get_file_size(entry_text)) > 0)
9056 file = g_strdup(entry_text);
9059 (_("File doesn't exist or is empty."));
9065 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.filename_entry));
9066 if (*entry_text != '\0') {
9067 g_free(ainfo->name);
9068 ainfo->name = g_strdup(entry_text);
9072 g_free(ainfo->content_type);
9073 ainfo->content_type = cnttype;
9076 g_free(ainfo->file);
9080 ainfo->size = (goffset)size;
9082 /* update tree store */
9083 text = to_human_readable(ainfo->size);
9084 gtk_tree_model_get_iter(model, &iter, path);
9085 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
9086 COL_MIMETYPE, ainfo->content_type,
9088 COL_NAME, ainfo->name,
9089 COL_CHARSET, ainfo->charset,
9095 gtk_tree_path_free(path);
9098 #define SET_LABEL_AND_ENTRY(str, entry, top) \
9100 label = gtk_label_new(str); \
9101 gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), \
9102 GTK_FILL, 0, 0, 0); \
9103 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); \
9105 entry = gtk_entry_new(); \
9106 gtk_table_attach(GTK_TABLE(table), entry, 1, 2, top, (top + 1), \
9107 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); \
9110 static void compose_attach_property_create(gboolean *cancelled)
9116 GtkWidget *mimetype_entry;
9119 GtkListStore *optmenu_menu;
9120 GtkWidget *path_entry;
9121 GtkWidget *filename_entry;
9124 GtkWidget *cancel_btn;
9125 GList *mime_type_list, *strlist;
9128 debug_print("Creating attach_property window...\n");
9130 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose_attach_property");
9131 gtk_widget_set_size_request(window, 480, -1);
9132 gtk_container_set_border_width(GTK_CONTAINER(window), 8);
9133 gtk_window_set_title(GTK_WINDOW(window), _("Properties"));
9134 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
9135 g_signal_connect(G_OBJECT(window), "delete_event",
9136 G_CALLBACK(attach_property_delete_event),
9138 g_signal_connect(G_OBJECT(window), "key_press_event",
9139 G_CALLBACK(attach_property_key_pressed),
9142 vbox = gtk_vbox_new(FALSE, 8);
9143 gtk_container_add(GTK_CONTAINER(window), vbox);
9145 table = gtk_table_new(4, 2, FALSE);
9146 gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
9147 gtk_table_set_row_spacings(GTK_TABLE(table), 8);
9148 gtk_table_set_col_spacings(GTK_TABLE(table), 8);
9150 label = gtk_label_new(_("MIME type"));
9151 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, (0 + 1),
9153 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
9154 #if !GTK_CHECK_VERSION(2, 24, 0)
9155 mimetype_entry = gtk_combo_box_entry_new_text();
9157 mimetype_entry = gtk_combo_box_text_new_with_entry();
9159 gtk_table_attach(GTK_TABLE(table), mimetype_entry, 1, 2, 0, (0 + 1),
9160 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
9162 /* stuff with list */
9163 mime_type_list = procmime_get_mime_type_list();
9165 for (; mime_type_list != NULL; mime_type_list = mime_type_list->next) {
9166 MimeType *type = (MimeType *) mime_type_list->data;
9169 tmp = g_strdup_printf("%s/%s", type->type, type->sub_type);
9171 if (g_list_find_custom(strlist, tmp, (GCompareFunc)strcmp2))
9174 strlist = g_list_insert_sorted(strlist, (gpointer)tmp,
9175 (GCompareFunc)strcmp2);
9178 for (mime_type_list = strlist; mime_type_list != NULL;
9179 mime_type_list = mime_type_list->next) {
9180 #if !GTK_CHECK_VERSION(2, 24, 0)
9181 gtk_combo_box_append_text(GTK_COMBO_BOX(mimetype_entry), mime_type_list->data);
9183 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(mimetype_entry), mime_type_list->data);
9185 g_free(mime_type_list->data);
9187 g_list_free(strlist);
9188 gtk_combo_box_set_active(GTK_COMBO_BOX(mimetype_entry), 0);
9189 mimetype_entry = gtk_bin_get_child(GTK_BIN((mimetype_entry)));
9191 label = gtk_label_new(_("Encoding"));
9192 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2,
9194 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
9196 hbox = gtk_hbox_new(FALSE, 0);
9197 gtk_table_attach(GTK_TABLE(table), hbox, 1, 2, 1, 2,
9198 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
9200 optmenu = gtkut_sc_combobox_create(NULL, TRUE);
9201 optmenu_menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
9203 COMBOBOX_ADD(optmenu_menu, "7bit", ENC_7BIT);
9204 COMBOBOX_ADD(optmenu_menu, "8bit", ENC_8BIT);
9205 COMBOBOX_ADD(optmenu_menu, "quoted-printable", ENC_QUOTED_PRINTABLE);
9206 COMBOBOX_ADD(optmenu_menu, "base64", ENC_BASE64);
9207 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), 0);
9209 gtk_box_pack_start(GTK_BOX(hbox), optmenu, TRUE, TRUE, 0);
9211 SET_LABEL_AND_ENTRY(_("Path"), path_entry, 2);
9212 SET_LABEL_AND_ENTRY(_("File name"), filename_entry, 3);
9214 gtkut_stock_button_set_create(&hbbox, &cancel_btn, GTK_STOCK_CANCEL,
9215 &ok_btn, GTK_STOCK_OK,
9217 gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
9218 gtk_widget_grab_default(ok_btn);
9220 g_signal_connect(G_OBJECT(ok_btn), "clicked",
9221 G_CALLBACK(attach_property_ok),
9223 g_signal_connect(G_OBJECT(cancel_btn), "clicked",
9224 G_CALLBACK(attach_property_cancel),
9227 gtk_widget_show_all(vbox);
9229 attach_prop.window = window;
9230 attach_prop.mimetype_entry = mimetype_entry;
9231 attach_prop.encoding_optmenu = optmenu;
9232 attach_prop.path_entry = path_entry;
9233 attach_prop.filename_entry = filename_entry;
9234 attach_prop.ok_btn = ok_btn;
9235 attach_prop.cancel_btn = cancel_btn;
9238 #undef SET_LABEL_AND_ENTRY
9240 static void attach_property_ok(GtkWidget *widget, gboolean *cancelled)
9246 static void attach_property_cancel(GtkWidget *widget, gboolean *cancelled)
9252 static gint attach_property_delete_event(GtkWidget *widget, GdkEventAny *event,
9253 gboolean *cancelled)
9261 static gboolean attach_property_key_pressed(GtkWidget *widget,
9263 gboolean *cancelled)
9265 if (event && event->keyval == GDK_KEY_Escape) {
9269 if (event && event->keyval == GDK_KEY_Return) {
9277 static void compose_exec_ext_editor(Compose *compose)
9282 GdkNativeWindow socket_wid = 0;
9286 tmp = g_strdup_printf("%s%ctmpmsg.%p", get_tmp_dir(),
9287 G_DIR_SEPARATOR, compose);
9289 if (compose_get_ext_editor_uses_socket()) {
9290 /* Only allow one socket */
9291 if (compose->exteditor_socket != NULL) {
9292 if (gtk_widget_is_focus(compose->exteditor_socket)) {
9293 /* Move the focus off of the socket */
9294 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9299 /* Create the receiving GtkSocket */
9300 socket = gtk_socket_new ();
9301 g_signal_connect (GTK_OBJECT(socket), "plug-removed",
9302 G_CALLBACK(compose_ext_editor_plug_removed_cb),
9304 gtk_box_pack_start(GTK_BOX(compose->edit_vbox), socket, TRUE, TRUE, 0);
9305 gtk_widget_set_size_request(socket, prefs_common.compose_width, -1);
9306 /* Realize the socket so that we can use its ID */
9307 gtk_widget_realize(socket);
9308 socket_wid = gtk_socket_get_id(GTK_SOCKET (socket));
9309 compose->exteditor_socket = socket;
9312 if (pipe(pipe_fds) < 0) {
9318 if ((pid = fork()) < 0) {
9325 /* close the write side of the pipe */
9328 compose->exteditor_file = g_strdup(tmp);
9329 compose->exteditor_pid = pid;
9331 compose_set_ext_editor_sensitive(compose, FALSE);
9334 compose->exteditor_ch = g_io_channel_unix_new(pipe_fds[0]);
9336 compose->exteditor_ch = g_io_channel_win32_new_fd(pipe_fds[0]);
9338 compose->exteditor_tag = g_io_add_watch(compose->exteditor_ch,
9342 } else { /* process-monitoring process */
9348 /* close the read side of the pipe */
9351 if (compose_write_body_to_file(compose, tmp) < 0) {
9352 fd_write_all(pipe_fds[1], "2\n", 2);
9356 pid_ed = compose_exec_ext_editor_real(tmp, socket_wid);
9358 fd_write_all(pipe_fds[1], "1\n", 2);
9362 /* wait until editor is terminated */
9363 waitpid(pid_ed, NULL, 0);
9365 fd_write_all(pipe_fds[1], "0\n", 2);
9372 #endif /* G_OS_UNIX */
9376 static gboolean compose_get_ext_editor_cmd_valid()
9378 gboolean has_s = FALSE;
9379 gboolean has_w = FALSE;
9380 const gchar *p = prefs_common_get_ext_editor_cmd();
9383 while ((p = strchr(p, '%'))) {
9389 } else if (*p == 'w') {
9400 static gint compose_exec_ext_editor_real(const gchar *file, GdkNativeWindow socket_wid)
9407 cm_return_val_if_fail(file != NULL, -1);
9409 if ((pid = fork()) < 0) {
9414 if (pid != 0) return pid;
9416 /* grandchild process */
9418 if (setpgid(0, getppid()))
9421 if (compose_get_ext_editor_cmd_valid()) {
9422 if (compose_get_ext_editor_uses_socket()) {
9423 p = g_strdup(prefs_common_get_ext_editor_cmd());
9424 s = strstr(p, "%w");
9426 if (strstr(p, "%s") < s)
9427 g_snprintf(buf, sizeof(buf), p, file, socket_wid);
9429 g_snprintf(buf, sizeof(buf), p, socket_wid, file);
9432 g_snprintf(buf, sizeof(buf),
9433 prefs_common_get_ext_editor_cmd(), file);
9436 if (prefs_common_get_ext_editor_cmd())
9437 g_warning("External editor command-line is invalid: '%s'",
9438 prefs_common_get_ext_editor_cmd());
9439 g_snprintf(buf, sizeof(buf), DEFAULT_EDITOR_CMD, file);
9442 cmdline = strsplit_with_quote(buf, " ", 1024);
9443 execvp(cmdline[0], cmdline);
9446 g_strfreev(cmdline);
9451 static gboolean compose_ext_editor_kill(Compose *compose)
9453 pid_t pgid = compose->exteditor_pid * -1;
9456 ret = kill(pgid, 0);
9458 if (ret == 0 || (ret == -1 && EPERM == errno)) {
9462 msg = g_strdup_printf
9463 (_("The external editor is still working.\n"
9464 "Force terminating the process?\n"
9465 "process group id: %d"), -pgid);
9466 val = alertpanel_full(_("Notice"), msg, GTK_STOCK_NO, GTK_STOCK_YES,
9467 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
9471 if (val == G_ALERTALTERNATE) {
9472 g_source_remove(compose->exteditor_tag);
9473 g_io_channel_shutdown(compose->exteditor_ch,
9475 g_io_channel_unref(compose->exteditor_ch);
9477 if (kill(pgid, SIGTERM) < 0) perror("kill");
9478 waitpid(compose->exteditor_pid, NULL, 0);
9480 g_warning("Terminated process group id: %d. "
9481 "Temporary file: %s", -pgid, compose->exteditor_file);
9483 compose_set_ext_editor_sensitive(compose, TRUE);
9485 g_free(compose->exteditor_file);
9486 compose->exteditor_file = NULL;
9487 compose->exteditor_pid = -1;
9488 compose->exteditor_ch = NULL;
9489 compose->exteditor_tag = -1;
9497 static gboolean compose_input_cb(GIOChannel *source, GIOCondition condition,
9501 Compose *compose = (Compose *)data;
9504 debug_print("Compose: input from monitoring process\n");
9506 if (g_io_channel_read_chars(source, buf, sizeof(buf), &bytes_read, NULL) != G_IO_STATUS_NORMAL) {
9511 g_io_channel_shutdown(source, FALSE, NULL);
9512 g_io_channel_unref(source);
9514 waitpid(compose->exteditor_pid, NULL, 0);
9516 if (buf[0] == '0') { /* success */
9517 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9518 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
9519 GtkTextIter start, end;
9522 gtk_text_buffer_set_text(buffer, "", -1);
9523 compose_insert_file(compose, compose->exteditor_file);
9524 compose_changed_cb(NULL, compose);
9525 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
9527 if (claws_unlink(compose->exteditor_file) < 0)
9528 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9530 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
9531 gtk_text_buffer_get_start_iter(buffer, &start);
9532 gtk_text_buffer_get_end_iter(buffer, &end);
9533 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
9534 if (chars && strlen(chars) > 0)
9535 compose->modified = TRUE;
9537 } else if (buf[0] == '1') { /* failed */
9538 g_warning("Couldn't exec external editor");
9539 if (claws_unlink(compose->exteditor_file) < 0)
9540 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9541 } else if (buf[0] == '2') {
9542 g_warning("Couldn't write to file");
9543 } else if (buf[0] == '3') {
9544 g_warning("Pipe read failed");
9547 compose_set_ext_editor_sensitive(compose, TRUE);
9549 g_free(compose->exteditor_file);
9550 compose->exteditor_file = NULL;
9551 compose->exteditor_pid = -1;
9552 compose->exteditor_ch = NULL;
9553 compose->exteditor_tag = -1;
9554 if (compose->exteditor_socket) {
9555 gtk_widget_destroy(compose->exteditor_socket);
9556 compose->exteditor_socket = NULL;
9563 static char *ext_editor_menu_entries[] = {
9564 "Menu/Message/Send",
9565 "Menu/Message/SendLater",
9566 "Menu/Message/InsertFile",
9567 "Menu/Message/InsertSig",
9568 "Menu/Message/ReplaceSig",
9569 "Menu/Message/Save",
9570 "Menu/Message/Print",
9575 "Menu/Tools/ShowRuler",
9576 "Menu/Tools/Actions",
9581 static void compose_set_ext_editor_sensitive(Compose *compose,
9586 for (i = 0; ext_editor_menu_entries[i]; ++i) {
9587 cm_menu_set_sensitive_full(compose->ui_manager,
9588 ext_editor_menu_entries[i], sensitive);
9591 if (compose_get_ext_editor_uses_socket()) {
9593 if (compose->exteditor_socket)
9594 gtk_widget_hide(compose->exteditor_socket);
9595 gtk_widget_show(compose->scrolledwin);
9596 if (prefs_common.show_ruler)
9597 gtk_widget_show(compose->ruler_hbox);
9598 /* Fix the focus, as it doesn't go anywhere when the
9599 * socket is hidden or destroyed */
9600 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9602 g_assert (compose->exteditor_socket != NULL);
9603 /* Fix the focus, as it doesn't go anywhere when the
9604 * edit box is hidden */
9605 if (gtk_widget_is_focus(compose->text))
9606 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9607 gtk_widget_hide(compose->scrolledwin);
9608 gtk_widget_hide(compose->ruler_hbox);
9609 gtk_widget_show(compose->exteditor_socket);
9612 gtk_widget_set_sensitive(compose->text, sensitive);
9614 if (compose->toolbar->send_btn)
9615 gtk_widget_set_sensitive(compose->toolbar->send_btn, sensitive);
9616 if (compose->toolbar->sendl_btn)
9617 gtk_widget_set_sensitive(compose->toolbar->sendl_btn, sensitive);
9618 if (compose->toolbar->draft_btn)
9619 gtk_widget_set_sensitive(compose->toolbar->draft_btn, sensitive);
9620 if (compose->toolbar->insert_btn)
9621 gtk_widget_set_sensitive(compose->toolbar->insert_btn, sensitive);
9622 if (compose->toolbar->sig_btn)
9623 gtk_widget_set_sensitive(compose->toolbar->sig_btn, sensitive);
9624 if (compose->toolbar->exteditor_btn)
9625 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, sensitive);
9626 if (compose->toolbar->linewrap_current_btn)
9627 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, sensitive);
9628 if (compose->toolbar->linewrap_all_btn)
9629 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, sensitive);
9632 static gboolean compose_get_ext_editor_uses_socket()
9634 return (prefs_common_get_ext_editor_cmd() &&
9635 strstr(prefs_common_get_ext_editor_cmd(), "%w"));
9638 static gboolean compose_ext_editor_plug_removed_cb(GtkSocket *socket, Compose *compose)
9640 compose->exteditor_socket = NULL;
9641 /* returning FALSE allows destruction of the socket */
9644 #endif /* G_OS_UNIX */
9647 * compose_undo_state_changed:
9649 * Change the sensivity of the menuentries undo and redo
9651 static void compose_undo_state_changed(UndoMain *undostruct, gint undo_state,
9652 gint redo_state, gpointer data)
9654 Compose *compose = (Compose *)data;
9656 switch (undo_state) {
9657 case UNDO_STATE_TRUE:
9658 if (!undostruct->undo_state) {
9659 undostruct->undo_state = TRUE;
9660 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", TRUE);
9663 case UNDO_STATE_FALSE:
9664 if (undostruct->undo_state) {
9665 undostruct->undo_state = FALSE;
9666 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
9669 case UNDO_STATE_UNCHANGED:
9671 case UNDO_STATE_REFRESH:
9672 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", undostruct->undo_state);
9675 g_warning("Undo state not recognized");
9679 switch (redo_state) {
9680 case UNDO_STATE_TRUE:
9681 if (!undostruct->redo_state) {
9682 undostruct->redo_state = TRUE;
9683 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", TRUE);
9686 case UNDO_STATE_FALSE:
9687 if (undostruct->redo_state) {
9688 undostruct->redo_state = FALSE;
9689 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
9692 case UNDO_STATE_UNCHANGED:
9694 case UNDO_STATE_REFRESH:
9695 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", undostruct->redo_state);
9698 g_warning("Redo state not recognized");
9703 /* callback functions */
9705 static void compose_notebook_size_alloc(GtkNotebook *notebook,
9706 GtkAllocation *allocation,
9709 prefs_common.compose_notebook_height = gtk_paned_get_position(paned);
9712 /* compose_edit_size_alloc() - called when resized. don't know whether Gtk
9713 * includes "non-client" (windows-izm) in calculation, so this calculation
9714 * may not be accurate.
9716 static gboolean compose_edit_size_alloc(GtkEditable *widget,
9717 GtkAllocation *allocation,
9718 GtkSHRuler *shruler)
9720 if (prefs_common.show_ruler) {
9721 gint char_width = 0, char_height = 0;
9722 gint line_width_in_chars;
9724 gtkut_get_font_size(GTK_WIDGET(widget),
9725 &char_width, &char_height);
9726 line_width_in_chars =
9727 (allocation->width - allocation->x) / char_width;
9729 /* got the maximum */
9730 gtk_shruler_set_range(GTK_SHRULER(shruler),
9731 0.0, line_width_in_chars, 0);
9740 ComposePrefType type;
9741 gboolean entry_marked;
9744 static void account_activated(GtkComboBox *optmenu, gpointer data)
9746 Compose *compose = (Compose *)data;
9749 gchar *folderidentifier;
9750 gint account_id = 0;
9753 GSList *list, *saved_list = NULL;
9754 HeaderEntryState *state;
9755 GtkRcStyle *style = NULL;
9756 #if !GTK_CHECK_VERSION(3, 0, 0)
9757 static GdkColor yellow;
9758 static gboolean color_set = FALSE;
9760 static GdkColor yellow = { (guint32)0, (guint32)0xf5, (guint32)0xf6, (guint32)0xbe };
9763 /* Get ID of active account in the combo box */
9764 menu = gtk_combo_box_get_model(optmenu);
9765 cm_return_if_fail(gtk_combo_box_get_active_iter(optmenu, &iter));
9766 gtk_tree_model_get(menu, &iter, 1, &account_id, -1);
9768 ac = account_find_from_id(account_id);
9769 cm_return_if_fail(ac != NULL);
9771 if (ac != compose->account) {
9772 compose_select_account(compose, ac, FALSE);
9774 for (list = compose->header_list; list; list = list->next) {
9775 ComposeHeaderEntry *hentry=(ComposeHeaderEntry *)list->data;
9777 if (hentry->type == PREF_ACCOUNT || !list->next) {
9778 compose_destroy_headerentry(compose, hentry);
9782 state = g_malloc0(sizeof(HeaderEntryState));
9783 state->header = gtk_editable_get_chars(GTK_EDITABLE(
9784 gtk_bin_get_child(GTK_BIN(hentry->combo))), 0, -1);
9785 state->entry = gtk_editable_get_chars(
9786 GTK_EDITABLE(hentry->entry), 0, -1);
9787 state->type = hentry->type;
9789 #if !GTK_CHECK_VERSION(3, 0, 0)
9791 gdk_color_parse("#f5f6be", &yellow);
9792 color_set = gdk_colormap_alloc_color(
9793 gdk_colormap_get_system(),
9794 &yellow, FALSE, TRUE);
9798 style = gtk_widget_get_modifier_style(hentry->entry);
9799 state->entry_marked = gdk_color_equal(&yellow,
9800 &style->base[GTK_STATE_NORMAL]);
9802 saved_list = g_slist_append(saved_list, state);
9803 compose_destroy_headerentry(compose, hentry);
9806 compose->header_last = NULL;
9807 g_slist_free(compose->header_list);
9808 compose->header_list = NULL;
9809 compose->header_nextrow = 1;
9810 compose_create_header_entry(compose);
9812 if (ac->set_autocc && ac->auto_cc)
9813 compose_entry_append(compose, ac->auto_cc,
9814 COMPOSE_CC, PREF_ACCOUNT);
9816 if (ac->set_autobcc && ac->auto_bcc)
9817 compose_entry_append(compose, ac->auto_bcc,
9818 COMPOSE_BCC, PREF_ACCOUNT);
9820 if (ac->set_autoreplyto && ac->auto_replyto)
9821 compose_entry_append(compose, ac->auto_replyto,
9822 COMPOSE_REPLYTO, PREF_ACCOUNT);
9824 for (list = saved_list; list; list = list->next) {
9825 state = (HeaderEntryState *) list->data;
9827 compose_add_header_entry(compose, state->header,
9828 state->entry, state->type);
9829 if (state->entry_marked)
9830 compose_entry_mark_default_to(compose, state->entry);
9832 g_free(state->header);
9833 g_free(state->entry);
9836 g_slist_free(saved_list);
9838 combobox_select_by_data(GTK_COMBO_BOX(compose->header_last->combo),
9839 (ac->protocol == A_NNTP) ?
9840 COMPOSE_NEWSGROUPS : COMPOSE_TO);
9843 /* Set message save folder */
9844 if (account_get_special_folder(compose->account, F_OUTBOX)) {
9845 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
9847 g_signal_connect(G_OBJECT(compose->savemsg_checkbtn), "toggled",
9848 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
9850 compose_set_save_to(compose, NULL);
9851 if (account_get_special_folder(compose->account, F_OUTBOX)) {
9852 folderidentifier = folder_item_get_identifier(account_get_special_folder
9853 (compose->account, F_OUTBOX));
9854 compose_set_save_to(compose, folderidentifier);
9855 g_free(folderidentifier);
9859 static void attach_selected(GtkTreeView *tree_view, GtkTreePath *tree_path,
9860 GtkTreeViewColumn *column, Compose *compose)
9862 compose_attach_property(NULL, compose);
9865 static gboolean attach_button_pressed(GtkWidget *widget, GdkEventButton *event,
9868 Compose *compose = (Compose *)data;
9869 GtkTreeSelection *attach_selection;
9870 gint attach_nr_selected;
9873 if (!event) return FALSE;
9875 if (event->button == 3) {
9876 attach_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
9877 attach_nr_selected = gtk_tree_selection_count_selected_rows(attach_selection);
9879 /* If no rows, or just one row is selected, right-click should
9880 * open menu relevant to the row being right-clicked on. We
9881 * achieve that by selecting the clicked row first. If more
9882 * than one row is selected, we shouldn't modify the selection,
9883 * as user may want to remove selected rows (attachments). */
9884 if (attach_nr_selected < 2) {
9885 gtk_tree_selection_unselect_all(attach_selection);
9886 attach_nr_selected = 0;
9887 gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget),
9888 event->x, event->y, &path, NULL, NULL, NULL);
9890 gtk_tree_selection_select_path(attach_selection, path);
9891 gtk_tree_path_free(path);
9892 attach_nr_selected++;
9896 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", (attach_nr_selected > 0));
9897 /* Properties menu item makes no sense with more than one row
9898 * selected, the properties dialog can only edit one attachment. */
9899 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", (attach_nr_selected == 1));
9901 gtk_menu_popup(GTK_MENU(compose->popupmenu), NULL, NULL,
9902 NULL, NULL, event->button, event->time);
9909 static gboolean attach_key_pressed(GtkWidget *widget, GdkEventKey *event,
9912 Compose *compose = (Compose *)data;
9914 if (!event) return FALSE;
9916 switch (event->keyval) {
9917 case GDK_KEY_Delete:
9918 compose_attach_remove_selected(NULL, compose);
9924 static void compose_allow_user_actions (Compose *compose, gboolean allow)
9926 toolbar_comp_set_sensitive(compose, allow);
9927 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message", allow);
9928 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", allow);
9930 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", allow);
9932 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", allow);
9933 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools", allow);
9934 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Help", allow);
9936 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), allow);
9940 static void compose_send_cb(GtkAction *action, gpointer data)
9942 Compose *compose = (Compose *)data;
9945 if (compose->exteditor_tag != -1) {
9946 debug_print("ignoring send: external editor still open\n");
9950 if (prefs_common.work_offline &&
9951 !inc_offline_should_override(TRUE,
9952 _("Claws Mail needs network access in order "
9953 "to send this email.")))
9956 if (compose->draft_timeout_tag >= 0) { /* CLAWS: disable draft timeout */
9957 g_source_remove(compose->draft_timeout_tag);
9958 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
9961 compose_send(compose);
9964 static void compose_send_later_cb(GtkAction *action, gpointer data)
9966 Compose *compose = (Compose *)data;
9970 compose_allow_user_actions(compose, FALSE);
9971 val = compose_queue_sub(compose, NULL, NULL, NULL, TRUE, TRUE);
9972 compose_allow_user_actions(compose, TRUE);
9976 compose_close(compose);
9977 } else if (val == -1) {
9978 alertpanel_error(_("Could not queue message."));
9979 } else if (val == -2) {
9980 alertpanel_error(_("Could not queue message:\n\n%s."), g_strerror(errno));
9981 } else if (val == -3) {
9982 if (privacy_peek_error())
9983 alertpanel_error(_("Could not queue message for sending:\n\n"
9984 "Signature failed: %s"), privacy_get_error());
9985 } else if (val == -4) {
9986 alertpanel_error(_("Could not queue message for sending:\n\n"
9987 "Charset conversion failed."));
9988 } else if (val == -5) {
9989 alertpanel_error(_("Could not queue message for sending:\n\n"
9990 "Couldn't get recipient encryption key."));
9991 } else if (val == -6) {
9994 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
9997 #define DRAFTED_AT_EXIT "drafted_at_exit"
9998 static void compose_register_draft(MsgInfo *info)
10000 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10001 DRAFTED_AT_EXIT, NULL);
10002 FILE *fp = g_fopen(filepath, "ab");
10005 fprintf(fp, "%s\t%d\n", folder_item_get_identifier(info->folder),
10013 gboolean compose_draft (gpointer data, guint action)
10015 Compose *compose = (Compose *)data;
10020 MsgFlags flag = {0, 0};
10021 static gboolean lock = FALSE;
10022 MsgInfo *newmsginfo;
10024 gboolean target_locked = FALSE;
10025 gboolean err = FALSE;
10027 if (lock) return FALSE;
10029 if (compose->sending)
10032 draft = account_get_special_folder(compose->account, F_DRAFT);
10033 cm_return_val_if_fail(draft != NULL, FALSE);
10035 if (!g_mutex_trylock(compose->mutex)) {
10036 /* we don't want to lock the mutex once it's available,
10037 * because as the only other part of compose.c locking
10038 * it is compose_close - which means once unlocked,
10039 * the compose struct will be freed */
10040 debug_print("couldn't lock mutex, probably sending\n");
10046 tmp = g_strdup_printf("%s%cdraft.%p", get_tmp_dir(),
10047 G_DIR_SEPARATOR, compose);
10048 if ((fp = g_fopen(tmp, "wb")) == NULL) {
10049 FILE_OP_ERROR(tmp, "fopen");
10053 /* chmod for security */
10054 if (change_file_mode_rw(fp, tmp) < 0) {
10055 FILE_OP_ERROR(tmp, "chmod");
10056 g_warning("can't change file mode");
10059 /* Save draft infos */
10060 err |= (fprintf(fp, "X-Claws-Account-Id:%d\n", compose->account->account_id) < 0);
10061 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
10063 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
10064 gchar *savefolderid;
10066 savefolderid = compose_get_save_to(compose);
10067 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
10068 g_free(savefolderid);
10070 if (compose->return_receipt) {
10071 err |= (fprintf(fp, "RRCPT:1\n") < 0);
10073 if (compose->privacy_system) {
10074 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
10075 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
10076 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
10079 /* Message-ID of message replying to */
10080 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
10081 gchar *folderid = NULL;
10083 if (compose->replyinfo->folder)
10084 folderid = folder_item_get_identifier(compose->replyinfo->folder);
10085 if (folderid == NULL)
10086 folderid = g_strdup("NULL");
10088 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
10091 /* Message-ID of message forwarding to */
10092 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
10093 gchar *folderid = NULL;
10095 if (compose->fwdinfo->folder)
10096 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
10097 if (folderid == NULL)
10098 folderid = g_strdup("NULL");
10100 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
10104 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
10105 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
10107 sheaders = compose_get_manual_headers_info(compose);
10108 err |= (fprintf(fp, "X-Claws-Manual-Headers:%s\n", sheaders) < 0);
10111 /* end of headers */
10112 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
10119 if (compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_STORE, action != COMPOSE_AUTO_SAVE) < 0) {
10123 if (fclose(fp) == EOF) {
10127 flag.perm_flags = MSG_NEW|MSG_UNREAD;
10128 if (compose->targetinfo) {
10129 target_locked = MSG_IS_LOCKED(compose->targetinfo->flags);
10131 flag.perm_flags |= MSG_LOCKED;
10133 flag.tmp_flags = MSG_DRAFT;
10135 folder_item_scan(draft);
10136 if ((msgnum = folder_item_add_msg(draft, tmp, &flag, TRUE)) < 0) {
10137 MsgInfo *tmpinfo = NULL;
10138 debug_print("didn't get msgnum after adding draft [%s]\n", compose->msgid?compose->msgid:"no msgid");
10139 if (compose->msgid) {
10140 tmpinfo = folder_item_get_msginfo_by_msgid(draft, compose->msgid);
10143 msgnum = tmpinfo->msgnum;
10144 procmsg_msginfo_free(&tmpinfo);
10145 debug_print("got draft msgnum %d from scanning\n", msgnum);
10147 debug_print("didn't get draft msgnum after scanning\n");
10150 debug_print("got draft msgnum %d from adding\n", msgnum);
10156 if (action != COMPOSE_AUTO_SAVE) {
10157 if (action != COMPOSE_DRAFT_FOR_EXIT)
10158 alertpanel_error(_("Could not save draft."));
10161 gtkut_window_popup(compose->window);
10162 val = alertpanel_full(_("Could not save draft"),
10163 _("Could not save draft.\n"
10164 "Do you want to cancel exit or discard this email?"),
10165 _("_Cancel exit"), _("_Discard email"), NULL,
10166 FALSE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
10167 if (val == G_ALERTALTERNATE) {
10169 g_mutex_unlock(compose->mutex); /* must be done before closing */
10170 compose_close(compose);
10174 g_mutex_unlock(compose->mutex); /* must be done before closing */
10183 if (compose->mode == COMPOSE_REEDIT) {
10184 compose_remove_reedit_target(compose, TRUE);
10187 newmsginfo = folder_item_get_msginfo(draft, msgnum);
10190 procmsg_msginfo_unset_flags(newmsginfo, ~0, ~0);
10192 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD|MSG_LOCKED, MSG_DRAFT);
10194 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD, MSG_DRAFT);
10195 if (compose_use_attach(compose) && action != COMPOSE_AUTO_SAVE)
10196 procmsg_msginfo_set_flags(newmsginfo, 0,
10197 MSG_HAS_ATTACHMENT);
10199 if (action == COMPOSE_DRAFT_FOR_EXIT) {
10200 compose_register_draft(newmsginfo);
10202 procmsg_msginfo_free(&newmsginfo);
10205 folder_item_scan(draft);
10207 if (action == COMPOSE_QUIT_EDITING || action == COMPOSE_DRAFT_FOR_EXIT) {
10209 g_mutex_unlock(compose->mutex); /* must be done before closing */
10210 compose_close(compose);
10216 path = folder_item_fetch_msg(draft, msgnum);
10217 if (path == NULL) {
10218 debug_print("can't fetch %s:%d\n", draft->path, msgnum);
10221 if (g_stat(path, &s) < 0) {
10222 FILE_OP_ERROR(path, "stat");
10228 procmsg_msginfo_free(&(compose->targetinfo));
10229 compose->targetinfo = procmsg_msginfo_new();
10230 compose->targetinfo->msgnum = msgnum;
10231 compose->targetinfo->size = (goffset)s.st_size;
10232 compose->targetinfo->mtime = s.st_mtime;
10233 compose->targetinfo->folder = draft;
10235 procmsg_msginfo_set_flags(compose->targetinfo, MSG_LOCKED, 0);
10236 compose->mode = COMPOSE_REEDIT;
10238 if (action == COMPOSE_AUTO_SAVE) {
10239 compose->autosaved_draft = compose->targetinfo;
10241 compose->modified = FALSE;
10242 compose_set_title(compose);
10246 g_mutex_unlock(compose->mutex);
10250 void compose_clear_exit_drafts(void)
10252 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10253 DRAFTED_AT_EXIT, NULL);
10254 if (is_file_exist(filepath))
10255 claws_unlink(filepath);
10260 void compose_reopen_exit_drafts(void)
10262 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10263 DRAFTED_AT_EXIT, NULL);
10264 FILE *fp = g_fopen(filepath, "rb");
10268 while (fgets(buf, sizeof(buf), fp)) {
10269 gchar **parts = g_strsplit(buf, "\t", 2);
10270 const gchar *folder = parts[0];
10271 int msgnum = parts[1] ? atoi(parts[1]):-1;
10273 if (folder && *folder && msgnum > -1) {
10274 FolderItem *item = folder_find_item_from_identifier(folder);
10275 MsgInfo *info = folder_item_get_msginfo(item, msgnum);
10277 compose_reedit(info, FALSE);
10284 compose_clear_exit_drafts();
10287 static void compose_save_cb(GtkAction *action, gpointer data)
10289 Compose *compose = (Compose *)data;
10290 compose_draft(compose, COMPOSE_KEEP_EDITING);
10291 compose->rmode = COMPOSE_REEDIT;
10294 void compose_attach_from_list(Compose *compose, GList *file_list, gboolean free_data)
10296 if (compose && file_list) {
10299 for ( tmp = file_list; tmp; tmp = tmp->next) {
10300 gchar *file = (gchar *) tmp->data;
10301 gchar *utf8_filename = conv_filename_to_utf8(file);
10302 compose_attach_append(compose, file, utf8_filename, NULL, NULL);
10303 compose_changed_cb(NULL, compose);
10308 g_free(utf8_filename);
10313 static void compose_attach_cb(GtkAction *action, gpointer data)
10315 Compose *compose = (Compose *)data;
10318 if (compose->redirect_filename != NULL)
10321 /* Set focus_window properly, in case we were called via popup menu,
10322 * which unsets it (via focus_out_event callback on compose window). */
10323 manage_window_focus_in(compose->window, NULL, NULL);
10325 file_list = filesel_select_multiple_files_open(_("Select file"), NULL);
10328 compose_attach_from_list(compose, file_list, TRUE);
10329 g_list_free(file_list);
10333 static void compose_insert_file_cb(GtkAction *action, gpointer data)
10335 Compose *compose = (Compose *)data;
10337 gint files_inserted = 0;
10339 file_list = filesel_select_multiple_files_open(_("Select file"), NULL);
10344 for ( tmp = file_list; tmp; tmp = tmp->next) {
10345 gchar *file = (gchar *) tmp->data;
10346 gchar *filedup = g_strdup(file);
10347 gchar *shortfile = g_path_get_basename(filedup);
10348 ComposeInsertResult res;
10349 /* insert the file if the file is short or if the user confirmed that
10350 he/she wants to insert the large file */
10351 res = compose_insert_file(compose, file);
10352 if (res == COMPOSE_INSERT_READ_ERROR) {
10353 alertpanel_error(_("File '%s' could not be read."), shortfile);
10354 } else if (res == COMPOSE_INSERT_INVALID_CHARACTER) {
10355 alertpanel_error(_("File '%s' contained invalid characters\n"
10356 "for the current encoding, insertion may be incorrect."),
10358 } else if (res == COMPOSE_INSERT_SUCCESS)
10365 g_list_free(file_list);
10369 if (files_inserted > 0 && compose->gtkaspell &&
10370 compose->gtkaspell->check_while_typing)
10371 gtkaspell_highlight_all(compose->gtkaspell);
10375 static void compose_insert_sig_cb(GtkAction *action, gpointer data)
10377 Compose *compose = (Compose *)data;
10379 compose_insert_sig(compose, FALSE);
10382 static void compose_replace_sig_cb(GtkAction *action, gpointer data)
10384 Compose *compose = (Compose *)data;
10386 compose_insert_sig(compose, TRUE);
10389 static gint compose_delete_cb(GtkWidget *widget, GdkEventAny *event,
10393 Compose *compose = (Compose *)data;
10395 gtkut_widget_get_uposition(widget, &x, &y);
10396 if (!compose->batch) {
10397 prefs_common.compose_x = x;
10398 prefs_common.compose_y = y;
10400 if (compose->sending || compose->updating)
10402 compose_close_cb(NULL, compose);
10406 void compose_close_toolbar(Compose *compose)
10408 compose_close_cb(NULL, compose);
10411 static gboolean compose_can_autosave(Compose *compose)
10413 if (compose->privacy_system && compose->use_encryption)
10414 return prefs_common.autosave && prefs_common.autosave_encrypted;
10416 return prefs_common.autosave;
10419 static void compose_close_cb(GtkAction *action, gpointer data)
10421 Compose *compose = (Compose *)data;
10425 if (compose->exteditor_tag != -1) {
10426 if (!compose_ext_editor_kill(compose))
10431 if (compose->modified) {
10432 gboolean reedit = (compose->rmode == COMPOSE_REEDIT);
10433 if (!g_mutex_trylock(compose->mutex)) {
10434 /* we don't want to lock the mutex once it's available,
10435 * because as the only other part of compose.c locking
10436 * it is compose_close - which means once unlocked,
10437 * the compose struct will be freed */
10438 debug_print("couldn't lock mutex, probably sending\n");
10442 val = alertpanel(_("Discard message"),
10443 _("This message has been modified. Discard it?"),
10444 _("_Discard"), _("_Save to Drafts"), GTK_STOCK_CANCEL);
10446 val = alertpanel(_("Save changes"),
10447 _("This message has been modified. Save the latest changes?"),
10448 _("_Don't save"), g_strconcat("+", _("_Save to Drafts"), NULL),
10451 g_mutex_unlock(compose->mutex);
10453 case G_ALERTDEFAULT:
10454 if (compose_can_autosave(compose) && !reedit)
10455 compose_remove_draft(compose);
10457 case G_ALERTALTERNATE:
10458 compose_draft(data, COMPOSE_QUIT_EDITING);
10465 compose_close(compose);
10468 static void compose_print_cb(GtkAction *action, gpointer data)
10470 Compose *compose = (Compose *) data;
10472 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
10473 if (compose->targetinfo)
10474 messageview_print(compose->targetinfo, FALSE, -1, -1, 0);
10477 static void compose_set_encoding_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
10479 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
10480 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
10481 Compose *compose = (Compose *) data;
10484 compose->out_encoding = (CharSet)value;
10487 static void compose_address_cb(GtkAction *action, gpointer data)
10489 Compose *compose = (Compose *)data;
10491 #ifndef USE_ALT_ADDRBOOK
10492 addressbook_open(compose);
10494 GError* error = NULL;
10495 addressbook_connect_signals(compose);
10496 addressbook_dbus_open(TRUE, &error);
10498 g_warning("%s", error->message);
10499 g_error_free(error);
10504 static void about_show_cb(GtkAction *action, gpointer data)
10509 static void compose_template_activate_cb(GtkWidget *widget, gpointer data)
10511 Compose *compose = (Compose *)data;
10516 tmpl = g_object_get_data(G_OBJECT(widget), "template");
10517 cm_return_if_fail(tmpl != NULL);
10519 msg = g_strdup_printf(_("Do you want to apply the template '%s'?"),
10521 val = alertpanel(_("Apply template"), msg,
10522 _("_Replace"), _("_Insert"), GTK_STOCK_CANCEL);
10525 if (val == G_ALERTDEFAULT)
10526 compose_template_apply(compose, tmpl, TRUE);
10527 else if (val == G_ALERTALTERNATE)
10528 compose_template_apply(compose, tmpl, FALSE);
10531 static void compose_ext_editor_cb(GtkAction *action, gpointer data)
10533 Compose *compose = (Compose *)data;
10536 if (compose->exteditor_tag != -1) {
10537 debug_print("ignoring open external editor: external editor still open\n");
10541 compose_exec_ext_editor(compose);
10544 static void compose_undo_cb(GtkAction *action, gpointer data)
10546 Compose *compose = (Compose *)data;
10547 gboolean prev_autowrap = compose->autowrap;
10549 compose->autowrap = FALSE;
10550 undo_undo(compose->undostruct);
10551 compose->autowrap = prev_autowrap;
10554 static void compose_redo_cb(GtkAction *action, gpointer data)
10556 Compose *compose = (Compose *)data;
10557 gboolean prev_autowrap = compose->autowrap;
10559 compose->autowrap = FALSE;
10560 undo_redo(compose->undostruct);
10561 compose->autowrap = prev_autowrap;
10564 static void entry_cut_clipboard(GtkWidget *entry)
10566 if (GTK_IS_EDITABLE(entry))
10567 gtk_editable_cut_clipboard (GTK_EDITABLE(entry));
10568 else if (GTK_IS_TEXT_VIEW(entry))
10569 gtk_text_buffer_cut_clipboard(
10570 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10571 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
10575 static void entry_copy_clipboard(GtkWidget *entry)
10577 if (GTK_IS_EDITABLE(entry))
10578 gtk_editable_copy_clipboard (GTK_EDITABLE(entry));
10579 else if (GTK_IS_TEXT_VIEW(entry))
10580 gtk_text_buffer_copy_clipboard(
10581 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10582 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
10585 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry,
10586 gboolean wrap, GdkAtom clip, GtkTextIter *insert_place)
10588 if (GTK_IS_TEXT_VIEW(entry)) {
10589 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10590 GtkTextMark *mark_start = gtk_text_buffer_get_insert(buffer);
10591 GtkTextIter start_iter, end_iter;
10593 gchar *contents = gtk_clipboard_wait_for_text(gtk_clipboard_get(clip));
10595 if (contents == NULL)
10598 /* we shouldn't delete the selection when middle-click-pasting, or we
10599 * can't mid-click-paste our own selection */
10600 if (clip != GDK_SELECTION_PRIMARY) {
10601 undo_paste_clipboard(GTK_TEXT_VIEW(compose->text), compose->undostruct);
10602 gtk_text_buffer_delete_selection(buffer, FALSE, TRUE);
10605 if (insert_place == NULL) {
10606 /* if insert_place isn't specified, insert at the cursor.
10607 * used for Ctrl-V pasting */
10608 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10609 start = gtk_text_iter_get_offset(&start_iter);
10610 gtk_text_buffer_insert(buffer, &start_iter, contents, strlen(contents));
10612 /* if insert_place is specified, paste here.
10613 * used for mid-click-pasting */
10614 start = gtk_text_iter_get_offset(insert_place);
10615 gtk_text_buffer_insert(buffer, insert_place, contents, strlen(contents));
10616 if (prefs_common.primary_paste_unselects)
10617 gtk_text_buffer_select_range(buffer, insert_place, insert_place);
10621 /* paste unwrapped: mark the paste so it's not wrapped later */
10622 end = start + strlen(contents);
10623 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, start);
10624 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, end);
10625 gtk_text_buffer_apply_tag_by_name(buffer, "no_wrap", &start_iter, &end_iter);
10626 } else if (wrap && clip == GDK_SELECTION_PRIMARY) {
10627 /* rewrap paragraph now (after a mid-click-paste) */
10628 mark_start = gtk_text_buffer_get_insert(buffer);
10629 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10630 gtk_text_iter_backward_char(&start_iter);
10631 compose_beautify_paragraph(compose, &start_iter, TRUE);
10633 } else if (GTK_IS_EDITABLE(entry))
10634 gtk_editable_paste_clipboard (GTK_EDITABLE(entry));
10636 compose->modified = TRUE;
10639 static void entry_allsel(GtkWidget *entry)
10641 if (GTK_IS_EDITABLE(entry))
10642 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
10643 else if (GTK_IS_TEXT_VIEW(entry)) {
10644 GtkTextIter startiter, enditer;
10645 GtkTextBuffer *textbuf;
10647 textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10648 gtk_text_buffer_get_start_iter(textbuf, &startiter);
10649 gtk_text_buffer_get_end_iter(textbuf, &enditer);
10651 gtk_text_buffer_move_mark_by_name(textbuf,
10652 "selection_bound", &startiter);
10653 gtk_text_buffer_move_mark_by_name(textbuf,
10654 "insert", &enditer);
10658 static void compose_cut_cb(GtkAction *action, gpointer data)
10660 Compose *compose = (Compose *)data;
10661 if (compose->focused_editable
10662 #ifndef GENERIC_UMPC
10663 && gtk_widget_has_focus(compose->focused_editable)
10666 entry_cut_clipboard(compose->focused_editable);
10669 static void compose_copy_cb(GtkAction *action, gpointer data)
10671 Compose *compose = (Compose *)data;
10672 if (compose->focused_editable
10673 #ifndef GENERIC_UMPC
10674 && gtk_widget_has_focus(compose->focused_editable)
10677 entry_copy_clipboard(compose->focused_editable);
10680 static void compose_paste_cb(GtkAction *action, gpointer data)
10682 Compose *compose = (Compose *)data;
10683 gint prev_autowrap;
10684 GtkTextBuffer *buffer;
10686 if (compose->focused_editable &&
10687 #ifndef GENERIC_UMPC
10688 gtk_widget_has_focus(compose->focused_editable)
10691 entry_paste_clipboard(compose, compose->focused_editable,
10692 prefs_common.linewrap_pastes,
10693 GDK_SELECTION_CLIPBOARD, NULL);
10698 #ifndef GENERIC_UMPC
10699 gtk_widget_has_focus(compose->text) &&
10701 compose->gtkaspell &&
10702 compose->gtkaspell->check_while_typing)
10703 gtkaspell_highlight_all(compose->gtkaspell);
10707 static void compose_paste_as_quote_cb(GtkAction *action, gpointer data)
10709 Compose *compose = (Compose *)data;
10710 gint wrap_quote = prefs_common.linewrap_quote;
10711 if (compose->focused_editable
10712 #ifndef GENERIC_UMPC
10713 && gtk_widget_has_focus(compose->focused_editable)
10716 /* let text_insert() (called directly or at a later time
10717 * after the gtk_editable_paste_clipboard) know that
10718 * text is to be inserted as a quotation. implemented
10719 * by using a simple refcount... */
10720 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data(
10721 G_OBJECT(compose->focused_editable),
10722 "paste_as_quotation"));
10723 g_object_set_data(G_OBJECT(compose->focused_editable),
10724 "paste_as_quotation",
10725 GINT_TO_POINTER(paste_as_quotation + 1));
10726 prefs_common.linewrap_quote = prefs_common.linewrap_pastes;
10727 entry_paste_clipboard(compose, compose->focused_editable,
10728 prefs_common.linewrap_pastes,
10729 GDK_SELECTION_CLIPBOARD, NULL);
10730 prefs_common.linewrap_quote = wrap_quote;
10734 static void compose_paste_no_wrap_cb(GtkAction *action, gpointer data)
10736 Compose *compose = (Compose *)data;
10737 gint prev_autowrap;
10738 GtkTextBuffer *buffer;
10740 if (compose->focused_editable
10741 #ifndef GENERIC_UMPC
10742 && gtk_widget_has_focus(compose->focused_editable)
10745 entry_paste_clipboard(compose, compose->focused_editable, FALSE,
10746 GDK_SELECTION_CLIPBOARD, NULL);
10751 #ifndef GENERIC_UMPC
10752 gtk_widget_has_focus(compose->text) &&
10754 compose->gtkaspell &&
10755 compose->gtkaspell->check_while_typing)
10756 gtkaspell_highlight_all(compose->gtkaspell);
10760 static void compose_paste_wrap_cb(GtkAction *action, gpointer data)
10762 Compose *compose = (Compose *)data;
10763 gint prev_autowrap;
10764 GtkTextBuffer *buffer;
10766 if (compose->focused_editable
10767 #ifndef GENERIC_UMPC
10768 && gtk_widget_has_focus(compose->focused_editable)
10771 entry_paste_clipboard(compose, compose->focused_editable, TRUE,
10772 GDK_SELECTION_CLIPBOARD, NULL);
10777 #ifndef GENERIC_UMPC
10778 gtk_widget_has_focus(compose->text) &&
10780 compose->gtkaspell &&
10781 compose->gtkaspell->check_while_typing)
10782 gtkaspell_highlight_all(compose->gtkaspell);
10786 static void compose_allsel_cb(GtkAction *action, gpointer data)
10788 Compose *compose = (Compose *)data;
10789 if (compose->focused_editable
10790 #ifndef GENERIC_UMPC
10791 && gtk_widget_has_focus(compose->focused_editable)
10794 entry_allsel(compose->focused_editable);
10797 static void textview_move_beginning_of_line (GtkTextView *text)
10799 GtkTextBuffer *buffer;
10803 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10805 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10806 mark = gtk_text_buffer_get_insert(buffer);
10807 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10808 gtk_text_iter_set_line_offset(&ins, 0);
10809 gtk_text_buffer_place_cursor(buffer, &ins);
10812 static void textview_move_forward_character (GtkTextView *text)
10814 GtkTextBuffer *buffer;
10818 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10820 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10821 mark = gtk_text_buffer_get_insert(buffer);
10822 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10823 if (gtk_text_iter_forward_cursor_position(&ins))
10824 gtk_text_buffer_place_cursor(buffer, &ins);
10827 static void textview_move_backward_character (GtkTextView *text)
10829 GtkTextBuffer *buffer;
10833 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10835 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10836 mark = gtk_text_buffer_get_insert(buffer);
10837 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10838 if (gtk_text_iter_backward_cursor_position(&ins))
10839 gtk_text_buffer_place_cursor(buffer, &ins);
10842 static void textview_move_forward_word (GtkTextView *text)
10844 GtkTextBuffer *buffer;
10849 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10851 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10852 mark = gtk_text_buffer_get_insert(buffer);
10853 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10854 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
10855 if (gtk_text_iter_forward_word_ends(&ins, count)) {
10856 gtk_text_iter_backward_word_start(&ins);
10857 gtk_text_buffer_place_cursor(buffer, &ins);
10861 static void textview_move_backward_word (GtkTextView *text)
10863 GtkTextBuffer *buffer;
10867 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10869 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10870 mark = gtk_text_buffer_get_insert(buffer);
10871 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10872 if (gtk_text_iter_backward_word_starts(&ins, 1))
10873 gtk_text_buffer_place_cursor(buffer, &ins);
10876 static void textview_move_end_of_line (GtkTextView *text)
10878 GtkTextBuffer *buffer;
10882 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10884 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10885 mark = gtk_text_buffer_get_insert(buffer);
10886 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10887 if (gtk_text_iter_forward_to_line_end(&ins))
10888 gtk_text_buffer_place_cursor(buffer, &ins);
10891 static void textview_move_next_line (GtkTextView *text)
10893 GtkTextBuffer *buffer;
10898 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10900 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10901 mark = gtk_text_buffer_get_insert(buffer);
10902 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10903 offset = gtk_text_iter_get_line_offset(&ins);
10904 if (gtk_text_iter_forward_line(&ins)) {
10905 gtk_text_iter_set_line_offset(&ins, offset);
10906 gtk_text_buffer_place_cursor(buffer, &ins);
10910 static void textview_move_previous_line (GtkTextView *text)
10912 GtkTextBuffer *buffer;
10917 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10919 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10920 mark = gtk_text_buffer_get_insert(buffer);
10921 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10922 offset = gtk_text_iter_get_line_offset(&ins);
10923 if (gtk_text_iter_backward_line(&ins)) {
10924 gtk_text_iter_set_line_offset(&ins, offset);
10925 gtk_text_buffer_place_cursor(buffer, &ins);
10929 static void textview_delete_forward_character (GtkTextView *text)
10931 GtkTextBuffer *buffer;
10933 GtkTextIter ins, end_iter;
10935 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10937 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10938 mark = gtk_text_buffer_get_insert(buffer);
10939 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10941 if (gtk_text_iter_forward_char(&end_iter)) {
10942 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10946 static void textview_delete_backward_character (GtkTextView *text)
10948 GtkTextBuffer *buffer;
10950 GtkTextIter ins, end_iter;
10952 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10954 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10955 mark = gtk_text_buffer_get_insert(buffer);
10956 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10958 if (gtk_text_iter_backward_char(&end_iter)) {
10959 gtk_text_buffer_delete(buffer, &end_iter, &ins);
10963 static void textview_delete_forward_word (GtkTextView *text)
10965 GtkTextBuffer *buffer;
10967 GtkTextIter ins, end_iter;
10969 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10971 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10972 mark = gtk_text_buffer_get_insert(buffer);
10973 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10975 if (gtk_text_iter_forward_word_end(&end_iter)) {
10976 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10980 static void textview_delete_backward_word (GtkTextView *text)
10982 GtkTextBuffer *buffer;
10984 GtkTextIter ins, end_iter;
10986 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10988 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10989 mark = gtk_text_buffer_get_insert(buffer);
10990 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10992 if (gtk_text_iter_backward_word_start(&end_iter)) {
10993 gtk_text_buffer_delete(buffer, &end_iter, &ins);
10997 static void textview_delete_line (GtkTextView *text)
10999 GtkTextBuffer *buffer;
11001 GtkTextIter ins, start_iter, end_iter;
11003 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11005 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11006 mark = gtk_text_buffer_get_insert(buffer);
11007 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11010 gtk_text_iter_set_line_offset(&start_iter, 0);
11013 if (gtk_text_iter_ends_line(&end_iter)){
11014 if (!gtk_text_iter_forward_char(&end_iter))
11015 gtk_text_iter_backward_char(&start_iter);
11018 gtk_text_iter_forward_to_line_end(&end_iter);
11019 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
11022 static void textview_delete_to_line_end (GtkTextView *text)
11024 GtkTextBuffer *buffer;
11026 GtkTextIter ins, end_iter;
11028 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11030 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11031 mark = gtk_text_buffer_get_insert(buffer);
11032 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11034 if (gtk_text_iter_ends_line(&end_iter))
11035 gtk_text_iter_forward_char(&end_iter);
11037 gtk_text_iter_forward_to_line_end(&end_iter);
11038 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11041 #define DO_ACTION(name, act) { \
11042 if(!strcmp(name, a_name)) { \
11046 static ComposeCallAdvancedAction compose_call_advanced_action_from_path(GtkAction *action)
11048 const gchar *a_name = gtk_action_get_name(action);
11049 DO_ACTION("Edit/Advanced/BackChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER);
11050 DO_ACTION("Edit/Advanced/ForwChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER);
11051 DO_ACTION("Edit/Advanced/BackWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD);
11052 DO_ACTION("Edit/Advanced/ForwWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD);
11053 DO_ACTION("Edit/Advanced/BegLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE);
11054 DO_ACTION("Edit/Advanced/EndLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE);
11055 DO_ACTION("Edit/Advanced/PrevLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE);
11056 DO_ACTION("Edit/Advanced/NextLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE);
11057 DO_ACTION("Edit/Advanced/DelBackChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER);
11058 DO_ACTION("Edit/Advanced/DelForwChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER);
11059 DO_ACTION("Edit/Advanced/DelBackWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD);
11060 DO_ACTION("Edit/Advanced/DelForwWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD);
11061 DO_ACTION("Edit/Advanced/DelLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE);
11062 DO_ACTION("Edit/Advanced/DelEndLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END);
11066 static void compose_advanced_action_cb(GtkAction *gaction, gpointer data)
11068 Compose *compose = (Compose *)data;
11069 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11070 ComposeCallAdvancedAction action = -1;
11072 action = compose_call_advanced_action_from_path(gaction);
11075 void (*do_action) (GtkTextView *text);
11076 } action_table[] = {
11077 {textview_move_beginning_of_line},
11078 {textview_move_forward_character},
11079 {textview_move_backward_character},
11080 {textview_move_forward_word},
11081 {textview_move_backward_word},
11082 {textview_move_end_of_line},
11083 {textview_move_next_line},
11084 {textview_move_previous_line},
11085 {textview_delete_forward_character},
11086 {textview_delete_backward_character},
11087 {textview_delete_forward_word},
11088 {textview_delete_backward_word},
11089 {textview_delete_line},
11090 {textview_delete_to_line_end}
11093 if (!gtk_widget_has_focus(GTK_WIDGET(text))) return;
11095 if (action >= COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE &&
11096 action <= COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END) {
11097 if (action_table[action].do_action)
11098 action_table[action].do_action(text);
11100 g_warning("Not implemented yet.");
11104 static void compose_grab_focus_cb(GtkWidget *widget, Compose *compose)
11106 GtkAllocation allocation;
11110 if (GTK_IS_EDITABLE(widget)) {
11111 str = gtk_editable_get_chars(GTK_EDITABLE(widget), 0, -1);
11112 gtk_editable_set_position(GTK_EDITABLE(widget),
11115 if ((parent = gtk_widget_get_parent(widget))
11116 && (parent = gtk_widget_get_parent(parent))
11117 && (parent = gtk_widget_get_parent(parent))) {
11118 if (GTK_IS_SCROLLED_WINDOW(parent)) {
11119 gtk_widget_get_allocation(widget, &allocation);
11120 gint y = allocation.y;
11121 gint height = allocation.height;
11122 GtkAdjustment *shown = gtk_scrolled_window_get_vadjustment
11123 (GTK_SCROLLED_WINDOW(parent));
11125 gfloat value = gtk_adjustment_get_value(shown);
11126 gfloat upper = gtk_adjustment_get_upper(shown);
11127 gfloat page_size = gtk_adjustment_get_page_size(shown);
11128 if (y < (int)value) {
11129 gtk_adjustment_set_value(shown, y - 1);
11131 if ((y + height) > ((int)value + (int)page_size)) {
11132 if ((y - height - 1) < ((int)upper - (int)page_size)) {
11133 gtk_adjustment_set_value(shown,
11134 y + height - (int)page_size - 1);
11136 gtk_adjustment_set_value(shown,
11137 (int)upper - (int)page_size - 1);
11144 if (GTK_IS_EDITABLE(widget) || GTK_IS_TEXT_VIEW(widget))
11145 compose->focused_editable = widget;
11147 #ifdef GENERIC_UMPC
11148 if (GTK_IS_TEXT_VIEW(widget)
11149 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->edit_vbox) {
11150 g_object_ref(compose->notebook);
11151 g_object_ref(compose->edit_vbox);
11152 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
11153 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
11154 gtk_paned_add1(GTK_PANED(compose->paned), compose->edit_vbox);
11155 gtk_paned_add2(GTK_PANED(compose->paned), compose->notebook);
11156 g_object_unref(compose->notebook);
11157 g_object_unref(compose->edit_vbox);
11158 g_signal_handlers_block_by_func(G_OBJECT(widget),
11159 G_CALLBACK(compose_grab_focus_cb),
11161 gtk_widget_grab_focus(widget);
11162 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
11163 G_CALLBACK(compose_grab_focus_cb),
11165 } else if (!GTK_IS_TEXT_VIEW(widget)
11166 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->notebook) {
11167 g_object_ref(compose->notebook);
11168 g_object_ref(compose->edit_vbox);
11169 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
11170 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
11171 gtk_paned_add1(GTK_PANED(compose->paned), compose->notebook);
11172 gtk_paned_add2(GTK_PANED(compose->paned), compose->edit_vbox);
11173 g_object_unref(compose->notebook);
11174 g_object_unref(compose->edit_vbox);
11175 g_signal_handlers_block_by_func(G_OBJECT(widget),
11176 G_CALLBACK(compose_grab_focus_cb),
11178 gtk_widget_grab_focus(widget);
11179 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
11180 G_CALLBACK(compose_grab_focus_cb),
11186 static void compose_changed_cb(GtkTextBuffer *textbuf, Compose *compose)
11188 compose->modified = TRUE;
11189 // compose_beautify_paragraph(compose, NULL, TRUE);
11190 #ifndef GENERIC_UMPC
11191 compose_set_title(compose);
11195 static void compose_wrap_cb(GtkAction *action, gpointer data)
11197 Compose *compose = (Compose *)data;
11198 compose_beautify_paragraph(compose, NULL, TRUE);
11201 static void compose_wrap_all_cb(GtkAction *action, gpointer data)
11203 Compose *compose = (Compose *)data;
11204 compose_wrap_all_full(compose, TRUE);
11207 static void compose_find_cb(GtkAction *action, gpointer data)
11209 Compose *compose = (Compose *)data;
11211 message_search_compose(compose);
11214 static void compose_toggle_autowrap_cb(GtkToggleAction *action,
11217 Compose *compose = (Compose *)data;
11218 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11219 if (compose->autowrap)
11220 compose_wrap_all_full(compose, TRUE);
11221 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11224 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
11227 Compose *compose = (Compose *)data;
11228 compose->autoindent = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11231 static void compose_toggle_sign_cb(GtkToggleAction *action, gpointer data)
11233 Compose *compose = (Compose *)data;
11235 compose->use_signing = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11238 static void compose_toggle_encrypt_cb(GtkToggleAction *action, gpointer data)
11240 Compose *compose = (Compose *)data;
11242 compose->use_encryption = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11245 static void activate_privacy_system(Compose *compose, PrefsAccount *account, gboolean warn)
11247 g_free(compose->privacy_system);
11248 g_free(compose->encdata);
11250 compose->privacy_system = g_strdup(account->default_privacy_system);
11251 compose_update_privacy_system_menu_item(compose, warn);
11254 static void compose_toggle_ruler_cb(GtkToggleAction *action, gpointer data)
11256 Compose *compose = (Compose *)data;
11258 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action))) {
11259 gtk_widget_show(compose->ruler_hbox);
11260 prefs_common.show_ruler = TRUE;
11262 gtk_widget_hide(compose->ruler_hbox);
11263 gtk_widget_queue_resize(compose->edit_vbox);
11264 prefs_common.show_ruler = FALSE;
11268 static void compose_attach_drag_received_cb (GtkWidget *widget,
11269 GdkDragContext *context,
11272 GtkSelectionData *data,
11275 gpointer user_data)
11277 Compose *compose = (Compose *)user_data;
11281 type = gtk_selection_data_get_data_type(data);
11282 if ((gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list"))
11283 && gtk_drag_get_source_widget(context) !=
11284 summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
11285 list = uri_list_extract_filenames(
11286 (const gchar *)gtk_selection_data_get_data(data));
11287 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11288 gchar *utf8_filename = conv_filename_to_utf8((const gchar *)tmp->data);
11289 compose_attach_append
11290 (compose, (const gchar *)tmp->data,
11291 utf8_filename, NULL, NULL);
11292 g_free(utf8_filename);
11294 if (list) compose_changed_cb(NULL, compose);
11295 list_free_strings(list);
11297 } else if (gtk_drag_get_source_widget(context)
11298 == summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
11299 /* comes from our summaryview */
11300 SummaryView * summaryview = NULL;
11301 GSList * list = NULL, *cur = NULL;
11303 if (mainwindow_get_mainwindow())
11304 summaryview = mainwindow_get_mainwindow()->summaryview;
11307 list = summary_get_selected_msg_list(summaryview);
11309 for (cur = list; cur; cur = cur->next) {
11310 MsgInfo *msginfo = (MsgInfo *)cur->data;
11311 gchar *file = NULL;
11313 file = procmsg_get_message_file_full(msginfo,
11316 compose_attach_append(compose, (const gchar *)file,
11317 (const gchar *)file, "message/rfc822", NULL);
11321 g_slist_free(list);
11325 static gboolean compose_drag_drop(GtkWidget *widget,
11326 GdkDragContext *drag_context,
11328 guint time, gpointer user_data)
11330 /* not handling this signal makes compose_insert_drag_received_cb
11335 static gboolean completion_set_focus_to_subject
11336 (GtkWidget *widget,
11337 GdkEventKey *event,
11340 cm_return_val_if_fail(compose != NULL, FALSE);
11342 /* make backtab move to subject field */
11343 if(event->keyval == GDK_KEY_ISO_Left_Tab) {
11344 gtk_widget_grab_focus(compose->subject_entry);
11350 static void compose_insert_drag_received_cb (GtkWidget *widget,
11351 GdkDragContext *drag_context,
11354 GtkSelectionData *data,
11357 gpointer user_data)
11359 Compose *compose = (Compose *)user_data;
11363 /* strangely, testing data->type == gdk_atom_intern("text/uri-list", TRUE)
11365 type = gtk_selection_data_get_data_type(data);
11366 if (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list")) {
11367 AlertValue val = G_ALERTDEFAULT;
11368 const gchar* ddata = (const gchar *)gtk_selection_data_get_data(data);
11370 list = uri_list_extract_filenames(ddata);
11371 if (list == NULL && strstr(ddata, "://")) {
11372 /* Assume a list of no files, and data has ://, is a remote link */
11373 gchar *tmpdata = g_strstrip(g_strdup(ddata));
11374 gchar *tmpfile = get_tmp_file();
11375 str_write_to_file(tmpdata, tmpfile);
11377 compose_insert_file(compose, tmpfile);
11378 claws_unlink(tmpfile);
11380 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11381 compose_beautify_paragraph(compose, NULL, TRUE);
11384 switch (prefs_common.compose_dnd_mode) {
11385 case COMPOSE_DND_ASK:
11386 val = alertpanel_full(_("Insert or attach?"),
11387 _("Do you want to insert the contents of the file(s) "
11388 "into the message body, or attach it to the email?"),
11389 GTK_STOCK_CANCEL, g_strconcat("+", _("_Insert"), NULL), _("_Attach"),
11390 TRUE, NULL, ALERT_QUESTION, G_ALERTALTERNATE);
11392 case COMPOSE_DND_INSERT:
11393 val = G_ALERTALTERNATE;
11395 case COMPOSE_DND_ATTACH:
11396 val = G_ALERTOTHER;
11399 /* unexpected case */
11400 g_warning("error: unexpected compose_dnd_mode option value in compose_insert_drag_received_cb()");
11403 if (val & G_ALERTDISABLE) {
11404 val &= ~G_ALERTDISABLE;
11405 /* remember what action to perform by default, only if we don't click Cancel */
11406 if (val == G_ALERTALTERNATE)
11407 prefs_common.compose_dnd_mode = COMPOSE_DND_INSERT;
11408 else if (val == G_ALERTOTHER)
11409 prefs_common.compose_dnd_mode = COMPOSE_DND_ATTACH;
11412 if (val == G_ALERTDEFAULT || val == G_ALERTCANCEL) {
11413 gtk_drag_finish(drag_context, FALSE, FALSE, time);
11414 list_free_strings(list);
11417 } else if (val == G_ALERTOTHER) {
11418 compose_attach_drag_received_cb(widget, drag_context, x, y, data, info, time, user_data);
11419 list_free_strings(list);
11424 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11425 compose_insert_file(compose, (const gchar *)tmp->data);
11427 list_free_strings(list);
11429 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11434 static void compose_header_drag_received_cb (GtkWidget *widget,
11435 GdkDragContext *drag_context,
11438 GtkSelectionData *data,
11441 gpointer user_data)
11443 GtkEditable *entry = (GtkEditable *)user_data;
11444 const gchar *email = (const gchar *)gtk_selection_data_get_data(data);
11446 /* strangely, testing data->type == gdk_atom_intern("text/plain", TRUE)
11449 if (!strncmp(email, "mailto:", strlen("mailto:"))) {
11450 gchar *decoded=g_new(gchar, strlen(email));
11453 decode_uri(decoded, email + strlen("mailto:")); /* will fit */
11454 gtk_editable_delete_text(entry, 0, -1);
11455 gtk_editable_insert_text(entry, decoded, strlen(decoded), &start);
11456 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11460 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11463 static void compose_toggle_return_receipt_cb(GtkToggleAction *action, gpointer data)
11465 Compose *compose = (Compose *)data;
11467 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11468 compose->return_receipt = TRUE;
11470 compose->return_receipt = FALSE;
11473 static void compose_toggle_remove_refs_cb(GtkToggleAction *action, gpointer data)
11475 Compose *compose = (Compose *)data;
11477 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11478 compose->remove_references = TRUE;
11480 compose->remove_references = FALSE;
11483 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
11484 ComposeHeaderEntry *headerentry)
11486 gtk_entry_set_text(GTK_ENTRY(headerentry->entry), "");
11490 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
11491 GdkEventKey *event,
11492 ComposeHeaderEntry *headerentry)
11494 if ((g_slist_length(headerentry->compose->header_list) > 0) &&
11495 ((headerentry->headernum + 1) != headerentry->compose->header_nextrow) &&
11496 !(event->state & GDK_MODIFIER_MASK) &&
11497 (event->keyval == GDK_KEY_BackSpace) &&
11498 (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) == 0)) {
11499 gtk_container_remove
11500 (GTK_CONTAINER(headerentry->compose->header_table),
11501 headerentry->combo);
11502 gtk_container_remove
11503 (GTK_CONTAINER(headerentry->compose->header_table),
11504 headerentry->entry);
11505 headerentry->compose->header_list =
11506 g_slist_remove(headerentry->compose->header_list,
11508 g_free(headerentry);
11509 } else if (event->keyval == GDK_KEY_Tab) {
11510 if (headerentry->compose->header_last == headerentry) {
11511 /* Override default next focus, and give it to subject_entry
11512 * instead of notebook tabs
11514 g_signal_stop_emission_by_name(G_OBJECT(entry), "key-press-event");
11515 gtk_widget_grab_focus(headerentry->compose->subject_entry);
11522 static gboolean scroll_postpone(gpointer data)
11524 Compose *compose = (Compose *)data;
11526 if (compose->batch)
11529 GTK_EVENTS_FLUSH();
11530 compose_show_first_last_header(compose, FALSE);
11534 static void compose_headerentry_changed_cb(GtkWidget *entry,
11535 ComposeHeaderEntry *headerentry)
11537 if (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) != 0) {
11538 compose_create_header_entry(headerentry->compose);
11539 g_signal_handlers_disconnect_matched
11540 (G_OBJECT(entry), G_SIGNAL_MATCH_DATA,
11541 0, 0, NULL, NULL, headerentry);
11543 if (!headerentry->compose->batch)
11544 g_timeout_add(0, scroll_postpone, headerentry->compose);
11548 static gboolean compose_defer_auto_save_draft(Compose *compose)
11550 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
11551 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
11555 static void compose_show_first_last_header(Compose *compose, gboolean show_first)
11557 GtkAdjustment *vadj;
11559 cm_return_if_fail(compose);
11564 cm_return_if_fail(GTK_IS_WIDGET(compose->header_table));
11565 cm_return_if_fail(GTK_IS_VIEWPORT(gtk_widget_get_parent(compose->header_table)));
11566 vadj = gtk_viewport_get_vadjustment(GTK_VIEWPORT(
11567 gtk_widget_get_parent(compose->header_table)));
11568 gtk_adjustment_set_value(vadj, (show_first ?
11569 gtk_adjustment_get_lower(vadj) :
11570 (gtk_adjustment_get_upper(vadj) -
11571 gtk_adjustment_get_page_size(vadj))));
11572 gtk_adjustment_changed(vadj);
11575 static void text_inserted(GtkTextBuffer *buffer, GtkTextIter *iter,
11576 const gchar *text, gint len, Compose *compose)
11578 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data
11579 (G_OBJECT(compose->text), "paste_as_quotation"));
11582 cm_return_if_fail(text != NULL);
11584 g_signal_handlers_block_by_func(G_OBJECT(buffer),
11585 G_CALLBACK(text_inserted),
11587 if (paste_as_quotation) {
11589 const gchar *qmark;
11591 GtkTextIter start_iter;
11594 len = strlen(text);
11596 new_text = g_strndup(text, len);
11598 qmark = compose_quote_char_from_context(compose);
11600 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11601 gtk_text_buffer_place_cursor(buffer, iter);
11603 pos = gtk_text_iter_get_offset(iter);
11605 compose_quote_fmt(compose, NULL, "%Q", qmark, new_text, TRUE, FALSE,
11606 _("Quote format error at line %d."));
11607 quote_fmt_reset_vartable();
11609 g_object_set_data(G_OBJECT(compose->text), "paste_as_quotation",
11610 GINT_TO_POINTER(paste_as_quotation - 1));
11612 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11613 gtk_text_buffer_place_cursor(buffer, iter);
11614 gtk_text_buffer_delete_mark(buffer, mark);
11616 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, pos);
11617 mark = gtk_text_buffer_create_mark(buffer, NULL, &start_iter, FALSE);
11618 compose_beautify_paragraph(compose, &start_iter, FALSE);
11619 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark);
11620 gtk_text_buffer_delete_mark(buffer, mark);
11622 if (strcmp(text, "\n") || compose->automatic_break
11623 || gtk_text_iter_starts_line(iter)) {
11624 GtkTextIter before_ins;
11625 gtk_text_buffer_insert(buffer, iter, text, len);
11626 if (!strstr(text, "\n") && gtk_text_iter_has_tag(iter, compose->no_join_tag)) {
11627 before_ins = *iter;
11628 gtk_text_iter_backward_chars(&before_ins, len);
11629 gtk_text_buffer_remove_tag_by_name(buffer, "no_join", &before_ins, iter);
11632 /* check if the preceding is just whitespace or quote */
11633 GtkTextIter start_line;
11634 gchar *tmp = NULL, *quote = NULL;
11635 gint quote_len = 0, is_normal = 0;
11636 start_line = *iter;
11637 gtk_text_iter_set_line_offset(&start_line, 0);
11638 tmp = gtk_text_buffer_get_text(buffer, &start_line, iter, FALSE);
11641 if (*tmp == '\0') {
11644 quote = compose_get_quote_str(buffer, &start_line, "e_len);
11652 gtk_text_buffer_insert(buffer, iter, text, len);
11654 gtk_text_buffer_insert_with_tags_by_name(buffer,
11655 iter, text, len, "no_join", NULL);
11660 if (!paste_as_quotation) {
11661 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11662 compose_beautify_paragraph(compose, iter, FALSE);
11663 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11664 gtk_text_buffer_delete_mark(buffer, mark);
11667 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
11668 G_CALLBACK(text_inserted),
11670 g_signal_stop_emission_by_name(G_OBJECT(buffer), "insert-text");
11672 if (compose_can_autosave(compose) &&
11673 gtk_text_buffer_get_char_count(buffer) % prefs_common.autosave_length == 0 &&
11674 compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN /* disabled while loading */)
11675 compose->draft_timeout_tag = g_timeout_add
11676 (500, (GSourceFunc) compose_defer_auto_save_draft, compose);
11680 static void compose_check_all(GtkAction *action, gpointer data)
11682 Compose *compose = (Compose *)data;
11683 if (!compose->gtkaspell)
11686 if (gtk_widget_has_focus(compose->subject_entry))
11687 claws_spell_entry_check_all(
11688 CLAWS_SPELL_ENTRY(compose->subject_entry));
11690 gtkaspell_check_all(compose->gtkaspell);
11693 static void compose_highlight_all(GtkAction *action, gpointer data)
11695 Compose *compose = (Compose *)data;
11696 if (compose->gtkaspell) {
11697 claws_spell_entry_recheck_all(
11698 CLAWS_SPELL_ENTRY(compose->subject_entry));
11699 gtkaspell_highlight_all(compose->gtkaspell);
11703 static void compose_check_backwards(GtkAction *action, gpointer data)
11705 Compose *compose = (Compose *)data;
11706 if (!compose->gtkaspell) {
11707 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
11711 if (gtk_widget_has_focus(compose->subject_entry))
11712 claws_spell_entry_check_backwards(
11713 CLAWS_SPELL_ENTRY(compose->subject_entry));
11715 gtkaspell_check_backwards(compose->gtkaspell);
11718 static void compose_check_forwards_go(GtkAction *action, gpointer data)
11720 Compose *compose = (Compose *)data;
11721 if (!compose->gtkaspell) {
11722 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
11726 if (gtk_widget_has_focus(compose->subject_entry))
11727 claws_spell_entry_check_forwards_go(
11728 CLAWS_SPELL_ENTRY(compose->subject_entry));
11730 gtkaspell_check_forwards_go(compose->gtkaspell);
11735 *\brief Guess originating forward account from MsgInfo and several
11736 * "common preference" settings. Return NULL if no guess.
11738 static PrefsAccount *compose_guess_forward_account_from_msginfo(MsgInfo *msginfo)
11740 PrefsAccount *account = NULL;
11742 cm_return_val_if_fail(msginfo, NULL);
11743 cm_return_val_if_fail(msginfo->folder, NULL);
11744 cm_return_val_if_fail(msginfo->folder->prefs, NULL);
11746 if (msginfo->folder->prefs->enable_default_account)
11747 account = account_find_from_id(msginfo->folder->prefs->default_account);
11750 account = msginfo->folder->folder->account;
11752 if (!account && msginfo->to && prefs_common.forward_account_autosel) {
11754 Xstrdup_a(to, msginfo->to, return NULL);
11755 extract_address(to);
11756 account = account_find_from_address(to, FALSE);
11759 if (!account && prefs_common.forward_account_autosel) {
11760 gchar cc[BUFFSIZE];
11761 if (!procheader_get_header_from_msginfo
11762 (msginfo, cc,sizeof cc , "Cc:")) {
11763 gchar *buf = cc + strlen("Cc:");
11764 extract_address(buf);
11765 account = account_find_from_address(buf, FALSE);
11769 if (!account && prefs_common.forward_account_autosel) {
11770 gchar deliveredto[BUFFSIZE];
11771 if (!procheader_get_header_from_msginfo
11772 (msginfo, deliveredto,sizeof deliveredto , "Delivered-To:")) {
11773 gchar *buf = deliveredto + strlen("Delivered-To:");
11774 extract_address(buf);
11775 account = account_find_from_address(buf, FALSE);
11782 gboolean compose_close(Compose *compose)
11786 cm_return_val_if_fail(compose, FALSE);
11788 if (!g_mutex_trylock(compose->mutex)) {
11789 /* we have to wait for the (possibly deferred by auto-save)
11790 * drafting to be done, before destroying the compose under
11792 debug_print("waiting for drafting to finish...\n");
11793 compose_allow_user_actions(compose, FALSE);
11794 if (compose->close_timeout_tag == 0) {
11795 compose->close_timeout_tag =
11796 g_timeout_add (500, (GSourceFunc) compose_close,
11802 if (compose->draft_timeout_tag >= 0) {
11803 g_source_remove(compose->draft_timeout_tag);
11804 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN;
11807 gtkut_widget_get_uposition(compose->window, &x, &y);
11808 if (!compose->batch) {
11809 prefs_common.compose_x = x;
11810 prefs_common.compose_y = y;
11812 g_mutex_unlock(compose->mutex);
11813 compose_destroy(compose);
11818 * Add entry field for each address in list.
11819 * \param compose E-Mail composition object.
11820 * \param listAddress List of (formatted) E-Mail addresses.
11822 static void compose_add_field_list( Compose *compose, GList *listAddress ) {
11825 node = listAddress;
11827 addr = ( gchar * ) node->data;
11828 compose_entry_append( compose, addr, COMPOSE_TO, PREF_NONE );
11829 node = g_list_next( node );
11833 static void compose_reply_from_messageview_real(MessageView *msgview, GSList *msginfo_list,
11834 guint action, gboolean opening_multiple)
11836 gchar *body = NULL;
11837 GSList *new_msglist = NULL;
11838 MsgInfo *tmp_msginfo = NULL;
11839 gboolean originally_enc = FALSE;
11840 gboolean originally_sig = FALSE;
11841 Compose *compose = NULL;
11842 gchar *s_system = NULL;
11844 cm_return_if_fail(msgview != NULL);
11846 cm_return_if_fail(msginfo_list != NULL);
11848 if (g_slist_length(msginfo_list) == 1 && !opening_multiple) {
11849 MimeInfo *mimeinfo = messageview_get_selected_mime_part(msgview);
11850 MsgInfo *orig_msginfo = (MsgInfo *)msginfo_list->data;
11852 if (mimeinfo != NULL && mimeinfo->type == MIMETYPE_MESSAGE &&
11853 !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
11854 tmp_msginfo = procmsg_msginfo_new_from_mimeinfo(
11855 orig_msginfo, mimeinfo);
11856 if (tmp_msginfo != NULL) {
11857 new_msglist = g_slist_append(NULL, tmp_msginfo);
11859 originally_enc = MSG_IS_ENCRYPTED(orig_msginfo->flags);
11860 privacy_msginfo_get_signed_state(orig_msginfo, &s_system);
11861 originally_sig = MSG_IS_SIGNED(orig_msginfo->flags);
11863 tmp_msginfo->folder = orig_msginfo->folder;
11864 tmp_msginfo->msgnum = orig_msginfo->msgnum;
11865 if (orig_msginfo->tags) {
11866 tmp_msginfo->tags = g_slist_copy(orig_msginfo->tags);
11867 tmp_msginfo->folder->tags_dirty = TRUE;
11873 if (!opening_multiple)
11874 body = messageview_get_selection(msgview);
11877 compose = compose_reply_mode((ComposeMode)action, new_msglist, body);
11878 procmsg_msginfo_free(&tmp_msginfo);
11879 g_slist_free(new_msglist);
11881 compose = compose_reply_mode((ComposeMode)action, msginfo_list, body);
11883 if (compose && originally_enc) {
11884 compose_force_encryption(compose, compose->account, FALSE, s_system);
11887 if (compose && originally_sig && compose->account->default_sign_reply) {
11888 compose_force_signing(compose, compose->account, s_system);
11892 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
11895 void compose_reply_from_messageview(MessageView *msgview, GSList *msginfo_list,
11898 if ((!prefs_common.forward_as_attachment || action != COMPOSE_FORWARD)
11899 && action != COMPOSE_FORWARD_AS_ATTACH && g_slist_length(msginfo_list) > 1) {
11900 GSList *cur = msginfo_list;
11901 gchar *msg = g_strdup_printf(_("You are about to reply to %d "
11902 "messages. Opening the windows "
11903 "could take some time. Do you "
11904 "want to continue?"),
11905 g_slist_length(msginfo_list));
11906 if (g_slist_length(msginfo_list) > 9
11907 && alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, "+" GTK_STOCK_YES, NULL)
11908 != G_ALERTALTERNATE) {
11913 /* We'll open multiple compose windows */
11914 /* let the WM place the next windows */
11915 compose_force_window_origin = FALSE;
11916 for (; cur; cur = cur->next) {
11918 tmplist.data = cur->data;
11919 tmplist.next = NULL;
11920 compose_reply_from_messageview_real(msgview, &tmplist, action, TRUE);
11922 compose_force_window_origin = TRUE;
11924 /* forwarding multiple mails as attachments is done via a
11925 * single compose window */
11926 compose_reply_from_messageview_real(msgview, msginfo_list, action, FALSE);
11930 void compose_check_for_email_account(Compose *compose)
11932 PrefsAccount *ac = NULL, *curr = NULL;
11938 if (compose->account && compose->account->protocol == A_NNTP) {
11939 ac = account_get_cur_account();
11940 if (ac->protocol == A_NNTP) {
11941 list = account_get_list();
11943 for( ; list != NULL ; list = g_list_next(list)) {
11944 curr = (PrefsAccount *) list->data;
11945 if (curr->protocol != A_NNTP) {
11951 combobox_select_by_data(GTK_COMBO_BOX(compose->account_combo),
11956 void compose_reply_to_address(MessageView *msgview, MsgInfo *msginfo,
11957 const gchar *address)
11959 GSList *msginfo_list = NULL;
11960 gchar *body = messageview_get_selection(msgview);
11963 msginfo_list = g_slist_prepend(msginfo_list, msginfo);
11965 compose = compose_reply_mode(COMPOSE_REPLY_TO_ADDRESS, msginfo_list, body);
11966 compose_check_for_email_account(compose);
11967 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
11968 compose_entry_append(compose, address, COMPOSE_TO, PREF_NONE);
11969 compose_reply_set_subject(compose, msginfo);
11972 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
11975 void compose_set_position(Compose *compose, gint pos)
11977 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11979 gtkut_text_view_set_position(text, pos);
11982 gboolean compose_search_string(Compose *compose,
11983 const gchar *str, gboolean case_sens)
11985 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11987 return gtkut_text_view_search_string(text, str, case_sens);
11990 gboolean compose_search_string_backward(Compose *compose,
11991 const gchar *str, gboolean case_sens)
11993 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11995 return gtkut_text_view_search_string_backward(text, str, case_sens);
11998 /* allocate a msginfo structure and populate its data from a compose data structure */
11999 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose)
12001 MsgInfo *newmsginfo;
12003 gchar buf[BUFFSIZE];
12005 cm_return_val_if_fail( compose != NULL, NULL );
12007 newmsginfo = procmsg_msginfo_new();
12010 get_rfc822_date(buf, sizeof(buf));
12011 newmsginfo->date = g_strdup(buf);
12014 if (compose->from_name) {
12015 newmsginfo->from = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
12016 newmsginfo->fromname = procheader_get_fromname(newmsginfo->from);
12020 if (compose->subject_entry)
12021 newmsginfo->subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
12023 /* to, cc, reply-to, newsgroups */
12024 for (list = compose->header_list; list; list = list->next) {
12025 gchar *header = gtk_editable_get_chars(
12027 gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
12028 gchar *entry = gtk_editable_get_chars(
12029 GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
12031 if ( strcasecmp(header, prefs_common_translated_header_name("To:")) == 0 ) {
12032 if ( newmsginfo->to == NULL ) {
12033 newmsginfo->to = g_strdup(entry);
12034 } else if (entry && *entry) {
12035 gchar *tmp = g_strconcat(newmsginfo->to, ", ", entry, NULL);
12036 g_free(newmsginfo->to);
12037 newmsginfo->to = tmp;
12040 if ( strcasecmp(header, prefs_common_translated_header_name("Cc:")) == 0 ) {
12041 if ( newmsginfo->cc == NULL ) {
12042 newmsginfo->cc = g_strdup(entry);
12043 } else if (entry && *entry) {
12044 gchar *tmp = g_strconcat(newmsginfo->cc, ", ", entry, NULL);
12045 g_free(newmsginfo->cc);
12046 newmsginfo->cc = tmp;
12049 if ( strcasecmp(header,
12050 prefs_common_translated_header_name("Newsgroups:")) == 0 ) {
12051 if ( newmsginfo->newsgroups == NULL ) {
12052 newmsginfo->newsgroups = g_strdup(entry);
12053 } else if (entry && *entry) {
12054 gchar *tmp = g_strconcat(newmsginfo->newsgroups, ", ", entry, NULL);
12055 g_free(newmsginfo->newsgroups);
12056 newmsginfo->newsgroups = tmp;
12064 /* other data is unset */
12070 /* update compose's dictionaries from folder dict settings */
12071 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
12072 FolderItem *folder_item)
12074 cm_return_if_fail(compose != NULL);
12076 if (compose->gtkaspell && folder_item && folder_item->prefs) {
12077 FolderItemPrefs *prefs = folder_item->prefs;
12079 if (prefs->enable_default_dictionary)
12080 gtkaspell_change_dict(compose->gtkaspell,
12081 prefs->default_dictionary, FALSE);
12082 if (folder_item->prefs->enable_default_alt_dictionary)
12083 gtkaspell_change_alt_dict(compose->gtkaspell,
12084 prefs->default_alt_dictionary);
12085 if (prefs->enable_default_dictionary
12086 || prefs->enable_default_alt_dictionary)
12087 compose_spell_menu_changed(compose);
12092 static void compose_subject_entry_activated(GtkWidget *widget, gpointer data)
12094 Compose *compose = (Compose *)data;
12096 cm_return_if_fail(compose != NULL);
12098 gtk_widget_grab_focus(compose->text);
12101 static void from_name_activate_cb(GtkWidget *widget, gpointer data)
12103 gtk_combo_box_popup(GTK_COMBO_BOX(data));