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);
2977 if (msginfo->msgid && *msginfo->msgid)
2978 compose->inreplyto = g_strdup(msginfo->msgid);
2980 if (!compose->references) {
2981 if (msginfo->msgid && *msginfo->msgid) {
2982 if (msginfo->inreplyto && *msginfo->inreplyto)
2983 compose->references =
2984 g_strdup_printf("<%s>\n\t<%s>",
2988 compose->references =
2989 g_strconcat("<", msginfo->msgid, ">",
2991 } else if (msginfo->inreplyto && *msginfo->inreplyto) {
2992 compose->references =
2993 g_strconcat("<", msginfo->inreplyto, ">",
3001 static gint compose_parse_manual_headers(Compose *compose, MsgInfo *msginfo, HeaderEntry *entries)
3006 cm_return_val_if_fail(msginfo != NULL, -1);
3008 if ((fp = procmsg_open_message(msginfo)) == NULL) return -1;
3009 procheader_get_header_fields(fp, entries);
3013 while (he != NULL && he->name != NULL) {
3015 GtkListStore *model = NULL;
3017 debug_print("Adding manual header: %s with value %s\n", he->name, he->body);
3018 model = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(compose->header_last->combo)));
3019 COMBOBOX_ADD(model, he->name, COMPOSE_TO);
3020 gtk_combo_box_set_active_iter(GTK_COMBO_BOX(compose->header_last->combo), &iter);
3021 gtk_entry_set_text(GTK_ENTRY(compose->header_last->entry), he->body);
3028 static gchar *compose_parse_references(const gchar *ref, const gchar *msgid)
3030 GSList *ref_id_list, *cur;
3034 ref_id_list = references_list_append(NULL, ref);
3035 if (!ref_id_list) return NULL;
3036 if (msgid && *msgid)
3037 ref_id_list = g_slist_append(ref_id_list, g_strdup(msgid));
3042 for (cur = ref_id_list; cur != NULL; cur = cur->next)
3043 /* "<" + Message-ID + ">" + CR+LF+TAB */
3044 len += strlen((gchar *)cur->data) + 5;
3046 if (len > MAX_REFERENCES_LEN) {
3047 /* remove second message-ID */
3048 if (ref_id_list && ref_id_list->next &&
3049 ref_id_list->next->next) {
3050 g_free(ref_id_list->next->data);
3051 ref_id_list = g_slist_remove
3052 (ref_id_list, ref_id_list->next->data);
3054 slist_free_strings_full(ref_id_list);
3061 new_ref = g_string_new("");
3062 for (cur = ref_id_list; cur != NULL; cur = cur->next) {
3063 if (new_ref->len > 0)
3064 g_string_append(new_ref, "\n\t");
3065 g_string_append_printf(new_ref, "<%s>", (gchar *)cur->data);
3068 slist_free_strings_full(ref_id_list);
3070 new_ref_str = new_ref->str;
3071 g_string_free(new_ref, FALSE);
3076 static gchar *compose_quote_fmt(Compose *compose, MsgInfo *msginfo,
3077 const gchar *fmt, const gchar *qmark,
3078 const gchar *body, gboolean rewrap,
3079 gboolean need_unescape,
3080 const gchar *err_msg)
3082 MsgInfo* dummyinfo = NULL;
3083 gchar *quote_str = NULL;
3085 gboolean prev_autowrap;
3086 const gchar *trimmed_body = body;
3087 gint cursor_pos = -1;
3088 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3089 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3094 SIGNAL_BLOCK(buffer);
3097 dummyinfo = compose_msginfo_new_from_compose(compose);
3098 msginfo = dummyinfo;
3101 if (qmark != NULL) {
3103 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
3104 compose->gtkaspell);
3106 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
3108 quote_fmt_scan_string(qmark);
3111 buf = quote_fmt_get_buffer();
3113 alertpanel_error(_("The \"Quotation mark\" of the template is invalid."));
3115 Xstrdup_a(quote_str, buf, goto error)
3118 if (fmt && *fmt != '\0') {
3121 while (*trimmed_body == '\n')
3125 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE,
3126 compose->gtkaspell);
3128 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE);
3130 if (need_unescape) {
3133 /* decode \-escape sequences in the internal representation of the quote format */
3134 tmp = g_malloc(strlen(fmt)+1);
3135 pref_get_unescaped_pref(tmp, fmt);
3136 quote_fmt_scan_string(tmp);
3140 quote_fmt_scan_string(fmt);
3144 buf = quote_fmt_get_buffer();
3146 gint line = quote_fmt_get_line();
3147 alertpanel_error(err_msg, line);
3153 prev_autowrap = compose->autowrap;
3154 compose->autowrap = FALSE;
3156 mark = gtk_text_buffer_get_insert(buffer);
3157 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3158 if (g_utf8_validate(buf, -1, NULL)) {
3159 gtk_text_buffer_insert(buffer, &iter, buf, -1);
3161 gchar *tmpout = NULL;
3162 tmpout = conv_codeset_strdup
3163 (buf, conv_get_locale_charset_str_no_utf8(),
3165 if (!tmpout || !g_utf8_validate(tmpout, -1, NULL)) {
3167 tmpout = g_malloc(strlen(buf)*2+1);
3168 conv_localetodisp(tmpout, strlen(buf)*2+1, buf);
3170 gtk_text_buffer_insert(buffer, &iter, tmpout, -1);
3174 cursor_pos = quote_fmt_get_cursor_pos();
3175 if (cursor_pos == -1)
3176 cursor_pos = gtk_text_iter_get_offset(&iter);
3177 compose->set_cursor_pos = cursor_pos;
3179 gtk_text_buffer_get_start_iter(buffer, &iter);
3180 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
3181 gtk_text_buffer_place_cursor(buffer, &iter);
3183 compose->autowrap = prev_autowrap;
3184 if (compose->autowrap && rewrap)
3185 compose_wrap_all(compose);
3192 SIGNAL_UNBLOCK(buffer);
3194 procmsg_msginfo_free( &dummyinfo );
3199 /* if ml_post is of type addr@host and from is of type
3200 * addr-anything@host, return TRUE
3202 static gboolean is_subscription(const gchar *ml_post, const gchar *from)
3204 gchar *left_ml = NULL;
3205 gchar *right_ml = NULL;
3206 gchar *left_from = NULL;
3207 gchar *right_from = NULL;
3208 gboolean result = FALSE;
3210 if (!ml_post || !from)
3213 left_ml = g_strdup(ml_post);
3214 if (strstr(left_ml, "@")) {
3215 right_ml = strstr(left_ml, "@")+1;
3216 *(strstr(left_ml, "@")) = '\0';
3219 left_from = g_strdup(from);
3220 if (strstr(left_from, "@")) {
3221 right_from = strstr(left_from, "@")+1;
3222 *(strstr(left_from, "@")) = '\0';
3225 if (right_ml && right_from
3226 && !strncmp(left_from, left_ml, strlen(left_ml))
3227 && !strcmp(right_from, right_ml)) {
3236 static void compose_set_folder_prefs(Compose *compose, FolderItem *folder,
3237 gboolean respect_default_to)
3241 if (!folder || !folder->prefs)
3244 if (respect_default_to && folder->prefs->enable_default_to) {
3245 compose_entry_append(compose, folder->prefs->default_to,
3246 COMPOSE_TO, PREF_FOLDER);
3247 compose_entry_mark_default_to(compose, folder->prefs->default_to);
3249 if (folder->prefs->enable_default_cc)
3250 compose_entry_append(compose, folder->prefs->default_cc,
3251 COMPOSE_CC, PREF_FOLDER);
3252 if (folder->prefs->enable_default_bcc)
3253 compose_entry_append(compose, folder->prefs->default_bcc,
3254 COMPOSE_BCC, PREF_FOLDER);
3255 if (folder->prefs->enable_default_replyto)
3256 compose_entry_append(compose, folder->prefs->default_replyto,
3257 COMPOSE_REPLYTO, PREF_FOLDER);
3260 static void compose_reply_set_subject(Compose *compose, MsgInfo *msginfo)
3265 if (!compose || !msginfo)
3268 if (msginfo->subject && *msginfo->subject) {
3269 buf = p = g_strdup(msginfo->subject);
3270 p += subject_get_prefix_length(p);
3271 memmove(buf, p, strlen(p) + 1);
3273 buf2 = g_strdup_printf("Re: %s", buf);
3274 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
3279 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), "Re: ");
3282 static void compose_reply_set_entry(Compose *compose, MsgInfo *msginfo,
3283 gboolean to_all, gboolean to_ml,
3285 gboolean followup_and_reply_to)
3287 GSList *cc_list = NULL;
3290 gchar *replyto = NULL;
3291 gchar *ac_email = NULL;
3293 gboolean reply_to_ml = FALSE;
3294 gboolean default_reply_to = FALSE;
3296 cm_return_if_fail(compose->account != NULL);
3297 cm_return_if_fail(msginfo != NULL);
3299 reply_to_ml = to_ml && compose->ml_post;
3301 default_reply_to = msginfo->folder &&
3302 msginfo->folder->prefs->enable_default_reply_to;
3304 if (compose->account->protocol != A_NNTP) {
3305 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
3307 if (reply_to_ml && !default_reply_to) {
3309 gboolean is_subscr = is_subscription(compose->ml_post,
3312 /* normal answer to ml post with a reply-to */
3313 compose_entry_append(compose,
3315 COMPOSE_TO, PREF_ML);
3316 if (compose->replyto)
3317 compose_entry_append(compose,
3319 COMPOSE_CC, PREF_ML);
3321 /* answer to subscription confirmation */
3322 if (compose->replyto)
3323 compose_entry_append(compose,
3325 COMPOSE_TO, PREF_ML);
3326 else if (msginfo->from)
3327 compose_entry_append(compose,
3329 COMPOSE_TO, PREF_ML);
3332 else if (!(to_all || to_sender) && default_reply_to) {
3333 compose_entry_append(compose,
3334 msginfo->folder->prefs->default_reply_to,
3335 COMPOSE_TO, PREF_FOLDER);
3336 compose_entry_mark_default_to(compose,
3337 msginfo->folder->prefs->default_reply_to);
3343 compose_entry_append(compose, msginfo->from,
3344 COMPOSE_TO, PREF_NONE);
3346 Xstrdup_a(tmp1, msginfo->from, return);
3347 extract_address(tmp1);
3348 compose_entry_append(compose,
3349 (!account_find_from_address(tmp1, FALSE))
3352 COMPOSE_TO, PREF_NONE);
3353 if (compose->replyto)
3354 compose_entry_append(compose,
3356 COMPOSE_CC, PREF_NONE);
3358 if (!folder_has_parent_of_type(msginfo->folder, F_QUEUE) &&
3359 !folder_has_parent_of_type(msginfo->folder, F_OUTBOX) &&
3360 !folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
3361 if (compose->replyto) {
3362 compose_entry_append(compose,
3364 COMPOSE_TO, PREF_NONE);
3366 compose_entry_append(compose,
3367 msginfo->from ? msginfo->from : "",
3368 COMPOSE_TO, PREF_NONE);
3371 /* replying to own mail, use original recp */
3372 compose_entry_append(compose,
3373 msginfo->to ? msginfo->to : "",
3374 COMPOSE_TO, PREF_NONE);
3375 compose_entry_append(compose,
3376 msginfo->cc ? msginfo->cc : "",
3377 COMPOSE_CC, PREF_NONE);
3382 if (to_sender || (compose->followup_to &&
3383 !strncmp(compose->followup_to, "poster", 6)))
3384 compose_entry_append
3386 (compose->replyto ? compose->replyto :
3387 msginfo->from ? msginfo->from : ""),
3388 COMPOSE_TO, PREF_NONE);
3390 else if (followup_and_reply_to || to_all) {
3391 compose_entry_append
3393 (compose->replyto ? compose->replyto :
3394 msginfo->from ? msginfo->from : ""),
3395 COMPOSE_TO, PREF_NONE);
3397 compose_entry_append
3399 compose->followup_to ? compose->followup_to :
3400 compose->newsgroups ? compose->newsgroups : "",
3401 COMPOSE_NEWSGROUPS, PREF_NONE);
3403 compose_entry_append
3405 msginfo->cc ? msginfo->cc : "",
3406 COMPOSE_CC, 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_reply_set_subject(compose, msginfo);
3417 if (to_ml && compose->ml_post) return;
3418 if (!to_all || compose->account->protocol == A_NNTP) return;
3420 if (compose->replyto) {
3421 Xstrdup_a(replyto, compose->replyto, return);
3422 extract_address(replyto);
3424 if (msginfo->from) {
3425 Xstrdup_a(from, msginfo->from, return);
3426 extract_address(from);
3429 if (replyto && from)
3430 cc_list = address_list_append_with_comments(cc_list, from);
3431 if (to_all && msginfo->folder &&
3432 msginfo->folder->prefs->enable_default_reply_to)
3433 cc_list = address_list_append_with_comments(cc_list,
3434 msginfo->folder->prefs->default_reply_to);
3435 cc_list = address_list_append_with_comments(cc_list, msginfo->to);
3436 cc_list = address_list_append_with_comments(cc_list, compose->cc);
3438 ac_email = g_utf8_strdown(compose->account->address, -1);
3441 for (cur = cc_list; cur != NULL; cur = cur->next) {
3442 gchar *addr = g_utf8_strdown(cur->data, -1);
3443 extract_address(addr);
3445 if (strcmp(ac_email, addr))
3446 compose_entry_append(compose, (gchar *)cur->data,
3447 COMPOSE_CC, PREF_NONE);
3449 debug_print("Cc address same as compose account's, ignoring\n");
3454 slist_free_strings_full(cc_list);
3460 #define SET_ENTRY(entry, str) \
3463 gtk_entry_set_text(GTK_ENTRY(compose->entry), str); \
3466 #define SET_ADDRESS(type, str) \
3469 compose_entry_append(compose, str, type, PREF_NONE); \
3472 static void compose_reedit_set_entry(Compose *compose, MsgInfo *msginfo)
3474 cm_return_if_fail(msginfo != NULL);
3476 SET_ENTRY(subject_entry, msginfo->subject);
3477 SET_ENTRY(from_name, msginfo->from);
3478 SET_ADDRESS(COMPOSE_TO, msginfo->to);
3479 SET_ADDRESS(COMPOSE_CC, compose->cc);
3480 SET_ADDRESS(COMPOSE_BCC, compose->bcc);
3481 SET_ADDRESS(COMPOSE_REPLYTO, compose->replyto);
3482 SET_ADDRESS(COMPOSE_NEWSGROUPS, compose->newsgroups);
3483 SET_ADDRESS(COMPOSE_FOLLOWUPTO, compose->followup_to);
3485 compose_update_priority_menu_item(compose);
3486 compose_update_privacy_system_menu_item(compose, FALSE);
3487 compose_show_first_last_header(compose, TRUE);
3493 static void compose_insert_sig(Compose *compose, gboolean replace)
3495 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3496 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3498 GtkTextIter iter, iter_end;
3499 gint cur_pos, ins_pos;
3500 gboolean prev_autowrap;
3501 gboolean found = FALSE;
3502 gboolean exists = FALSE;
3504 cm_return_if_fail(compose->account != NULL);
3508 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3509 G_CALLBACK(compose_changed_cb),
3512 mark = gtk_text_buffer_get_insert(buffer);
3513 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3514 cur_pos = gtk_text_iter_get_offset (&iter);
3517 gtk_text_buffer_get_end_iter(buffer, &iter);
3519 exists = (compose->sig_str != NULL);
3522 GtkTextIter first_iter, start_iter, end_iter;
3524 gtk_text_buffer_get_start_iter(buffer, &first_iter);
3526 if (!exists || compose->sig_str[0] == '\0')
3529 found = gtk_text_iter_forward_to_tag_toggle(&first_iter,
3530 compose->signature_tag);
3533 /* include previous \n\n */
3534 gtk_text_iter_backward_chars(&first_iter, 1);
3535 start_iter = first_iter;
3536 end_iter = first_iter;
3538 found = gtk_text_iter_forward_to_tag_toggle(&end_iter,
3539 compose->signature_tag);
3540 found &= gtk_text_iter_forward_to_tag_toggle(&end_iter,
3541 compose->signature_tag);
3543 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
3549 g_free(compose->sig_str);
3550 compose->sig_str = account_get_signature_str(compose->account);
3552 cur_pos = gtk_text_iter_get_offset(&iter);
3554 if (!compose->sig_str || (replace && !compose->account->auto_sig)) {
3555 g_free(compose->sig_str);
3556 compose->sig_str = NULL;
3558 if (compose->sig_inserted == FALSE)
3559 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
3560 compose->sig_inserted = TRUE;
3562 cur_pos = gtk_text_iter_get_offset(&iter);
3563 gtk_text_buffer_insert(buffer, &iter, compose->sig_str, -1);
3565 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cur_pos);
3566 gtk_text_iter_forward_chars(&iter, 1);
3567 gtk_text_buffer_get_end_iter(buffer, &iter_end);
3568 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &iter_end);
3570 if (cur_pos > gtk_text_buffer_get_char_count (buffer))
3571 cur_pos = gtk_text_buffer_get_char_count (buffer);
3574 /* put the cursor where it should be
3575 * either where the quote_fmt says, either where it was */
3576 if (compose->set_cursor_pos < 0)
3577 gtk_text_buffer_get_iter_at_offset(buffer, &iter, ins_pos);
3579 gtk_text_buffer_get_iter_at_offset(buffer, &iter,
3580 compose->set_cursor_pos);
3582 compose->set_cursor_pos = -1;
3583 gtk_text_buffer_place_cursor(buffer, &iter);
3584 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3585 G_CALLBACK(compose_changed_cb),
3591 static ComposeInsertResult compose_insert_file(Compose *compose, const gchar *file)
3594 GtkTextBuffer *buffer;
3597 const gchar *cur_encoding;
3598 gchar buf[BUFFSIZE];
3601 gboolean prev_autowrap;
3604 GString *file_contents = NULL;
3605 ComposeInsertResult result = COMPOSE_INSERT_SUCCESS;
3607 cm_return_val_if_fail(file != NULL, COMPOSE_INSERT_NO_FILE);
3609 /* get the size of the file we are about to insert */
3610 ret = g_stat(file, &file_stat);
3612 gchar *shortfile = g_path_get_basename(file);
3613 alertpanel_error(_("Could not get size of file '%s'."), shortfile);
3615 return COMPOSE_INSERT_NO_FILE;
3616 } else if (prefs_common.warn_large_insert == TRUE) {
3618 /* ask user for confirmation if the file is large */
3619 if (prefs_common.warn_large_insert_size < 0 ||
3620 file_stat.st_size > (prefs_common.warn_large_insert_size * 1024)) {
3624 msg = g_strdup_printf(_("You are about to insert a file of %s "
3625 "in the message body. Are you sure you want to do that?"),
3626 to_human_readable(file_stat.st_size));
3627 aval = alertpanel_full(_("Are you sure?"), msg, GTK_STOCK_CANCEL,
3628 g_strconcat("+", _("_Insert"), NULL), NULL, TRUE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
3631 /* do we ask for confirmation next time? */
3632 if (aval & G_ALERTDISABLE) {
3633 /* no confirmation next time, disable feature in preferences */
3634 aval &= ~G_ALERTDISABLE;
3635 prefs_common.warn_large_insert = FALSE;
3638 /* abort file insertion if user canceled action */
3639 if (aval != G_ALERTALTERNATE) {
3640 return COMPOSE_INSERT_NO_FILE;
3646 if ((fp = g_fopen(file, "rb")) == NULL) {
3647 FILE_OP_ERROR(file, "fopen");
3648 return COMPOSE_INSERT_READ_ERROR;
3651 prev_autowrap = compose->autowrap;
3652 compose->autowrap = FALSE;
3654 text = GTK_TEXT_VIEW(compose->text);
3655 buffer = gtk_text_view_get_buffer(text);
3656 mark = gtk_text_buffer_get_insert(buffer);
3657 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3659 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3660 G_CALLBACK(text_inserted),
3663 cur_encoding = conv_get_locale_charset_str_no_utf8();
3665 file_contents = g_string_new("");
3666 while (fgets(buf, sizeof(buf), fp) != NULL) {
3669 if (g_utf8_validate(buf, -1, NULL) == TRUE)
3670 str = g_strdup(buf);
3672 codeconv_set_strict(TRUE);
3673 str = conv_codeset_strdup
3674 (buf, cur_encoding, CS_INTERNAL);
3675 codeconv_set_strict(FALSE);
3678 result = COMPOSE_INSERT_INVALID_CHARACTER;
3684 /* strip <CR> if DOS/Windows file,
3685 replace <CR> with <LF> if Macintosh file. */
3688 if (len > 0 && str[len - 1] != '\n') {
3690 if (str[len] == '\r') str[len] = '\n';
3693 file_contents = g_string_append(file_contents, str);
3697 if (result == COMPOSE_INSERT_SUCCESS) {
3698 gtk_text_buffer_insert(buffer, &iter, file_contents->str, -1);
3700 compose_changed_cb(NULL, compose);
3701 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3702 G_CALLBACK(text_inserted),
3704 compose->autowrap = prev_autowrap;
3705 if (compose->autowrap)
3706 compose_wrap_all(compose);
3709 g_string_free(file_contents, TRUE);
3715 static gboolean compose_attach_append(Compose *compose, const gchar *file,
3716 const gchar *filename,
3717 const gchar *content_type,
3718 const gchar *charset)
3726 GtkListStore *store;
3728 gboolean has_binary = FALSE;
3730 if (!is_file_exist(file)) {
3731 gchar *file_from_uri = g_filename_from_uri(file, NULL, NULL);
3732 gboolean result = FALSE;
3733 if (file_from_uri && is_file_exist(file_from_uri)) {
3734 result = compose_attach_append(
3735 compose, file_from_uri,
3736 filename, content_type,
3739 g_free(file_from_uri);
3742 alertpanel_error("File %s doesn't exist\n", filename);
3745 if ((size = get_file_size(file)) < 0) {
3746 alertpanel_error("Can't get file size of %s\n", filename);
3750 /* In batch mode, we allow 0-length files to be attached no questions asked */
3751 if (size == 0 && !compose->batch) {
3752 gchar * msg = g_strdup_printf(_("File %s is empty."), filename);
3753 AlertValue aval = alertpanel_full(_("Empty file"), msg,
3754 GTK_STOCK_CANCEL, g_strconcat("+", _("_Attach anyway"), NULL), NULL, FALSE,
3755 NULL, ALERT_WARNING, G_ALERTDEFAULT);
3758 if (aval != G_ALERTALTERNATE) {
3762 if ((fp = g_fopen(file, "rb")) == NULL) {
3763 alertpanel_error(_("Can't read %s."), filename);
3768 ainfo = g_new0(AttachInfo, 1);
3769 auto_ainfo = g_auto_pointer_new_with_free
3770 (ainfo, (GFreeFunc) compose_attach_info_free);
3771 ainfo->file = g_strdup(file);
3774 ainfo->content_type = g_strdup(content_type);
3775 if (!g_ascii_strcasecmp(content_type, "message/rfc822")) {
3777 MsgFlags flags = {0, 0};
3779 if (procmime_get_encoding_for_text_file(file, &has_binary) == ENC_7BIT)
3780 ainfo->encoding = ENC_7BIT;
3782 ainfo->encoding = ENC_8BIT;
3784 msginfo = procheader_parse_file(file, flags, FALSE, FALSE);
3785 if (msginfo && msginfo->subject)
3786 name = g_strdup(msginfo->subject);
3788 name = g_path_get_basename(filename ? filename : file);
3790 ainfo->name = g_strdup_printf(_("Message: %s"), name);
3792 procmsg_msginfo_free(&msginfo);
3794 if (!g_ascii_strncasecmp(content_type, "text/", 5)) {
3795 ainfo->charset = g_strdup(charset);
3796 ainfo->encoding = procmime_get_encoding_for_text_file(file, &has_binary);
3798 ainfo->encoding = ENC_BASE64;
3800 name = g_path_get_basename(filename ? filename : file);
3801 ainfo->name = g_strdup(name);
3805 ainfo->content_type = procmime_get_mime_type(file);
3806 if (!ainfo->content_type) {
3807 ainfo->content_type =
3808 g_strdup("application/octet-stream");
3809 ainfo->encoding = ENC_BASE64;
3810 } else if (!g_ascii_strncasecmp(ainfo->content_type, "text/", 5))
3812 procmime_get_encoding_for_text_file(file, &has_binary);
3814 ainfo->encoding = ENC_BASE64;
3815 name = g_path_get_basename(filename ? filename : file);
3816 ainfo->name = g_strdup(name);
3820 if (ainfo->name != NULL
3821 && !strcmp(ainfo->name, ".")) {
3822 g_free(ainfo->name);
3826 if (!strcmp(ainfo->content_type, "unknown") || has_binary) {
3827 g_free(ainfo->content_type);
3828 ainfo->content_type = g_strdup("application/octet-stream");
3829 g_free(ainfo->charset);
3830 ainfo->charset = NULL;
3833 ainfo->size = (goffset)size;
3834 size_text = to_human_readable((goffset)size);
3836 store = GTK_LIST_STORE(gtk_tree_view_get_model
3837 (GTK_TREE_VIEW(compose->attach_clist)));
3839 gtk_list_store_append(store, &iter);
3840 gtk_list_store_set(store, &iter,
3841 COL_MIMETYPE, ainfo->content_type,
3842 COL_SIZE, size_text,
3843 COL_NAME, ainfo->name,
3844 COL_CHARSET, ainfo->charset,
3846 COL_AUTODATA, auto_ainfo,
3849 g_auto_pointer_free(auto_ainfo);
3850 compose_attach_update_label(compose);
3854 static void compose_use_signing(Compose *compose, gboolean use_signing)
3856 compose->use_signing = use_signing;
3857 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", use_signing);
3860 static void compose_use_encryption(Compose *compose, gboolean use_encryption)
3862 compose->use_encryption = use_encryption;
3863 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", use_encryption);
3866 #define NEXT_PART_NOT_CHILD(info) \
3868 node = info->node; \
3869 while (node->children) \
3870 node = g_node_last_child(node); \
3871 info = procmime_mimeinfo_next((MimeInfo *)node->data); \
3874 static void compose_attach_parts(Compose *compose, MsgInfo *msginfo)
3878 MimeInfo *firsttext = NULL;
3879 MimeInfo *encrypted = NULL;
3882 const gchar *partname = NULL;
3884 mimeinfo = procmime_scan_message(msginfo);
3885 if (!mimeinfo) return;
3887 if (mimeinfo->node->children == NULL) {
3888 procmime_mimeinfo_free_all(&mimeinfo);
3892 /* find first content part */
3893 child = (MimeInfo *) mimeinfo->node->children->data;
3894 while (child && child->node->children && (child->type == MIMETYPE_MULTIPART))
3895 child = (MimeInfo *)child->node->children->data;
3898 if (child->type == MIMETYPE_TEXT) {
3900 debug_print("First text part found\n");
3901 } else if (compose->mode == COMPOSE_REEDIT &&
3902 child->type == MIMETYPE_APPLICATION &&
3903 !g_ascii_strcasecmp(child->subtype, "pgp-encrypted")) {
3904 encrypted = (MimeInfo *)child->node->parent->data;
3907 child = (MimeInfo *) mimeinfo->node->children->data;
3908 while (child != NULL) {
3911 if (child == encrypted) {
3912 /* skip this part of tree */
3913 NEXT_PART_NOT_CHILD(child);
3917 if (child->type == MIMETYPE_MULTIPART) {
3918 /* get the actual content */
3919 child = procmime_mimeinfo_next(child);
3923 if (child == firsttext) {
3924 child = procmime_mimeinfo_next(child);
3928 outfile = procmime_get_tmp_file_name(child);
3929 if ((err = procmime_get_part(outfile, child)) < 0)
3930 g_warning("Can't get the part of multipart message. (%s)", g_strerror(-err));
3932 gchar *content_type;
3934 content_type = procmime_get_content_type_str(child->type, child->subtype);
3936 /* if we meet a pgp signature, we don't attach it, but
3937 * we force signing. */
3938 if ((strcmp(content_type, "application/pgp-signature") &&
3939 strcmp(content_type, "application/pkcs7-signature") &&
3940 strcmp(content_type, "application/x-pkcs7-signature"))
3941 || compose->mode == COMPOSE_REDIRECT) {
3942 partname = procmime_mimeinfo_get_parameter(child, "filename");
3943 if (partname == NULL)
3944 partname = procmime_mimeinfo_get_parameter(child, "name");
3945 if (partname == NULL)
3947 compose_attach_append(compose, outfile,
3948 partname, content_type,
3949 procmime_mimeinfo_get_parameter(child, "charset"));
3951 compose_force_signing(compose, compose->account, NULL);
3953 g_free(content_type);
3956 NEXT_PART_NOT_CHILD(child);
3958 procmime_mimeinfo_free_all(&mimeinfo);
3961 #undef NEXT_PART_NOT_CHILD
3966 WAIT_FOR_INDENT_CHAR,
3967 WAIT_FOR_INDENT_CHAR_OR_SPACE,
3970 /* return indent length, we allow:
3971 indent characters followed by indent characters or spaces/tabs,
3972 alphabets and numbers immediately followed by indent characters,
3973 and the repeating sequences of the above
3974 If quote ends with multiple spaces, only the first one is included. */
3975 static gchar *compose_get_quote_str(GtkTextBuffer *buffer,
3976 const GtkTextIter *start, gint *len)
3978 GtkTextIter iter = *start;
3982 IndentState state = WAIT_FOR_INDENT_CHAR;
3985 gint alnum_count = 0;
3986 gint space_count = 0;
3989 if (prefs_common.quote_chars == NULL) {
3993 while (!gtk_text_iter_ends_line(&iter)) {
3994 wc = gtk_text_iter_get_char(&iter);
3995 if (g_unichar_iswide(wc))
3997 clen = g_unichar_to_utf8(wc, ch);
4001 is_indent = strchr(prefs_common.quote_chars, ch[0]) ? TRUE : FALSE;
4002 is_space = g_unichar_isspace(wc);
4004 if (state == WAIT_FOR_INDENT_CHAR) {
4005 if (!is_indent && !g_unichar_isalnum(wc))
4008 quote_len += alnum_count + space_count + 1;
4009 alnum_count = space_count = 0;
4010 state = WAIT_FOR_INDENT_CHAR_OR_SPACE;
4013 } else if (state == WAIT_FOR_INDENT_CHAR_OR_SPACE) {
4014 if (!is_indent && !is_space && !g_unichar_isalnum(wc))
4018 else if (is_indent) {
4019 quote_len += alnum_count + space_count + 1;
4020 alnum_count = space_count = 0;
4023 state = WAIT_FOR_INDENT_CHAR;
4027 gtk_text_iter_forward_char(&iter);
4030 if (quote_len > 0 && space_count > 0)
4036 if (quote_len > 0) {
4038 gtk_text_iter_forward_chars(&iter, quote_len);
4039 return gtk_text_buffer_get_text(buffer, start, &iter, FALSE);
4045 /* return >0 if the line is itemized */
4046 static int compose_itemized_length(GtkTextBuffer *buffer,
4047 const GtkTextIter *start)
4049 GtkTextIter iter = *start;
4054 if (gtk_text_iter_ends_line(&iter))
4059 wc = gtk_text_iter_get_char(&iter);
4060 if (!g_unichar_isspace(wc))
4062 gtk_text_iter_forward_char(&iter);
4063 if (gtk_text_iter_ends_line(&iter))
4067 clen = g_unichar_to_utf8(wc, ch);
4071 if (!strchr("*-+", ch[0]))
4074 gtk_text_iter_forward_char(&iter);
4075 if (gtk_text_iter_ends_line(&iter))
4077 wc = gtk_text_iter_get_char(&iter);
4078 if (g_unichar_isspace(wc)) {
4084 /* return the string at the start of the itemization */
4085 static gchar * compose_get_itemized_chars(GtkTextBuffer *buffer,
4086 const GtkTextIter *start)
4088 GtkTextIter iter = *start;
4091 GString *item_chars = g_string_new("");
4094 if (gtk_text_iter_ends_line(&iter))
4099 wc = gtk_text_iter_get_char(&iter);
4100 if (!g_unichar_isspace(wc))
4102 gtk_text_iter_forward_char(&iter);
4103 if (gtk_text_iter_ends_line(&iter))
4105 g_string_append_unichar(item_chars, wc);
4108 str = item_chars->str;
4109 g_string_free(item_chars, FALSE);
4113 /* return the number of spaces at a line's start */
4114 static int compose_left_offset_length(GtkTextBuffer *buffer,
4115 const GtkTextIter *start)
4117 GtkTextIter iter = *start;
4120 if (gtk_text_iter_ends_line(&iter))
4124 wc = gtk_text_iter_get_char(&iter);
4125 if (!g_unichar_isspace(wc))
4128 gtk_text_iter_forward_char(&iter);
4129 if (gtk_text_iter_ends_line(&iter))
4133 gtk_text_iter_forward_char(&iter);
4134 if (gtk_text_iter_ends_line(&iter))
4139 static gboolean compose_get_line_break_pos(GtkTextBuffer *buffer,
4140 const GtkTextIter *start,
4141 GtkTextIter *break_pos,
4145 GtkTextIter iter = *start, line_end = *start;
4146 PangoLogAttr *attrs;
4153 gboolean can_break = FALSE;
4154 gboolean do_break = FALSE;
4155 gboolean was_white = FALSE;
4156 gboolean prev_dont_break = FALSE;
4158 gtk_text_iter_forward_to_line_end(&line_end);
4159 str = gtk_text_buffer_get_text(buffer, &iter, &line_end, FALSE);
4160 len = g_utf8_strlen(str, -1);
4164 g_warning("compose_get_line_break_pos: len = 0!");
4168 /* g_print("breaking line: %d: %s (len = %d)\n",
4169 gtk_text_iter_get_line(&iter), str, len); */
4171 attrs = g_new(PangoLogAttr, len + 1);
4173 pango_default_break(str, -1, NULL, attrs, len + 1);
4177 /* skip quote and leading spaces */
4178 for (i = 0; *p != '\0' && i < len; i++) {
4181 wc = g_utf8_get_char(p);
4182 if (i >= quote_len && !g_unichar_isspace(wc))
4184 if (g_unichar_iswide(wc))
4186 else if (*p == '\t')
4190 p = g_utf8_next_char(p);
4193 for (; *p != '\0' && i < len; i++) {
4194 PangoLogAttr *attr = attrs + i;
4198 if (attr->is_line_break && can_break && was_white && !prev_dont_break)
4201 was_white = attr->is_white;
4203 /* don't wrap URI */
4204 if ((uri_len = get_uri_len(p)) > 0) {
4206 if (pos > 0 && col > max_col) {
4216 wc = g_utf8_get_char(p);
4217 if (g_unichar_iswide(wc)) {
4219 if (prev_dont_break && can_break && attr->is_line_break)
4221 } else if (*p == '\t')
4225 if (pos > 0 && col > max_col) {
4230 if (*p == '-' || *p == '/')
4231 prev_dont_break = TRUE;
4233 prev_dont_break = FALSE;
4235 p = g_utf8_next_char(p);
4239 // debug_print("compose_get_line_break_pos(): do_break = %d, pos = %d, col = %d\n", do_break, pos, col);
4244 *break_pos = *start;
4245 gtk_text_iter_set_line_offset(break_pos, pos);
4250 static gboolean compose_join_next_line(Compose *compose,
4251 GtkTextBuffer *buffer,
4253 const gchar *quote_str)
4255 GtkTextIter iter_ = *iter, cur, prev, next, end;
4256 PangoLogAttr attrs[3];
4258 gchar *next_quote_str;
4261 gboolean keep_cursor = FALSE;
4263 if (!gtk_text_iter_forward_line(&iter_) ||
4264 gtk_text_iter_ends_line(&iter_)) {
4267 next_quote_str = compose_get_quote_str(buffer, &iter_, "e_len);
4269 if ((quote_str || next_quote_str) &&
4270 strcmp2(quote_str, next_quote_str) != 0) {
4271 g_free(next_quote_str);
4274 g_free(next_quote_str);
4277 if (quote_len > 0) {
4278 gtk_text_iter_forward_chars(&end, quote_len);
4279 if (gtk_text_iter_ends_line(&end)) {
4284 /* don't join itemized lines */
4285 if (compose_itemized_length(buffer, &end) > 0) {
4289 /* don't join signature separator */
4290 if (compose_is_sig_separator(compose, buffer, &iter_)) {
4293 /* delete quote str */
4295 gtk_text_buffer_delete(buffer, &iter_, &end);
4297 /* don't join line breaks put by the user */
4299 gtk_text_iter_backward_char(&cur);
4300 if (gtk_text_iter_has_tag(&cur, compose->no_join_tag)) {
4301 gtk_text_iter_forward_char(&cur);
4305 gtk_text_iter_forward_char(&cur);
4306 /* delete linebreak and extra spaces */
4307 while (gtk_text_iter_backward_char(&cur)) {
4308 wc1 = gtk_text_iter_get_char(&cur);
4309 if (!g_unichar_isspace(wc1))
4314 while (!gtk_text_iter_ends_line(&cur)) {
4315 wc1 = gtk_text_iter_get_char(&cur);
4316 if (!g_unichar_isspace(wc1))
4318 gtk_text_iter_forward_char(&cur);
4321 if (!gtk_text_iter_equal(&prev, &next)) {
4324 mark = gtk_text_buffer_get_insert(buffer);
4325 gtk_text_buffer_get_iter_at_mark(buffer, &cur, mark);
4326 if (gtk_text_iter_equal(&prev, &cur))
4328 gtk_text_buffer_delete(buffer, &prev, &next);
4332 /* insert space if required */
4333 gtk_text_iter_backward_char(&prev);
4334 wc1 = gtk_text_iter_get_char(&prev);
4335 wc2 = gtk_text_iter_get_char(&next);
4336 gtk_text_iter_forward_char(&next);
4337 str = gtk_text_buffer_get_text(buffer, &prev, &next, FALSE);
4338 pango_default_break(str, -1, NULL, attrs, 3);
4339 if (!attrs[1].is_line_break ||
4340 (!g_unichar_iswide(wc1) || !g_unichar_iswide(wc2))) {
4341 gtk_text_buffer_insert(buffer, &iter_, " ", 1);
4343 gtk_text_iter_backward_char(&iter_);
4344 gtk_text_buffer_place_cursor(buffer, &iter_);
4353 #define ADD_TXT_POS(bp_, ep_, pti_) \
4354 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
4355 last = last->next; \
4356 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
4357 last->next = NULL; \
4359 g_warning("alloc error scanning URIs"); \
4362 static gboolean compose_beautify_paragraph(Compose *compose, GtkTextIter *par_iter, gboolean force)
4364 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4365 GtkTextBuffer *buffer;
4366 GtkTextIter iter, break_pos, end_of_line;
4367 gchar *quote_str = NULL;
4369 gboolean wrap_quote = force || prefs_common.linewrap_quote;
4370 gboolean prev_autowrap = compose->autowrap;
4371 gint startq_offset = -1, noq_offset = -1;
4372 gint uri_start = -1, uri_stop = -1;
4373 gint nouri_start = -1, nouri_stop = -1;
4374 gint num_blocks = 0;
4375 gint quotelevel = -1;
4376 gboolean modified = force;
4377 gboolean removed = FALSE;
4378 gboolean modified_before_remove = FALSE;
4380 gboolean start = TRUE;
4381 gint itemized_len = 0, rem_item_len = 0;
4382 gchar *itemized_chars = NULL;
4383 gboolean item_continuation = FALSE;
4388 if (compose->draft_timeout_tag == COMPOSE_DRAFT_TIMEOUT_FORBIDDEN) {
4392 compose->autowrap = FALSE;
4394 buffer = gtk_text_view_get_buffer(text);
4395 undo_wrapping(compose->undostruct, TRUE);
4400 mark = gtk_text_buffer_get_insert(buffer);
4401 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
4405 if (compose->draft_timeout_tag == COMPOSE_DRAFT_TIMEOUT_FORBIDDEN) {
4406 if (gtk_text_iter_ends_line(&iter)) {
4407 while (gtk_text_iter_ends_line(&iter) &&
4408 gtk_text_iter_forward_line(&iter))
4411 while (gtk_text_iter_backward_line(&iter)) {
4412 if (gtk_text_iter_ends_line(&iter)) {
4413 gtk_text_iter_forward_line(&iter);
4419 /* move to line start */
4420 gtk_text_iter_set_line_offset(&iter, 0);
4423 itemized_len = compose_itemized_length(buffer, &iter);
4425 if (!itemized_len) {
4426 itemized_len = compose_left_offset_length(buffer, &iter);
4427 item_continuation = TRUE;
4431 itemized_chars = compose_get_itemized_chars(buffer, &iter);
4433 /* go until paragraph end (empty line) */
4434 while (start || !gtk_text_iter_ends_line(&iter)) {
4435 gchar *scanpos = NULL;
4436 /* parse table - in order of priority */
4438 const gchar *needle; /* token */
4440 /* token search function */
4441 gchar *(*search) (const gchar *haystack,
4442 const gchar *needle);
4443 /* part parsing function */
4444 gboolean (*parse) (const gchar *start,
4445 const gchar *scanpos,
4449 /* part to URI function */
4450 gchar *(*build_uri) (const gchar *bp,
4454 static struct table parser[] = {
4455 {"http://", strcasestr, get_uri_part, make_uri_string},
4456 {"https://", strcasestr, get_uri_part, make_uri_string},
4457 {"ftp://", strcasestr, get_uri_part, make_uri_string},
4458 {"sftp://", strcasestr, get_uri_part, make_uri_string},
4459 {"gopher://",strcasestr, get_uri_part, make_uri_string},
4460 {"www.", strcasestr, get_uri_part, make_http_string},
4461 {"mailto:", strcasestr, get_uri_part, make_uri_string},
4462 {"@", strcasestr, get_email_part, make_email_string}
4464 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
4465 gint last_index = PARSE_ELEMS;
4467 gchar *o_walk = NULL, *walk = NULL, *bp = NULL, *ep = NULL;
4471 if (!prev_autowrap && num_blocks == 0) {
4473 g_signal_handlers_block_by_func(G_OBJECT(buffer),
4474 G_CALLBACK(text_inserted),
4477 if (gtk_text_iter_has_tag(&iter, compose->no_wrap_tag) && !force)
4480 uri_start = uri_stop = -1;
4482 quote_str = compose_get_quote_str(buffer, &iter, "e_len);
4485 // debug_print("compose_beautify_paragraph(): quote_str = '%s'\n", quote_str);
4486 if (startq_offset == -1)
4487 startq_offset = gtk_text_iter_get_offset(&iter);
4488 quotelevel = get_quote_level(quote_str, prefs_common.quote_chars);
4489 if (quotelevel > 2) {
4490 /* recycle colors */
4491 if (prefs_common.recycle_quote_colors)
4500 if (startq_offset == -1)
4501 noq_offset = gtk_text_iter_get_offset(&iter);
4505 if (prev_autowrap == FALSE && !force && !wrap_quote) {
4508 if (gtk_text_iter_ends_line(&iter)) {
4510 } else if (compose_get_line_break_pos(buffer, &iter, &break_pos,
4511 prefs_common.linewrap_len,
4513 GtkTextIter prev, next, cur;
4514 if (prev_autowrap != FALSE || force) {
4515 compose->automatic_break = TRUE;
4517 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4518 compose->automatic_break = FALSE;
4519 if (itemized_len && compose->autoindent) {
4520 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4521 if (!item_continuation)
4522 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4524 } else if (quote_str && wrap_quote) {
4525 compose->automatic_break = TRUE;
4527 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4528 compose->automatic_break = FALSE;
4529 if (itemized_len && compose->autoindent) {
4530 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4531 if (!item_continuation)
4532 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4536 /* remove trailing spaces */
4538 rem_item_len = itemized_len;
4539 while (compose->autoindent && rem_item_len-- > 0)
4540 gtk_text_iter_backward_char(&cur);
4541 gtk_text_iter_backward_char(&cur);
4544 while (!gtk_text_iter_starts_line(&cur)) {
4547 gtk_text_iter_backward_char(&cur);
4548 wc = gtk_text_iter_get_char(&cur);
4549 if (!g_unichar_isspace(wc))
4553 if (!gtk_text_iter_equal(&prev, &next)) {
4554 gtk_text_buffer_delete(buffer, &prev, &next);
4556 gtk_text_iter_forward_char(&break_pos);
4560 gtk_text_buffer_insert(buffer, &break_pos,
4564 modified |= compose_join_next_line(compose, buffer, &iter, quote_str);
4566 /* move iter to current line start */
4567 gtk_text_iter_set_line_offset(&iter, 0);
4574 /* move iter to next line start */
4580 if (!prev_autowrap && num_blocks > 0) {
4582 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
4583 G_CALLBACK(text_inserted),
4587 while (!gtk_text_iter_ends_line(&end_of_line)) {
4588 gtk_text_iter_forward_char(&end_of_line);
4590 o_walk = walk = gtk_text_buffer_get_text(buffer, &iter, &end_of_line, FALSE);
4592 nouri_start = gtk_text_iter_get_offset(&iter);
4593 nouri_stop = gtk_text_iter_get_offset(&end_of_line);
4595 walk_pos = gtk_text_iter_get_offset(&iter);
4596 /* FIXME: this looks phony. scanning for anything in the parse table */
4597 for (n = 0; n < PARSE_ELEMS; n++) {
4600 tmp = parser[n].search(walk, parser[n].needle);
4602 if (scanpos == NULL || tmp < scanpos) {
4611 /* check if URI can be parsed */
4612 if (parser[last_index].parse(walk, scanpos, (const gchar **)&bp,
4613 (const gchar **)&ep, FALSE)
4614 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
4618 strlen(parser[last_index].needle);
4621 uri_start = walk_pos + (bp - o_walk);
4622 uri_stop = walk_pos + (ep - o_walk);
4626 gtk_text_iter_forward_line(&iter);
4629 if (startq_offset != -1) {
4630 GtkTextIter startquote, endquote;
4631 gtk_text_buffer_get_iter_at_offset(
4632 buffer, &startquote, startq_offset);
4635 switch (quotelevel) {
4637 if (!gtk_text_iter_has_tag(&startquote, compose->quote0_tag) ||
4638 !gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) {
4639 gtk_text_buffer_apply_tag_by_name(
4640 buffer, "quote0", &startquote, &endquote);
4641 gtk_text_buffer_remove_tag_by_name(
4642 buffer, "quote1", &startquote, &endquote);
4643 gtk_text_buffer_remove_tag_by_name(
4644 buffer, "quote2", &startquote, &endquote);
4649 if (!gtk_text_iter_has_tag(&startquote, compose->quote1_tag) ||
4650 !gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) {
4651 gtk_text_buffer_apply_tag_by_name(
4652 buffer, "quote1", &startquote, &endquote);
4653 gtk_text_buffer_remove_tag_by_name(
4654 buffer, "quote0", &startquote, &endquote);
4655 gtk_text_buffer_remove_tag_by_name(
4656 buffer, "quote2", &startquote, &endquote);
4661 if (!gtk_text_iter_has_tag(&startquote, compose->quote2_tag) ||
4662 !gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag)) {
4663 gtk_text_buffer_apply_tag_by_name(
4664 buffer, "quote2", &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, "quote1", &startquote, &endquote);
4674 } else if (noq_offset != -1) {
4675 GtkTextIter startnoquote, endnoquote;
4676 gtk_text_buffer_get_iter_at_offset(
4677 buffer, &startnoquote, noq_offset);
4680 if ((gtk_text_iter_has_tag(&startnoquote, compose->quote0_tag)
4681 && gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) ||
4682 (gtk_text_iter_has_tag(&startnoquote, compose->quote1_tag)
4683 && gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) ||
4684 (gtk_text_iter_has_tag(&startnoquote, compose->quote2_tag)
4685 && gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag))) {
4686 gtk_text_buffer_remove_tag_by_name(
4687 buffer, "quote0", &startnoquote, &endnoquote);
4688 gtk_text_buffer_remove_tag_by_name(
4689 buffer, "quote1", &startnoquote, &endnoquote);
4690 gtk_text_buffer_remove_tag_by_name(
4691 buffer, "quote2", &startnoquote, &endnoquote);
4697 if (uri_start != nouri_start && uri_stop != nouri_stop) {
4698 GtkTextIter nouri_start_iter, nouri_end_iter;
4699 gtk_text_buffer_get_iter_at_offset(
4700 buffer, &nouri_start_iter, nouri_start);
4701 gtk_text_buffer_get_iter_at_offset(
4702 buffer, &nouri_end_iter, nouri_stop);
4703 if (gtk_text_iter_has_tag(&nouri_start_iter, compose->uri_tag) &&
4704 gtk_text_iter_has_tag(&nouri_end_iter, compose->uri_tag)) {
4705 gtk_text_buffer_remove_tag_by_name(
4706 buffer, "link", &nouri_start_iter, &nouri_end_iter);
4707 modified_before_remove = modified;
4712 if (uri_start >= 0 && uri_stop > 0) {
4713 GtkTextIter uri_start_iter, uri_end_iter, back;
4714 gtk_text_buffer_get_iter_at_offset(
4715 buffer, &uri_start_iter, uri_start);
4716 gtk_text_buffer_get_iter_at_offset(
4717 buffer, &uri_end_iter, uri_stop);
4718 back = uri_end_iter;
4719 gtk_text_iter_backward_char(&back);
4720 if (!gtk_text_iter_has_tag(&uri_start_iter, compose->uri_tag) ||
4721 !gtk_text_iter_has_tag(&back, compose->uri_tag)) {
4722 gtk_text_buffer_apply_tag_by_name(
4723 buffer, "link", &uri_start_iter, &uri_end_iter);
4725 if (removed && !modified_before_remove) {
4731 // debug_print("not modified, out after %d lines\n", lines);
4735 // debug_print("modified, out after %d lines\n", lines);
4737 g_free(itemized_chars);
4740 undo_wrapping(compose->undostruct, FALSE);
4741 compose->autowrap = prev_autowrap;
4746 void compose_action_cb(void *data)
4748 Compose *compose = (Compose *)data;
4749 compose_wrap_all(compose);
4752 static void compose_wrap_all(Compose *compose)
4754 compose_wrap_all_full(compose, FALSE);
4757 static void compose_wrap_all_full(Compose *compose, gboolean force)
4759 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4760 GtkTextBuffer *buffer;
4762 gboolean modified = TRUE;
4764 buffer = gtk_text_view_get_buffer(text);
4766 gtk_text_buffer_get_start_iter(buffer, &iter);
4768 undo_wrapping(compose->undostruct, TRUE);
4770 while (!gtk_text_iter_is_end(&iter) && modified)
4771 modified = compose_beautify_paragraph(compose, &iter, force);
4773 undo_wrapping(compose->undostruct, FALSE);
4777 static void compose_set_title(Compose *compose)
4783 edited = compose->modified ? _(" [Edited]") : "";
4785 subject = gtk_editable_get_chars(
4786 GTK_EDITABLE(compose->subject_entry), 0, -1);
4788 #ifndef GENERIC_UMPC
4789 if (subject && strlen(subject))
4790 str = g_strdup_printf(_("%s - Compose message%s"),
4793 str = g_strdup_printf(_("[no subject] - Compose message%s"), edited);
4795 str = g_strdup(_("Compose message"));
4798 gtk_window_set_title(GTK_WINDOW(compose->window), str);
4804 * compose_current_mail_account:
4806 * Find a current mail account (the currently selected account, or the
4807 * default account, if a news account is currently selected). If a
4808 * mail account cannot be found, display an error message.
4810 * Return value: Mail account, or NULL if not found.
4812 static PrefsAccount *
4813 compose_current_mail_account(void)
4817 if (cur_account && cur_account->protocol != A_NNTP)
4820 ac = account_get_default();
4821 if (!ac || ac->protocol == A_NNTP) {
4822 alertpanel_error(_("Account for sending mail is not specified.\n"
4823 "Please select a mail account before sending."));
4830 #define QUOTE_IF_REQUIRED(out, str) \
4832 if (*str != '"' && strpbrk(str, ",.:;[]<>()@\\\"")) { \
4836 len = strlen(str) + 3; \
4837 if ((__tmp = alloca(len)) == NULL) { \
4838 g_warning("can't allocate memory"); \
4839 g_string_free(header, TRUE); \
4842 g_snprintf(__tmp, len, "\"%s\"", str); \
4847 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4848 g_warning("can't allocate memory"); \
4849 g_string_free(header, TRUE); \
4852 strcpy(__tmp, str); \
4858 #define QUOTE_IF_REQUIRED_NORMAL(out, str, errret) \
4860 if (*str != '"' && strpbrk(str, ",.:;[]<>()@\\\"")) { \
4864 len = strlen(str) + 3; \
4865 if ((__tmp = alloca(len)) == NULL) { \
4866 g_warning("can't allocate memory"); \
4869 g_snprintf(__tmp, len, "\"%s\"", str); \
4874 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4875 g_warning("can't allocate memory"); \
4878 strcpy(__tmp, str); \
4884 static void compose_select_account(Compose *compose, PrefsAccount *account,
4887 gchar *from = NULL, *header = NULL;
4888 ComposeHeaderEntry *header_entry;
4889 #if GTK_CHECK_VERSION(2, 24, 0)
4893 cm_return_if_fail(account != NULL);
4895 compose->account = account;
4896 if (account->name && *account->name) {
4898 QUOTE_IF_REQUIRED_NORMAL(buf, account->name, return);
4899 qbuf = escape_internal_quotes(buf, '"');
4900 from = g_strdup_printf("%s <%s>",
4901 qbuf, account->address);
4904 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4906 from = g_strdup_printf("<%s>",
4908 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4913 compose_set_title(compose);
4915 if (account->default_sign && compose->mode != COMPOSE_REDIRECT)
4916 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", TRUE);
4918 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", FALSE);
4919 if (account->default_encrypt && compose->mode != COMPOSE_REDIRECT)
4920 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", TRUE);
4922 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", FALSE);
4924 activate_privacy_system(compose, account, FALSE);
4926 if (!init && compose->mode != COMPOSE_REDIRECT) {
4927 undo_block(compose->undostruct);
4928 compose_insert_sig(compose, TRUE);
4929 undo_unblock(compose->undostruct);
4932 header_entry = (ComposeHeaderEntry *) compose->header_list->data;
4933 #if !GTK_CHECK_VERSION(2, 24, 0)
4934 header = gtk_combo_box_get_active_text(GTK_COMBO_BOX(header_entry->combo));
4936 if (gtk_combo_box_get_active_iter(GTK_COMBO_BOX(header_entry->combo), &iter))
4937 gtk_tree_model_get(gtk_combo_box_get_model(GTK_COMBO_BOX(
4938 header_entry->combo)), &iter, COMBOBOX_TEXT, &header, -1);
4941 if (header && !strlen(gtk_entry_get_text(GTK_ENTRY(header_entry->entry)))) {
4942 if (account->protocol == A_NNTP) {
4943 if (!strcmp(header, _("To:")))
4944 combobox_select_by_text(
4945 GTK_COMBO_BOX(header_entry->combo),
4948 if (!strcmp(header, _("Newsgroups:")))
4949 combobox_select_by_text(
4950 GTK_COMBO_BOX(header_entry->combo),
4958 /* use account's dict info if set */
4959 if (compose->gtkaspell) {
4960 if (account->enable_default_dictionary)
4961 gtkaspell_change_dict(compose->gtkaspell,
4962 account->default_dictionary, FALSE);
4963 if (account->enable_default_alt_dictionary)
4964 gtkaspell_change_alt_dict(compose->gtkaspell,
4965 account->default_alt_dictionary);
4966 if (account->enable_default_dictionary
4967 || account->enable_default_alt_dictionary)
4968 compose_spell_menu_changed(compose);
4973 gboolean compose_check_for_valid_recipient(Compose *compose) {
4974 gchar *recipient_headers_mail[] = {"To:", "Cc:", "Bcc:", NULL};
4975 gchar *recipient_headers_news[] = {"Newsgroups:", NULL};
4976 gboolean recipient_found = FALSE;
4980 /* free to and newsgroup list */
4981 slist_free_strings_full(compose->to_list);
4982 compose->to_list = NULL;
4984 slist_free_strings_full(compose->newsgroup_list);
4985 compose->newsgroup_list = NULL;
4987 /* search header entries for to and newsgroup entries */
4988 for (list = compose->header_list; list; list = list->next) {
4991 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
4992 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4995 if (entry[0] != '\0') {
4996 for (strptr = recipient_headers_mail; *strptr != NULL; strptr++) {
4997 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
4998 compose->to_list = address_list_append(compose->to_list, entry);
4999 recipient_found = TRUE;
5002 for (strptr = recipient_headers_news; *strptr != NULL; strptr++) {
5003 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
5004 compose->newsgroup_list = newsgroup_list_append(compose->newsgroup_list, entry);
5005 recipient_found = TRUE;
5012 return recipient_found;
5015 static gboolean compose_check_for_set_recipients(Compose *compose)
5017 if (compose->account->set_autocc && compose->account->auto_cc) {
5018 gboolean found_other = FALSE;
5020 /* search header entries for to and newsgroup entries */
5021 for (list = compose->header_list; list; list = list->next) {
5024 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5025 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5028 if (strcmp(entry, compose->account->auto_cc)
5029 || strcmp(header, prefs_common_translated_header_name("Cc:"))) {
5039 if (compose->batch) {
5040 gtk_widget_show_all(compose->window);
5042 aval = alertpanel(_("Send"),
5043 _("The only recipient is the default CC address. Send anyway?"),
5044 GTK_STOCK_CANCEL, g_strconcat("+", _("_Send"), NULL), NULL);
5045 if (aval != G_ALERTALTERNATE)
5049 if (compose->account->set_autobcc && compose->account->auto_bcc) {
5050 gboolean found_other = FALSE;
5052 /* search header entries for to and newsgroup entries */
5053 for (list = compose->header_list; list; list = list->next) {
5056 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5057 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5060 if (strcmp(entry, compose->account->auto_bcc)
5061 || strcmp(header, prefs_common_translated_header_name("Bcc:"))) {
5071 if (compose->batch) {
5072 gtk_widget_show_all(compose->window);
5074 aval = alertpanel(_("Send"),
5075 _("The only recipient is the default BCC address. Send anyway?"),
5076 GTK_STOCK_CANCEL, g_strconcat("+", _("_Send"), NULL), NULL);
5077 if (aval != G_ALERTALTERNATE)
5084 static gboolean compose_check_entries(Compose *compose, gboolean check_everything)
5088 if (compose_check_for_valid_recipient(compose) == FALSE) {
5089 if (compose->batch) {
5090 gtk_widget_show_all(compose->window);
5092 alertpanel_error(_("Recipient is not specified."));
5096 if (compose_check_for_set_recipients(compose) == FALSE) {
5100 if (!compose->batch && prefs_common.warn_empty_subj == TRUE) {
5101 str = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5102 if (*str == '\0' && check_everything == TRUE &&
5103 compose->mode != COMPOSE_REDIRECT) {
5105 gchar *button_label;
5108 if (compose->sending)
5109 button_label = g_strconcat("+", _("_Send"), NULL);
5111 button_label = g_strconcat("+", _("_Queue"), NULL);
5112 message = g_strdup_printf(_("Subject is empty. %s"),
5113 compose->sending?_("Send it anyway?"):
5114 _("Queue it anyway?"));
5116 aval = alertpanel_full(compose->sending?_("Send"):_("Send later"), message,
5117 GTK_STOCK_CANCEL, button_label, NULL, TRUE, NULL,
5118 ALERT_QUESTION, G_ALERTDEFAULT);
5120 if (aval & G_ALERTDISABLE) {
5121 aval &= ~G_ALERTDISABLE;
5122 prefs_common.warn_empty_subj = FALSE;
5124 if (aval != G_ALERTALTERNATE)
5129 if (check_everything && hooks_invoke(COMPOSE_CHECK_BEFORE_SEND_HOOKLIST, compose))
5135 gint compose_send(Compose *compose)
5138 FolderItem *folder = NULL;
5140 gchar *msgpath = NULL;
5141 gboolean discard_window = FALSE;
5142 gchar *errstr = NULL;
5143 gchar *tmsgid = NULL;
5144 MainWindow *mainwin = mainwindow_get_mainwindow();
5145 gboolean queued_removed = FALSE;
5147 if (prefs_common.send_dialog_invisible
5148 || compose->batch == TRUE)
5149 discard_window = TRUE;
5151 compose_allow_user_actions (compose, FALSE);
5152 compose->sending = TRUE;
5154 if (compose_check_entries(compose, TRUE) == FALSE) {
5155 if (compose->batch) {
5156 gtk_widget_show_all(compose->window);
5162 val = compose_queue(compose, &msgnum, &folder, &msgpath, TRUE);
5165 if (compose->batch) {
5166 gtk_widget_show_all(compose->window);
5169 alertpanel_error(_("Could not queue message for sending:\n\n"
5170 "Charset conversion failed."));
5171 } else if (val == -5) {
5172 alertpanel_error(_("Could not queue message for sending:\n\n"
5173 "Couldn't get recipient encryption key."));
5174 } else if (val == -6) {
5176 } else if (val == -3) {
5177 if (privacy_peek_error())
5178 alertpanel_error(_("Could not queue message for sending:\n\n"
5179 "Signature failed: %s"), privacy_get_error());
5180 } else if (val == -2 && errno != 0) {
5181 alertpanel_error(_("Could not queue message for sending:\n\n%s."), g_strerror(errno));
5183 alertpanel_error(_("Could not queue message for sending."));
5188 tmsgid = compose->msgid ? g_strdup(compose->msgid) : NULL;
5189 if (discard_window) {
5190 compose->sending = FALSE;
5191 compose_close(compose);
5192 /* No more compose access in the normal codepath
5193 * after this point! */
5198 alertpanel_error(_("The message was queued but could not be "
5199 "sent.\nUse \"Send queued messages\" from "
5200 "the main window to retry."));
5201 if (!discard_window) {
5208 if (msgpath == NULL) {
5209 msgpath = folder_item_fetch_msg(folder, msgnum);
5210 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5213 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5214 claws_unlink(msgpath);
5217 if (!discard_window) {
5219 if (!queued_removed)
5220 folder_item_remove_msg(folder, msgnum);
5221 folder_item_scan(folder);
5223 /* make sure we delete that */
5224 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5226 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5227 folder_item_remove_msg(folder, tmp->msgnum);
5228 procmsg_msginfo_free(&tmp);
5235 if (!queued_removed)
5236 folder_item_remove_msg(folder, msgnum);
5237 folder_item_scan(folder);
5239 /* make sure we delete that */
5240 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5242 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5243 folder_item_remove_msg(folder, tmp->msgnum);
5244 procmsg_msginfo_free(&tmp);
5247 if (!discard_window) {
5248 compose->sending = FALSE;
5249 compose_allow_user_actions (compose, TRUE);
5250 compose_close(compose);
5254 alertpanel_error_log(_("%s\nUse \"Send queued messages\" from "
5255 "the main window to retry."), errstr);
5258 alertpanel_error_log(_("The message was queued but could not be "
5259 "sent.\nUse \"Send queued messages\" from "
5260 "the main window to retry."));
5262 if (!discard_window) {
5271 toolbar_main_set_sensitive(mainwin);
5272 main_window_set_menu_sensitive(mainwin);
5278 compose_allow_user_actions (compose, TRUE);
5279 compose->sending = FALSE;
5280 compose->modified = TRUE;
5281 toolbar_main_set_sensitive(mainwin);
5282 main_window_set_menu_sensitive(mainwin);
5287 static gboolean compose_use_attach(Compose *compose)
5289 GtkTreeModel *model = gtk_tree_view_get_model
5290 (GTK_TREE_VIEW(compose->attach_clist));
5291 return gtk_tree_model_iter_n_children(model, NULL) > 0;
5294 static gint compose_redirect_write_headers_from_headerlist(Compose *compose,
5297 gchar buf[BUFFSIZE];
5299 gboolean first_to_address;
5300 gboolean first_cc_address;
5302 ComposeHeaderEntry *headerentry;
5303 const gchar *headerentryname;
5304 const gchar *cc_hdr;
5305 const gchar *to_hdr;
5306 gboolean err = FALSE;
5308 debug_print("Writing redirect header\n");
5310 cc_hdr = prefs_common_translated_header_name("Cc:");
5311 to_hdr = prefs_common_translated_header_name("To:");
5313 first_to_address = TRUE;
5314 for (list = compose->header_list; list; list = list->next) {
5315 headerentry = ((ComposeHeaderEntry *)list->data);
5316 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5318 if (g_utf8_collate(headerentryname, to_hdr) == 0) {
5319 const gchar *entstr = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5320 Xstrdup_a(str, entstr, return -1);
5322 if (str[0] != '\0') {
5323 compose_convert_header
5324 (compose, buf, sizeof(buf), str,
5325 strlen("Resent-To") + 2, TRUE);
5327 if (first_to_address) {
5328 err |= (fprintf(fp, "Resent-To: ") < 0);
5329 first_to_address = FALSE;
5331 err |= (fprintf(fp, ",") < 0);
5333 err |= (fprintf(fp, "%s", buf) < 0);
5337 if (!first_to_address) {
5338 err |= (fprintf(fp, "\n") < 0);
5341 first_cc_address = TRUE;
5342 for (list = compose->header_list; list; list = list->next) {
5343 headerentry = ((ComposeHeaderEntry *)list->data);
5344 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5346 if (g_utf8_collate(headerentryname, cc_hdr) == 0) {
5347 const gchar *strg = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5348 Xstrdup_a(str, strg, return -1);
5350 if (str[0] != '\0') {
5351 compose_convert_header
5352 (compose, buf, sizeof(buf), str,
5353 strlen("Resent-Cc") + 2, TRUE);
5355 if (first_cc_address) {
5356 err |= (fprintf(fp, "Resent-Cc: ") < 0);
5357 first_cc_address = FALSE;
5359 err |= (fprintf(fp, ",") < 0);
5361 err |= (fprintf(fp, "%s", buf) < 0);
5365 if (!first_cc_address) {
5366 err |= (fprintf(fp, "\n") < 0);
5369 return (err ? -1:0);
5372 static gint compose_redirect_write_headers(Compose *compose, FILE *fp)
5374 gchar buf[BUFFSIZE];
5376 const gchar *entstr;
5377 /* struct utsname utsbuf; */
5378 gboolean err = FALSE;
5380 cm_return_val_if_fail(fp != NULL, -1);
5381 cm_return_val_if_fail(compose->account != NULL, -1);
5382 cm_return_val_if_fail(compose->account->address != NULL, -1);
5385 get_rfc822_date(buf, sizeof(buf));
5386 err |= (fprintf(fp, "Resent-Date: %s\n", buf) < 0);
5389 if (compose->account->name && *compose->account->name) {
5390 compose_convert_header
5391 (compose, buf, sizeof(buf), compose->account->name,
5392 strlen("From: "), TRUE);
5393 err |= (fprintf(fp, "Resent-From: %s <%s>\n",
5394 buf, compose->account->address) < 0);
5396 err |= (fprintf(fp, "Resent-From: %s\n", compose->account->address) < 0);
5399 entstr = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5400 if (*entstr != '\0') {
5401 Xstrdup_a(str, entstr, return -1);
5404 compose_convert_header(compose, buf, sizeof(buf), str,
5405 strlen("Subject: "), FALSE);
5406 err |= (fprintf(fp, "Subject: %s\n", buf) < 0);
5410 /* Resent-Message-ID */
5411 if (compose->account->set_domain && compose->account->domain) {
5412 g_snprintf(buf, sizeof(buf), "%s", compose->account->domain);
5413 } else if (!strncmp(get_domain_name(), "localhost", strlen("localhost"))) {
5414 g_snprintf(buf, sizeof(buf), "%s",
5415 strchr(compose->account->address, '@') ?
5416 strchr(compose->account->address, '@')+1 :
5417 compose->account->address);
5419 g_snprintf(buf, sizeof(buf), "%s", "");
5422 if (compose->account->gen_msgid) {
5424 if (compose->account->msgid_with_addr) {
5425 addr = compose->account->address;
5427 generate_msgid(buf, sizeof(buf), addr);
5428 err |= (fprintf(fp, "Resent-Message-ID: <%s>\n", buf) < 0);
5430 g_free(compose->msgid);
5431 compose->msgid = g_strdup(buf);
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 /* create message MimeInfo */
5553 mimemsg = procmime_mimeinfo_new();
5554 mimemsg->type = MIMETYPE_MESSAGE;
5555 mimemsg->subtype = g_strdup("rfc822");
5556 mimemsg->content = MIMECONTENT_MEM;
5557 mimemsg->tmp = TRUE; /* must free content later */
5558 mimemsg->data.mem = compose_get_header(compose);
5560 /* Create text part MimeInfo */
5561 /* get all composed text */
5562 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5563 gtk_text_buffer_get_start_iter(buffer, &start);
5564 gtk_text_buffer_get_end_iter(buffer, &end);
5565 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5567 out_codeset = conv_get_charset_str(compose->out_encoding);
5569 if (!out_codeset && is_ascii_str(chars)) {
5570 out_codeset = CS_US_ASCII;
5571 } else if (prefs_common.outgoing_fallback_to_ascii &&
5572 is_ascii_str(chars)) {
5573 out_codeset = CS_US_ASCII;
5574 encoding = ENC_7BIT;
5578 gchar *test_conv_global_out = NULL;
5579 gchar *test_conv_reply = NULL;
5581 /* automatic mode. be automatic. */
5582 codeconv_set_strict(TRUE);
5584 out_codeset = conv_get_outgoing_charset_str();
5586 debug_print("trying to convert to %s\n", out_codeset);
5587 test_conv_global_out = conv_codeset_strdup(chars, src_codeset, out_codeset);
5590 if (!test_conv_global_out && compose->orig_charset
5591 && strcmp(compose->orig_charset, CS_US_ASCII)) {
5592 out_codeset = compose->orig_charset;
5593 debug_print("failure; trying to convert to %s\n", out_codeset);
5594 test_conv_reply = conv_codeset_strdup(chars, src_codeset, out_codeset);
5597 if (!test_conv_global_out && !test_conv_reply) {
5599 out_codeset = CS_INTERNAL;
5600 debug_print("failure; finally using %s\n", out_codeset);
5602 g_free(test_conv_global_out);
5603 g_free(test_conv_reply);
5604 codeconv_set_strict(FALSE);
5607 if (encoding == ENC_UNKNOWN) {
5608 if (prefs_common.encoding_method == CTE_BASE64)
5609 encoding = ENC_BASE64;
5610 else if (prefs_common.encoding_method == CTE_QUOTED_PRINTABLE)
5611 encoding = ENC_QUOTED_PRINTABLE;
5612 else if (prefs_common.encoding_method == CTE_8BIT)
5613 encoding = ENC_8BIT;
5615 encoding = procmime_get_encoding_for_charset(out_codeset);
5618 debug_print("src encoding = %s, out encoding = %s, transfer encoding = %s\n",
5619 src_codeset, out_codeset, procmime_get_encoding_str(encoding));
5621 if (action == COMPOSE_WRITE_FOR_SEND) {
5622 codeconv_set_strict(TRUE);
5623 buf = conv_codeset_strdup(chars, src_codeset, out_codeset);
5624 codeconv_set_strict(FALSE);
5629 msg = g_strdup_printf(_("Can't convert the character encoding of the message \n"
5630 "to the specified %s charset.\n"
5631 "Send it as %s?"), out_codeset, src_codeset);
5632 aval = alertpanel_full(_("Error"), msg, GTK_STOCK_CANCEL,
5633 g_strconcat("+", _("_Send"), NULL), NULL, FALSE,
5634 NULL, ALERT_ERROR, G_ALERTDEFAULT);
5637 if (aval != G_ALERTALTERNATE) {
5642 out_codeset = src_codeset;
5648 out_codeset = src_codeset;
5653 if (encoding == ENC_8BIT || encoding == ENC_7BIT) {
5654 if (!strncmp(buf, "From ", sizeof("From ")-1) ||
5655 strstr(buf, "\nFrom ") != NULL) {
5656 encoding = ENC_QUOTED_PRINTABLE;
5660 mimetext = procmime_mimeinfo_new();
5661 mimetext->content = MIMECONTENT_MEM;
5662 mimetext->tmp = TRUE; /* must free content later */
5663 /* dup'ed because procmime_encode_content can turn it into a tmpfile
5664 * and free the data, which we need later. */
5665 mimetext->data.mem = g_strdup(buf);
5666 mimetext->type = MIMETYPE_TEXT;
5667 mimetext->subtype = g_strdup("plain");
5668 g_hash_table_insert(mimetext->typeparameters, g_strdup("charset"),
5669 g_strdup(out_codeset));
5671 /* protect trailing spaces when signing message */
5672 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5673 privacy_system_can_sign(compose->privacy_system)) {
5674 encoding = ENC_QUOTED_PRINTABLE;
5677 debug_print("main text: %zd bytes encoded as %s in %d\n",
5678 strlen(buf), out_codeset, encoding);
5680 /* check for line length limit */
5681 if (action == COMPOSE_WRITE_FOR_SEND &&
5682 encoding != ENC_QUOTED_PRINTABLE && encoding != ENC_BASE64 &&
5683 check_line_length(buf, 1000, &line) < 0) {
5686 msg = g_strdup_printf
5687 (_("Line %d exceeds the line length limit (998 bytes).\n"
5688 "The contents of the message might be broken on the way to the delivery.\n"
5690 "Send it anyway?"), line + 1);
5691 aval = alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, GTK_STOCK_OK, NULL);
5693 if (aval != G_ALERTALTERNATE) {
5699 if (encoding != ENC_UNKNOWN)
5700 procmime_encode_content(mimetext, encoding);
5702 /* append attachment parts */
5703 if (compose_use_attach(compose) && attach_parts) {
5704 MimeInfo *mimempart;
5705 gchar *boundary = NULL;
5706 mimempart = procmime_mimeinfo_new();
5707 mimempart->content = MIMECONTENT_EMPTY;
5708 mimempart->type = MIMETYPE_MULTIPART;
5709 mimempart->subtype = g_strdup("mixed");
5713 boundary = generate_mime_boundary(NULL);
5714 } while (strstr(buf, boundary) != NULL);
5716 g_hash_table_insert(mimempart->typeparameters, g_strdup("boundary"),
5719 mimetext->disposition = DISPOSITIONTYPE_INLINE;
5721 g_node_append(mimempart->node, mimetext->node);
5722 g_node_append(mimemsg->node, mimempart->node);
5724 if (compose_add_attachments(compose, mimempart) < 0)
5727 g_node_append(mimemsg->node, mimetext->node);
5731 if (strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) != 0) {
5732 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
5733 /* extract name and address */
5734 if (strstr(spec, " <") && strstr(spec, ">")) {
5735 from_addr = g_strdup(strrchr(spec, '<')+1);
5736 *(strrchr(from_addr, '>')) = '\0';
5737 from_name = g_strdup(spec);
5738 *(strrchr(from_name, '<')) = '\0';
5745 /* sign message if sending */
5746 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5747 privacy_system_can_sign(compose->privacy_system))
5748 if (!privacy_sign(compose->privacy_system, mimemsg,
5749 compose->account, from_addr)) {
5757 if (compose->use_encryption) {
5758 if (compose->encdata != NULL &&
5759 strcmp(compose->encdata, "_DONT_ENCRYPT_")) {
5761 /* First, write an unencrypted copy and save it to outbox, if
5762 * user wants that. */
5763 if (compose->account->save_encrypted_as_clear_text) {
5764 debug_print("saving sent message unencrypted...\n");
5765 FILE *tmpfp = get_tmpfile_in_dir(get_mime_tmp_dir(), &tmp_enc_file);
5769 /* fp now points to a file with headers written,
5770 * let's make a copy. */
5772 content = file_read_stream_to_str(fp);
5774 str_write_to_file(content, tmp_enc_file);
5777 /* Now write the unencrypted body. */
5778 if ((tmpfp = g_fopen(tmp_enc_file, "a")) != NULL) {
5779 procmime_write_mimeinfo(mimemsg, tmpfp);
5782 outbox = folder_find_item_from_identifier(compose_get_save_to(compose));
5784 outbox = folder_get_default_outbox();
5786 procmsg_save_to_outbox(outbox, tmp_enc_file, TRUE);
5787 claws_unlink(tmp_enc_file);
5789 g_warning("Can't open file '%s'", tmp_enc_file);
5792 g_warning("couldn't get tempfile");
5795 if (!privacy_encrypt(compose->privacy_system, mimemsg, compose->encdata)) {
5796 debug_print("Couldn't encrypt mime structure: %s.\n",
5797 privacy_get_error());
5798 alertpanel_error(_("Couldn't encrypt the email: %s"),
5799 privacy_get_error());
5804 procmime_write_mimeinfo(mimemsg, fp);
5806 procmime_mimeinfo_free_all(&mimemsg);
5811 static gint compose_write_body_to_file(Compose *compose, const gchar *file)
5813 GtkTextBuffer *buffer;
5814 GtkTextIter start, end;
5819 if ((fp = g_fopen(file, "wb")) == NULL) {
5820 FILE_OP_ERROR(file, "fopen");
5824 /* chmod for security */
5825 if (change_file_mode_rw(fp, file) < 0) {
5826 FILE_OP_ERROR(file, "chmod");
5827 g_warning("can't change file mode");
5830 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5831 gtk_text_buffer_get_start_iter(buffer, &start);
5832 gtk_text_buffer_get_end_iter(buffer, &end);
5833 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5835 chars = conv_codeset_strdup
5836 (tmp, CS_INTERNAL, conv_get_locale_charset_str());
5845 len = strlen(chars);
5846 if (fwrite(chars, sizeof(gchar), len, fp) != len) {
5847 FILE_OP_ERROR(file, "fwrite");
5856 if (fclose(fp) == EOF) {
5857 FILE_OP_ERROR(file, "fclose");
5864 static gint compose_remove_reedit_target(Compose *compose, gboolean force)
5867 MsgInfo *msginfo = compose->targetinfo;
5869 cm_return_val_if_fail(compose->mode == COMPOSE_REEDIT, -1);
5870 if (!msginfo) return -1;
5872 if (!force && MSG_IS_LOCKED(msginfo->flags))
5875 item = msginfo->folder;
5876 cm_return_val_if_fail(item != NULL, -1);
5878 if (procmsg_msg_exist(msginfo) &&
5879 (folder_has_parent_of_type(item, F_QUEUE) ||
5880 folder_has_parent_of_type(item, F_DRAFT)
5881 || msginfo == compose->autosaved_draft)) {
5882 if (folder_item_remove_msg(item, msginfo->msgnum) < 0) {
5883 g_warning("can't remove the old message");
5886 debug_print("removed reedit target %d\n", msginfo->msgnum);
5893 static void compose_remove_draft(Compose *compose)
5896 MsgInfo *msginfo = compose->targetinfo;
5897 drafts = account_get_special_folder(compose->account, F_DRAFT);
5899 if (procmsg_msg_exist(msginfo)) {
5900 folder_item_remove_msg(drafts, msginfo->msgnum);
5905 gint compose_queue(Compose *compose, gint *msgnum, FolderItem **item, gchar **msgpath,
5906 gboolean remove_reedit_target)
5908 return compose_queue_sub (compose, msgnum, item, msgpath, FALSE, remove_reedit_target);
5911 static gboolean compose_warn_encryption(Compose *compose)
5913 const gchar *warning = privacy_get_encrypt_warning(compose->privacy_system);
5914 AlertValue val = G_ALERTALTERNATE;
5916 if (warning == NULL)
5919 val = alertpanel_full(_("Encryption warning"), warning,
5920 GTK_STOCK_CANCEL, g_strconcat("+", _("C_ontinue"), NULL), NULL,
5921 TRUE, NULL, ALERT_WARNING, G_ALERTALTERNATE);
5922 if (val & G_ALERTDISABLE) {
5923 val &= ~G_ALERTDISABLE;
5924 if (val == G_ALERTALTERNATE)
5925 privacy_inhibit_encrypt_warning(compose->privacy_system,
5929 if (val == G_ALERTALTERNATE) {
5936 static gint compose_queue_sub(Compose *compose, gint *msgnum, FolderItem **item,
5937 gchar **msgpath, gboolean check_subject,
5938 gboolean remove_reedit_target)
5945 PrefsAccount *mailac = NULL, *newsac = NULL;
5946 gboolean err = FALSE;
5948 debug_print("queueing message...\n");
5949 cm_return_val_if_fail(compose->account != NULL, -1);
5951 if (compose_check_entries(compose, check_subject) == FALSE) {
5952 if (compose->batch) {
5953 gtk_widget_show_all(compose->window);
5958 if (!compose->to_list && !compose->newsgroup_list) {
5959 g_warning("can't get recipient list.");
5963 if (compose->to_list) {
5964 if (compose->account->protocol != A_NNTP)
5965 mailac = compose->account;
5966 else if (cur_account && cur_account->protocol != A_NNTP)
5967 mailac = cur_account;
5968 else if (!(mailac = compose_current_mail_account())) {
5969 alertpanel_error(_("No account for sending mails available!"));
5974 if (compose->newsgroup_list) {
5975 if (compose->account->protocol == A_NNTP)
5976 newsac = compose->account;
5978 alertpanel_error(_("Selected account isn't NNTP: Posting is impossible."));
5983 /* write queue header */
5984 tmp = g_strdup_printf("%s%cqueue.%p%08x", get_tmp_dir(),
5985 G_DIR_SEPARATOR, compose, (guint) rand());
5986 debug_print("queuing to %s\n", tmp);
5987 if ((fp = g_fopen(tmp, "w+b")) == NULL) {
5988 FILE_OP_ERROR(tmp, "fopen");
5993 if (change_file_mode_rw(fp, tmp) < 0) {
5994 FILE_OP_ERROR(tmp, "chmod");
5995 g_warning("can't change file mode");
5998 /* queueing variables */
5999 err |= (fprintf(fp, "AF:\n") < 0);
6000 err |= (fprintf(fp, "NF:0\n") < 0);
6001 err |= (fprintf(fp, "PS:10\n") < 0);
6002 err |= (fprintf(fp, "SRH:1\n") < 0);
6003 err |= (fprintf(fp, "SFN:\n") < 0);
6004 err |= (fprintf(fp, "DSR:\n") < 0);
6006 err |= (fprintf(fp, "MID:<%s>\n", compose->msgid) < 0);
6008 err |= (fprintf(fp, "MID:\n") < 0);
6009 err |= (fprintf(fp, "CFG:\n") < 0);
6010 err |= (fprintf(fp, "PT:0\n") < 0);
6011 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
6012 err |= (fprintf(fp, "RQ:\n") < 0);
6014 err |= (fprintf(fp, "SSV:%s\n", mailac->smtp_server) < 0);
6016 err |= (fprintf(fp, "SSV:\n") < 0);
6018 err |= (fprintf(fp, "NSV:%s\n", newsac->nntp_server) < 0);
6020 err |= (fprintf(fp, "NSV:\n") < 0);
6021 err |= (fprintf(fp, "SSH:\n") < 0);
6022 /* write recepient list */
6023 if (compose->to_list) {
6024 err |= (fprintf(fp, "R:<%s>", (gchar *)compose->to_list->data) < 0);
6025 for (cur = compose->to_list->next; cur != NULL;
6027 err |= (fprintf(fp, ",<%s>", (gchar *)cur->data) < 0);
6028 err |= (fprintf(fp, "\n") < 0);
6030 /* write newsgroup list */
6031 if (compose->newsgroup_list) {
6032 err |= (fprintf(fp, "NG:") < 0);
6033 err |= (fprintf(fp, "%s", (gchar *)compose->newsgroup_list->data) < 0);
6034 for (cur = compose->newsgroup_list->next; cur != NULL; cur = cur->next)
6035 err |= (fprintf(fp, ",%s", (gchar *)cur->data) < 0);
6036 err |= (fprintf(fp, "\n") < 0);
6038 /* Sylpheed account IDs */
6040 err |= (fprintf(fp, "MAID:%d\n", mailac->account_id) < 0);
6042 err |= (fprintf(fp, "NAID:%d\n", newsac->account_id) < 0);
6045 if (compose->privacy_system != NULL) {
6046 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
6047 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
6048 if (compose->use_encryption) {
6049 if (!compose_warn_encryption(compose)) {
6055 if (mailac && mailac->encrypt_to_self) {
6056 GSList *tmp_list = g_slist_copy(compose->to_list);
6057 tmp_list = g_slist_append(tmp_list, compose->account->address);
6058 compose->encdata = privacy_get_encrypt_data(compose->privacy_system, tmp_list);
6059 g_slist_free(tmp_list);
6061 compose->encdata = privacy_get_encrypt_data(compose->privacy_system, compose->to_list);
6063 if (compose->encdata != NULL) {
6064 if (strcmp(compose->encdata, "_DONT_ENCRYPT_")) {
6065 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
6066 err |= (fprintf(fp, "X-Claws-Encrypt-Data:%s\n",
6067 compose->encdata) < 0);
6068 } /* else we finally dont want to encrypt */
6070 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
6071 /* and if encdata was null, it means there's been a problem in
6074 g_warning("failed to write queue message");
6083 /* Save copy folder */
6084 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
6085 gchar *savefolderid;
6087 savefolderid = compose_get_save_to(compose);
6088 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
6089 g_free(savefolderid);
6091 /* Save copy folder */
6092 if (compose->return_receipt) {
6093 err |= (fprintf(fp, "RRCPT:1\n") < 0);
6095 /* Message-ID of message replying to */
6096 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
6097 gchar *folderid = NULL;
6099 if (compose->replyinfo->folder)
6100 folderid = folder_item_get_identifier(compose->replyinfo->folder);
6101 if (folderid == NULL)
6102 folderid = g_strdup("NULL");
6104 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
6107 /* Message-ID of message forwarding to */
6108 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
6109 gchar *folderid = NULL;
6111 if (compose->fwdinfo->folder)
6112 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
6113 if (folderid == NULL)
6114 folderid = g_strdup("NULL");
6116 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
6120 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
6121 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
6123 /* end of headers */
6124 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
6126 if (compose->redirect_filename != NULL) {
6127 if (compose_redirect_write_to_file(compose, fp) < 0) {
6135 if ((result = compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_SEND, TRUE)) < 0) {
6139 return result - 1; /* -2 for a generic error, -3 for signing error, -4 for encoding */
6143 g_warning("failed to write queue message");
6149 if (fclose(fp) == EOF) {
6150 FILE_OP_ERROR(tmp, "fclose");
6156 if (item && *item) {
6159 queue = account_get_special_folder(compose->account, F_QUEUE);
6162 g_warning("can't find queue folder");
6167 folder_item_scan(queue);
6168 if ((num = folder_item_add_msg(queue, tmp, NULL, FALSE)) < 0) {
6169 g_warning("can't queue the message");
6175 if (msgpath == NULL) {
6181 if (compose->mode == COMPOSE_REEDIT && remove_reedit_target) {
6182 compose_remove_reedit_target(compose, FALSE);
6185 if ((msgnum != NULL) && (item != NULL)) {
6193 static int compose_add_attachments(Compose *compose, MimeInfo *parent)
6196 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
6199 gchar *type, *subtype;
6200 GtkTreeModel *model;
6203 model = gtk_tree_view_get_model(tree_view);
6205 if (!gtk_tree_model_get_iter_first(model, &iter))
6208 gtk_tree_model_get(model, &iter,
6212 if (!is_file_exist(ainfo->file)) {
6213 gchar *msg = g_strdup_printf(_("Attachment %s doesn't exist anymore. Ignore?"), ainfo->file);
6214 AlertValue val = alertpanel_full(_("Warning"), msg, _("Cancel sending"), _("Ignore attachment"),
6215 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
6217 if (val == G_ALERTDEFAULT) {
6222 if (g_stat(ainfo->file, &statbuf) < 0)
6225 mimepart = procmime_mimeinfo_new();
6226 mimepart->content = MIMECONTENT_FILE;
6227 mimepart->data.filename = g_strdup(ainfo->file);
6228 mimepart->tmp = FALSE; /* or we destroy our attachment */
6229 mimepart->offset = 0;
6230 mimepart->length = statbuf.st_size;
6232 type = g_strdup(ainfo->content_type);
6234 if (!strchr(type, '/')) {
6236 type = g_strdup("application/octet-stream");
6239 subtype = strchr(type, '/') + 1;
6240 *(subtype - 1) = '\0';
6241 mimepart->type = procmime_get_media_type(type);
6242 mimepart->subtype = g_strdup(subtype);
6245 if (mimepart->type == MIMETYPE_MESSAGE &&
6246 !g_ascii_strcasecmp(mimepart->subtype, "rfc822")) {
6247 mimepart->disposition = DISPOSITIONTYPE_INLINE;
6248 } else if (mimepart->type == MIMETYPE_TEXT) {
6249 if (!ainfo->name && g_ascii_strcasecmp(mimepart->subtype, "plain")) {
6250 /* Text parts with no name come from multipart/alternative
6251 * forwards. Make sure the recipient won't look at the
6252 * original HTML part by mistake. */
6253 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6254 ainfo->name = g_strdup_printf(_("Original %s part"),
6258 g_hash_table_insert(mimepart->typeparameters,
6259 g_strdup("charset"), g_strdup(ainfo->charset));
6261 if (ainfo->name && mimepart->type != MIMETYPE_MESSAGE) {
6262 if (mimepart->type == MIMETYPE_APPLICATION &&
6263 !strcmp2(mimepart->subtype, "octet-stream"))
6264 g_hash_table_insert(mimepart->typeparameters,
6265 g_strdup("name"), g_strdup(ainfo->name));
6266 g_hash_table_insert(mimepart->dispositionparameters,
6267 g_strdup("filename"), g_strdup(ainfo->name));
6268 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6271 if (mimepart->type == MIMETYPE_MESSAGE
6272 || mimepart->type == MIMETYPE_MULTIPART)
6273 ainfo->encoding = ENC_BINARY;
6274 else if (compose->use_signing) {
6275 if (ainfo->encoding == ENC_7BIT)
6276 ainfo->encoding = ENC_QUOTED_PRINTABLE;
6277 else if (ainfo->encoding == ENC_8BIT)
6278 ainfo->encoding = ENC_BASE64;
6283 procmime_encode_content(mimepart, ainfo->encoding);
6285 g_node_append(parent->node, mimepart->node);
6286 } while (gtk_tree_model_iter_next(model, &iter));
6291 static gchar *compose_quote_list_of_addresses(gchar *str)
6293 GSList *list = NULL, *item = NULL;
6294 gchar *qname = NULL, *faddr = NULL, *result = NULL;
6296 list = address_list_append_with_comments(list, str);
6297 for (item = list; item != NULL; item = item->next) {
6298 gchar *spec = item->data;
6299 gchar *endofname = strstr(spec, " <");
6300 if (endofname != NULL) {
6303 QUOTE_IF_REQUIRED_NORMAL(qname, spec, return NULL);
6304 qqname = escape_internal_quotes(qname, '"');
6306 if (*qname != *spec || qqname != qname) { /* has been quoted, compute new */
6307 gchar *addr = g_strdup(endofname);
6308 gchar *name = (qqname != qname)? qqname: g_strdup(qname);
6309 faddr = g_strconcat(name, addr, NULL);
6312 debug_print("new auto-quoted address: '%s'\n", faddr);
6316 result = g_strdup((faddr != NULL)? faddr: spec);
6318 result = g_strconcat(result,
6320 (faddr != NULL)? faddr: spec,
6323 if (faddr != NULL) {
6328 slist_free_strings_full(list);
6333 #define IS_IN_CUSTOM_HEADER(header) \
6334 (compose->account->add_customhdr && \
6335 custom_header_find(compose->account->customhdr_list, header) != NULL)
6337 static void compose_add_headerfield_from_headerlist(Compose *compose,
6339 const gchar *fieldname,
6340 const gchar *seperator)
6342 gchar *str, *fieldname_w_colon;
6343 gboolean add_field = FALSE;
6345 ComposeHeaderEntry *headerentry;
6346 const gchar *headerentryname;
6347 const gchar *trans_fieldname;
6350 if (IS_IN_CUSTOM_HEADER(fieldname))
6353 debug_print("Adding %s-fields\n", fieldname);
6355 fieldstr = g_string_sized_new(64);
6357 fieldname_w_colon = g_strconcat(fieldname, ":", NULL);
6358 trans_fieldname = prefs_common_translated_header_name(fieldname_w_colon);
6360 for (list = compose->header_list; list; list = list->next) {
6361 headerentry = ((ComposeHeaderEntry *)list->data);
6362 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
6364 if (!g_utf8_collate(trans_fieldname, headerentryname)) {
6365 gchar * ustr = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
6367 str = compose_quote_list_of_addresses(ustr);
6369 if (str != NULL && str[0] != '\0') {
6371 g_string_append(fieldstr, seperator);
6372 g_string_append(fieldstr, str);
6381 buf = g_new0(gchar, fieldstr->len * 4 + 256);
6382 compose_convert_header
6383 (compose, buf, fieldstr->len * 4 + 256, fieldstr->str,
6384 strlen(fieldname) + 2, TRUE);
6385 g_string_append_printf(header, "%s: %s\n", fieldname, buf);
6389 g_free(fieldname_w_colon);
6390 g_string_free(fieldstr, TRUE);
6395 static gchar *compose_get_manual_headers_info(Compose *compose)
6397 GString *sh_header = g_string_new(" ");
6399 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6401 for (list = compose->header_list; list; list = list->next) {
6402 ComposeHeaderEntry *headerentry;
6405 gchar *headername_wcolon;
6406 const gchar *headername_trans;
6408 gboolean standard_header = FALSE;
6410 headerentry = ((ComposeHeaderEntry *)list->data);
6412 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6414 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6419 if (!strstr(tmp, ":")) {
6420 headername_wcolon = g_strconcat(tmp, ":", NULL);
6421 headername = g_strdup(tmp);
6423 headername_wcolon = g_strdup(tmp);
6424 headername = g_strdup(strtok(tmp, ":"));
6428 string = std_headers;
6429 while (*string != NULL) {
6430 headername_trans = prefs_common_translated_header_name(*string);
6431 if (!strcmp(headername_trans, headername_wcolon))
6432 standard_header = TRUE;
6435 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
6436 g_string_append_printf(sh_header, "%s ", headername);
6438 g_free(headername_wcolon);
6440 g_string_truncate(sh_header, strlen(sh_header->str) - 1); /* remove last space */
6441 return g_string_free(sh_header, FALSE);
6444 static gchar *compose_get_header(Compose *compose)
6446 gchar buf[BUFFSIZE];
6447 const gchar *entry_str;
6451 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6453 gchar *from_name = NULL, *from_address = NULL;
6456 cm_return_val_if_fail(compose->account != NULL, NULL);
6457 cm_return_val_if_fail(compose->account->address != NULL, NULL);
6459 header = g_string_sized_new(64);
6462 get_rfc822_date(buf, sizeof(buf));
6463 g_string_append_printf(header, "Date: %s\n", buf);
6467 if (compose->account->name && *compose->account->name) {
6469 QUOTE_IF_REQUIRED(buf, compose->account->name);
6470 tmp = g_strdup_printf("%s <%s>",
6471 buf, compose->account->address);
6473 tmp = g_strdup_printf("%s",
6474 compose->account->address);
6476 if (!strcmp(gtk_entry_get_text(GTK_ENTRY(compose->from_name)), tmp)
6477 || strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) == 0) {
6479 from_name = compose->account->name ? g_strdup(compose->account->name):NULL;
6480 from_address = g_strdup(compose->account->address);
6482 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
6483 /* extract name and address */
6484 if (strstr(spec, " <") && strstr(spec, ">")) {
6485 from_address = g_strdup(strrchr(spec, '<')+1);
6486 *(strrchr(from_address, '>')) = '\0';
6487 from_name = g_strdup(spec);
6488 *(strrchr(from_name, '<')) = '\0';
6491 from_address = g_strdup(spec);
6498 if (from_name && *from_name) {
6500 compose_convert_header
6501 (compose, buf, sizeof(buf), from_name,
6502 strlen("From: "), TRUE);
6503 QUOTE_IF_REQUIRED(name, buf);
6504 qname = escape_internal_quotes(name, '"');
6506 g_string_append_printf(header, "From: %s <%s>\n",
6507 qname, from_address);
6508 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To") &&
6509 compose->return_receipt) {
6510 compose_convert_header(compose, buf, sizeof(buf), from_name,
6511 strlen("Disposition-Notification-To: "),
6513 g_string_append_printf(header, "Disposition-Notification-To: %s <%s>\n", buf, from_address);
6518 g_string_append_printf(header, "From: %s\n", from_address);
6519 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To") &&
6520 compose->return_receipt)
6521 g_string_append_printf(header, "Disposition-Notification-To: %s\n", from_address);
6525 g_free(from_address);
6528 compose_add_headerfield_from_headerlist(compose, header, "To", ", ");
6531 compose_add_headerfield_from_headerlist(compose, header, "Newsgroups", ",");
6534 compose_add_headerfield_from_headerlist(compose, header, "Cc", ", ");
6538 * If this account is a NNTP account remove Bcc header from
6539 * message body since it otherwise will be publicly shown
6541 if (compose->account->protocol != A_NNTP)
6542 compose_add_headerfield_from_headerlist(compose, header, "Bcc", ", ");
6545 str = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
6547 if (*str != '\0' && !IS_IN_CUSTOM_HEADER("Subject")) {
6550 compose_convert_header(compose, buf, sizeof(buf), str,
6551 strlen("Subject: "), FALSE);
6552 g_string_append_printf(header, "Subject: %s\n", buf);
6558 if (compose->account->set_domain && compose->account->domain) {
6559 g_snprintf(buf, sizeof(buf), "%s", compose->account->domain);
6560 } else if (!strncmp(get_domain_name(), "localhost", strlen("localhost"))) {
6561 g_snprintf(buf, sizeof(buf), "%s",
6562 strchr(compose->account->address, '@') ?
6563 strchr(compose->account->address, '@')+1 :
6564 compose->account->address);
6566 g_snprintf(buf, sizeof(buf), "%s", "");
6569 if (compose->account->gen_msgid) {
6571 if (compose->account->msgid_with_addr) {
6572 addr = compose->account->address;
6574 generate_msgid(buf, sizeof(buf), addr);
6575 g_string_append_printf(header, "Message-ID: <%s>\n", buf);
6577 g_free(compose->msgid);
6578 compose->msgid = g_strdup(buf);
6580 compose->msgid = NULL;
6583 if (compose->remove_references == FALSE) {
6585 if (compose->inreplyto && compose->to_list)
6586 g_string_append_printf(header, "In-Reply-To: <%s>\n", compose->inreplyto);
6589 if (compose->references)
6590 g_string_append_printf(header, "References: %s\n", compose->references);
6594 compose_add_headerfield_from_headerlist(compose, header, "Followup-To", ",");
6597 compose_add_headerfield_from_headerlist(compose, header, "Reply-To", ", ");
6600 if (compose->account->organization &&
6601 strlen(compose->account->organization) &&
6602 !IS_IN_CUSTOM_HEADER("Organization")) {
6603 compose_convert_header(compose, buf, sizeof(buf),
6604 compose->account->organization,
6605 strlen("Organization: "), FALSE);
6606 g_string_append_printf(header, "Organization: %s\n", buf);
6609 /* Program version and system info */
6610 if (compose->account->gen_xmailer &&
6611 g_slist_length(compose->to_list) && !IS_IN_CUSTOM_HEADER("X-Mailer") &&
6612 !compose->newsgroup_list) {
6613 g_string_append_printf(header, "X-Mailer: %s (GTK+ %d.%d.%d; %s)\n",
6615 gtk_major_version, gtk_minor_version, gtk_micro_version,
6618 if (compose->account->gen_xmailer &&
6619 g_slist_length(compose->newsgroup_list) && !IS_IN_CUSTOM_HEADER("X-Newsreader")) {
6620 g_string_append_printf(header, "X-Newsreader: %s (GTK+ %d.%d.%d; %s)\n",
6622 gtk_major_version, gtk_minor_version, gtk_micro_version,
6626 /* custom headers */
6627 if (compose->account->add_customhdr) {
6630 for (cur = compose->account->customhdr_list; cur != NULL;
6632 CustomHeader *chdr = (CustomHeader *)cur->data;
6634 if (custom_header_is_allowed(chdr->name)
6635 && chdr->value != NULL
6636 && *(chdr->value) != '\0') {
6637 compose_convert_header
6638 (compose, buf, sizeof(buf),
6640 strlen(chdr->name) + 2, FALSE);
6641 g_string_append_printf(header, "%s: %s\n", chdr->name, buf);
6646 /* Automatic Faces and X-Faces */
6647 if (get_account_xface (buf, sizeof(buf), compose->account->account_name) == 0) {
6648 g_string_append_printf(header, "X-Face: %s\n", buf);
6650 else if (get_default_xface (buf, sizeof(buf)) == 0) {
6651 g_string_append_printf(header, "X-Face: %s\n", buf);
6653 if (get_account_face (buf, sizeof(buf), compose->account->account_name) == 0) {
6654 g_string_append_printf(header, "Face: %s\n", buf);
6656 else if (get_default_face (buf, sizeof(buf)) == 0) {
6657 g_string_append_printf(header, "Face: %s\n", buf);
6661 switch (compose->priority) {
6662 case PRIORITY_HIGHEST: g_string_append_printf(header, "Importance: high\n"
6663 "X-Priority: 1 (Highest)\n");
6665 case PRIORITY_HIGH: g_string_append_printf(header, "Importance: high\n"
6666 "X-Priority: 2 (High)\n");
6668 case PRIORITY_NORMAL: break;
6669 case PRIORITY_LOW: g_string_append_printf(header, "Importance: low\n"
6670 "X-Priority: 4 (Low)\n");
6672 case PRIORITY_LOWEST: g_string_append_printf(header, "Importance: low\n"
6673 "X-Priority: 5 (Lowest)\n");
6675 default: debug_print("compose: priority unknown : %d\n",
6679 /* get special headers */
6680 for (list = compose->header_list; list; list = list->next) {
6681 ComposeHeaderEntry *headerentry;
6684 gchar *headername_wcolon;
6685 const gchar *headername_trans;
6688 gboolean standard_header = FALSE;
6690 headerentry = ((ComposeHeaderEntry *)list->data);
6692 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6694 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6699 if (!strstr(tmp, ":")) {
6700 headername_wcolon = g_strconcat(tmp, ":", NULL);
6701 headername = g_strdup(tmp);
6703 headername_wcolon = g_strdup(tmp);
6704 headername = g_strdup(strtok(tmp, ":"));
6708 entry_str = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
6709 Xstrdup_a(headervalue, entry_str, return NULL);
6710 subst_char(headervalue, '\r', ' ');
6711 subst_char(headervalue, '\n', ' ');
6712 string = std_headers;
6713 while (*string != NULL) {
6714 headername_trans = prefs_common_translated_header_name(*string);
6715 if (!strcmp(headername_trans, headername_wcolon))
6716 standard_header = TRUE;
6719 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
6720 g_string_append_printf(header, "%s %s\n", headername_wcolon, headervalue);
6723 g_free(headername_wcolon);
6727 g_string_free(header, FALSE);
6732 #undef IS_IN_CUSTOM_HEADER
6734 static void compose_convert_header(Compose *compose, gchar *dest, gint len, gchar *src,
6735 gint header_len, gboolean addr_field)
6737 gchar *tmpstr = NULL;
6738 const gchar *out_codeset = NULL;
6740 cm_return_if_fail(src != NULL);
6741 cm_return_if_fail(dest != NULL);
6743 if (len < 1) return;
6745 tmpstr = g_strdup(src);
6747 subst_char(tmpstr, '\n', ' ');
6748 subst_char(tmpstr, '\r', ' ');
6751 if (!g_utf8_validate(tmpstr, -1, NULL)) {
6752 gchar *mybuf = g_malloc(strlen(tmpstr)*2 +1);
6753 conv_localetodisp(mybuf, strlen(tmpstr)*2 +1, tmpstr);
6758 codeconv_set_strict(TRUE);
6759 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6760 conv_get_charset_str(compose->out_encoding));
6761 codeconv_set_strict(FALSE);
6763 if (!dest || *dest == '\0') {
6764 gchar *test_conv_global_out = NULL;
6765 gchar *test_conv_reply = NULL;
6767 /* automatic mode. be automatic. */
6768 codeconv_set_strict(TRUE);
6770 out_codeset = conv_get_outgoing_charset_str();
6772 debug_print("trying to convert to %s\n", out_codeset);
6773 test_conv_global_out = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6776 if (!test_conv_global_out && compose->orig_charset
6777 && strcmp(compose->orig_charset, CS_US_ASCII)) {
6778 out_codeset = compose->orig_charset;
6779 debug_print("failure; trying to convert to %s\n", out_codeset);
6780 test_conv_reply = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6783 if (!test_conv_global_out && !test_conv_reply) {
6785 out_codeset = CS_INTERNAL;
6786 debug_print("finally using %s\n", out_codeset);
6788 g_free(test_conv_global_out);
6789 g_free(test_conv_reply);
6790 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6792 codeconv_set_strict(FALSE);
6797 static void compose_add_to_addressbook_cb(GtkMenuItem *menuitem, gpointer user_data)
6801 cm_return_if_fail(user_data != NULL);
6803 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(user_data)));
6804 g_strstrip(address);
6805 if (*address != '\0') {
6806 gchar *name = procheader_get_fromname(address);
6807 extract_address(address);
6808 #ifndef USE_ALT_ADDRBOOK
6809 addressbook_add_contact(name, address, NULL, NULL);
6811 debug_print("%s: %s\n", name, address);
6812 if (addressadd_selection(name, address, NULL, NULL)) {
6813 debug_print( "addressbook_add_contact - added\n" );
6820 static void compose_entry_popup_extend(GtkEntry *entry, GtkMenu *menu, gpointer user_data)
6822 GtkWidget *menuitem;
6825 cm_return_if_fail(menu != NULL);
6826 cm_return_if_fail(GTK_IS_MENU_SHELL(menu));
6828 menuitem = gtk_separator_menu_item_new();
6829 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6830 gtk_widget_show(menuitem);
6832 menuitem = gtk_menu_item_new_with_mnemonic(_("Add to address _book"));
6833 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6835 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
6836 g_strstrip(address);
6837 if (*address == '\0') {
6838 gtk_widget_set_sensitive(GTK_WIDGET(menuitem), FALSE);
6841 g_signal_connect(G_OBJECT(menuitem), "activate",
6842 G_CALLBACK(compose_add_to_addressbook_cb), entry);
6843 gtk_widget_show(menuitem);
6846 void compose_add_extra_header(gchar *header, GtkListStore *model)
6849 if (strcmp(header, "")) {
6850 COMBOBOX_ADD(model, header, COMPOSE_TO);
6854 void compose_add_extra_header_entries(GtkListStore *model)
6858 gchar buf[BUFFSIZE];
6861 if (extra_headers == NULL) {
6862 exhrc = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, "extraheaderrc", NULL);
6863 if ((exh = g_fopen(exhrc, "rb")) == NULL) {
6864 debug_print("extra headers file not found\n");
6865 goto extra_headers_done;
6867 while (fgets(buf, BUFFSIZE, exh) != NULL) {
6868 lastc = strlen(buf) - 1; /* remove trailing control chars */
6869 while (lastc >= 0 && buf[lastc] != ':')
6870 buf[lastc--] = '\0';
6871 if (lastc > 0 && buf[0] != '#' && buf[lastc] == ':') {
6872 buf[lastc] = '\0'; /* remove trailing : for comparison */
6873 if (custom_header_is_allowed(buf)) {
6875 extra_headers = g_slist_prepend(extra_headers, g_strdup(buf));
6878 g_message("disallowed extra header line: %s\n", buf);
6882 g_message("invalid extra header line: %s\n", buf);
6888 extra_headers = g_slist_prepend(extra_headers, g_strdup("")); /* end of list */
6889 extra_headers = g_slist_reverse(extra_headers);
6891 g_slist_foreach(extra_headers, (GFunc)compose_add_extra_header, (gpointer)model);
6894 static void compose_create_header_entry(Compose *compose)
6896 gchar *headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6903 const gchar *header = NULL;
6904 ComposeHeaderEntry *headerentry;
6905 gboolean standard_header = FALSE;
6906 GtkListStore *model;
6909 headerentry = g_new0(ComposeHeaderEntry, 1);
6911 /* Combo box model */
6912 model = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_BOOLEAN);
6913 #if !GTK_CHECK_VERSION(2, 24, 0)
6914 combo = gtk_combo_box_entry_new_with_model(GTK_TREE_MODEL(model), 0);
6916 COMBOBOX_ADD(model, prefs_common_translated_header_name("To:"),
6918 COMBOBOX_ADD(model, prefs_common_translated_header_name("Cc:"),
6920 COMBOBOX_ADD(model, prefs_common_translated_header_name("Bcc:"),
6922 COMBOBOX_ADD(model, prefs_common_translated_header_name("Newsgroups:"),
6923 COMPOSE_NEWSGROUPS);
6924 COMBOBOX_ADD(model, prefs_common_translated_header_name("Reply-To:"),
6926 COMBOBOX_ADD(model, prefs_common_translated_header_name("Followup-To:"),
6927 COMPOSE_FOLLOWUPTO);
6928 compose_add_extra_header_entries(model);
6931 #if GTK_CHECK_VERSION(2, 24, 0)
6932 combo = gtk_combo_box_new_with_model_and_entry(GTK_TREE_MODEL(model));
6933 GtkCellRenderer *cell = gtk_cell_renderer_text_new();
6934 gtk_cell_renderer_set_alignment(cell, 0.0, 0.5);
6935 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), cell, TRUE);
6936 gtk_combo_box_set_entry_text_column(GTK_COMBO_BOX(combo), 0);
6938 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
6939 g_signal_connect(G_OBJECT(gtk_bin_get_child(GTK_BIN(combo))), "grab_focus",
6940 G_CALLBACK(compose_grab_focus_cb), compose);
6941 gtk_widget_show(combo);
6943 /* Putting only the combobox child into focus chain of its parent causes
6944 * the parent to be skipped when changing focus via Tab or Shift+Tab.
6945 * This eliminates need to pres Tab twice in order to really get from the
6946 * combobox to next widget. */
6948 l = g_list_prepend(l, gtk_bin_get_child(GTK_BIN(combo)));
6949 gtk_container_set_focus_chain(GTK_CONTAINER(combo), l);
6952 gtk_table_attach(GTK_TABLE(compose->header_table), combo, 0, 1,
6953 compose->header_nextrow, compose->header_nextrow+1,
6954 GTK_SHRINK, GTK_FILL, 0, 0);
6955 if (compose->header_last && (compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN)) {
6956 const gchar *last_header_entry = gtk_entry_get_text(
6957 GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
6959 while (*string != NULL) {
6960 if (!strcmp(prefs_common_translated_header_name(*string), last_header_entry))
6961 standard_header = TRUE;
6964 if (standard_header)
6965 header = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
6967 if (!compose->header_last || !standard_header) {
6968 switch(compose->account->protocol) {
6970 header = prefs_common_translated_header_name("Newsgroups:");
6973 header = prefs_common_translated_header_name("To:");
6978 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((combo)))), header);
6980 gtk_editable_set_editable(
6981 GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((combo)))),
6982 prefs_common.type_any_header);
6984 g_signal_connect_after(G_OBJECT(gtk_bin_get_child(GTK_BIN((combo)))), "grab_focus",
6985 G_CALLBACK(compose_grab_focus_cb), compose);
6987 /* Entry field with cleanup button */
6988 button = gtk_button_new();
6989 gtk_button_set_image(GTK_BUTTON(button),
6990 gtk_image_new_from_stock(GTK_STOCK_CLEAR, GTK_ICON_SIZE_MENU));
6991 gtk_widget_show(button);
6992 CLAWS_SET_TIP(button,
6993 _("Delete entry contents"));
6994 entry = gtk_entry_new();
6995 gtk_widget_show(entry);
6996 CLAWS_SET_TIP(entry,
6997 _("Use <tab> to autocomplete from addressbook"));
6998 hbox = gtk_hbox_new (FALSE, 0);
6999 gtk_widget_show(hbox);
7000 gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, TRUE, 0);
7001 gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
7002 gtk_table_attach(GTK_TABLE(compose->header_table), hbox, 1, 2,
7003 compose->header_nextrow, compose->header_nextrow+1,
7004 GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
7006 g_signal_connect(G_OBJECT(entry), "key-press-event",
7007 G_CALLBACK(compose_headerentry_key_press_event_cb),
7009 g_signal_connect(G_OBJECT(entry), "changed",
7010 G_CALLBACK(compose_headerentry_changed_cb),
7012 g_signal_connect_after(G_OBJECT(entry), "grab_focus",
7013 G_CALLBACK(compose_grab_focus_cb), compose);
7015 g_signal_connect(G_OBJECT(button), "clicked",
7016 G_CALLBACK(compose_headerentry_button_clicked_cb),
7020 gtk_drag_dest_set(entry, GTK_DEST_DEFAULT_ALL, compose_mime_types,
7021 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7022 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7023 g_signal_connect(G_OBJECT(entry), "drag_data_received",
7024 G_CALLBACK(compose_header_drag_received_cb),
7026 g_signal_connect(G_OBJECT(entry), "drag-drop",
7027 G_CALLBACK(compose_drag_drop),
7029 g_signal_connect(G_OBJECT(entry), "populate-popup",
7030 G_CALLBACK(compose_entry_popup_extend),
7033 address_completion_register_entry(GTK_ENTRY(entry), TRUE);
7035 headerentry->compose = compose;
7036 headerentry->combo = combo;
7037 headerentry->entry = entry;
7038 headerentry->button = button;
7039 headerentry->hbox = hbox;
7040 headerentry->headernum = compose->header_nextrow;
7041 headerentry->type = PREF_NONE;
7043 compose->header_nextrow++;
7044 compose->header_last = headerentry;
7045 compose->header_list =
7046 g_slist_append(compose->header_list,
7050 static void compose_add_header_entry(Compose *compose, const gchar *header,
7051 gchar *text, ComposePrefType pref_type)
7053 ComposeHeaderEntry *last_header = compose->header_last;
7054 gchar *tmp = g_strdup(text), *email;
7055 gboolean replyto_hdr;
7057 replyto_hdr = (!strcasecmp(header,
7058 prefs_common_translated_header_name("Reply-To:")) ||
7060 prefs_common_translated_header_name("Followup-To:")) ||
7062 prefs_common_translated_header_name("In-Reply-To:")));
7064 extract_address(tmp);
7065 email = g_utf8_strdown(tmp, -1);
7067 if (replyto_hdr == FALSE &&
7068 g_hash_table_lookup(compose->email_hashtable, email) != NULL)
7070 debug_print("Ignoring duplicate address - %s %s, pref_type: %d\n",
7071 header, text, (gint) pref_type);
7077 if (!strcasecmp(header, prefs_common_translated_header_name("In-Reply-To:")))
7078 gtk_entry_set_text(GTK_ENTRY(
7079 gtk_bin_get_child(GTK_BIN(last_header->combo))), header);
7081 combobox_select_by_text(GTK_COMBO_BOX(last_header->combo), header);
7082 gtk_entry_set_text(GTK_ENTRY(last_header->entry), text);
7083 last_header->type = pref_type;
7085 if (replyto_hdr == FALSE)
7086 g_hash_table_insert(compose->email_hashtable, email,
7087 GUINT_TO_POINTER(1));
7094 static void compose_destroy_headerentry(Compose *compose,
7095 ComposeHeaderEntry *headerentry)
7097 gchar *text = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
7100 extract_address(text);
7101 email = g_utf8_strdown(text, -1);
7102 g_hash_table_remove(compose->email_hashtable, email);
7106 gtk_widget_destroy(headerentry->combo);
7107 gtk_widget_destroy(headerentry->entry);
7108 gtk_widget_destroy(headerentry->button);
7109 gtk_widget_destroy(headerentry->hbox);
7110 g_free(headerentry);
7113 static void compose_remove_header_entries(Compose *compose)
7116 for (list = compose->header_list; list; list = list->next)
7117 compose_destroy_headerentry(compose, (ComposeHeaderEntry *)list->data);
7119 compose->header_last = NULL;
7120 g_slist_free(compose->header_list);
7121 compose->header_list = NULL;
7122 compose->header_nextrow = 1;
7123 compose_create_header_entry(compose);
7126 static GtkWidget *compose_create_header(Compose *compose)
7128 GtkWidget *from_optmenu_hbox;
7129 GtkWidget *header_table_main;
7130 GtkWidget *header_scrolledwin;
7131 GtkWidget *header_table;
7133 /* parent with account selection and from header */
7134 header_table_main = gtk_table_new(2, 2, FALSE);
7135 gtk_widget_show(header_table_main);
7136 gtk_container_set_border_width(GTK_CONTAINER(header_table_main), BORDER_WIDTH);
7138 from_optmenu_hbox = compose_account_option_menu_create(compose);
7139 gtk_table_attach(GTK_TABLE(header_table_main), from_optmenu_hbox,
7140 0, 2, 0, 1, GTK_EXPAND | GTK_FILL, GTK_SHRINK, 0, 0);
7142 /* child with header labels and entries */
7143 header_scrolledwin = gtk_scrolled_window_new(NULL, NULL);
7144 gtk_widget_show(header_scrolledwin);
7145 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
7147 header_table = gtk_table_new(2, 2, FALSE);
7148 gtk_widget_show(header_table);
7149 gtk_container_set_border_width(GTK_CONTAINER(header_table), 0);
7150 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin), header_table);
7151 gtk_container_set_focus_vadjustment(GTK_CONTAINER(header_table),
7152 gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(header_scrolledwin)));
7153 gtk_viewport_set_shadow_type(GTK_VIEWPORT(gtk_bin_get_child(GTK_BIN(header_scrolledwin))), GTK_SHADOW_NONE);
7155 gtk_table_attach(GTK_TABLE(header_table_main), header_scrolledwin,
7156 0, 2, 1, 2, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 2);
7158 compose->header_table = header_table;
7159 compose->header_list = NULL;
7160 compose->header_nextrow = 0;
7162 compose_create_header_entry(compose);
7164 compose->table = NULL;
7166 return header_table_main;
7169 static gboolean popup_attach_button_pressed(GtkWidget *widget, gpointer data)
7171 Compose *compose = (Compose *)data;
7172 GdkEventButton event;
7175 event.time = gtk_get_current_event_time();
7177 return attach_button_pressed(compose->attach_clist, &event, compose);
7180 static GtkWidget *compose_create_attach(Compose *compose)
7182 GtkWidget *attach_scrwin;
7183 GtkWidget *attach_clist;
7185 GtkListStore *store;
7186 GtkCellRenderer *renderer;
7187 GtkTreeViewColumn *column;
7188 GtkTreeSelection *selection;
7190 /* attachment list */
7191 attach_scrwin = gtk_scrolled_window_new(NULL, NULL);
7192 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(attach_scrwin),
7193 GTK_POLICY_AUTOMATIC,
7194 GTK_POLICY_AUTOMATIC);
7195 gtk_widget_set_size_request(attach_scrwin, -1, 80);
7197 store = gtk_list_store_new(N_ATTACH_COLS,
7203 G_TYPE_AUTO_POINTER,
7205 attach_clist = GTK_WIDGET(gtk_tree_view_new_with_model
7206 (GTK_TREE_MODEL(store)));
7207 gtk_container_add(GTK_CONTAINER(attach_scrwin), attach_clist);
7208 g_object_unref(store);
7210 renderer = gtk_cell_renderer_text_new();
7211 column = gtk_tree_view_column_new_with_attributes
7212 (_("Mime type"), renderer, "text",
7213 COL_MIMETYPE, NULL);
7214 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7216 renderer = gtk_cell_renderer_text_new();
7217 column = gtk_tree_view_column_new_with_attributes
7218 (_("Size"), renderer, "text",
7220 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7222 renderer = gtk_cell_renderer_text_new();
7223 column = gtk_tree_view_column_new_with_attributes
7224 (_("Name"), renderer, "text",
7226 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7228 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(attach_clist),
7229 prefs_common.use_stripes_everywhere);
7230 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(attach_clist));
7231 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
7233 g_signal_connect(G_OBJECT(attach_clist), "row_activated",
7234 G_CALLBACK(attach_selected), compose);
7235 g_signal_connect(G_OBJECT(attach_clist), "button_press_event",
7236 G_CALLBACK(attach_button_pressed), compose);
7237 g_signal_connect(G_OBJECT(attach_clist), "popup-menu",
7238 G_CALLBACK(popup_attach_button_pressed), compose);
7239 g_signal_connect(G_OBJECT(attach_clist), "key_press_event",
7240 G_CALLBACK(attach_key_pressed), compose);
7243 gtk_drag_dest_set(attach_clist,
7244 GTK_DEST_DEFAULT_ALL, compose_mime_types,
7245 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7246 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7247 g_signal_connect(G_OBJECT(attach_clist), "drag_data_received",
7248 G_CALLBACK(compose_attach_drag_received_cb),
7250 g_signal_connect(G_OBJECT(attach_clist), "drag-drop",
7251 G_CALLBACK(compose_drag_drop),
7254 compose->attach_scrwin = attach_scrwin;
7255 compose->attach_clist = attach_clist;
7257 return attach_scrwin;
7260 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose);
7261 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose);
7263 static GtkWidget *compose_create_others(Compose *compose)
7266 GtkWidget *savemsg_checkbtn;
7267 GtkWidget *savemsg_combo;
7268 GtkWidget *savemsg_select;
7271 gchar *folderidentifier;
7273 /* Table for settings */
7274 table = gtk_table_new(3, 1, FALSE);
7275 gtk_container_set_border_width(GTK_CONTAINER(table), BORDER_WIDTH);
7276 gtk_widget_show(table);
7277 gtk_table_set_row_spacings(GTK_TABLE(table), VSPACING_NARROW);
7280 /* Save Message to folder */
7281 savemsg_checkbtn = gtk_check_button_new_with_label(_("Save Message to "));
7282 gtk_widget_show(savemsg_checkbtn);
7283 gtk_table_attach(GTK_TABLE(table), savemsg_checkbtn, 0, 1, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7284 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7285 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), prefs_common.savemsg);
7287 g_signal_connect(G_OBJECT(savemsg_checkbtn), "toggled",
7288 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
7290 #if !GTK_CHECK_VERSION(2, 24, 0)
7291 savemsg_combo = gtk_combo_box_entry_new_text();
7293 savemsg_combo = gtk_combo_box_text_new_with_entry();
7295 compose->savemsg_checkbtn = savemsg_checkbtn;
7296 compose->savemsg_combo = savemsg_combo;
7297 gtk_widget_show(savemsg_combo);
7299 if (prefs_common.compose_save_to_history)
7300 #if !GTK_CHECK_VERSION(2, 24, 0)
7301 combobox_set_popdown_strings(GTK_COMBO_BOX(savemsg_combo),
7302 prefs_common.compose_save_to_history);
7304 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(savemsg_combo),
7305 prefs_common.compose_save_to_history);
7307 gtk_table_attach(GTK_TABLE(table), savemsg_combo, 1, 2, rowcount, rowcount + 1, GTK_FILL|GTK_EXPAND, GTK_SHRINK, 0, 0);
7308 gtk_widget_set_sensitive(GTK_WIDGET(savemsg_combo), prefs_common.savemsg);
7309 g_signal_connect_after(G_OBJECT(savemsg_combo), "grab_focus",
7310 G_CALLBACK(compose_grab_focus_cb), compose);
7311 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7312 folderidentifier = folder_item_get_identifier(account_get_special_folder
7313 (compose->account, F_OUTBOX));
7314 compose_set_save_to(compose, folderidentifier);
7315 g_free(folderidentifier);
7318 savemsg_select = gtkut_get_browse_file_btn(_("_Browse"));
7319 gtk_widget_show(savemsg_select);
7320 gtk_table_attach(GTK_TABLE(table), savemsg_select, 2, 3, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7321 g_signal_connect(G_OBJECT(savemsg_select), "clicked",
7322 G_CALLBACK(compose_savemsg_select_cb),
7328 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose)
7330 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo),
7331 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn)));
7334 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose)
7339 dest = foldersel_folder_sel(NULL, FOLDER_SEL_COPY, NULL, FALSE);
7342 path = folder_item_get_identifier(dest);
7344 compose_set_save_to(compose, path);
7348 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry, gboolean wrap,
7349 GdkAtom clip, GtkTextIter *insert_place);
7352 static gboolean text_clicked(GtkWidget *text, GdkEventButton *event,
7356 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
7358 if (event->button == 3) {
7360 GtkTextIter sel_start, sel_end;
7361 gboolean stuff_selected;
7363 /* move the cursor to allow GtkAspell to check the word
7364 * under the mouse */
7365 if (event->x && event->y) {
7366 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7367 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7369 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7372 GtkTextMark *mark = gtk_text_buffer_get_insert(buffer);
7373 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7376 stuff_selected = gtk_text_buffer_get_selection_bounds(
7378 &sel_start, &sel_end);
7380 gtk_text_buffer_place_cursor (buffer, &iter);
7381 /* reselect stuff */
7383 && gtk_text_iter_in_range(&iter, &sel_start, &sel_end)) {
7384 gtk_text_buffer_select_range(buffer,
7385 &sel_start, &sel_end);
7387 return FALSE; /* pass the event so that the right-click goes through */
7390 if (event->button == 2) {
7395 /* get the middle-click position to paste at the correct place */
7396 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7397 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7399 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7402 entry_paste_clipboard(compose, text,
7403 prefs_common.linewrap_pastes,
7404 GDK_SELECTION_PRIMARY, &iter);
7412 static void compose_spell_menu_changed(void *data)
7414 Compose *compose = (Compose *)data;
7416 GtkWidget *menuitem;
7417 GtkWidget *parent_item;
7418 GtkMenu *menu = GTK_MENU(gtk_menu_new());
7421 if (compose->gtkaspell == NULL)
7424 parent_item = gtk_ui_manager_get_widget(compose->ui_manager,
7425 "/Menu/Spelling/Options");
7427 /* setting the submenu removes /Spelling/Options from the factory
7428 * so we need to save it */
7430 if (parent_item == NULL) {
7431 parent_item = compose->aspell_options_menu;
7432 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), NULL);
7434 compose->aspell_options_menu = parent_item;
7436 spell_menu = gtkaspell_make_config_menu(compose->gtkaspell);
7438 spell_menu = g_slist_reverse(spell_menu);
7439 for (items = spell_menu;
7440 items; items = items->next) {
7441 menuitem = GTK_WIDGET(GTK_MENU_ITEM(items->data));
7442 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), GTK_WIDGET(menuitem));
7443 gtk_widget_show(GTK_WIDGET(menuitem));
7445 g_slist_free(spell_menu);
7447 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), GTK_WIDGET(menu));
7448 gtk_widget_show(parent_item);
7451 static void compose_dict_changed(void *data)
7453 Compose *compose = (Compose *) data;
7455 if(!compose->gtkaspell)
7457 if(compose->gtkaspell->recheck_when_changing_dict == FALSE)
7460 gtkaspell_highlight_all(compose->gtkaspell);
7461 claws_spell_entry_recheck_all(CLAWS_SPELL_ENTRY(compose->subject_entry));
7465 static gboolean compose_popup_menu(GtkWidget *widget, gpointer data)
7467 Compose *compose = (Compose *)data;
7468 GdkEventButton event;
7471 event.time = gtk_get_current_event_time();
7475 return text_clicked(compose->text, &event, compose);
7478 static gboolean compose_force_window_origin = TRUE;
7479 static Compose *compose_create(PrefsAccount *account,
7488 GtkWidget *handlebox;
7490 GtkWidget *notebook;
7492 GtkWidget *attach_hbox;
7493 GtkWidget *attach_lab1;
7494 GtkWidget *attach_lab2;
7499 GtkWidget *subject_hbox;
7500 GtkWidget *subject_frame;
7501 GtkWidget *subject_entry;
7505 GtkWidget *edit_vbox;
7506 GtkWidget *ruler_hbox;
7508 GtkWidget *scrolledwin;
7510 GtkTextBuffer *buffer;
7511 GtkClipboard *clipboard;
7513 UndoMain *undostruct;
7515 GtkWidget *popupmenu;
7516 GtkWidget *tmpl_menu;
7517 GtkActionGroup *action_group = NULL;
7520 GtkAspell * gtkaspell = NULL;
7523 static GdkGeometry geometry;
7525 cm_return_val_if_fail(account != NULL, NULL);
7527 debug_print("Creating compose window...\n");
7528 compose = g_new0(Compose, 1);
7530 compose->batch = batch;
7531 compose->account = account;
7532 compose->folder = folder;
7534 compose->mutex = cm_mutex_new();
7535 compose->set_cursor_pos = -1;
7537 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose");
7539 gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
7540 gtk_widget_set_size_request(window, prefs_common.compose_width,
7541 prefs_common.compose_height);
7543 if (!geometry.max_width) {
7544 geometry.max_width = gdk_screen_width();
7545 geometry.max_height = gdk_screen_height();
7548 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7549 &geometry, GDK_HINT_MAX_SIZE);
7550 if (!geometry.min_width) {
7551 geometry.min_width = 600;
7552 geometry.min_height = 440;
7554 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7555 &geometry, GDK_HINT_MIN_SIZE);
7557 #ifndef GENERIC_UMPC
7558 if (compose_force_window_origin)
7559 gtk_window_move(GTK_WINDOW(window), prefs_common.compose_x,
7560 prefs_common.compose_y);
7562 g_signal_connect(G_OBJECT(window), "delete_event",
7563 G_CALLBACK(compose_delete_cb), compose);
7564 MANAGE_WINDOW_SIGNALS_CONNECT(window);
7565 gtk_widget_realize(window);
7567 gtkut_widget_set_composer_icon(window);
7569 vbox = gtk_vbox_new(FALSE, 0);
7570 gtk_container_add(GTK_CONTAINER(window), vbox);
7572 compose->ui_manager = gtk_ui_manager_new();
7573 action_group = cm_menu_create_action_group_full(compose->ui_manager,"Menu", compose_entries,
7574 G_N_ELEMENTS(compose_entries), (gpointer)compose);
7575 gtk_action_group_add_toggle_actions(action_group, compose_toggle_entries,
7576 G_N_ELEMENTS(compose_toggle_entries), (gpointer)compose);
7577 gtk_action_group_add_radio_actions(action_group, compose_radio_rm_entries,
7578 G_N_ELEMENTS(compose_radio_rm_entries), COMPOSE_REPLY, G_CALLBACK(compose_reply_change_mode_cb), (gpointer)compose);
7579 gtk_action_group_add_radio_actions(action_group, compose_radio_prio_entries,
7580 G_N_ELEMENTS(compose_radio_prio_entries), PRIORITY_NORMAL, G_CALLBACK(compose_set_priority_cb), (gpointer)compose);
7581 gtk_action_group_add_radio_actions(action_group, compose_radio_enc_entries,
7582 G_N_ELEMENTS(compose_radio_enc_entries), C_AUTO, G_CALLBACK(compose_set_encoding_cb), (gpointer)compose);
7584 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Menu", NULL, GTK_UI_MANAGER_MENUBAR)
7586 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Message", "Message", GTK_UI_MANAGER_MENU)
7587 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Edit", "Edit", GTK_UI_MANAGER_MENU)
7589 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Spelling", "Spelling", GTK_UI_MANAGER_MENU)
7591 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Options", "Options", GTK_UI_MANAGER_MENU)
7592 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Tools", "Tools", GTK_UI_MANAGER_MENU)
7593 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Help", "Help", GTK_UI_MANAGER_MENU)
7596 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Send", "Message/Send", GTK_UI_MANAGER_MENUITEM)
7597 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "SendLater", "Message/SendLater", GTK_UI_MANAGER_MENUITEM)
7598 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator1", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7599 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "AttachFile", "Message/AttachFile", GTK_UI_MANAGER_MENUITEM)
7600 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertFile", "Message/InsertFile", GTK_UI_MANAGER_MENUITEM)
7601 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertSig", "Message/InsertSig", GTK_UI_MANAGER_MENUITEM)
7602 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "ReplaceSig", "Message/ReplaceSig", GTK_UI_MANAGER_MENUITEM)
7603 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator2", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7604 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Save", "Message/Save", GTK_UI_MANAGER_MENUITEM)
7605 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator3", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7606 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Print", "Message/Print", GTK_UI_MANAGER_MENUITEM)
7607 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator4", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7608 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Close", "Message/Close", GTK_UI_MANAGER_MENUITEM)
7611 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Undo", "Edit/Undo", GTK_UI_MANAGER_MENUITEM)
7612 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Redo", "Edit/Redo", GTK_UI_MANAGER_MENUITEM)
7613 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator1", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7615 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Cut", "Edit/Cut", GTK_UI_MANAGER_MENUITEM)
7616 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Copy", "Edit/Copy", GTK_UI_MANAGER_MENUITEM)
7617 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Paste", "Edit/Paste", GTK_UI_MANAGER_MENUITEM)
7619 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SpecialPaste", "Edit/SpecialPaste", GTK_UI_MANAGER_MENU)
7620 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "AsQuotation", "Edit/SpecialPaste/AsQuotation", GTK_UI_MANAGER_MENUITEM)
7621 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Wrapped", "Edit/SpecialPaste/Wrapped", GTK_UI_MANAGER_MENUITEM)
7622 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Unwrapped", "Edit/SpecialPaste/Unwrapped", GTK_UI_MANAGER_MENUITEM)
7624 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SelectAll", "Edit/SelectAll", GTK_UI_MANAGER_MENUITEM)
7626 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Advanced", "Edit/Advanced", GTK_UI_MANAGER_MENU)
7627 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackChar", "Edit/Advanced/BackChar", GTK_UI_MANAGER_MENUITEM)
7628 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwChar", "Edit/Advanced/ForwChar", GTK_UI_MANAGER_MENUITEM)
7629 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackWord", "Edit/Advanced/BackWord", GTK_UI_MANAGER_MENUITEM)
7630 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwWord", "Edit/Advanced/ForwWord", GTK_UI_MANAGER_MENUITEM)
7631 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BegLine", "Edit/Advanced/BegLine", GTK_UI_MANAGER_MENUITEM)
7632 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "EndLine", "Edit/Advanced/EndLine", GTK_UI_MANAGER_MENUITEM)
7633 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "PrevLine", "Edit/Advanced/PrevLine", GTK_UI_MANAGER_MENUITEM)
7634 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "NextLine", "Edit/Advanced/NextLine", GTK_UI_MANAGER_MENUITEM)
7635 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackChar", "Edit/Advanced/DelBackChar", GTK_UI_MANAGER_MENUITEM)
7636 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwChar", "Edit/Advanced/DelForwChar", GTK_UI_MANAGER_MENUITEM)
7637 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackWord", "Edit/Advanced/DelBackWord", GTK_UI_MANAGER_MENUITEM)
7638 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwWord", "Edit/Advanced/DelForwWord", GTK_UI_MANAGER_MENUITEM)
7639 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelLine", "Edit/Advanced/DelLine", GTK_UI_MANAGER_MENUITEM)
7640 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelEndLine", "Edit/Advanced/DelEndLine", GTK_UI_MANAGER_MENUITEM)
7642 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator2", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7644 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Find", "Edit/Find", GTK_UI_MANAGER_MENUITEM)
7645 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapPara", "Edit/WrapPara", GTK_UI_MANAGER_MENUITEM)
7646 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapAllLines", "Edit/WrapAllLines", GTK_UI_MANAGER_MENUITEM)
7647 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoWrap", "Edit/AutoWrap", GTK_UI_MANAGER_MENUITEM)
7648 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoIndent", "Edit/AutoIndent", GTK_UI_MANAGER_MENUITEM)
7650 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator3", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7652 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "ExtEditor", "Edit/ExtEditor", GTK_UI_MANAGER_MENUITEM)
7656 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckAllSel", "Spelling/CheckAllSel", GTK_UI_MANAGER_MENUITEM)
7657 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "HighlightAll", "Spelling/HighlightAll", GTK_UI_MANAGER_MENUITEM)
7658 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckBackwards", "Spelling/CheckBackwards", GTK_UI_MANAGER_MENUITEM)
7659 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "ForwardNext", "Spelling/ForwardNext", GTK_UI_MANAGER_MENUITEM)
7660 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Separator1", "Spelling/---", GTK_UI_MANAGER_SEPARATOR)
7661 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Options", "Spelling/Options", GTK_UI_MANAGER_MENU)
7665 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "ReplyMode", "Options/ReplyMode", GTK_UI_MANAGER_MENU)
7666 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Normal", "Options/ReplyMode/Normal", GTK_UI_MANAGER_MENUITEM)
7667 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "All", "Options/ReplyMode/All", GTK_UI_MANAGER_MENUITEM)
7668 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Sender", "Options/ReplyMode/Sender", GTK_UI_MANAGER_MENUITEM)
7669 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "List", "Options/ReplyMode/List", GTK_UI_MANAGER_MENUITEM)
7671 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator1", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7672 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "PrivacySystem", "Options/PrivacySystem", GTK_UI_MANAGER_MENU)
7673 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/PrivacySystem", "PlaceHolder", "Options/PrivacySystem/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7674 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Sign", "Options/Sign", GTK_UI_MANAGER_MENUITEM)
7675 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encrypt", "Options/Encrypt", GTK_UI_MANAGER_MENUITEM)
7678 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator2", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7679 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Priority", "Options/Priority", GTK_UI_MANAGER_MENU)
7680 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Highest", "Options/Priority/Highest", GTK_UI_MANAGER_MENUITEM)
7681 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "High", "Options/Priority/High", GTK_UI_MANAGER_MENUITEM)
7682 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Normal", "Options/Priority/Normal", GTK_UI_MANAGER_MENUITEM)
7683 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Low", "Options/Priority/Low", GTK_UI_MANAGER_MENUITEM)
7684 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Lowest", "Options/Priority/Lowest", GTK_UI_MANAGER_MENUITEM)
7686 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator3", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7687 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RequestRetRcpt", "Options/RequestRetRcpt", GTK_UI_MANAGER_MENUITEM)
7688 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator4", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7689 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RemoveReferences", "Options/RemoveReferences", GTK_UI_MANAGER_MENUITEM)
7690 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator5", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7692 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encoding", "Options/Encoding", GTK_UI_MANAGER_MENU)
7694 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_AUTO, "Options/Encoding/"CS_AUTO, GTK_UI_MANAGER_MENUITEM)
7695 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator1", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7696 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_US_ASCII, "Options/Encoding/"CS_US_ASCII, GTK_UI_MANAGER_MENUITEM)
7697 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_UTF_8, "Options/Encoding/"CS_UTF_8, GTK_UI_MANAGER_MENUITEM)
7698 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator2", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7700 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Western", "Options/Encoding/Western", GTK_UI_MANAGER_MENU)
7701 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)
7702 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)
7703 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Western", CS_WINDOWS_1252, "Options/Encoding/Western/"CS_WINDOWS_1252, GTK_UI_MANAGER_MENUITEM)
7705 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_2, "Options/Encoding/"CS_ISO_8859_2, GTK_UI_MANAGER_MENUITEM)
7707 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Baltic", "Options/Encoding/Baltic", GTK_UI_MANAGER_MENU)
7708 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)
7709 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)
7711 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_7, "Options/Encoding/"CS_ISO_8859_7, GTK_UI_MANAGER_MENUITEM)
7713 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Hebrew", "Options/Encoding/Hebrew", GTK_UI_MANAGER_MENU)
7714 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)
7715 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Hebrew", CS_WINDOWS_1255, "Options/Encoding/Hebrew/"CS_WINDOWS_1255, GTK_UI_MANAGER_MENUITEM)
7717 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Arabic", "Options/Encoding/Arabic", GTK_UI_MANAGER_MENU)
7718 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)
7719 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Arabic", CS_WINDOWS_1256, "Options/Encoding/Arabic/"CS_WINDOWS_1256, GTK_UI_MANAGER_MENUITEM)
7721 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_9, "Options/Encoding/"CS_ISO_8859_9, GTK_UI_MANAGER_MENUITEM)
7723 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Cyrillic", "Options/Encoding/Cyrillic", GTK_UI_MANAGER_MENU)
7724 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)
7725 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_R, "Options/Encoding/Cyrillic/"CS_KOI8_R, GTK_UI_MANAGER_MENUITEM)
7726 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_MACCYR, "Options/Encoding/Cyrillic/"CS_MACCYR, GTK_UI_MANAGER_MENUITEM)
7727 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_U, "Options/Encoding/Cyrillic/"CS_KOI8_U, GTK_UI_MANAGER_MENUITEM)
7728 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_WINDOWS_1251, "Options/Encoding/Cyrillic/"CS_WINDOWS_1251, GTK_UI_MANAGER_MENUITEM)
7730 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Japanese", "Options/Encoding/Japanese", GTK_UI_MANAGER_MENU)
7731 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)
7732 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)
7733 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_EUC_JP, "Options/Encoding/Japanese/"CS_EUC_JP, GTK_UI_MANAGER_MENUITEM)
7734 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_SHIFT_JIS, "Options/Encoding/Japanese/"CS_SHIFT_JIS, GTK_UI_MANAGER_MENUITEM)
7736 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Chinese", "Options/Encoding/Chinese", GTK_UI_MANAGER_MENU)
7737 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB18030, "Options/Encoding/Chinese/"CS_GB18030, GTK_UI_MANAGER_MENUITEM)
7738 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB2312, "Options/Encoding/Chinese/"CS_GB2312, GTK_UI_MANAGER_MENUITEM)
7739 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GBK, "Options/Encoding/Chinese/"CS_GBK, GTK_UI_MANAGER_MENUITEM)
7740 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_BIG5, "Options/Encoding/Chinese/"CS_BIG5, GTK_UI_MANAGER_MENUITEM)
7741 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_EUC_TW, "Options/Encoding/Chinese/"CS_EUC_TW, GTK_UI_MANAGER_MENUITEM)
7743 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Korean", "Options/Encoding/Korean", GTK_UI_MANAGER_MENU)
7744 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Korean", CS_EUC_KR, "Options/Encoding/Korean/"CS_EUC_KR, GTK_UI_MANAGER_MENUITEM)
7745 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)
7747 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Thai", "Options/Encoding/Thai", GTK_UI_MANAGER_MENU)
7748 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_TIS_620, "Options/Encoding/Thai/"CS_TIS_620, GTK_UI_MANAGER_MENUITEM)
7749 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_WINDOWS_874, "Options/Encoding/Thai/"CS_WINDOWS_874, GTK_UI_MANAGER_MENUITEM)
7753 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "ShowRuler", "Tools/ShowRuler", GTK_UI_MANAGER_MENUITEM)
7754 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "AddressBook", "Tools/AddressBook", GTK_UI_MANAGER_MENUITEM)
7755 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Template", "Tools/Template", GTK_UI_MANAGER_MENU)
7756 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Template", "PlaceHolder", "Tools/Template/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7757 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Actions", "Tools/Actions", GTK_UI_MANAGER_MENU)
7758 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Actions", "PlaceHolder", "Tools/Actions/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7761 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Help", "About", "Help/About", GTK_UI_MANAGER_MENUITEM)
7763 menubar = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu");
7764 gtk_widget_show_all(menubar);
7766 gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(compose->ui_manager));
7767 gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, TRUE, 0);
7769 if (prefs_common.toolbar_detachable) {
7770 handlebox = gtk_handle_box_new();
7772 handlebox = gtk_hbox_new(FALSE, 0);
7774 gtk_box_pack_start(GTK_BOX(vbox), handlebox, FALSE, FALSE, 0);
7776 gtk_widget_realize(handlebox);
7777 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, handlebox,
7780 vbox2 = gtk_vbox_new(FALSE, 2);
7781 gtk_box_pack_start(GTK_BOX(vbox), vbox2, TRUE, TRUE, 0);
7782 gtk_container_set_border_width(GTK_CONTAINER(vbox2), 0);
7785 notebook = gtk_notebook_new();
7786 gtk_widget_show(notebook);
7788 /* header labels and entries */
7789 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7790 compose_create_header(compose),
7791 gtk_label_new_with_mnemonic(_("Hea_der")));
7792 /* attachment list */
7793 attach_hbox = gtk_hbox_new(FALSE, 0);
7794 gtk_widget_show(attach_hbox);
7796 attach_lab1 = gtk_label_new_with_mnemonic(_("_Attachments"));
7797 gtk_widget_show(attach_lab1);
7798 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab1, TRUE, TRUE, 0);
7800 attach_lab2 = gtk_label_new("");
7801 gtk_widget_show(attach_lab2);
7802 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab2, FALSE, FALSE, 0);
7804 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7805 compose_create_attach(compose),
7808 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7809 compose_create_others(compose),
7810 gtk_label_new_with_mnemonic(_("Othe_rs")));
7813 subject_hbox = gtk_hbox_new(FALSE, 0);
7814 gtk_widget_show(subject_hbox);
7816 subject_frame = gtk_frame_new(NULL);
7817 gtk_frame_set_shadow_type(GTK_FRAME(subject_frame), GTK_SHADOW_NONE);
7818 gtk_box_pack_start(GTK_BOX(subject_hbox), subject_frame, TRUE, TRUE, 0);
7819 gtk_widget_show(subject_frame);
7821 subject = gtk_hbox_new(FALSE, HSPACING_NARROW);
7822 gtk_container_set_border_width(GTK_CONTAINER(subject), 0);
7823 gtk_widget_show(subject);
7825 label = gtk_label_new_with_mnemonic(_("S_ubject:"));
7826 gtk_box_pack_start(GTK_BOX(subject), label, FALSE, FALSE, 0);
7827 gtk_widget_show(label);
7830 subject_entry = claws_spell_entry_new();
7832 subject_entry = gtk_entry_new();
7834 gtk_box_pack_start(GTK_BOX(subject), subject_entry, TRUE, TRUE, 0);
7835 g_signal_connect_after(G_OBJECT(subject_entry), "grab_focus",
7836 G_CALLBACK(compose_grab_focus_cb), compose);
7837 gtk_label_set_mnemonic_widget(GTK_LABEL(label), subject_entry);
7838 gtk_widget_show(subject_entry);
7839 compose->subject_entry = subject_entry;
7840 gtk_container_add(GTK_CONTAINER(subject_frame), subject);
7842 edit_vbox = gtk_vbox_new(FALSE, 0);
7844 gtk_box_pack_start(GTK_BOX(edit_vbox), subject_hbox, FALSE, FALSE, 0);
7847 ruler_hbox = gtk_hbox_new(FALSE, 0);
7848 gtk_box_pack_start(GTK_BOX(edit_vbox), ruler_hbox, FALSE, FALSE, 0);
7850 ruler = gtk_shruler_new(GTK_ORIENTATION_HORIZONTAL);
7851 gtk_shruler_set_range(GTK_SHRULER(ruler), 0.0, 100.0, 1.0);
7852 gtk_box_pack_start(GTK_BOX(ruler_hbox), ruler, TRUE, TRUE,
7856 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
7857 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
7858 GTK_POLICY_AUTOMATIC,
7859 GTK_POLICY_AUTOMATIC);
7860 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
7862 gtk_box_pack_start(GTK_BOX(edit_vbox), scrolledwin, TRUE, TRUE, 0);
7864 text = gtk_text_view_new();
7865 if (prefs_common.show_compose_margin) {
7866 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text), 6);
7867 gtk_text_view_set_right_margin(GTK_TEXT_VIEW(text), 6);
7869 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
7870 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
7871 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), TRUE);
7872 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
7873 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
7875 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
7876 g_signal_connect_after(G_OBJECT(text), "size_allocate",
7877 G_CALLBACK(compose_edit_size_alloc),
7879 g_signal_connect(G_OBJECT(buffer), "changed",
7880 G_CALLBACK(compose_changed_cb), compose);
7881 g_signal_connect(G_OBJECT(text), "grab_focus",
7882 G_CALLBACK(compose_grab_focus_cb), compose);
7883 g_signal_connect(G_OBJECT(buffer), "insert_text",
7884 G_CALLBACK(text_inserted), compose);
7885 g_signal_connect(G_OBJECT(text), "button_press_event",
7886 G_CALLBACK(text_clicked), compose);
7887 g_signal_connect(G_OBJECT(text), "popup-menu",
7888 G_CALLBACK(compose_popup_menu), compose);
7889 g_signal_connect(G_OBJECT(subject_entry), "changed",
7890 G_CALLBACK(compose_changed_cb), compose);
7891 g_signal_connect(G_OBJECT(subject_entry), "activate",
7892 G_CALLBACK(compose_subject_entry_activated), compose);
7895 gtk_drag_dest_set(text, GTK_DEST_DEFAULT_ALL, compose_mime_types,
7896 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7897 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7898 g_signal_connect(G_OBJECT(text), "drag_data_received",
7899 G_CALLBACK(compose_insert_drag_received_cb),
7901 g_signal_connect(G_OBJECT(text), "drag-drop",
7902 G_CALLBACK(compose_drag_drop),
7904 g_signal_connect(G_OBJECT(text), "key-press-event",
7905 G_CALLBACK(completion_set_focus_to_subject),
7907 gtk_widget_show_all(vbox);
7909 /* pane between attach clist and text */
7910 paned = gtk_vpaned_new();
7911 gtk_container_add(GTK_CONTAINER(vbox2), paned);
7912 gtk_paned_pack1(GTK_PANED(paned), notebook, FALSE, FALSE);
7913 gtk_paned_pack2(GTK_PANED(paned), edit_vbox, TRUE, FALSE);
7914 gtk_paned_set_position(GTK_PANED(paned), prefs_common.compose_notebook_height);
7915 g_signal_connect(G_OBJECT(notebook), "size_allocate",
7916 G_CALLBACK(compose_notebook_size_alloc), paned);
7918 gtk_widget_show_all(paned);
7921 if (prefs_common.textfont) {
7922 PangoFontDescription *font_desc;
7924 font_desc = pango_font_description_from_string
7925 (prefs_common.textfont);
7927 gtk_widget_modify_font(text, font_desc);
7928 pango_font_description_free(font_desc);
7932 gtk_action_group_add_actions(action_group, compose_popup_entries,
7933 G_N_ELEMENTS(compose_popup_entries), (gpointer)compose);
7934 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Popup", NULL, GTK_UI_MANAGER_MENUBAR)
7935 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup", "Compose", "Compose", GTK_UI_MANAGER_MENU)
7936 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Add", "Compose/Add", GTK_UI_MANAGER_MENUITEM)
7937 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Remove", "Compose/Remove", GTK_UI_MANAGER_MENUITEM)
7938 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Separator1", "Compose/---", GTK_UI_MANAGER_SEPARATOR)
7939 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Properties", "Compose/Properties", GTK_UI_MANAGER_MENUITEM)
7941 popupmenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(gtk_ui_manager_get_widget(compose->ui_manager, "/Popup/Compose")));
7943 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
7944 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
7945 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
7947 tmpl_menu = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu/Tools/Template");
7949 undostruct = undo_init(text);
7950 undo_set_change_state_func(undostruct, &compose_undo_state_changed,
7953 address_completion_start(window);
7955 compose->window = window;
7956 compose->vbox = vbox;
7957 compose->menubar = menubar;
7958 compose->handlebox = handlebox;
7960 compose->vbox2 = vbox2;
7962 compose->paned = paned;
7964 compose->attach_label = attach_lab2;
7966 compose->notebook = notebook;
7967 compose->edit_vbox = edit_vbox;
7968 compose->ruler_hbox = ruler_hbox;
7969 compose->ruler = ruler;
7970 compose->scrolledwin = scrolledwin;
7971 compose->text = text;
7973 compose->focused_editable = NULL;
7975 compose->popupmenu = popupmenu;
7977 compose->tmpl_menu = tmpl_menu;
7979 compose->mode = mode;
7980 compose->rmode = mode;
7982 compose->targetinfo = NULL;
7983 compose->replyinfo = NULL;
7984 compose->fwdinfo = NULL;
7986 compose->email_hashtable = g_hash_table_new_full(g_str_hash,
7987 g_str_equal, (GDestroyNotify) g_free, NULL);
7989 compose->replyto = NULL;
7991 compose->bcc = NULL;
7992 compose->followup_to = NULL;
7994 compose->ml_post = NULL;
7996 compose->inreplyto = NULL;
7997 compose->references = NULL;
7998 compose->msgid = NULL;
7999 compose->boundary = NULL;
8001 compose->autowrap = prefs_common.autowrap;
8002 compose->autoindent = prefs_common.auto_indent;
8003 compose->use_signing = FALSE;
8004 compose->use_encryption = FALSE;
8005 compose->privacy_system = NULL;
8006 compose->encdata = NULL;
8008 compose->modified = FALSE;
8010 compose->return_receipt = FALSE;
8012 compose->to_list = NULL;
8013 compose->newsgroup_list = NULL;
8015 compose->undostruct = undostruct;
8017 compose->sig_str = NULL;
8019 compose->exteditor_file = NULL;
8020 compose->exteditor_pid = -1;
8021 compose->exteditor_tag = -1;
8022 compose->exteditor_socket = NULL;
8023 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN; /* inhibit auto-drafting while loading */
8025 compose->folder_update_callback_id =
8026 hooks_register_hook(FOLDER_UPDATE_HOOKLIST,
8027 compose_update_folder_hook,
8028 (gpointer) compose);
8031 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
8032 if (mode != COMPOSE_REDIRECT) {
8033 if (prefs_common.enable_aspell && prefs_common.dictionary &&
8034 strcmp(prefs_common.dictionary, "")) {
8035 gtkaspell = gtkaspell_new(prefs_common.dictionary,
8036 prefs_common.alt_dictionary,
8037 conv_get_locale_charset_str(),
8038 prefs_common.misspelled_col,
8039 prefs_common.check_while_typing,
8040 prefs_common.recheck_when_changing_dict,
8041 prefs_common.use_alternate,
8042 prefs_common.use_both_dicts,
8043 GTK_TEXT_VIEW(text),
8044 GTK_WINDOW(compose->window),
8045 compose_dict_changed,
8046 compose_spell_menu_changed,
8049 alertpanel_error(_("Spell checker could not "
8051 gtkaspell_checkers_strerror());
8052 gtkaspell_checkers_reset_error();
8054 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", TRUE);
8058 compose->gtkaspell = gtkaspell;
8059 compose_spell_menu_changed(compose);
8060 claws_spell_entry_set_gtkaspell(CLAWS_SPELL_ENTRY(subject_entry), gtkaspell);
8063 compose_select_account(compose, account, TRUE);
8065 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", prefs_common.autowrap);
8066 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", prefs_common.auto_indent);
8068 if (account->set_autocc && account->auto_cc && mode != COMPOSE_REEDIT)
8069 compose_entry_append(compose, account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8071 if (account->set_autobcc && account->auto_bcc && mode != COMPOSE_REEDIT)
8072 compose_entry_append(compose, account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8074 if (account->set_autoreplyto && account->auto_replyto && mode != COMPOSE_REEDIT)
8075 compose_entry_append(compose, account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8077 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", compose->mode == COMPOSE_REPLY);
8078 if (account->protocol != A_NNTP)
8079 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
8080 prefs_common_translated_header_name("To:"));
8082 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
8083 prefs_common_translated_header_name("Newsgroups:"));
8085 #ifndef USE_ALT_ADDRBOOK
8086 addressbook_set_target_compose(compose);
8088 if (mode != COMPOSE_REDIRECT)
8089 compose_set_template_menu(compose);
8091 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Template", FALSE);
8094 compose_list = g_list_append(compose_list, compose);
8096 if (!prefs_common.show_ruler)
8097 gtk_widget_hide(ruler_hbox);
8099 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Tools/ShowRuler", prefs_common.show_ruler);
8102 compose->priority = PRIORITY_NORMAL;
8103 compose_update_priority_menu_item(compose);
8105 compose_set_out_encoding(compose);
8108 compose_update_actions_menu(compose);
8110 /* Privacy Systems menu */
8111 compose_update_privacy_systems_menu(compose);
8113 activate_privacy_system(compose, account, TRUE);
8114 toolbar_set_style(compose->toolbar->toolbar, compose->handlebox, prefs_common.toolbar_style);
8116 gtk_widget_realize(window);
8118 gtk_widget_show(window);
8124 static GtkWidget *compose_account_option_menu_create(Compose *compose)
8129 GtkWidget *optmenubox;
8130 GtkWidget *fromlabel;
8133 GtkWidget *from_name = NULL;
8135 gint num = 0, def_menu = 0;
8137 accounts = account_get_list();
8138 cm_return_val_if_fail(accounts != NULL, NULL);
8140 optmenubox = gtk_event_box_new();
8141 optmenu = gtkut_sc_combobox_create(optmenubox, FALSE);
8142 menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
8144 hbox = gtk_hbox_new(FALSE, 4);
8145 from_name = gtk_entry_new();
8147 g_signal_connect_after(G_OBJECT(from_name), "grab_focus",
8148 G_CALLBACK(compose_grab_focus_cb), compose);
8149 g_signal_connect_after(G_OBJECT(from_name), "activate",
8150 G_CALLBACK(from_name_activate_cb), optmenu);
8152 for (; accounts != NULL; accounts = accounts->next, num++) {
8153 PrefsAccount *ac = (PrefsAccount *)accounts->data;
8154 gchar *name, *from = NULL;
8156 if (ac == compose->account) def_menu = num;
8158 name = g_markup_printf_escaped("<i>%s</i>",
8161 if (ac == compose->account) {
8162 if (ac->name && *ac->name) {
8164 QUOTE_IF_REQUIRED_NORMAL(buf, ac->name, return NULL);
8165 from = g_strdup_printf("%s <%s>",
8167 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8169 from = g_strdup_printf("%s",
8171 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8174 COMBOBOX_ADD(menu, name, ac->account_id);
8179 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), def_menu);
8181 g_signal_connect(G_OBJECT(optmenu), "changed",
8182 G_CALLBACK(account_activated),
8184 g_signal_connect(G_OBJECT(from_name), "populate-popup",
8185 G_CALLBACK(compose_entry_popup_extend),
8188 fromlabel = gtk_label_new_with_mnemonic(_("_From:"));
8189 gtk_label_set_mnemonic_widget(GTK_LABEL(fromlabel), from_name);
8191 gtk_box_pack_start(GTK_BOX(hbox), fromlabel, FALSE, FALSE, 4);
8192 gtk_box_pack_start(GTK_BOX(hbox), optmenubox, FALSE, FALSE, 0);
8193 gtk_box_pack_start(GTK_BOX(hbox), from_name, TRUE, TRUE, 0);
8195 /* Putting only the GtkEntry into focus chain of parent hbox causes
8196 * the account selector combobox next to it to be unreachable when
8197 * navigating widgets in GtkTable with up/down arrow keys.
8198 * Note: gtk_widget_set_can_focus() was not enough. */
8200 l = g_list_prepend(l, from_name);
8201 gtk_container_set_focus_chain(GTK_CONTAINER(hbox), l);
8204 CLAWS_SET_TIP(optmenubox,
8205 _("Account to use for this email"));
8206 CLAWS_SET_TIP(from_name,
8207 _("Sender address to be used"));
8209 compose->account_combo = optmenu;
8210 compose->from_name = from_name;
8215 static void compose_set_priority_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8217 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8218 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8219 Compose *compose = (Compose *) data;
8221 compose->priority = value;
8225 static void compose_reply_change_mode(Compose *compose,
8228 gboolean was_modified = compose->modified;
8230 gboolean all = FALSE, ml = FALSE, sender = FALSE, followup = FALSE;
8232 cm_return_if_fail(compose->replyinfo != NULL);
8234 if (action == COMPOSE_REPLY && prefs_common.default_reply_list)
8236 if (action == COMPOSE_REPLY && compose->rmode == COMPOSE_FOLLOWUP_AND_REPLY_TO)
8238 if (action == COMPOSE_REPLY_TO_ALL)
8240 if (action == COMPOSE_REPLY_TO_SENDER)
8242 if (action == COMPOSE_REPLY_TO_LIST)
8245 compose_remove_header_entries(compose);
8246 compose_reply_set_entry(compose, compose->replyinfo, all, ml, sender, followup);
8247 if (compose->account->set_autocc && compose->account->auto_cc)
8248 compose_entry_append(compose, compose->account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8250 if (compose->account->set_autobcc && compose->account->auto_bcc)
8251 compose_entry_append(compose, compose->account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8253 if (compose->account->set_autoreplyto && compose->account->auto_replyto)
8254 compose_entry_append(compose, compose->account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8255 compose_show_first_last_header(compose, TRUE);
8256 compose->modified = was_modified;
8257 compose_set_title(compose);
8260 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8262 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8263 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8264 Compose *compose = (Compose *) data;
8267 compose_reply_change_mode(compose, value);
8270 static void compose_update_priority_menu_item(Compose * compose)
8272 GtkWidget *menuitem = NULL;
8273 switch (compose->priority) {
8274 case PRIORITY_HIGHEST:
8275 menuitem = gtk_ui_manager_get_widget
8276 (compose->ui_manager, "/Menu/Options/Priority/Highest");
8279 menuitem = gtk_ui_manager_get_widget
8280 (compose->ui_manager, "/Menu/Options/Priority/High");
8282 case PRIORITY_NORMAL:
8283 menuitem = gtk_ui_manager_get_widget
8284 (compose->ui_manager, "/Menu/Options/Priority/Normal");
8287 menuitem = gtk_ui_manager_get_widget
8288 (compose->ui_manager, "/Menu/Options/Priority/Low");
8290 case PRIORITY_LOWEST:
8291 menuitem = gtk_ui_manager_get_widget
8292 (compose->ui_manager, "/Menu/Options/Priority/Lowest");
8295 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8298 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data)
8300 Compose *compose = (Compose *) data;
8302 gboolean can_sign = FALSE, can_encrypt = FALSE;
8304 cm_return_if_fail(GTK_IS_CHECK_MENU_ITEM(widget));
8306 if (!gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget)))
8309 systemid = g_object_get_data(G_OBJECT(widget), "privacy_system");
8310 g_free(compose->privacy_system);
8311 compose->privacy_system = NULL;
8312 g_free(compose->encdata);
8313 compose->encdata = NULL;
8314 if (systemid != NULL) {
8315 compose->privacy_system = g_strdup(systemid);
8317 can_sign = privacy_system_can_sign(systemid);
8318 can_encrypt = privacy_system_can_encrypt(systemid);
8321 debug_print("activated privacy system: %s\n", systemid != NULL ? systemid : "None");
8323 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8324 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8327 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn)
8329 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8330 GtkWidget *menuitem = NULL;
8331 GList *children, *amenu;
8332 gboolean can_sign = FALSE, can_encrypt = FALSE;
8333 gboolean found = FALSE;
8335 if (compose->privacy_system != NULL) {
8337 menuitem = gtk_menu_item_get_submenu(GTK_MENU_ITEM(
8338 gtk_ui_manager_get_widget(compose->ui_manager, branch_path)));
8339 cm_return_if_fail(menuitem != NULL);
8341 children = gtk_container_get_children(GTK_CONTAINER(GTK_MENU_SHELL(menuitem)));
8344 while (amenu != NULL) {
8345 systemid = g_object_get_data(G_OBJECT(amenu->data), "privacy_system");
8346 if (systemid != NULL) {
8347 if (strcmp(systemid, compose->privacy_system) == 0 &&
8348 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8349 menuitem = GTK_WIDGET(amenu->data);
8351 can_sign = privacy_system_can_sign(systemid);
8352 can_encrypt = privacy_system_can_encrypt(systemid);
8356 } else if (strlen(compose->privacy_system) == 0 &&
8357 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8358 menuitem = GTK_WIDGET(amenu->data);
8361 can_encrypt = FALSE;
8366 amenu = amenu->next;
8368 g_list_free(children);
8369 if (menuitem != NULL)
8370 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8372 if (warn && !found && strlen(compose->privacy_system)) {
8373 alertpanel_warning(_("The privacy system '%s' cannot be loaded. You "
8374 "will not be able to sign or encrypt this message."),
8375 compose->privacy_system);
8379 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8380 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8383 static void compose_set_out_encoding(Compose *compose)
8385 CharSet out_encoding;
8386 const gchar *branch = NULL;
8387 out_encoding = conv_get_charset_from_str(prefs_common.outgoing_charset);
8389 switch(out_encoding) {
8390 case C_AUTO: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8391 case C_US_ASCII: branch = "Menu/Options/Encoding/" CS_US_ASCII; break;
8392 case C_UTF_8: branch = "Menu/Options/Encoding/" CS_UTF_8; break;
8393 case C_ISO_8859_2: branch = "Menu/Options/Encoding/" CS_ISO_8859_2; break;
8394 case C_ISO_8859_7: branch = "Menu/Options/Encoding/" CS_ISO_8859_7; break;
8395 case C_ISO_8859_9: branch = "Menu/Options/Encoding/" CS_ISO_8859_9; break;
8396 case C_ISO_8859_1: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_1; break;
8397 case C_ISO_8859_15: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_15; break;
8398 case C_WINDOWS_1252: branch = "Menu/Options/Encoding/Western/" CS_WINDOWS_1252; break;
8399 case C_ISO_8859_13: branch = "Menu/Options/Encoding/Baltic/" CS_ISO_8859_13; break;
8400 case C_ISO_8859_4: branch = "Menu/Options/Encoding/Baltic" CS_ISO_8859_4; break;
8401 case C_ISO_8859_8: branch = "Menu/Options/Encoding/Hebrew/" CS_ISO_8859_8; break;
8402 case C_WINDOWS_1255: branch = "Menu/Options/Encoding/Hebrew/" CS_WINDOWS_1255; break;
8403 case C_ISO_8859_6: branch = "Menu/Options/Encoding/Arabic/" CS_ISO_8859_6; break;
8404 case C_WINDOWS_1256: branch = "Menu/Options/Encoding/Arabic/" CS_WINDOWS_1256; break;
8405 case C_ISO_8859_5: branch = "Menu/Options/Encoding/Cyrillic/" CS_ISO_8859_5; break;
8406 case C_KOI8_R: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_R; break;
8407 case C_MACCYR: branch = "Menu/Options/Encoding/Cyrillic/" CS_MACCYR; break;
8408 case C_KOI8_U: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_U; break;
8409 case C_WINDOWS_1251: branch = "Menu/Options/Encoding/Cyrillic/" CS_WINDOWS_1251; break;
8410 case C_ISO_2022_JP: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP; break;
8411 case C_ISO_2022_JP_2: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP_2; break;
8412 case C_EUC_JP: branch = "Menu/Options/Encoding/Japanese/" CS_EUC_JP; break;
8413 case C_SHIFT_JIS: branch = "Menu/Options/Encoding/Japanese/" CS_SHIFT_JIS; break;
8414 case C_GB18030: branch = "Menu/Options/Encoding/Chinese/" CS_GB18030; break;
8415 case C_GB2312: branch = "Menu/Options/Encoding/Chinese/" CS_GB2312; break;
8416 case C_GBK: branch = "Menu/Options/Encoding/Chinese/" CS_GBK; break;
8417 case C_BIG5: branch = "Menu/Options/Encoding/Chinese/" CS_BIG5; break;
8418 case C_EUC_TW: branch = "Menu/Options/Encoding/Chinese/" CS_EUC_TW; break;
8419 case C_EUC_KR: branch = "Menu/Options/Encoding/Korean/" CS_EUC_KR; break;
8420 case C_ISO_2022_KR: branch = "Menu/Options/Encoding/Korean/" CS_ISO_2022_KR; break;
8421 case C_TIS_620: branch = "Menu/Options/Encoding/Thai/" CS_TIS_620; break;
8422 case C_WINDOWS_874: branch = "Menu/Options/Encoding/Thai/" CS_WINDOWS_874; break;
8423 default: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8425 cm_toggle_menu_set_active_full(compose->ui_manager, (gchar *)branch, TRUE);
8428 static void compose_set_template_menu(Compose *compose)
8430 GSList *tmpl_list, *cur;
8434 tmpl_list = template_get_config();
8436 menu = gtk_menu_new();
8438 gtk_menu_set_accel_group (GTK_MENU (menu),
8439 gtk_ui_manager_get_accel_group(compose->ui_manager));
8440 for (cur = tmpl_list; cur != NULL; cur = cur->next) {
8441 Template *tmpl = (Template *)cur->data;
8442 gchar *accel_path = NULL;
8443 item = gtk_menu_item_new_with_label(tmpl->name);
8444 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
8445 g_signal_connect(G_OBJECT(item), "activate",
8446 G_CALLBACK(compose_template_activate_cb),
8448 g_object_set_data(G_OBJECT(item), "template", tmpl);
8449 gtk_widget_show(item);
8450 accel_path = g_strconcat("<ComposeTemplates>" , "/", tmpl->name, NULL);
8451 gtk_menu_item_set_accel_path(GTK_MENU_ITEM(item), accel_path);
8455 gtk_widget_show(menu);
8456 gtk_menu_item_set_submenu(GTK_MENU_ITEM(compose->tmpl_menu), menu);
8459 void compose_update_actions_menu(Compose *compose)
8461 action_update_compose_menu(compose->ui_manager, "/Menu/Tools/Actions", compose);
8464 static void compose_update_privacy_systems_menu(Compose *compose)
8466 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8467 GSList *systems, *cur;
8469 GtkWidget *system_none;
8471 GtkWidget *privacy_menuitem = gtk_ui_manager_get_widget(compose->ui_manager, branch_path);
8472 GtkWidget *privacy_menu = gtk_menu_new();
8474 system_none = gtk_radio_menu_item_new_with_mnemonic(NULL, _("_None"));
8475 g_object_set_data_full(G_OBJECT(system_none), "privacy_system", NULL, NULL);
8477 g_signal_connect(G_OBJECT(system_none), "activate",
8478 G_CALLBACK(compose_set_privacy_system_cb), compose);
8480 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), system_none);
8481 gtk_widget_show(system_none);
8483 systems = privacy_get_system_ids();
8484 for (cur = systems; cur != NULL; cur = g_slist_next(cur)) {
8485 gchar *systemid = cur->data;
8487 group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(system_none));
8488 widget = gtk_radio_menu_item_new_with_label(group,
8489 privacy_system_get_name(systemid));
8490 g_object_set_data_full(G_OBJECT(widget), "privacy_system",
8491 g_strdup(systemid), g_free);
8492 g_signal_connect(G_OBJECT(widget), "activate",
8493 G_CALLBACK(compose_set_privacy_system_cb), compose);
8495 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), widget);
8496 gtk_widget_show(widget);
8499 g_slist_free(systems);
8500 gtk_menu_item_set_submenu(GTK_MENU_ITEM(privacy_menuitem), privacy_menu);
8501 gtk_widget_show_all(privacy_menu);
8502 gtk_widget_show_all(privacy_menuitem);
8505 void compose_reflect_prefs_all(void)
8510 for (cur = compose_list; cur != NULL; cur = cur->next) {
8511 compose = (Compose *)cur->data;
8512 compose_set_template_menu(compose);
8516 void compose_reflect_prefs_pixmap_theme(void)
8521 for (cur = compose_list; cur != NULL; cur = cur->next) {
8522 compose = (Compose *)cur->data;
8523 toolbar_update(TOOLBAR_COMPOSE, compose);
8527 static const gchar *compose_quote_char_from_context(Compose *compose)
8529 const gchar *qmark = NULL;
8531 cm_return_val_if_fail(compose != NULL, NULL);
8533 switch (compose->mode) {
8534 /* use forward-specific quote char */
8535 case COMPOSE_FORWARD:
8536 case COMPOSE_FORWARD_AS_ATTACH:
8537 case COMPOSE_FORWARD_INLINE:
8538 if (compose->folder && compose->folder->prefs &&
8539 compose->folder->prefs->forward_with_format)
8540 qmark = compose->folder->prefs->forward_quotemark;
8541 else if (compose->account->forward_with_format)
8542 qmark = compose->account->forward_quotemark;
8544 qmark = prefs_common.fw_quotemark;
8547 /* use reply-specific quote char in all other modes */
8549 if (compose->folder && compose->folder->prefs &&
8550 compose->folder->prefs->reply_with_format)
8551 qmark = compose->folder->prefs->reply_quotemark;
8552 else if (compose->account->reply_with_format)
8553 qmark = compose->account->reply_quotemark;
8555 qmark = prefs_common.quotemark;
8559 if (qmark == NULL || *qmark == '\0')
8565 static void compose_template_apply(Compose *compose, Template *tmpl,
8569 GtkTextBuffer *buffer;
8573 gchar *parsed_str = NULL;
8574 gint cursor_pos = 0;
8575 const gchar *err_msg = _("The body of the template has an error at line %d.");
8578 /* process the body */
8580 text = GTK_TEXT_VIEW(compose->text);
8581 buffer = gtk_text_view_get_buffer(text);
8584 qmark = compose_quote_char_from_context(compose);
8586 if (compose->replyinfo != NULL) {
8589 gtk_text_buffer_set_text(buffer, "", -1);
8590 mark = gtk_text_buffer_get_insert(buffer);
8591 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8593 parsed_str = compose_quote_fmt(compose, compose->replyinfo,
8594 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8596 } else if (compose->fwdinfo != NULL) {
8599 gtk_text_buffer_set_text(buffer, "", -1);
8600 mark = gtk_text_buffer_get_insert(buffer);
8601 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8603 parsed_str = compose_quote_fmt(compose, compose->fwdinfo,
8604 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8607 MsgInfo* dummyinfo = compose_msginfo_new_from_compose(compose);
8609 GtkTextIter start, end;
8612 gtk_text_buffer_get_start_iter(buffer, &start);
8613 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
8614 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
8616 /* clear the buffer now */
8618 gtk_text_buffer_set_text(buffer, "", -1);
8620 parsed_str = compose_quote_fmt(compose, dummyinfo,
8621 tmpl->value, qmark, tmp, FALSE, FALSE, err_msg);
8622 procmsg_msginfo_free( &dummyinfo );
8628 gtk_text_buffer_set_text(buffer, "", -1);
8629 mark = gtk_text_buffer_get_insert(buffer);
8630 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8633 if (replace && parsed_str && compose->account->auto_sig)
8634 compose_insert_sig(compose, FALSE);
8636 if (replace && parsed_str) {
8637 gtk_text_buffer_get_start_iter(buffer, &iter);
8638 gtk_text_buffer_place_cursor(buffer, &iter);
8642 cursor_pos = quote_fmt_get_cursor_pos();
8643 compose->set_cursor_pos = cursor_pos;
8644 if (cursor_pos == -1)
8646 gtk_text_buffer_get_start_iter(buffer, &iter);
8647 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
8648 gtk_text_buffer_place_cursor(buffer, &iter);
8651 /* process the other fields */
8653 compose_template_apply_fields(compose, tmpl);
8654 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
8655 quote_fmt_reset_vartable();
8656 compose_changed_cb(NULL, compose);
8659 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
8660 gtkaspell_highlight_all(compose->gtkaspell);
8664 static void compose_template_apply_fields(Compose *compose, Template *tmpl)
8666 MsgInfo* dummyinfo = NULL;
8667 MsgInfo *msginfo = NULL;
8670 if (compose->replyinfo != NULL)
8671 msginfo = compose->replyinfo;
8672 else if (compose->fwdinfo != NULL)
8673 msginfo = compose->fwdinfo;
8675 dummyinfo = compose_msginfo_new_from_compose(compose);
8676 msginfo = dummyinfo;
8679 if (tmpl->from && *tmpl->from != '\0') {
8681 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8682 compose->gtkaspell);
8684 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8686 quote_fmt_scan_string(tmpl->from);
8689 buf = quote_fmt_get_buffer();
8691 alertpanel_error(_("Template From format error."));
8693 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
8697 if (tmpl->to && *tmpl->to != '\0') {
8699 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8700 compose->gtkaspell);
8702 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8704 quote_fmt_scan_string(tmpl->to);
8707 buf = quote_fmt_get_buffer();
8709 alertpanel_error(_("Template To format error."));
8711 compose_entry_append(compose, buf, COMPOSE_TO, PREF_TEMPLATE);
8715 if (tmpl->cc && *tmpl->cc != '\0') {
8717 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8718 compose->gtkaspell);
8720 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8722 quote_fmt_scan_string(tmpl->cc);
8725 buf = quote_fmt_get_buffer();
8727 alertpanel_error(_("Template Cc format error."));
8729 compose_entry_append(compose, buf, COMPOSE_CC, PREF_TEMPLATE);
8733 if (tmpl->bcc && *tmpl->bcc != '\0') {
8735 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8736 compose->gtkaspell);
8738 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8740 quote_fmt_scan_string(tmpl->bcc);
8743 buf = quote_fmt_get_buffer();
8745 alertpanel_error(_("Template Bcc format error."));
8747 compose_entry_append(compose, buf, COMPOSE_BCC, PREF_TEMPLATE);
8751 if (tmpl->replyto && *tmpl->replyto != '\0') {
8753 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8754 compose->gtkaspell);
8756 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8758 quote_fmt_scan_string(tmpl->replyto);
8761 buf = quote_fmt_get_buffer();
8763 alertpanel_error(_("Template Reply-To format error."));
8765 compose_entry_append(compose, buf, COMPOSE_REPLYTO, PREF_TEMPLATE);
8769 /* process the subject */
8770 if (tmpl->subject && *tmpl->subject != '\0') {
8772 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8773 compose->gtkaspell);
8775 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8777 quote_fmt_scan_string(tmpl->subject);
8780 buf = quote_fmt_get_buffer();
8782 alertpanel_error(_("Template subject format error."));
8784 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
8788 procmsg_msginfo_free( &dummyinfo );
8791 static void compose_destroy(Compose *compose)
8793 GtkAllocation allocation;
8794 GtkTextBuffer *buffer;
8795 GtkClipboard *clipboard;
8797 compose_list = g_list_remove(compose_list, compose);
8799 if (compose->updating) {
8800 debug_print("danger, not destroying anything now\n");
8801 compose->deferred_destroy = TRUE;
8805 /* NOTE: address_completion_end() does nothing with the window
8806 * however this may change. */
8807 address_completion_end(compose->window);
8809 slist_free_strings_full(compose->to_list);
8810 slist_free_strings_full(compose->newsgroup_list);
8811 slist_free_strings_full(compose->header_list);
8813 slist_free_strings_full(extra_headers);
8814 extra_headers = NULL;
8816 compose->header_list = compose->newsgroup_list = compose->to_list = NULL;
8818 g_hash_table_destroy(compose->email_hashtable);
8820 hooks_unregister_hook(FOLDER_UPDATE_HOOKLIST,
8821 compose->folder_update_callback_id);
8823 procmsg_msginfo_free(&(compose->targetinfo));
8824 procmsg_msginfo_free(&(compose->replyinfo));
8825 procmsg_msginfo_free(&(compose->fwdinfo));
8827 g_free(compose->replyto);
8828 g_free(compose->cc);
8829 g_free(compose->bcc);
8830 g_free(compose->newsgroups);
8831 g_free(compose->followup_to);
8833 g_free(compose->ml_post);
8835 g_free(compose->inreplyto);
8836 g_free(compose->references);
8837 g_free(compose->msgid);
8838 g_free(compose->boundary);
8840 g_free(compose->redirect_filename);
8841 if (compose->undostruct)
8842 undo_destroy(compose->undostruct);
8844 g_free(compose->sig_str);
8846 g_free(compose->exteditor_file);
8848 g_free(compose->orig_charset);
8850 g_free(compose->privacy_system);
8851 g_free(compose->encdata);
8853 #ifndef USE_ALT_ADDRBOOK
8854 if (addressbook_get_target_compose() == compose)
8855 addressbook_set_target_compose(NULL);
8858 if (compose->gtkaspell) {
8859 gtkaspell_delete(compose->gtkaspell);
8860 compose->gtkaspell = NULL;
8864 if (!compose->batch) {
8865 gtk_widget_get_allocation(compose->window, &allocation);
8866 prefs_common.compose_width = allocation.width;
8867 prefs_common.compose_height = allocation.height;
8870 if (!gtk_widget_get_parent(compose->paned))
8871 gtk_widget_destroy(compose->paned);
8872 gtk_widget_destroy(compose->popupmenu);
8874 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
8875 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
8876 gtk_text_buffer_remove_selection_clipboard(buffer, clipboard);
8878 gtk_widget_destroy(compose->window);
8879 toolbar_destroy(compose->toolbar);
8880 g_free(compose->toolbar);
8881 cm_mutex_free(compose->mutex);
8885 static void compose_attach_info_free(AttachInfo *ainfo)
8887 g_free(ainfo->file);
8888 g_free(ainfo->content_type);
8889 g_free(ainfo->name);
8890 g_free(ainfo->charset);
8894 static void compose_attach_update_label(Compose *compose)
8899 GtkTreeModel *model;
8904 model = gtk_tree_view_get_model(GTK_TREE_VIEW(compose->attach_clist));
8905 if(!gtk_tree_model_get_iter_first(model, &iter)) {
8906 gtk_label_set_text(GTK_LABEL(compose->attach_label), "");
8910 while(gtk_tree_model_iter_next(model, &iter))
8913 text = g_strdup_printf("(%d)", i);
8914 gtk_label_set_text(GTK_LABEL(compose->attach_label), text);
8918 static void compose_attach_remove_selected(GtkAction *action, gpointer data)
8920 Compose *compose = (Compose *)data;
8921 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
8922 GtkTreeSelection *selection;
8924 GtkTreeModel *model;
8926 selection = gtk_tree_view_get_selection(tree_view);
8927 sel = gtk_tree_selection_get_selected_rows(selection, &model);
8932 for (cur = sel; cur != NULL; cur = cur->next) {
8933 GtkTreePath *path = cur->data;
8934 GtkTreeRowReference *ref = gtk_tree_row_reference_new
8937 gtk_tree_path_free(path);
8940 for (cur = sel; cur != NULL; cur = cur->next) {
8941 GtkTreeRowReference *ref = cur->data;
8942 GtkTreePath *path = gtk_tree_row_reference_get_path(ref);
8945 if (gtk_tree_model_get_iter(model, &iter, path))
8946 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
8948 gtk_tree_path_free(path);
8949 gtk_tree_row_reference_free(ref);
8953 compose_attach_update_label(compose);
8956 static struct _AttachProperty
8959 GtkWidget *mimetype_entry;
8960 GtkWidget *encoding_optmenu;
8961 GtkWidget *path_entry;
8962 GtkWidget *filename_entry;
8964 GtkWidget *cancel_btn;
8967 static void gtk_tree_path_free_(gpointer ptr, gpointer data)
8969 gtk_tree_path_free((GtkTreePath *)ptr);
8972 static void compose_attach_property(GtkAction *action, gpointer data)
8974 Compose *compose = (Compose *)data;
8975 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
8977 GtkComboBox *optmenu;
8978 GtkTreeSelection *selection;
8980 GtkTreeModel *model;
8983 static gboolean cancelled;
8985 /* only if one selected */
8986 selection = gtk_tree_view_get_selection(tree_view);
8987 if (gtk_tree_selection_count_selected_rows(selection) != 1)
8990 sel = gtk_tree_selection_get_selected_rows(selection, &model);
8994 path = (GtkTreePath *) sel->data;
8995 gtk_tree_model_get_iter(model, &iter, path);
8996 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
8999 g_list_foreach(sel, gtk_tree_path_free_, NULL);
9005 if (!attach_prop.window)
9006 compose_attach_property_create(&cancelled);
9007 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), TRUE);
9008 gtk_widget_grab_focus(attach_prop.ok_btn);
9009 gtk_widget_show(attach_prop.window);
9010 gtk_window_set_transient_for(GTK_WINDOW(attach_prop.window),
9011 GTK_WINDOW(compose->window));
9013 optmenu = GTK_COMBO_BOX(attach_prop.encoding_optmenu);
9014 if (ainfo->encoding == ENC_UNKNOWN)
9015 combobox_select_by_data(optmenu, ENC_BASE64);
9017 combobox_select_by_data(optmenu, ainfo->encoding);
9019 gtk_entry_set_text(GTK_ENTRY(attach_prop.mimetype_entry),
9020 ainfo->content_type ? ainfo->content_type : "");
9021 gtk_entry_set_text(GTK_ENTRY(attach_prop.path_entry),
9022 ainfo->file ? ainfo->file : "");
9023 gtk_entry_set_text(GTK_ENTRY(attach_prop.filename_entry),
9024 ainfo->name ? ainfo->name : "");
9027 const gchar *entry_text;
9029 gchar *cnttype = NULL;
9036 gtk_widget_hide(attach_prop.window);
9037 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), FALSE);
9042 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.mimetype_entry));
9043 if (*entry_text != '\0') {
9046 text = g_strstrip(g_strdup(entry_text));
9047 if ((p = strchr(text, '/')) && !strchr(p + 1, '/')) {
9048 cnttype = g_strdup(text);
9051 alertpanel_error(_("Invalid MIME type."));
9057 ainfo->encoding = combobox_get_active_data(optmenu);
9059 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.path_entry));
9060 if (*entry_text != '\0') {
9061 if (is_file_exist(entry_text) &&
9062 (size = get_file_size(entry_text)) > 0)
9063 file = g_strdup(entry_text);
9066 (_("File doesn't exist or is empty."));
9072 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.filename_entry));
9073 if (*entry_text != '\0') {
9074 g_free(ainfo->name);
9075 ainfo->name = g_strdup(entry_text);
9079 g_free(ainfo->content_type);
9080 ainfo->content_type = cnttype;
9083 g_free(ainfo->file);
9087 ainfo->size = (goffset)size;
9089 /* update tree store */
9090 text = to_human_readable(ainfo->size);
9091 gtk_tree_model_get_iter(model, &iter, path);
9092 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
9093 COL_MIMETYPE, ainfo->content_type,
9095 COL_NAME, ainfo->name,
9096 COL_CHARSET, ainfo->charset,
9102 gtk_tree_path_free(path);
9105 #define SET_LABEL_AND_ENTRY(str, entry, top) \
9107 label = gtk_label_new(str); \
9108 gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), \
9109 GTK_FILL, 0, 0, 0); \
9110 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); \
9112 entry = gtk_entry_new(); \
9113 gtk_table_attach(GTK_TABLE(table), entry, 1, 2, top, (top + 1), \
9114 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); \
9117 static void compose_attach_property_create(gboolean *cancelled)
9123 GtkWidget *mimetype_entry;
9126 GtkListStore *optmenu_menu;
9127 GtkWidget *path_entry;
9128 GtkWidget *filename_entry;
9131 GtkWidget *cancel_btn;
9132 GList *mime_type_list, *strlist;
9135 debug_print("Creating attach_property window...\n");
9137 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose_attach_property");
9138 gtk_widget_set_size_request(window, 480, -1);
9139 gtk_container_set_border_width(GTK_CONTAINER(window), 8);
9140 gtk_window_set_title(GTK_WINDOW(window), _("Properties"));
9141 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
9142 g_signal_connect(G_OBJECT(window), "delete_event",
9143 G_CALLBACK(attach_property_delete_event),
9145 g_signal_connect(G_OBJECT(window), "key_press_event",
9146 G_CALLBACK(attach_property_key_pressed),
9149 vbox = gtk_vbox_new(FALSE, 8);
9150 gtk_container_add(GTK_CONTAINER(window), vbox);
9152 table = gtk_table_new(4, 2, FALSE);
9153 gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
9154 gtk_table_set_row_spacings(GTK_TABLE(table), 8);
9155 gtk_table_set_col_spacings(GTK_TABLE(table), 8);
9157 label = gtk_label_new(_("MIME type"));
9158 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, (0 + 1),
9160 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
9161 #if !GTK_CHECK_VERSION(2, 24, 0)
9162 mimetype_entry = gtk_combo_box_entry_new_text();
9164 mimetype_entry = gtk_combo_box_text_new_with_entry();
9166 gtk_table_attach(GTK_TABLE(table), mimetype_entry, 1, 2, 0, (0 + 1),
9167 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
9169 /* stuff with list */
9170 mime_type_list = procmime_get_mime_type_list();
9172 for (; mime_type_list != NULL; mime_type_list = mime_type_list->next) {
9173 MimeType *type = (MimeType *) mime_type_list->data;
9176 tmp = g_strdup_printf("%s/%s", type->type, type->sub_type);
9178 if (g_list_find_custom(strlist, tmp, (GCompareFunc)strcmp2))
9181 strlist = g_list_insert_sorted(strlist, (gpointer)tmp,
9182 (GCompareFunc)strcmp2);
9185 for (mime_type_list = strlist; mime_type_list != NULL;
9186 mime_type_list = mime_type_list->next) {
9187 #if !GTK_CHECK_VERSION(2, 24, 0)
9188 gtk_combo_box_append_text(GTK_COMBO_BOX(mimetype_entry), mime_type_list->data);
9190 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(mimetype_entry), mime_type_list->data);
9192 g_free(mime_type_list->data);
9194 g_list_free(strlist);
9195 gtk_combo_box_set_active(GTK_COMBO_BOX(mimetype_entry), 0);
9196 mimetype_entry = gtk_bin_get_child(GTK_BIN((mimetype_entry)));
9198 label = gtk_label_new(_("Encoding"));
9199 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2,
9201 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
9203 hbox = gtk_hbox_new(FALSE, 0);
9204 gtk_table_attach(GTK_TABLE(table), hbox, 1, 2, 1, 2,
9205 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
9207 optmenu = gtkut_sc_combobox_create(NULL, TRUE);
9208 optmenu_menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
9210 COMBOBOX_ADD(optmenu_menu, "7bit", ENC_7BIT);
9211 COMBOBOX_ADD(optmenu_menu, "8bit", ENC_8BIT);
9212 COMBOBOX_ADD(optmenu_menu, "quoted-printable", ENC_QUOTED_PRINTABLE);
9213 COMBOBOX_ADD(optmenu_menu, "base64", ENC_BASE64);
9214 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), 0);
9216 gtk_box_pack_start(GTK_BOX(hbox), optmenu, TRUE, TRUE, 0);
9218 SET_LABEL_AND_ENTRY(_("Path"), path_entry, 2);
9219 SET_LABEL_AND_ENTRY(_("File name"), filename_entry, 3);
9221 gtkut_stock_button_set_create(&hbbox, &cancel_btn, GTK_STOCK_CANCEL,
9222 &ok_btn, GTK_STOCK_OK,
9224 gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
9225 gtk_widget_grab_default(ok_btn);
9227 g_signal_connect(G_OBJECT(ok_btn), "clicked",
9228 G_CALLBACK(attach_property_ok),
9230 g_signal_connect(G_OBJECT(cancel_btn), "clicked",
9231 G_CALLBACK(attach_property_cancel),
9234 gtk_widget_show_all(vbox);
9236 attach_prop.window = window;
9237 attach_prop.mimetype_entry = mimetype_entry;
9238 attach_prop.encoding_optmenu = optmenu;
9239 attach_prop.path_entry = path_entry;
9240 attach_prop.filename_entry = filename_entry;
9241 attach_prop.ok_btn = ok_btn;
9242 attach_prop.cancel_btn = cancel_btn;
9245 #undef SET_LABEL_AND_ENTRY
9247 static void attach_property_ok(GtkWidget *widget, gboolean *cancelled)
9253 static void attach_property_cancel(GtkWidget *widget, gboolean *cancelled)
9259 static gint attach_property_delete_event(GtkWidget *widget, GdkEventAny *event,
9260 gboolean *cancelled)
9268 static gboolean attach_property_key_pressed(GtkWidget *widget,
9270 gboolean *cancelled)
9272 if (event && event->keyval == GDK_KEY_Escape) {
9276 if (event && event->keyval == GDK_KEY_Return) {
9284 static void compose_exec_ext_editor(Compose *compose)
9289 GdkNativeWindow socket_wid = 0;
9293 tmp = g_strdup_printf("%s%ctmpmsg.%p", get_tmp_dir(),
9294 G_DIR_SEPARATOR, compose);
9296 if (compose_get_ext_editor_uses_socket()) {
9297 /* Only allow one socket */
9298 if (compose->exteditor_socket != NULL) {
9299 if (gtk_widget_is_focus(compose->exteditor_socket)) {
9300 /* Move the focus off of the socket */
9301 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9306 /* Create the receiving GtkSocket */
9307 socket = gtk_socket_new ();
9308 g_signal_connect (GTK_OBJECT(socket), "plug-removed",
9309 G_CALLBACK(compose_ext_editor_plug_removed_cb),
9311 gtk_box_pack_start(GTK_BOX(compose->edit_vbox), socket, TRUE, TRUE, 0);
9312 gtk_widget_set_size_request(socket, prefs_common.compose_width, -1);
9313 /* Realize the socket so that we can use its ID */
9314 gtk_widget_realize(socket);
9315 socket_wid = gtk_socket_get_id(GTK_SOCKET (socket));
9316 compose->exteditor_socket = socket;
9319 if (pipe(pipe_fds) < 0) {
9325 if ((pid = fork()) < 0) {
9332 /* close the write side of the pipe */
9335 compose->exteditor_file = g_strdup(tmp);
9336 compose->exteditor_pid = pid;
9338 compose_set_ext_editor_sensitive(compose, FALSE);
9341 compose->exteditor_ch = g_io_channel_unix_new(pipe_fds[0]);
9343 compose->exteditor_ch = g_io_channel_win32_new_fd(pipe_fds[0]);
9345 compose->exteditor_tag = g_io_add_watch(compose->exteditor_ch,
9349 } else { /* process-monitoring process */
9355 /* close the read side of the pipe */
9358 if (compose_write_body_to_file(compose, tmp) < 0) {
9359 fd_write_all(pipe_fds[1], "2\n", 2);
9363 pid_ed = compose_exec_ext_editor_real(tmp, socket_wid);
9365 fd_write_all(pipe_fds[1], "1\n", 2);
9369 /* wait until editor is terminated */
9370 waitpid(pid_ed, NULL, 0);
9372 fd_write_all(pipe_fds[1], "0\n", 2);
9379 #endif /* G_OS_UNIX */
9383 static gboolean compose_get_ext_editor_cmd_valid()
9385 gboolean has_s = FALSE;
9386 gboolean has_w = FALSE;
9387 const gchar *p = prefs_common_get_ext_editor_cmd();
9390 while ((p = strchr(p, '%'))) {
9396 } else if (*p == 'w') {
9407 static gint compose_exec_ext_editor_real(const gchar *file, GdkNativeWindow socket_wid)
9414 cm_return_val_if_fail(file != NULL, -1);
9416 if ((pid = fork()) < 0) {
9421 if (pid != 0) return pid;
9423 /* grandchild process */
9425 if (setpgid(0, getppid()))
9428 if (compose_get_ext_editor_cmd_valid()) {
9429 if (compose_get_ext_editor_uses_socket()) {
9430 p = g_strdup(prefs_common_get_ext_editor_cmd());
9431 s = strstr(p, "%w");
9433 if (strstr(p, "%s") < s)
9434 g_snprintf(buf, sizeof(buf), p, file, socket_wid);
9436 g_snprintf(buf, sizeof(buf), p, socket_wid, file);
9439 g_snprintf(buf, sizeof(buf),
9440 prefs_common_get_ext_editor_cmd(), file);
9443 if (prefs_common_get_ext_editor_cmd())
9444 g_warning("External editor command-line is invalid: '%s'",
9445 prefs_common_get_ext_editor_cmd());
9446 g_snprintf(buf, sizeof(buf), DEFAULT_EDITOR_CMD, file);
9449 cmdline = strsplit_with_quote(buf, " ", 1024);
9450 execvp(cmdline[0], cmdline);
9453 g_strfreev(cmdline);
9458 static gboolean compose_ext_editor_kill(Compose *compose)
9460 pid_t pgid = compose->exteditor_pid * -1;
9463 ret = kill(pgid, 0);
9465 if (ret == 0 || (ret == -1 && EPERM == errno)) {
9469 msg = g_strdup_printf
9470 (_("The external editor is still working.\n"
9471 "Force terminating the process?\n"
9472 "process group id: %d"), -pgid);
9473 val = alertpanel_full(_("Notice"), msg, GTK_STOCK_NO, GTK_STOCK_YES,
9474 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
9478 if (val == G_ALERTALTERNATE) {
9479 g_source_remove(compose->exteditor_tag);
9480 g_io_channel_shutdown(compose->exteditor_ch,
9482 g_io_channel_unref(compose->exteditor_ch);
9484 if (kill(pgid, SIGTERM) < 0) perror("kill");
9485 waitpid(compose->exteditor_pid, NULL, 0);
9487 g_warning("Terminated process group id: %d. "
9488 "Temporary file: %s", -pgid, compose->exteditor_file);
9490 compose_set_ext_editor_sensitive(compose, TRUE);
9492 g_free(compose->exteditor_file);
9493 compose->exteditor_file = NULL;
9494 compose->exteditor_pid = -1;
9495 compose->exteditor_ch = NULL;
9496 compose->exteditor_tag = -1;
9504 static gboolean compose_input_cb(GIOChannel *source, GIOCondition condition,
9508 Compose *compose = (Compose *)data;
9511 debug_print("Compose: input from monitoring process\n");
9513 if (g_io_channel_read_chars(source, buf, sizeof(buf), &bytes_read, NULL) != G_IO_STATUS_NORMAL) {
9518 g_io_channel_shutdown(source, FALSE, NULL);
9519 g_io_channel_unref(source);
9521 waitpid(compose->exteditor_pid, NULL, 0);
9523 if (buf[0] == '0') { /* success */
9524 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9525 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
9526 GtkTextIter start, end;
9529 gtk_text_buffer_set_text(buffer, "", -1);
9530 compose_insert_file(compose, compose->exteditor_file);
9531 compose_changed_cb(NULL, compose);
9532 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
9534 if (claws_unlink(compose->exteditor_file) < 0)
9535 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9537 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
9538 gtk_text_buffer_get_start_iter(buffer, &start);
9539 gtk_text_buffer_get_end_iter(buffer, &end);
9540 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
9541 if (chars && strlen(chars) > 0)
9542 compose->modified = TRUE;
9544 } else if (buf[0] == '1') { /* failed */
9545 g_warning("Couldn't exec external editor");
9546 if (claws_unlink(compose->exteditor_file) < 0)
9547 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9548 } else if (buf[0] == '2') {
9549 g_warning("Couldn't write to file");
9550 } else if (buf[0] == '3') {
9551 g_warning("Pipe read failed");
9554 compose_set_ext_editor_sensitive(compose, TRUE);
9556 g_free(compose->exteditor_file);
9557 compose->exteditor_file = NULL;
9558 compose->exteditor_pid = -1;
9559 compose->exteditor_ch = NULL;
9560 compose->exteditor_tag = -1;
9561 if (compose->exteditor_socket) {
9562 gtk_widget_destroy(compose->exteditor_socket);
9563 compose->exteditor_socket = NULL;
9570 static char *ext_editor_menu_entries[] = {
9571 "Menu/Message/Send",
9572 "Menu/Message/SendLater",
9573 "Menu/Message/InsertFile",
9574 "Menu/Message/InsertSig",
9575 "Menu/Message/ReplaceSig",
9576 "Menu/Message/Save",
9577 "Menu/Message/Print",
9582 "Menu/Tools/ShowRuler",
9583 "Menu/Tools/Actions",
9588 static void compose_set_ext_editor_sensitive(Compose *compose,
9593 for (i = 0; ext_editor_menu_entries[i]; ++i) {
9594 cm_menu_set_sensitive_full(compose->ui_manager,
9595 ext_editor_menu_entries[i], sensitive);
9598 if (compose_get_ext_editor_uses_socket()) {
9600 if (compose->exteditor_socket)
9601 gtk_widget_hide(compose->exteditor_socket);
9602 gtk_widget_show(compose->scrolledwin);
9603 if (prefs_common.show_ruler)
9604 gtk_widget_show(compose->ruler_hbox);
9605 /* Fix the focus, as it doesn't go anywhere when the
9606 * socket is hidden or destroyed */
9607 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9609 g_assert (compose->exteditor_socket != NULL);
9610 /* Fix the focus, as it doesn't go anywhere when the
9611 * edit box is hidden */
9612 if (gtk_widget_is_focus(compose->text))
9613 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9614 gtk_widget_hide(compose->scrolledwin);
9615 gtk_widget_hide(compose->ruler_hbox);
9616 gtk_widget_show(compose->exteditor_socket);
9619 gtk_widget_set_sensitive(compose->text, sensitive);
9621 if (compose->toolbar->send_btn)
9622 gtk_widget_set_sensitive(compose->toolbar->send_btn, sensitive);
9623 if (compose->toolbar->sendl_btn)
9624 gtk_widget_set_sensitive(compose->toolbar->sendl_btn, sensitive);
9625 if (compose->toolbar->draft_btn)
9626 gtk_widget_set_sensitive(compose->toolbar->draft_btn, sensitive);
9627 if (compose->toolbar->insert_btn)
9628 gtk_widget_set_sensitive(compose->toolbar->insert_btn, sensitive);
9629 if (compose->toolbar->sig_btn)
9630 gtk_widget_set_sensitive(compose->toolbar->sig_btn, sensitive);
9631 if (compose->toolbar->exteditor_btn)
9632 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, sensitive);
9633 if (compose->toolbar->linewrap_current_btn)
9634 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, sensitive);
9635 if (compose->toolbar->linewrap_all_btn)
9636 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, sensitive);
9639 static gboolean compose_get_ext_editor_uses_socket()
9641 return (prefs_common_get_ext_editor_cmd() &&
9642 strstr(prefs_common_get_ext_editor_cmd(), "%w"));
9645 static gboolean compose_ext_editor_plug_removed_cb(GtkSocket *socket, Compose *compose)
9647 compose->exteditor_socket = NULL;
9648 /* returning FALSE allows destruction of the socket */
9651 #endif /* G_OS_UNIX */
9654 * compose_undo_state_changed:
9656 * Change the sensivity of the menuentries undo and redo
9658 static void compose_undo_state_changed(UndoMain *undostruct, gint undo_state,
9659 gint redo_state, gpointer data)
9661 Compose *compose = (Compose *)data;
9663 switch (undo_state) {
9664 case UNDO_STATE_TRUE:
9665 if (!undostruct->undo_state) {
9666 undostruct->undo_state = TRUE;
9667 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", TRUE);
9670 case UNDO_STATE_FALSE:
9671 if (undostruct->undo_state) {
9672 undostruct->undo_state = FALSE;
9673 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
9676 case UNDO_STATE_UNCHANGED:
9678 case UNDO_STATE_REFRESH:
9679 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", undostruct->undo_state);
9682 g_warning("Undo state not recognized");
9686 switch (redo_state) {
9687 case UNDO_STATE_TRUE:
9688 if (!undostruct->redo_state) {
9689 undostruct->redo_state = TRUE;
9690 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", TRUE);
9693 case UNDO_STATE_FALSE:
9694 if (undostruct->redo_state) {
9695 undostruct->redo_state = FALSE;
9696 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
9699 case UNDO_STATE_UNCHANGED:
9701 case UNDO_STATE_REFRESH:
9702 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", undostruct->redo_state);
9705 g_warning("Redo state not recognized");
9710 /* callback functions */
9712 static void compose_notebook_size_alloc(GtkNotebook *notebook,
9713 GtkAllocation *allocation,
9716 prefs_common.compose_notebook_height = gtk_paned_get_position(paned);
9719 /* compose_edit_size_alloc() - called when resized. don't know whether Gtk
9720 * includes "non-client" (windows-izm) in calculation, so this calculation
9721 * may not be accurate.
9723 static gboolean compose_edit_size_alloc(GtkEditable *widget,
9724 GtkAllocation *allocation,
9725 GtkSHRuler *shruler)
9727 if (prefs_common.show_ruler) {
9728 gint char_width = 0, char_height = 0;
9729 gint line_width_in_chars;
9731 gtkut_get_font_size(GTK_WIDGET(widget),
9732 &char_width, &char_height);
9733 line_width_in_chars =
9734 (allocation->width - allocation->x) / char_width;
9736 /* got the maximum */
9737 gtk_shruler_set_range(GTK_SHRULER(shruler),
9738 0.0, line_width_in_chars, 0);
9747 ComposePrefType type;
9748 gboolean entry_marked;
9751 static void account_activated(GtkComboBox *optmenu, gpointer data)
9753 Compose *compose = (Compose *)data;
9756 gchar *folderidentifier;
9757 gint account_id = 0;
9760 GSList *list, *saved_list = NULL;
9761 HeaderEntryState *state;
9762 GtkRcStyle *style = NULL;
9763 #if !GTK_CHECK_VERSION(3, 0, 0)
9764 static GdkColor yellow;
9765 static gboolean color_set = FALSE;
9767 static GdkColor yellow = { (guint32)0, (guint32)0xf5, (guint32)0xf6, (guint32)0xbe };
9770 /* Get ID of active account in the combo box */
9771 menu = gtk_combo_box_get_model(optmenu);
9772 cm_return_if_fail(gtk_combo_box_get_active_iter(optmenu, &iter));
9773 gtk_tree_model_get(menu, &iter, 1, &account_id, -1);
9775 ac = account_find_from_id(account_id);
9776 cm_return_if_fail(ac != NULL);
9778 if (ac != compose->account) {
9779 compose_select_account(compose, ac, FALSE);
9781 for (list = compose->header_list; list; list = list->next) {
9782 ComposeHeaderEntry *hentry=(ComposeHeaderEntry *)list->data;
9784 if (hentry->type == PREF_ACCOUNT || !list->next) {
9785 compose_destroy_headerentry(compose, hentry);
9789 state = g_malloc0(sizeof(HeaderEntryState));
9790 state->header = gtk_editable_get_chars(GTK_EDITABLE(
9791 gtk_bin_get_child(GTK_BIN(hentry->combo))), 0, -1);
9792 state->entry = gtk_editable_get_chars(
9793 GTK_EDITABLE(hentry->entry), 0, -1);
9794 state->type = hentry->type;
9796 #if !GTK_CHECK_VERSION(3, 0, 0)
9798 gdk_color_parse("#f5f6be", &yellow);
9799 color_set = gdk_colormap_alloc_color(
9800 gdk_colormap_get_system(),
9801 &yellow, FALSE, TRUE);
9805 style = gtk_widget_get_modifier_style(hentry->entry);
9806 state->entry_marked = gdk_color_equal(&yellow,
9807 &style->base[GTK_STATE_NORMAL]);
9809 saved_list = g_slist_append(saved_list, state);
9810 compose_destroy_headerentry(compose, hentry);
9813 compose->header_last = NULL;
9814 g_slist_free(compose->header_list);
9815 compose->header_list = NULL;
9816 compose->header_nextrow = 1;
9817 compose_create_header_entry(compose);
9819 if (ac->set_autocc && ac->auto_cc)
9820 compose_entry_append(compose, ac->auto_cc,
9821 COMPOSE_CC, PREF_ACCOUNT);
9823 if (ac->set_autobcc && ac->auto_bcc)
9824 compose_entry_append(compose, ac->auto_bcc,
9825 COMPOSE_BCC, PREF_ACCOUNT);
9827 if (ac->set_autoreplyto && ac->auto_replyto)
9828 compose_entry_append(compose, ac->auto_replyto,
9829 COMPOSE_REPLYTO, PREF_ACCOUNT);
9831 for (list = saved_list; list; list = list->next) {
9832 state = (HeaderEntryState *) list->data;
9834 compose_add_header_entry(compose, state->header,
9835 state->entry, state->type);
9836 if (state->entry_marked)
9837 compose_entry_mark_default_to(compose, state->entry);
9839 g_free(state->header);
9840 g_free(state->entry);
9843 g_slist_free(saved_list);
9845 combobox_select_by_data(GTK_COMBO_BOX(compose->header_last->combo),
9846 (ac->protocol == A_NNTP) ?
9847 COMPOSE_NEWSGROUPS : COMPOSE_TO);
9850 /* Set message save folder */
9851 if (account_get_special_folder(compose->account, F_OUTBOX)) {
9852 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
9854 g_signal_connect(G_OBJECT(compose->savemsg_checkbtn), "toggled",
9855 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
9857 compose_set_save_to(compose, NULL);
9858 if (account_get_special_folder(compose->account, F_OUTBOX)) {
9859 folderidentifier = folder_item_get_identifier(account_get_special_folder
9860 (compose->account, F_OUTBOX));
9861 compose_set_save_to(compose, folderidentifier);
9862 g_free(folderidentifier);
9866 static void attach_selected(GtkTreeView *tree_view, GtkTreePath *tree_path,
9867 GtkTreeViewColumn *column, Compose *compose)
9869 compose_attach_property(NULL, compose);
9872 static gboolean attach_button_pressed(GtkWidget *widget, GdkEventButton *event,
9875 Compose *compose = (Compose *)data;
9876 GtkTreeSelection *attach_selection;
9877 gint attach_nr_selected;
9880 if (!event) return FALSE;
9882 if (event->button == 3) {
9883 attach_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
9884 attach_nr_selected = gtk_tree_selection_count_selected_rows(attach_selection);
9886 /* If no rows, or just one row is selected, right-click should
9887 * open menu relevant to the row being right-clicked on. We
9888 * achieve that by selecting the clicked row first. If more
9889 * than one row is selected, we shouldn't modify the selection,
9890 * as user may want to remove selected rows (attachments). */
9891 if (attach_nr_selected < 2) {
9892 gtk_tree_selection_unselect_all(attach_selection);
9893 attach_nr_selected = 0;
9894 gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget),
9895 event->x, event->y, &path, NULL, NULL, NULL);
9897 gtk_tree_selection_select_path(attach_selection, path);
9898 gtk_tree_path_free(path);
9899 attach_nr_selected++;
9903 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", (attach_nr_selected > 0));
9904 /* Properties menu item makes no sense with more than one row
9905 * selected, the properties dialog can only edit one attachment. */
9906 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", (attach_nr_selected == 1));
9908 gtk_menu_popup(GTK_MENU(compose->popupmenu), NULL, NULL,
9909 NULL, NULL, event->button, event->time);
9916 static gboolean attach_key_pressed(GtkWidget *widget, GdkEventKey *event,
9919 Compose *compose = (Compose *)data;
9921 if (!event) return FALSE;
9923 switch (event->keyval) {
9924 case GDK_KEY_Delete:
9925 compose_attach_remove_selected(NULL, compose);
9931 static void compose_allow_user_actions (Compose *compose, gboolean allow)
9933 toolbar_comp_set_sensitive(compose, allow);
9934 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message", allow);
9935 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", allow);
9937 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", allow);
9939 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", allow);
9940 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools", allow);
9941 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Help", allow);
9943 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), allow);
9947 static void compose_send_cb(GtkAction *action, gpointer data)
9949 Compose *compose = (Compose *)data;
9952 if (compose->exteditor_tag != -1) {
9953 debug_print("ignoring send: external editor still open\n");
9957 if (prefs_common.work_offline &&
9958 !inc_offline_should_override(TRUE,
9959 _("Claws Mail needs network access in order "
9960 "to send this email.")))
9963 if (compose->draft_timeout_tag >= 0) { /* CLAWS: disable draft timeout */
9964 g_source_remove(compose->draft_timeout_tag);
9965 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
9968 compose_send(compose);
9971 static void compose_send_later_cb(GtkAction *action, gpointer data)
9973 Compose *compose = (Compose *)data;
9977 compose_allow_user_actions(compose, FALSE);
9978 val = compose_queue_sub(compose, NULL, NULL, NULL, TRUE, TRUE);
9979 compose_allow_user_actions(compose, TRUE);
9983 compose_close(compose);
9984 } else if (val == -1) {
9985 alertpanel_error(_("Could not queue message."));
9986 } else if (val == -2) {
9987 alertpanel_error(_("Could not queue message:\n\n%s."), g_strerror(errno));
9988 } else if (val == -3) {
9989 if (privacy_peek_error())
9990 alertpanel_error(_("Could not queue message for sending:\n\n"
9991 "Signature failed: %s"), privacy_get_error());
9992 } else if (val == -4) {
9993 alertpanel_error(_("Could not queue message for sending:\n\n"
9994 "Charset conversion failed."));
9995 } else if (val == -5) {
9996 alertpanel_error(_("Could not queue message for sending:\n\n"
9997 "Couldn't get recipient encryption key."));
9998 } else if (val == -6) {
10001 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
10004 #define DRAFTED_AT_EXIT "drafted_at_exit"
10005 static void compose_register_draft(MsgInfo *info)
10007 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10008 DRAFTED_AT_EXIT, NULL);
10009 FILE *fp = g_fopen(filepath, "ab");
10012 fprintf(fp, "%s\t%d\n", folder_item_get_identifier(info->folder),
10020 gboolean compose_draft (gpointer data, guint action)
10022 Compose *compose = (Compose *)data;
10027 MsgFlags flag = {0, 0};
10028 static gboolean lock = FALSE;
10029 MsgInfo *newmsginfo;
10031 gboolean target_locked = FALSE;
10032 gboolean err = FALSE;
10034 if (lock) return FALSE;
10036 if (compose->sending)
10039 draft = account_get_special_folder(compose->account, F_DRAFT);
10040 cm_return_val_if_fail(draft != NULL, FALSE);
10042 if (!g_mutex_trylock(compose->mutex)) {
10043 /* we don't want to lock the mutex once it's available,
10044 * because as the only other part of compose.c locking
10045 * it is compose_close - which means once unlocked,
10046 * the compose struct will be freed */
10047 debug_print("couldn't lock mutex, probably sending\n");
10053 tmp = g_strdup_printf("%s%cdraft.%p", get_tmp_dir(),
10054 G_DIR_SEPARATOR, compose);
10055 if ((fp = g_fopen(tmp, "wb")) == NULL) {
10056 FILE_OP_ERROR(tmp, "fopen");
10060 /* chmod for security */
10061 if (change_file_mode_rw(fp, tmp) < 0) {
10062 FILE_OP_ERROR(tmp, "chmod");
10063 g_warning("can't change file mode");
10066 /* Save draft infos */
10067 err |= (fprintf(fp, "X-Claws-Account-Id:%d\n", compose->account->account_id) < 0);
10068 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
10070 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
10071 gchar *savefolderid;
10073 savefolderid = compose_get_save_to(compose);
10074 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
10075 g_free(savefolderid);
10077 if (compose->return_receipt) {
10078 err |= (fprintf(fp, "RRCPT:1\n") < 0);
10080 if (compose->privacy_system) {
10081 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
10082 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
10083 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
10086 /* Message-ID of message replying to */
10087 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
10088 gchar *folderid = NULL;
10090 if (compose->replyinfo->folder)
10091 folderid = folder_item_get_identifier(compose->replyinfo->folder);
10092 if (folderid == NULL)
10093 folderid = g_strdup("NULL");
10095 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
10098 /* Message-ID of message forwarding to */
10099 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
10100 gchar *folderid = NULL;
10102 if (compose->fwdinfo->folder)
10103 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
10104 if (folderid == NULL)
10105 folderid = g_strdup("NULL");
10107 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
10111 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
10112 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
10114 sheaders = compose_get_manual_headers_info(compose);
10115 err |= (fprintf(fp, "X-Claws-Manual-Headers:%s\n", sheaders) < 0);
10118 /* end of headers */
10119 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
10126 if (compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_STORE, action != COMPOSE_AUTO_SAVE) < 0) {
10130 if (fclose(fp) == EOF) {
10134 flag.perm_flags = MSG_NEW|MSG_UNREAD;
10135 if (compose->targetinfo) {
10136 target_locked = MSG_IS_LOCKED(compose->targetinfo->flags);
10138 flag.perm_flags |= MSG_LOCKED;
10140 flag.tmp_flags = MSG_DRAFT;
10142 folder_item_scan(draft);
10143 if ((msgnum = folder_item_add_msg(draft, tmp, &flag, TRUE)) < 0) {
10144 MsgInfo *tmpinfo = NULL;
10145 debug_print("didn't get msgnum after adding draft [%s]\n", compose->msgid?compose->msgid:"no msgid");
10146 if (compose->msgid) {
10147 tmpinfo = folder_item_get_msginfo_by_msgid(draft, compose->msgid);
10150 msgnum = tmpinfo->msgnum;
10151 procmsg_msginfo_free(&tmpinfo);
10152 debug_print("got draft msgnum %d from scanning\n", msgnum);
10154 debug_print("didn't get draft msgnum after scanning\n");
10157 debug_print("got draft msgnum %d from adding\n", msgnum);
10163 if (action != COMPOSE_AUTO_SAVE) {
10164 if (action != COMPOSE_DRAFT_FOR_EXIT)
10165 alertpanel_error(_("Could not save draft."));
10168 gtkut_window_popup(compose->window);
10169 val = alertpanel_full(_("Could not save draft"),
10170 _("Could not save draft.\n"
10171 "Do you want to cancel exit or discard this email?"),
10172 _("_Cancel exit"), _("_Discard email"), NULL,
10173 FALSE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
10174 if (val == G_ALERTALTERNATE) {
10176 g_mutex_unlock(compose->mutex); /* must be done before closing */
10177 compose_close(compose);
10181 g_mutex_unlock(compose->mutex); /* must be done before closing */
10190 if (compose->mode == COMPOSE_REEDIT) {
10191 compose_remove_reedit_target(compose, TRUE);
10194 newmsginfo = folder_item_get_msginfo(draft, msgnum);
10197 procmsg_msginfo_unset_flags(newmsginfo, ~0, ~0);
10199 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD|MSG_LOCKED, MSG_DRAFT);
10201 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD, MSG_DRAFT);
10202 if (compose_use_attach(compose) && action != COMPOSE_AUTO_SAVE)
10203 procmsg_msginfo_set_flags(newmsginfo, 0,
10204 MSG_HAS_ATTACHMENT);
10206 if (action == COMPOSE_DRAFT_FOR_EXIT) {
10207 compose_register_draft(newmsginfo);
10209 procmsg_msginfo_free(&newmsginfo);
10212 folder_item_scan(draft);
10214 if (action == COMPOSE_QUIT_EDITING || action == COMPOSE_DRAFT_FOR_EXIT) {
10216 g_mutex_unlock(compose->mutex); /* must be done before closing */
10217 compose_close(compose);
10223 path = folder_item_fetch_msg(draft, msgnum);
10224 if (path == NULL) {
10225 debug_print("can't fetch %s:%d\n", draft->path, msgnum);
10228 if (g_stat(path, &s) < 0) {
10229 FILE_OP_ERROR(path, "stat");
10235 procmsg_msginfo_free(&(compose->targetinfo));
10236 compose->targetinfo = procmsg_msginfo_new();
10237 compose->targetinfo->msgnum = msgnum;
10238 compose->targetinfo->size = (goffset)s.st_size;
10239 compose->targetinfo->mtime = s.st_mtime;
10240 compose->targetinfo->folder = draft;
10242 procmsg_msginfo_set_flags(compose->targetinfo, MSG_LOCKED, 0);
10243 compose->mode = COMPOSE_REEDIT;
10245 if (action == COMPOSE_AUTO_SAVE) {
10246 compose->autosaved_draft = compose->targetinfo;
10248 compose->modified = FALSE;
10249 compose_set_title(compose);
10253 g_mutex_unlock(compose->mutex);
10257 void compose_clear_exit_drafts(void)
10259 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10260 DRAFTED_AT_EXIT, NULL);
10261 if (is_file_exist(filepath))
10262 claws_unlink(filepath);
10267 void compose_reopen_exit_drafts(void)
10269 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10270 DRAFTED_AT_EXIT, NULL);
10271 FILE *fp = g_fopen(filepath, "rb");
10275 while (fgets(buf, sizeof(buf), fp)) {
10276 gchar **parts = g_strsplit(buf, "\t", 2);
10277 const gchar *folder = parts[0];
10278 int msgnum = parts[1] ? atoi(parts[1]):-1;
10280 if (folder && *folder && msgnum > -1) {
10281 FolderItem *item = folder_find_item_from_identifier(folder);
10282 MsgInfo *info = folder_item_get_msginfo(item, msgnum);
10284 compose_reedit(info, FALSE);
10291 compose_clear_exit_drafts();
10294 static void compose_save_cb(GtkAction *action, gpointer data)
10296 Compose *compose = (Compose *)data;
10297 compose_draft(compose, COMPOSE_KEEP_EDITING);
10298 compose->rmode = COMPOSE_REEDIT;
10301 void compose_attach_from_list(Compose *compose, GList *file_list, gboolean free_data)
10303 if (compose && file_list) {
10306 for ( tmp = file_list; tmp; tmp = tmp->next) {
10307 gchar *file = (gchar *) tmp->data;
10308 gchar *utf8_filename = conv_filename_to_utf8(file);
10309 compose_attach_append(compose, file, utf8_filename, NULL, NULL);
10310 compose_changed_cb(NULL, compose);
10315 g_free(utf8_filename);
10320 static void compose_attach_cb(GtkAction *action, gpointer data)
10322 Compose *compose = (Compose *)data;
10325 if (compose->redirect_filename != NULL)
10328 /* Set focus_window properly, in case we were called via popup menu,
10329 * which unsets it (via focus_out_event callback on compose window). */
10330 manage_window_focus_in(compose->window, NULL, NULL);
10332 file_list = filesel_select_multiple_files_open(_("Select file"));
10335 compose_attach_from_list(compose, file_list, TRUE);
10336 g_list_free(file_list);
10340 static void compose_insert_file_cb(GtkAction *action, gpointer data)
10342 Compose *compose = (Compose *)data;
10344 gint files_inserted = 0;
10346 file_list = filesel_select_multiple_files_open(_("Select file"));
10351 for ( tmp = file_list; tmp; tmp = tmp->next) {
10352 gchar *file = (gchar *) tmp->data;
10353 gchar *filedup = g_strdup(file);
10354 gchar *shortfile = g_path_get_basename(filedup);
10355 ComposeInsertResult res;
10356 /* insert the file if the file is short or if the user confirmed that
10357 he/she wants to insert the large file */
10358 res = compose_insert_file(compose, file);
10359 if (res == COMPOSE_INSERT_READ_ERROR) {
10360 alertpanel_error(_("File '%s' could not be read."), shortfile);
10361 } else if (res == COMPOSE_INSERT_INVALID_CHARACTER) {
10362 alertpanel_error(_("File '%s' contained invalid characters\n"
10363 "for the current encoding, insertion may be incorrect."),
10365 } else if (res == COMPOSE_INSERT_SUCCESS)
10372 g_list_free(file_list);
10376 if (files_inserted > 0 && compose->gtkaspell &&
10377 compose->gtkaspell->check_while_typing)
10378 gtkaspell_highlight_all(compose->gtkaspell);
10382 static void compose_insert_sig_cb(GtkAction *action, gpointer data)
10384 Compose *compose = (Compose *)data;
10386 compose_insert_sig(compose, FALSE);
10389 static void compose_replace_sig_cb(GtkAction *action, gpointer data)
10391 Compose *compose = (Compose *)data;
10393 compose_insert_sig(compose, TRUE);
10396 static gint compose_delete_cb(GtkWidget *widget, GdkEventAny *event,
10400 Compose *compose = (Compose *)data;
10402 gtkut_widget_get_uposition(widget, &x, &y);
10403 if (!compose->batch) {
10404 prefs_common.compose_x = x;
10405 prefs_common.compose_y = y;
10407 if (compose->sending || compose->updating)
10409 compose_close_cb(NULL, compose);
10413 void compose_close_toolbar(Compose *compose)
10415 compose_close_cb(NULL, compose);
10418 static gboolean compose_can_autosave(Compose *compose)
10420 if (compose->privacy_system && compose->use_encryption)
10421 return prefs_common.autosave && prefs_common.autosave_encrypted;
10423 return prefs_common.autosave;
10426 static void compose_close_cb(GtkAction *action, gpointer data)
10428 Compose *compose = (Compose *)data;
10432 if (compose->exteditor_tag != -1) {
10433 if (!compose_ext_editor_kill(compose))
10438 if (compose->modified) {
10439 gboolean reedit = (compose->rmode == COMPOSE_REEDIT);
10440 if (!g_mutex_trylock(compose->mutex)) {
10441 /* we don't want to lock the mutex once it's available,
10442 * because as the only other part of compose.c locking
10443 * it is compose_close - which means once unlocked,
10444 * the compose struct will be freed */
10445 debug_print("couldn't lock mutex, probably sending\n");
10449 val = alertpanel(_("Discard message"),
10450 _("This message has been modified. Discard it?"),
10451 _("_Discard"), _("_Save to Drafts"), GTK_STOCK_CANCEL);
10453 val = alertpanel(_("Save changes"),
10454 _("This message has been modified. Save the latest changes?"),
10455 _("_Don't save"), g_strconcat("+", _("_Save to Drafts"), NULL),
10458 g_mutex_unlock(compose->mutex);
10460 case G_ALERTDEFAULT:
10461 if (compose_can_autosave(compose) && !reedit)
10462 compose_remove_draft(compose);
10464 case G_ALERTALTERNATE:
10465 compose_draft(data, COMPOSE_QUIT_EDITING);
10472 compose_close(compose);
10475 static void compose_print_cb(GtkAction *action, gpointer data)
10477 Compose *compose = (Compose *) data;
10479 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
10480 if (compose->targetinfo)
10481 messageview_print(compose->targetinfo, FALSE, -1, -1, 0);
10484 static void compose_set_encoding_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
10486 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
10487 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
10488 Compose *compose = (Compose *) data;
10491 compose->out_encoding = (CharSet)value;
10494 static void compose_address_cb(GtkAction *action, gpointer data)
10496 Compose *compose = (Compose *)data;
10498 #ifndef USE_ALT_ADDRBOOK
10499 addressbook_open(compose);
10501 GError* error = NULL;
10502 addressbook_connect_signals(compose);
10503 addressbook_dbus_open(TRUE, &error);
10505 g_warning("%s", error->message);
10506 g_error_free(error);
10511 static void about_show_cb(GtkAction *action, gpointer data)
10516 static void compose_template_activate_cb(GtkWidget *widget, gpointer data)
10518 Compose *compose = (Compose *)data;
10523 tmpl = g_object_get_data(G_OBJECT(widget), "template");
10524 cm_return_if_fail(tmpl != NULL);
10526 msg = g_strdup_printf(_("Do you want to apply the template '%s'?"),
10528 val = alertpanel(_("Apply template"), msg,
10529 _("_Replace"), _("_Insert"), GTK_STOCK_CANCEL);
10532 if (val == G_ALERTDEFAULT)
10533 compose_template_apply(compose, tmpl, TRUE);
10534 else if (val == G_ALERTALTERNATE)
10535 compose_template_apply(compose, tmpl, FALSE);
10538 static void compose_ext_editor_cb(GtkAction *action, gpointer data)
10540 Compose *compose = (Compose *)data;
10542 compose_exec_ext_editor(compose);
10545 static void compose_undo_cb(GtkAction *action, gpointer data)
10547 Compose *compose = (Compose *)data;
10548 gboolean prev_autowrap = compose->autowrap;
10550 compose->autowrap = FALSE;
10551 undo_undo(compose->undostruct);
10552 compose->autowrap = prev_autowrap;
10555 static void compose_redo_cb(GtkAction *action, gpointer data)
10557 Compose *compose = (Compose *)data;
10558 gboolean prev_autowrap = compose->autowrap;
10560 compose->autowrap = FALSE;
10561 undo_redo(compose->undostruct);
10562 compose->autowrap = prev_autowrap;
10565 static void entry_cut_clipboard(GtkWidget *entry)
10567 if (GTK_IS_EDITABLE(entry))
10568 gtk_editable_cut_clipboard (GTK_EDITABLE(entry));
10569 else if (GTK_IS_TEXT_VIEW(entry))
10570 gtk_text_buffer_cut_clipboard(
10571 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10572 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
10576 static void entry_copy_clipboard(GtkWidget *entry)
10578 if (GTK_IS_EDITABLE(entry))
10579 gtk_editable_copy_clipboard (GTK_EDITABLE(entry));
10580 else if (GTK_IS_TEXT_VIEW(entry))
10581 gtk_text_buffer_copy_clipboard(
10582 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10583 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
10586 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry,
10587 gboolean wrap, GdkAtom clip, GtkTextIter *insert_place)
10589 if (GTK_IS_TEXT_VIEW(entry)) {
10590 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10591 GtkTextMark *mark_start = gtk_text_buffer_get_insert(buffer);
10592 GtkTextIter start_iter, end_iter;
10594 gchar *contents = gtk_clipboard_wait_for_text(gtk_clipboard_get(clip));
10596 if (contents == NULL)
10599 /* we shouldn't delete the selection when middle-click-pasting, or we
10600 * can't mid-click-paste our own selection */
10601 if (clip != GDK_SELECTION_PRIMARY) {
10602 undo_paste_clipboard(GTK_TEXT_VIEW(compose->text), compose->undostruct);
10603 gtk_text_buffer_delete_selection(buffer, FALSE, TRUE);
10606 if (insert_place == NULL) {
10607 /* if insert_place isn't specified, insert at the cursor.
10608 * used for Ctrl-V pasting */
10609 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10610 start = gtk_text_iter_get_offset(&start_iter);
10611 gtk_text_buffer_insert(buffer, &start_iter, contents, strlen(contents));
10613 /* if insert_place is specified, paste here.
10614 * used for mid-click-pasting */
10615 start = gtk_text_iter_get_offset(insert_place);
10616 gtk_text_buffer_insert(buffer, insert_place, contents, strlen(contents));
10617 if (prefs_common.primary_paste_unselects)
10618 gtk_text_buffer_select_range(buffer, insert_place, insert_place);
10622 /* paste unwrapped: mark the paste so it's not wrapped later */
10623 end = start + strlen(contents);
10624 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, start);
10625 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, end);
10626 gtk_text_buffer_apply_tag_by_name(buffer, "no_wrap", &start_iter, &end_iter);
10627 } else if (wrap && clip == GDK_SELECTION_PRIMARY) {
10628 /* rewrap paragraph now (after a mid-click-paste) */
10629 mark_start = gtk_text_buffer_get_insert(buffer);
10630 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10631 gtk_text_iter_backward_char(&start_iter);
10632 compose_beautify_paragraph(compose, &start_iter, TRUE);
10634 } else if (GTK_IS_EDITABLE(entry))
10635 gtk_editable_paste_clipboard (GTK_EDITABLE(entry));
10637 compose->modified = TRUE;
10640 static void entry_allsel(GtkWidget *entry)
10642 if (GTK_IS_EDITABLE(entry))
10643 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
10644 else if (GTK_IS_TEXT_VIEW(entry)) {
10645 GtkTextIter startiter, enditer;
10646 GtkTextBuffer *textbuf;
10648 textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10649 gtk_text_buffer_get_start_iter(textbuf, &startiter);
10650 gtk_text_buffer_get_end_iter(textbuf, &enditer);
10652 gtk_text_buffer_move_mark_by_name(textbuf,
10653 "selection_bound", &startiter);
10654 gtk_text_buffer_move_mark_by_name(textbuf,
10655 "insert", &enditer);
10659 static void compose_cut_cb(GtkAction *action, gpointer data)
10661 Compose *compose = (Compose *)data;
10662 if (compose->focused_editable
10663 #ifndef GENERIC_UMPC
10664 && gtk_widget_has_focus(compose->focused_editable)
10667 entry_cut_clipboard(compose->focused_editable);
10670 static void compose_copy_cb(GtkAction *action, gpointer data)
10672 Compose *compose = (Compose *)data;
10673 if (compose->focused_editable
10674 #ifndef GENERIC_UMPC
10675 && gtk_widget_has_focus(compose->focused_editable)
10678 entry_copy_clipboard(compose->focused_editable);
10681 static void compose_paste_cb(GtkAction *action, gpointer data)
10683 Compose *compose = (Compose *)data;
10684 gint prev_autowrap;
10685 GtkTextBuffer *buffer;
10687 if (compose->focused_editable &&
10688 #ifndef GENERIC_UMPC
10689 gtk_widget_has_focus(compose->focused_editable)
10692 entry_paste_clipboard(compose, compose->focused_editable,
10693 prefs_common.linewrap_pastes,
10694 GDK_SELECTION_CLIPBOARD, NULL);
10699 #ifndef GENERIC_UMPC
10700 gtk_widget_has_focus(compose->text) &&
10702 compose->gtkaspell &&
10703 compose->gtkaspell->check_while_typing)
10704 gtkaspell_highlight_all(compose->gtkaspell);
10708 static void compose_paste_as_quote_cb(GtkAction *action, gpointer data)
10710 Compose *compose = (Compose *)data;
10711 gint wrap_quote = prefs_common.linewrap_quote;
10712 if (compose->focused_editable
10713 #ifndef GENERIC_UMPC
10714 && gtk_widget_has_focus(compose->focused_editable)
10717 /* let text_insert() (called directly or at a later time
10718 * after the gtk_editable_paste_clipboard) know that
10719 * text is to be inserted as a quotation. implemented
10720 * by using a simple refcount... */
10721 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data(
10722 G_OBJECT(compose->focused_editable),
10723 "paste_as_quotation"));
10724 g_object_set_data(G_OBJECT(compose->focused_editable),
10725 "paste_as_quotation",
10726 GINT_TO_POINTER(paste_as_quotation + 1));
10727 prefs_common.linewrap_quote = prefs_common.linewrap_pastes;
10728 entry_paste_clipboard(compose, compose->focused_editable,
10729 prefs_common.linewrap_pastes,
10730 GDK_SELECTION_CLIPBOARD, NULL);
10731 prefs_common.linewrap_quote = wrap_quote;
10735 static void compose_paste_no_wrap_cb(GtkAction *action, gpointer data)
10737 Compose *compose = (Compose *)data;
10738 gint prev_autowrap;
10739 GtkTextBuffer *buffer;
10741 if (compose->focused_editable
10742 #ifndef GENERIC_UMPC
10743 && gtk_widget_has_focus(compose->focused_editable)
10746 entry_paste_clipboard(compose, compose->focused_editable, FALSE,
10747 GDK_SELECTION_CLIPBOARD, NULL);
10752 #ifndef GENERIC_UMPC
10753 gtk_widget_has_focus(compose->text) &&
10755 compose->gtkaspell &&
10756 compose->gtkaspell->check_while_typing)
10757 gtkaspell_highlight_all(compose->gtkaspell);
10761 static void compose_paste_wrap_cb(GtkAction *action, gpointer data)
10763 Compose *compose = (Compose *)data;
10764 gint prev_autowrap;
10765 GtkTextBuffer *buffer;
10767 if (compose->focused_editable
10768 #ifndef GENERIC_UMPC
10769 && gtk_widget_has_focus(compose->focused_editable)
10772 entry_paste_clipboard(compose, compose->focused_editable, TRUE,
10773 GDK_SELECTION_CLIPBOARD, NULL);
10778 #ifndef GENERIC_UMPC
10779 gtk_widget_has_focus(compose->text) &&
10781 compose->gtkaspell &&
10782 compose->gtkaspell->check_while_typing)
10783 gtkaspell_highlight_all(compose->gtkaspell);
10787 static void compose_allsel_cb(GtkAction *action, gpointer data)
10789 Compose *compose = (Compose *)data;
10790 if (compose->focused_editable
10791 #ifndef GENERIC_UMPC
10792 && gtk_widget_has_focus(compose->focused_editable)
10795 entry_allsel(compose->focused_editable);
10798 static void textview_move_beginning_of_line (GtkTextView *text)
10800 GtkTextBuffer *buffer;
10804 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10806 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10807 mark = gtk_text_buffer_get_insert(buffer);
10808 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10809 gtk_text_iter_set_line_offset(&ins, 0);
10810 gtk_text_buffer_place_cursor(buffer, &ins);
10813 static void textview_move_forward_character (GtkTextView *text)
10815 GtkTextBuffer *buffer;
10819 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10821 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10822 mark = gtk_text_buffer_get_insert(buffer);
10823 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10824 if (gtk_text_iter_forward_cursor_position(&ins))
10825 gtk_text_buffer_place_cursor(buffer, &ins);
10828 static void textview_move_backward_character (GtkTextView *text)
10830 GtkTextBuffer *buffer;
10834 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10836 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10837 mark = gtk_text_buffer_get_insert(buffer);
10838 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10839 if (gtk_text_iter_backward_cursor_position(&ins))
10840 gtk_text_buffer_place_cursor(buffer, &ins);
10843 static void textview_move_forward_word (GtkTextView *text)
10845 GtkTextBuffer *buffer;
10850 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10852 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10853 mark = gtk_text_buffer_get_insert(buffer);
10854 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10855 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
10856 if (gtk_text_iter_forward_word_ends(&ins, count)) {
10857 gtk_text_iter_backward_word_start(&ins);
10858 gtk_text_buffer_place_cursor(buffer, &ins);
10862 static void textview_move_backward_word (GtkTextView *text)
10864 GtkTextBuffer *buffer;
10868 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10870 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10871 mark = gtk_text_buffer_get_insert(buffer);
10872 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10873 if (gtk_text_iter_backward_word_starts(&ins, 1))
10874 gtk_text_buffer_place_cursor(buffer, &ins);
10877 static void textview_move_end_of_line (GtkTextView *text)
10879 GtkTextBuffer *buffer;
10883 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10885 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10886 mark = gtk_text_buffer_get_insert(buffer);
10887 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10888 if (gtk_text_iter_forward_to_line_end(&ins))
10889 gtk_text_buffer_place_cursor(buffer, &ins);
10892 static void textview_move_next_line (GtkTextView *text)
10894 GtkTextBuffer *buffer;
10899 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10901 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10902 mark = gtk_text_buffer_get_insert(buffer);
10903 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10904 offset = gtk_text_iter_get_line_offset(&ins);
10905 if (gtk_text_iter_forward_line(&ins)) {
10906 gtk_text_iter_set_line_offset(&ins, offset);
10907 gtk_text_buffer_place_cursor(buffer, &ins);
10911 static void textview_move_previous_line (GtkTextView *text)
10913 GtkTextBuffer *buffer;
10918 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10920 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10921 mark = gtk_text_buffer_get_insert(buffer);
10922 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10923 offset = gtk_text_iter_get_line_offset(&ins);
10924 if (gtk_text_iter_backward_line(&ins)) {
10925 gtk_text_iter_set_line_offset(&ins, offset);
10926 gtk_text_buffer_place_cursor(buffer, &ins);
10930 static void textview_delete_forward_character (GtkTextView *text)
10932 GtkTextBuffer *buffer;
10934 GtkTextIter ins, end_iter;
10936 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10938 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10939 mark = gtk_text_buffer_get_insert(buffer);
10940 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10942 if (gtk_text_iter_forward_char(&end_iter)) {
10943 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10947 static void textview_delete_backward_character (GtkTextView *text)
10949 GtkTextBuffer *buffer;
10951 GtkTextIter ins, end_iter;
10953 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10955 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10956 mark = gtk_text_buffer_get_insert(buffer);
10957 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10959 if (gtk_text_iter_backward_char(&end_iter)) {
10960 gtk_text_buffer_delete(buffer, &end_iter, &ins);
10964 static void textview_delete_forward_word (GtkTextView *text)
10966 GtkTextBuffer *buffer;
10968 GtkTextIter ins, end_iter;
10970 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10972 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10973 mark = gtk_text_buffer_get_insert(buffer);
10974 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10976 if (gtk_text_iter_forward_word_end(&end_iter)) {
10977 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10981 static void textview_delete_backward_word (GtkTextView *text)
10983 GtkTextBuffer *buffer;
10985 GtkTextIter ins, end_iter;
10987 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10989 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10990 mark = gtk_text_buffer_get_insert(buffer);
10991 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10993 if (gtk_text_iter_backward_word_start(&end_iter)) {
10994 gtk_text_buffer_delete(buffer, &end_iter, &ins);
10998 static void textview_delete_line (GtkTextView *text)
11000 GtkTextBuffer *buffer;
11002 GtkTextIter ins, start_iter, end_iter;
11004 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11006 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11007 mark = gtk_text_buffer_get_insert(buffer);
11008 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11011 gtk_text_iter_set_line_offset(&start_iter, 0);
11014 if (gtk_text_iter_ends_line(&end_iter)){
11015 if (!gtk_text_iter_forward_char(&end_iter))
11016 gtk_text_iter_backward_char(&start_iter);
11019 gtk_text_iter_forward_to_line_end(&end_iter);
11020 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
11023 static void textview_delete_to_line_end (GtkTextView *text)
11025 GtkTextBuffer *buffer;
11027 GtkTextIter ins, end_iter;
11029 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11031 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11032 mark = gtk_text_buffer_get_insert(buffer);
11033 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11035 if (gtk_text_iter_ends_line(&end_iter))
11036 gtk_text_iter_forward_char(&end_iter);
11038 gtk_text_iter_forward_to_line_end(&end_iter);
11039 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11042 #define DO_ACTION(name, act) { \
11043 if(!strcmp(name, a_name)) { \
11047 static ComposeCallAdvancedAction compose_call_advanced_action_from_path(GtkAction *action)
11049 const gchar *a_name = gtk_action_get_name(action);
11050 DO_ACTION("Edit/Advanced/BackChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER);
11051 DO_ACTION("Edit/Advanced/ForwChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER);
11052 DO_ACTION("Edit/Advanced/BackWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD);
11053 DO_ACTION("Edit/Advanced/ForwWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD);
11054 DO_ACTION("Edit/Advanced/BegLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE);
11055 DO_ACTION("Edit/Advanced/EndLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE);
11056 DO_ACTION("Edit/Advanced/PrevLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE);
11057 DO_ACTION("Edit/Advanced/NextLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE);
11058 DO_ACTION("Edit/Advanced/DelBackChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER);
11059 DO_ACTION("Edit/Advanced/DelForwChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER);
11060 DO_ACTION("Edit/Advanced/DelBackWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD);
11061 DO_ACTION("Edit/Advanced/DelForwWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD);
11062 DO_ACTION("Edit/Advanced/DelLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE);
11063 DO_ACTION("Edit/Advanced/DelEndLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END);
11067 static void compose_advanced_action_cb(GtkAction *gaction, gpointer data)
11069 Compose *compose = (Compose *)data;
11070 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11071 ComposeCallAdvancedAction action = -1;
11073 action = compose_call_advanced_action_from_path(gaction);
11076 void (*do_action) (GtkTextView *text);
11077 } action_table[] = {
11078 {textview_move_beginning_of_line},
11079 {textview_move_forward_character},
11080 {textview_move_backward_character},
11081 {textview_move_forward_word},
11082 {textview_move_backward_word},
11083 {textview_move_end_of_line},
11084 {textview_move_next_line},
11085 {textview_move_previous_line},
11086 {textview_delete_forward_character},
11087 {textview_delete_backward_character},
11088 {textview_delete_forward_word},
11089 {textview_delete_backward_word},
11090 {textview_delete_line},
11091 {textview_delete_to_line_end}
11094 if (!gtk_widget_has_focus(GTK_WIDGET(text))) return;
11096 if (action >= COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE &&
11097 action <= COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END) {
11098 if (action_table[action].do_action)
11099 action_table[action].do_action(text);
11101 g_warning("Not implemented yet.");
11105 static void compose_grab_focus_cb(GtkWidget *widget, Compose *compose)
11107 GtkAllocation allocation;
11111 if (GTK_IS_EDITABLE(widget)) {
11112 str = gtk_editable_get_chars(GTK_EDITABLE(widget), 0, -1);
11113 gtk_editable_set_position(GTK_EDITABLE(widget),
11116 if ((parent = gtk_widget_get_parent(widget))
11117 && (parent = gtk_widget_get_parent(parent))
11118 && (parent = gtk_widget_get_parent(parent))) {
11119 if (GTK_IS_SCROLLED_WINDOW(parent)) {
11120 gtk_widget_get_allocation(widget, &allocation);
11121 gint y = allocation.y;
11122 gint height = allocation.height;
11123 GtkAdjustment *shown = gtk_scrolled_window_get_vadjustment
11124 (GTK_SCROLLED_WINDOW(parent));
11126 gfloat value = gtk_adjustment_get_value(shown);
11127 gfloat upper = gtk_adjustment_get_upper(shown);
11128 gfloat page_size = gtk_adjustment_get_page_size(shown);
11129 if (y < (int)value) {
11130 gtk_adjustment_set_value(shown, y - 1);
11132 if ((y + height) > ((int)value + (int)page_size)) {
11133 if ((y - height - 1) < ((int)upper - (int)page_size)) {
11134 gtk_adjustment_set_value(shown,
11135 y + height - (int)page_size - 1);
11137 gtk_adjustment_set_value(shown,
11138 (int)upper - (int)page_size - 1);
11145 if (GTK_IS_EDITABLE(widget) || GTK_IS_TEXT_VIEW(widget))
11146 compose->focused_editable = widget;
11148 #ifdef GENERIC_UMPC
11149 if (GTK_IS_TEXT_VIEW(widget)
11150 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->edit_vbox) {
11151 g_object_ref(compose->notebook);
11152 g_object_ref(compose->edit_vbox);
11153 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
11154 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
11155 gtk_paned_add1(GTK_PANED(compose->paned), compose->edit_vbox);
11156 gtk_paned_add2(GTK_PANED(compose->paned), compose->notebook);
11157 g_object_unref(compose->notebook);
11158 g_object_unref(compose->edit_vbox);
11159 g_signal_handlers_block_by_func(G_OBJECT(widget),
11160 G_CALLBACK(compose_grab_focus_cb),
11162 gtk_widget_grab_focus(widget);
11163 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
11164 G_CALLBACK(compose_grab_focus_cb),
11166 } else if (!GTK_IS_TEXT_VIEW(widget)
11167 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->notebook) {
11168 g_object_ref(compose->notebook);
11169 g_object_ref(compose->edit_vbox);
11170 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
11171 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
11172 gtk_paned_add1(GTK_PANED(compose->paned), compose->notebook);
11173 gtk_paned_add2(GTK_PANED(compose->paned), compose->edit_vbox);
11174 g_object_unref(compose->notebook);
11175 g_object_unref(compose->edit_vbox);
11176 g_signal_handlers_block_by_func(G_OBJECT(widget),
11177 G_CALLBACK(compose_grab_focus_cb),
11179 gtk_widget_grab_focus(widget);
11180 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
11181 G_CALLBACK(compose_grab_focus_cb),
11187 static void compose_changed_cb(GtkTextBuffer *textbuf, Compose *compose)
11189 compose->modified = TRUE;
11190 // compose_beautify_paragraph(compose, NULL, TRUE);
11191 #ifndef GENERIC_UMPC
11192 compose_set_title(compose);
11196 static void compose_wrap_cb(GtkAction *action, gpointer data)
11198 Compose *compose = (Compose *)data;
11199 compose_beautify_paragraph(compose, NULL, TRUE);
11202 static void compose_wrap_all_cb(GtkAction *action, gpointer data)
11204 Compose *compose = (Compose *)data;
11205 compose_wrap_all_full(compose, TRUE);
11208 static void compose_find_cb(GtkAction *action, gpointer data)
11210 Compose *compose = (Compose *)data;
11212 message_search_compose(compose);
11215 static void compose_toggle_autowrap_cb(GtkToggleAction *action,
11218 Compose *compose = (Compose *)data;
11219 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11220 if (compose->autowrap)
11221 compose_wrap_all_full(compose, TRUE);
11222 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11225 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
11228 Compose *compose = (Compose *)data;
11229 compose->autoindent = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11232 static void compose_toggle_sign_cb(GtkToggleAction *action, gpointer data)
11234 Compose *compose = (Compose *)data;
11236 compose->use_signing = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11239 static void compose_toggle_encrypt_cb(GtkToggleAction *action, gpointer data)
11241 Compose *compose = (Compose *)data;
11243 compose->use_encryption = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11246 static void activate_privacy_system(Compose *compose, PrefsAccount *account, gboolean warn)
11248 g_free(compose->privacy_system);
11249 g_free(compose->encdata);
11251 compose->privacy_system = g_strdup(account->default_privacy_system);
11252 compose_update_privacy_system_menu_item(compose, warn);
11255 static void compose_toggle_ruler_cb(GtkToggleAction *action, gpointer data)
11257 Compose *compose = (Compose *)data;
11259 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action))) {
11260 gtk_widget_show(compose->ruler_hbox);
11261 prefs_common.show_ruler = TRUE;
11263 gtk_widget_hide(compose->ruler_hbox);
11264 gtk_widget_queue_resize(compose->edit_vbox);
11265 prefs_common.show_ruler = FALSE;
11269 static void compose_attach_drag_received_cb (GtkWidget *widget,
11270 GdkDragContext *context,
11273 GtkSelectionData *data,
11276 gpointer user_data)
11278 Compose *compose = (Compose *)user_data;
11282 type = gtk_selection_data_get_data_type(data);
11283 if ((gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list"))
11284 && gtk_drag_get_source_widget(context) !=
11285 summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
11286 list = uri_list_extract_filenames(
11287 (const gchar *)gtk_selection_data_get_data(data));
11288 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11289 gchar *utf8_filename = conv_filename_to_utf8((const gchar *)tmp->data);
11290 compose_attach_append
11291 (compose, (const gchar *)tmp->data,
11292 utf8_filename, NULL, NULL);
11293 g_free(utf8_filename);
11295 if (list) compose_changed_cb(NULL, compose);
11296 list_free_strings(list);
11298 } else if (gtk_drag_get_source_widget(context)
11299 == summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
11300 /* comes from our summaryview */
11301 SummaryView * summaryview = NULL;
11302 GSList * list = NULL, *cur = NULL;
11304 if (mainwindow_get_mainwindow())
11305 summaryview = mainwindow_get_mainwindow()->summaryview;
11308 list = summary_get_selected_msg_list(summaryview);
11310 for (cur = list; cur; cur = cur->next) {
11311 MsgInfo *msginfo = (MsgInfo *)cur->data;
11312 gchar *file = NULL;
11314 file = procmsg_get_message_file_full(msginfo,
11317 compose_attach_append(compose, (const gchar *)file,
11318 (const gchar *)file, "message/rfc822", NULL);
11322 g_slist_free(list);
11326 static gboolean compose_drag_drop(GtkWidget *widget,
11327 GdkDragContext *drag_context,
11329 guint time, gpointer user_data)
11331 /* not handling this signal makes compose_insert_drag_received_cb
11336 static gboolean completion_set_focus_to_subject
11337 (GtkWidget *widget,
11338 GdkEventKey *event,
11341 cm_return_val_if_fail(compose != NULL, FALSE);
11343 /* make backtab move to subject field */
11344 if(event->keyval == GDK_KEY_ISO_Left_Tab) {
11345 gtk_widget_grab_focus(compose->subject_entry);
11351 static void compose_insert_drag_received_cb (GtkWidget *widget,
11352 GdkDragContext *drag_context,
11355 GtkSelectionData *data,
11358 gpointer user_data)
11360 Compose *compose = (Compose *)user_data;
11364 /* strangely, testing data->type == gdk_atom_intern("text/uri-list", TRUE)
11366 type = gtk_selection_data_get_data_type(data);
11367 if (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list")) {
11368 AlertValue val = G_ALERTDEFAULT;
11369 const gchar* ddata = (const gchar *)gtk_selection_data_get_data(data);
11371 list = uri_list_extract_filenames(ddata);
11372 if (list == NULL && strstr(ddata, "://")) {
11373 /* Assume a list of no files, and data has ://, is a remote link */
11374 gchar *tmpdata = g_strstrip(g_strdup(ddata));
11375 gchar *tmpfile = get_tmp_file();
11376 str_write_to_file(tmpdata, tmpfile);
11378 compose_insert_file(compose, tmpfile);
11379 claws_unlink(tmpfile);
11381 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11382 compose_beautify_paragraph(compose, NULL, TRUE);
11385 switch (prefs_common.compose_dnd_mode) {
11386 case COMPOSE_DND_ASK:
11387 val = alertpanel_full(_("Insert or attach?"),
11388 _("Do you want to insert the contents of the file(s) "
11389 "into the message body, or attach it to the email?"),
11390 GTK_STOCK_CANCEL, g_strconcat("+", _("_Insert"), NULL), _("_Attach"),
11391 TRUE, NULL, ALERT_QUESTION, G_ALERTALTERNATE);
11393 case COMPOSE_DND_INSERT:
11394 val = G_ALERTALTERNATE;
11396 case COMPOSE_DND_ATTACH:
11397 val = G_ALERTOTHER;
11400 /* unexpected case */
11401 g_warning("error: unexpected compose_dnd_mode option value in compose_insert_drag_received_cb()");
11404 if (val & G_ALERTDISABLE) {
11405 val &= ~G_ALERTDISABLE;
11406 /* remember what action to perform by default, only if we don't click Cancel */
11407 if (val == G_ALERTALTERNATE)
11408 prefs_common.compose_dnd_mode = COMPOSE_DND_INSERT;
11409 else if (val == G_ALERTOTHER)
11410 prefs_common.compose_dnd_mode = COMPOSE_DND_ATTACH;
11413 if (val == G_ALERTDEFAULT || val == G_ALERTCANCEL) {
11414 gtk_drag_finish(drag_context, FALSE, FALSE, time);
11415 list_free_strings(list);
11418 } else if (val == G_ALERTOTHER) {
11419 compose_attach_drag_received_cb(widget, drag_context, x, y, data, info, time, user_data);
11420 list_free_strings(list);
11425 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11426 compose_insert_file(compose, (const gchar *)tmp->data);
11428 list_free_strings(list);
11430 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11435 static void compose_header_drag_received_cb (GtkWidget *widget,
11436 GdkDragContext *drag_context,
11439 GtkSelectionData *data,
11442 gpointer user_data)
11444 GtkEditable *entry = (GtkEditable *)user_data;
11445 const gchar *email = (const gchar *)gtk_selection_data_get_data(data);
11447 /* strangely, testing data->type == gdk_atom_intern("text/plain", TRUE)
11450 if (!strncmp(email, "mailto:", strlen("mailto:"))) {
11451 gchar *decoded=g_new(gchar, strlen(email));
11454 decode_uri(decoded, email + strlen("mailto:")); /* will fit */
11455 gtk_editable_delete_text(entry, 0, -1);
11456 gtk_editable_insert_text(entry, decoded, strlen(decoded), &start);
11457 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11461 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11464 static void compose_toggle_return_receipt_cb(GtkToggleAction *action, gpointer data)
11466 Compose *compose = (Compose *)data;
11468 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11469 compose->return_receipt = TRUE;
11471 compose->return_receipt = FALSE;
11474 static void compose_toggle_remove_refs_cb(GtkToggleAction *action, gpointer data)
11476 Compose *compose = (Compose *)data;
11478 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11479 compose->remove_references = TRUE;
11481 compose->remove_references = FALSE;
11484 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
11485 ComposeHeaderEntry *headerentry)
11487 gtk_entry_set_text(GTK_ENTRY(headerentry->entry), "");
11491 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
11492 GdkEventKey *event,
11493 ComposeHeaderEntry *headerentry)
11495 if ((g_slist_length(headerentry->compose->header_list) > 0) &&
11496 ((headerentry->headernum + 1) != headerentry->compose->header_nextrow) &&
11497 !(event->state & GDK_MODIFIER_MASK) &&
11498 (event->keyval == GDK_KEY_BackSpace) &&
11499 (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) == 0)) {
11500 gtk_container_remove
11501 (GTK_CONTAINER(headerentry->compose->header_table),
11502 headerentry->combo);
11503 gtk_container_remove
11504 (GTK_CONTAINER(headerentry->compose->header_table),
11505 headerentry->entry);
11506 headerentry->compose->header_list =
11507 g_slist_remove(headerentry->compose->header_list,
11509 g_free(headerentry);
11510 } else if (event->keyval == GDK_KEY_Tab) {
11511 if (headerentry->compose->header_last == headerentry) {
11512 /* Override default next focus, and give it to subject_entry
11513 * instead of notebook tabs
11515 g_signal_stop_emission_by_name(G_OBJECT(entry), "key-press-event");
11516 gtk_widget_grab_focus(headerentry->compose->subject_entry);
11523 static gboolean scroll_postpone(gpointer data)
11525 Compose *compose = (Compose *)data;
11527 if (compose->batch)
11530 GTK_EVENTS_FLUSH();
11531 compose_show_first_last_header(compose, FALSE);
11535 static void compose_headerentry_changed_cb(GtkWidget *entry,
11536 ComposeHeaderEntry *headerentry)
11538 if (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) != 0) {
11539 compose_create_header_entry(headerentry->compose);
11540 g_signal_handlers_disconnect_matched
11541 (G_OBJECT(entry), G_SIGNAL_MATCH_DATA,
11542 0, 0, NULL, NULL, headerentry);
11544 if (!headerentry->compose->batch)
11545 g_timeout_add(0, scroll_postpone, headerentry->compose);
11549 static gboolean compose_defer_auto_save_draft(Compose *compose)
11551 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
11552 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
11556 static void compose_show_first_last_header(Compose *compose, gboolean show_first)
11558 GtkAdjustment *vadj;
11560 cm_return_if_fail(compose);
11565 cm_return_if_fail(GTK_IS_WIDGET(compose->header_table));
11566 cm_return_if_fail(GTK_IS_VIEWPORT(gtk_widget_get_parent(compose->header_table)));
11567 vadj = gtk_viewport_get_vadjustment(GTK_VIEWPORT(
11568 gtk_widget_get_parent(compose->header_table)));
11569 gtk_adjustment_set_value(vadj, (show_first ?
11570 gtk_adjustment_get_lower(vadj) :
11571 (gtk_adjustment_get_upper(vadj) -
11572 gtk_adjustment_get_page_size(vadj))));
11573 gtk_adjustment_changed(vadj);
11576 static void text_inserted(GtkTextBuffer *buffer, GtkTextIter *iter,
11577 const gchar *text, gint len, Compose *compose)
11579 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data
11580 (G_OBJECT(compose->text), "paste_as_quotation"));
11583 cm_return_if_fail(text != NULL);
11585 g_signal_handlers_block_by_func(G_OBJECT(buffer),
11586 G_CALLBACK(text_inserted),
11588 if (paste_as_quotation) {
11590 const gchar *qmark;
11592 GtkTextIter start_iter;
11595 len = strlen(text);
11597 new_text = g_strndup(text, len);
11599 qmark = compose_quote_char_from_context(compose);
11601 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11602 gtk_text_buffer_place_cursor(buffer, iter);
11604 pos = gtk_text_iter_get_offset(iter);
11606 compose_quote_fmt(compose, NULL, "%Q", qmark, new_text, TRUE, FALSE,
11607 _("Quote format error at line %d."));
11608 quote_fmt_reset_vartable();
11610 g_object_set_data(G_OBJECT(compose->text), "paste_as_quotation",
11611 GINT_TO_POINTER(paste_as_quotation - 1));
11613 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11614 gtk_text_buffer_place_cursor(buffer, iter);
11615 gtk_text_buffer_delete_mark(buffer, mark);
11617 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, pos);
11618 mark = gtk_text_buffer_create_mark(buffer, NULL, &start_iter, FALSE);
11619 compose_beautify_paragraph(compose, &start_iter, FALSE);
11620 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark);
11621 gtk_text_buffer_delete_mark(buffer, mark);
11623 if (strcmp(text, "\n") || compose->automatic_break
11624 || gtk_text_iter_starts_line(iter)) {
11625 GtkTextIter before_ins;
11626 gtk_text_buffer_insert(buffer, iter, text, len);
11627 if (!strstr(text, "\n") && gtk_text_iter_has_tag(iter, compose->no_join_tag)) {
11628 before_ins = *iter;
11629 gtk_text_iter_backward_chars(&before_ins, len);
11630 gtk_text_buffer_remove_tag_by_name(buffer, "no_join", &before_ins, iter);
11633 /* check if the preceding is just whitespace or quote */
11634 GtkTextIter start_line;
11635 gchar *tmp = NULL, *quote = NULL;
11636 gint quote_len = 0, is_normal = 0;
11637 start_line = *iter;
11638 gtk_text_iter_set_line_offset(&start_line, 0);
11639 tmp = gtk_text_buffer_get_text(buffer, &start_line, iter, FALSE);
11642 if (*tmp == '\0') {
11645 quote = compose_get_quote_str(buffer, &start_line, "e_len);
11653 gtk_text_buffer_insert(buffer, iter, text, len);
11655 gtk_text_buffer_insert_with_tags_by_name(buffer,
11656 iter, text, len, "no_join", NULL);
11661 if (!paste_as_quotation) {
11662 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11663 compose_beautify_paragraph(compose, iter, FALSE);
11664 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11665 gtk_text_buffer_delete_mark(buffer, mark);
11668 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
11669 G_CALLBACK(text_inserted),
11671 g_signal_stop_emission_by_name(G_OBJECT(buffer), "insert-text");
11673 if (compose_can_autosave(compose) &&
11674 gtk_text_buffer_get_char_count(buffer) % prefs_common.autosave_length == 0 &&
11675 compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN /* disabled while loading */)
11676 compose->draft_timeout_tag = g_timeout_add
11677 (500, (GSourceFunc) compose_defer_auto_save_draft, compose);
11681 static void compose_check_all(GtkAction *action, gpointer data)
11683 Compose *compose = (Compose *)data;
11684 if (!compose->gtkaspell)
11687 if (gtk_widget_has_focus(compose->subject_entry))
11688 claws_spell_entry_check_all(
11689 CLAWS_SPELL_ENTRY(compose->subject_entry));
11691 gtkaspell_check_all(compose->gtkaspell);
11694 static void compose_highlight_all(GtkAction *action, gpointer data)
11696 Compose *compose = (Compose *)data;
11697 if (compose->gtkaspell) {
11698 claws_spell_entry_recheck_all(
11699 CLAWS_SPELL_ENTRY(compose->subject_entry));
11700 gtkaspell_highlight_all(compose->gtkaspell);
11704 static void compose_check_backwards(GtkAction *action, gpointer data)
11706 Compose *compose = (Compose *)data;
11707 if (!compose->gtkaspell) {
11708 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
11712 if (gtk_widget_has_focus(compose->subject_entry))
11713 claws_spell_entry_check_backwards(
11714 CLAWS_SPELL_ENTRY(compose->subject_entry));
11716 gtkaspell_check_backwards(compose->gtkaspell);
11719 static void compose_check_forwards_go(GtkAction *action, gpointer data)
11721 Compose *compose = (Compose *)data;
11722 if (!compose->gtkaspell) {
11723 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
11727 if (gtk_widget_has_focus(compose->subject_entry))
11728 claws_spell_entry_check_forwards_go(
11729 CLAWS_SPELL_ENTRY(compose->subject_entry));
11731 gtkaspell_check_forwards_go(compose->gtkaspell);
11736 *\brief Guess originating forward account from MsgInfo and several
11737 * "common preference" settings. Return NULL if no guess.
11739 static PrefsAccount *compose_guess_forward_account_from_msginfo(MsgInfo *msginfo)
11741 PrefsAccount *account = NULL;
11743 cm_return_val_if_fail(msginfo, NULL);
11744 cm_return_val_if_fail(msginfo->folder, NULL);
11745 cm_return_val_if_fail(msginfo->folder->prefs, NULL);
11747 if (msginfo->folder->prefs->enable_default_account)
11748 account = account_find_from_id(msginfo->folder->prefs->default_account);
11751 account = msginfo->folder->folder->account;
11753 if (!account && msginfo->to && prefs_common.forward_account_autosel) {
11755 Xstrdup_a(to, msginfo->to, return NULL);
11756 extract_address(to);
11757 account = account_find_from_address(to, FALSE);
11760 if (!account && prefs_common.forward_account_autosel) {
11761 gchar cc[BUFFSIZE];
11762 if (!procheader_get_header_from_msginfo
11763 (msginfo, cc,sizeof cc , "Cc:")) {
11764 gchar *buf = cc + strlen("Cc:");
11765 extract_address(buf);
11766 account = account_find_from_address(buf, FALSE);
11770 if (!account && prefs_common.forward_account_autosel) {
11771 gchar deliveredto[BUFFSIZE];
11772 if (!procheader_get_header_from_msginfo
11773 (msginfo, deliveredto,sizeof deliveredto , "Delivered-To:")) {
11774 gchar *buf = deliveredto + strlen("Delivered-To:");
11775 extract_address(buf);
11776 account = account_find_from_address(buf, FALSE);
11783 gboolean compose_close(Compose *compose)
11787 cm_return_val_if_fail(compose, FALSE);
11789 if (!g_mutex_trylock(compose->mutex)) {
11790 /* we have to wait for the (possibly deferred by auto-save)
11791 * drafting to be done, before destroying the compose under
11793 debug_print("waiting for drafting to finish...\n");
11794 compose_allow_user_actions(compose, FALSE);
11795 if (compose->close_timeout_tag == 0) {
11796 compose->close_timeout_tag =
11797 g_timeout_add (500, (GSourceFunc) compose_close,
11803 if (compose->draft_timeout_tag >= 0) {
11804 g_source_remove(compose->draft_timeout_tag);
11805 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN;
11808 gtkut_widget_get_uposition(compose->window, &x, &y);
11809 if (!compose->batch) {
11810 prefs_common.compose_x = x;
11811 prefs_common.compose_y = y;
11813 g_mutex_unlock(compose->mutex);
11814 compose_destroy(compose);
11819 * Add entry field for each address in list.
11820 * \param compose E-Mail composition object.
11821 * \param listAddress List of (formatted) E-Mail addresses.
11823 static void compose_add_field_list( Compose *compose, GList *listAddress ) {
11826 node = listAddress;
11828 addr = ( gchar * ) node->data;
11829 compose_entry_append( compose, addr, COMPOSE_TO, PREF_NONE );
11830 node = g_list_next( node );
11834 static void compose_reply_from_messageview_real(MessageView *msgview, GSList *msginfo_list,
11835 guint action, gboolean opening_multiple)
11837 gchar *body = NULL;
11838 GSList *new_msglist = NULL;
11839 MsgInfo *tmp_msginfo = NULL;
11840 gboolean originally_enc = FALSE;
11841 gboolean originally_sig = FALSE;
11842 Compose *compose = NULL;
11843 gchar *s_system = NULL;
11845 cm_return_if_fail(msgview != NULL);
11847 cm_return_if_fail(msginfo_list != NULL);
11849 if (g_slist_length(msginfo_list) == 1 && !opening_multiple) {
11850 MimeInfo *mimeinfo = messageview_get_selected_mime_part(msgview);
11851 MsgInfo *orig_msginfo = (MsgInfo *)msginfo_list->data;
11853 if (mimeinfo != NULL && mimeinfo->type == MIMETYPE_MESSAGE &&
11854 !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
11855 tmp_msginfo = procmsg_msginfo_new_from_mimeinfo(
11856 orig_msginfo, mimeinfo);
11857 if (tmp_msginfo != NULL) {
11858 new_msglist = g_slist_append(NULL, tmp_msginfo);
11860 originally_enc = MSG_IS_ENCRYPTED(orig_msginfo->flags);
11861 privacy_msginfo_get_signed_state(orig_msginfo, &s_system);
11862 originally_sig = MSG_IS_SIGNED(orig_msginfo->flags);
11864 tmp_msginfo->folder = orig_msginfo->folder;
11865 tmp_msginfo->msgnum = orig_msginfo->msgnum;
11866 if (orig_msginfo->tags) {
11867 tmp_msginfo->tags = g_slist_copy(orig_msginfo->tags);
11868 tmp_msginfo->folder->tags_dirty = TRUE;
11874 if (!opening_multiple)
11875 body = messageview_get_selection(msgview);
11878 compose = compose_reply_mode((ComposeMode)action, new_msglist, body);
11879 procmsg_msginfo_free(&tmp_msginfo);
11880 g_slist_free(new_msglist);
11882 compose = compose_reply_mode((ComposeMode)action, msginfo_list, body);
11884 if (compose && originally_enc) {
11885 compose_force_encryption(compose, compose->account, FALSE, s_system);
11888 if (compose && originally_sig && compose->account->default_sign_reply) {
11889 compose_force_signing(compose, compose->account, s_system);
11893 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
11896 void compose_reply_from_messageview(MessageView *msgview, GSList *msginfo_list,
11899 if ((!prefs_common.forward_as_attachment || action != COMPOSE_FORWARD)
11900 && action != COMPOSE_FORWARD_AS_ATTACH && g_slist_length(msginfo_list) > 1) {
11901 GSList *cur = msginfo_list;
11902 gchar *msg = g_strdup_printf(_("You are about to reply to %d "
11903 "messages. Opening the windows "
11904 "could take some time. Do you "
11905 "want to continue?"),
11906 g_slist_length(msginfo_list));
11907 if (g_slist_length(msginfo_list) > 9
11908 && alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, "+" GTK_STOCK_YES, NULL)
11909 != G_ALERTALTERNATE) {
11914 /* We'll open multiple compose windows */
11915 /* let the WM place the next windows */
11916 compose_force_window_origin = FALSE;
11917 for (; cur; cur = cur->next) {
11919 tmplist.data = cur->data;
11920 tmplist.next = NULL;
11921 compose_reply_from_messageview_real(msgview, &tmplist, action, TRUE);
11923 compose_force_window_origin = TRUE;
11925 /* forwarding multiple mails as attachments is done via a
11926 * single compose window */
11927 compose_reply_from_messageview_real(msgview, msginfo_list, action, FALSE);
11931 void compose_check_for_email_account(Compose *compose)
11933 PrefsAccount *ac = NULL, *curr = NULL;
11939 if (compose->account && compose->account->protocol == A_NNTP) {
11940 ac = account_get_cur_account();
11941 if (ac->protocol == A_NNTP) {
11942 list = account_get_list();
11944 for( ; list != NULL ; list = g_list_next(list)) {
11945 curr = (PrefsAccount *) list->data;
11946 if (curr->protocol != A_NNTP) {
11952 combobox_select_by_data(GTK_COMBO_BOX(compose->account_combo),
11957 void compose_reply_to_address(MessageView *msgview, MsgInfo *msginfo,
11958 const gchar *address)
11960 GSList *msginfo_list = NULL;
11961 gchar *body = messageview_get_selection(msgview);
11964 msginfo_list = g_slist_prepend(msginfo_list, msginfo);
11966 compose = compose_reply_mode(COMPOSE_REPLY_TO_ADDRESS, msginfo_list, body);
11967 compose_check_for_email_account(compose);
11968 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
11969 compose_entry_append(compose, address, COMPOSE_TO, PREF_NONE);
11970 compose_reply_set_subject(compose, msginfo);
11973 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
11976 void compose_set_position(Compose *compose, gint pos)
11978 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11980 gtkut_text_view_set_position(text, pos);
11983 gboolean compose_search_string(Compose *compose,
11984 const gchar *str, gboolean case_sens)
11986 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11988 return gtkut_text_view_search_string(text, str, case_sens);
11991 gboolean compose_search_string_backward(Compose *compose,
11992 const gchar *str, gboolean case_sens)
11994 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11996 return gtkut_text_view_search_string_backward(text, str, case_sens);
11999 /* allocate a msginfo structure and populate its data from a compose data structure */
12000 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose)
12002 MsgInfo *newmsginfo;
12004 gchar buf[BUFFSIZE];
12006 cm_return_val_if_fail( compose != NULL, NULL );
12008 newmsginfo = procmsg_msginfo_new();
12011 get_rfc822_date(buf, sizeof(buf));
12012 newmsginfo->date = g_strdup(buf);
12015 if (compose->from_name) {
12016 newmsginfo->from = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
12017 newmsginfo->fromname = procheader_get_fromname(newmsginfo->from);
12021 if (compose->subject_entry)
12022 newmsginfo->subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
12024 /* to, cc, reply-to, newsgroups */
12025 for (list = compose->header_list; list; list = list->next) {
12026 gchar *header = gtk_editable_get_chars(
12028 gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
12029 gchar *entry = gtk_editable_get_chars(
12030 GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
12032 if ( strcasecmp(header, prefs_common_translated_header_name("To:")) == 0 ) {
12033 if ( newmsginfo->to == NULL ) {
12034 newmsginfo->to = g_strdup(entry);
12035 } else if (entry && *entry) {
12036 gchar *tmp = g_strconcat(newmsginfo->to, ", ", entry, NULL);
12037 g_free(newmsginfo->to);
12038 newmsginfo->to = tmp;
12041 if ( strcasecmp(header, prefs_common_translated_header_name("Cc:")) == 0 ) {
12042 if ( newmsginfo->cc == NULL ) {
12043 newmsginfo->cc = g_strdup(entry);
12044 } else if (entry && *entry) {
12045 gchar *tmp = g_strconcat(newmsginfo->cc, ", ", entry, NULL);
12046 g_free(newmsginfo->cc);
12047 newmsginfo->cc = tmp;
12050 if ( strcasecmp(header,
12051 prefs_common_translated_header_name("Newsgroups:")) == 0 ) {
12052 if ( newmsginfo->newsgroups == NULL ) {
12053 newmsginfo->newsgroups = g_strdup(entry);
12054 } else if (entry && *entry) {
12055 gchar *tmp = g_strconcat(newmsginfo->newsgroups, ", ", entry, NULL);
12056 g_free(newmsginfo->newsgroups);
12057 newmsginfo->newsgroups = tmp;
12065 /* other data is unset */
12071 /* update compose's dictionaries from folder dict settings */
12072 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
12073 FolderItem *folder_item)
12075 cm_return_if_fail(compose != NULL);
12077 if (compose->gtkaspell && folder_item && folder_item->prefs) {
12078 FolderItemPrefs *prefs = folder_item->prefs;
12080 if (prefs->enable_default_dictionary)
12081 gtkaspell_change_dict(compose->gtkaspell,
12082 prefs->default_dictionary, FALSE);
12083 if (folder_item->prefs->enable_default_alt_dictionary)
12084 gtkaspell_change_alt_dict(compose->gtkaspell,
12085 prefs->default_alt_dictionary);
12086 if (prefs->enable_default_dictionary
12087 || prefs->enable_default_alt_dictionary)
12088 compose_spell_menu_changed(compose);
12093 static void compose_subject_entry_activated(GtkWidget *widget, gpointer data)
12095 Compose *compose = (Compose *)data;
12097 cm_return_if_fail(compose != NULL);
12099 gtk_widget_grab_focus(compose->text);
12102 static void from_name_activate_cb(GtkWidget *widget, gpointer data)
12104 gtk_combo_box_popup(GTK_COMBO_BOX(data));