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"
122 #define N_ATTACH_COLS (N_COL_COLUMNS)
126 COMPOSE_CALL_ADVANCED_ACTION_UNDEFINED = -1,
127 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE = 0,
128 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER,
129 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER,
130 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD,
131 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD,
132 COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE,
133 COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE,
134 COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE,
135 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER,
136 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER,
137 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD,
138 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD,
139 COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE,
140 COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END
141 } ComposeCallAdvancedAction;
145 PRIORITY_HIGHEST = 1,
154 COMPOSE_INSERT_SUCCESS,
155 COMPOSE_INSERT_READ_ERROR,
156 COMPOSE_INSERT_INVALID_CHARACTER,
157 COMPOSE_INSERT_NO_FILE
158 } ComposeInsertResult;
162 COMPOSE_WRITE_FOR_SEND,
163 COMPOSE_WRITE_FOR_STORE
168 COMPOSE_QUOTE_FORCED,
175 SUBJECT_FIELD_PRESENT,
180 #define B64_LINE_SIZE 57
181 #define B64_BUFFSIZE 77
183 #define MAX_REFERENCES_LEN 999
185 #define COMPOSE_DRAFT_TIMEOUT_UNSET -1
186 #define COMPOSE_DRAFT_TIMEOUT_FORBIDDEN -2
188 static GdkColor default_header_bgcolor = {
195 static GdkColor default_header_color = {
202 static GList *compose_list = NULL;
203 static GSList *extra_headers = NULL;
205 static Compose *compose_generic_new (PrefsAccount *account,
209 GList *listAddress );
211 static Compose *compose_create (PrefsAccount *account,
216 static void compose_entry_indicate (Compose *compose,
217 const gchar *address);
218 static Compose *compose_followup_and_reply_to (MsgInfo *msginfo,
219 ComposeQuoteMode quote_mode,
223 static Compose *compose_forward_multiple (PrefsAccount *account,
224 GSList *msginfo_list);
225 static Compose *compose_reply (MsgInfo *msginfo,
226 ComposeQuoteMode quote_mode,
231 static Compose *compose_reply_mode (ComposeMode mode,
232 GSList *msginfo_list,
234 static void compose_template_apply_fields(Compose *compose, Template *tmpl);
235 static void compose_update_privacy_systems_menu(Compose *compose);
237 static GtkWidget *compose_account_option_menu_create
239 static void compose_set_out_encoding (Compose *compose);
240 static void compose_set_template_menu (Compose *compose);
241 static void compose_destroy (Compose *compose);
243 static MailField compose_entries_set (Compose *compose,
245 ComposeEntryType to_type);
246 static gint compose_parse_header (Compose *compose,
248 static gint compose_parse_manual_headers (Compose *compose,
250 HeaderEntry *entries);
251 static gchar *compose_parse_references (const gchar *ref,
254 static gchar *compose_quote_fmt (Compose *compose,
260 gboolean need_unescape,
261 const gchar *err_msg);
263 static void compose_reply_set_entry (Compose *compose,
269 followup_and_reply_to);
270 static void compose_reedit_set_entry (Compose *compose,
273 static void compose_insert_sig (Compose *compose,
275 static ComposeInsertResult compose_insert_file (Compose *compose,
278 static gboolean compose_attach_append (Compose *compose,
281 const gchar *content_type,
282 const gchar *charset);
283 static void compose_attach_parts (Compose *compose,
286 static gboolean compose_beautify_paragraph (Compose *compose,
287 GtkTextIter *par_iter,
289 static void compose_wrap_all (Compose *compose);
290 static void compose_wrap_all_full (Compose *compose,
293 static void compose_set_title (Compose *compose);
294 static void compose_select_account (Compose *compose,
295 PrefsAccount *account,
298 static PrefsAccount *compose_current_mail_account(void);
299 /* static gint compose_send (Compose *compose); */
300 static gboolean compose_check_for_valid_recipient
302 static gboolean compose_check_entries (Compose *compose,
303 gboolean check_everything);
304 static gint compose_write_to_file (Compose *compose,
307 gboolean attach_parts);
308 static gint compose_write_body_to_file (Compose *compose,
310 static gint compose_remove_reedit_target (Compose *compose,
312 static void compose_remove_draft (Compose *compose);
313 static gint compose_queue_sub (Compose *compose,
317 gboolean perform_checks,
318 gboolean remove_reedit_target);
319 static int compose_add_attachments (Compose *compose,
321 static gchar *compose_get_header (Compose *compose);
322 static gchar *compose_get_manual_headers_info (Compose *compose);
324 static void compose_convert_header (Compose *compose,
329 gboolean addr_field);
331 static void compose_attach_info_free (AttachInfo *ainfo);
332 static void compose_attach_remove_selected (GtkAction *action,
335 static void compose_template_apply (Compose *compose,
338 static void compose_attach_property (GtkAction *action,
340 static void compose_attach_property_create (gboolean *cancelled);
341 static void attach_property_ok (GtkWidget *widget,
342 gboolean *cancelled);
343 static void attach_property_cancel (GtkWidget *widget,
344 gboolean *cancelled);
345 static gint attach_property_delete_event (GtkWidget *widget,
347 gboolean *cancelled);
348 static gboolean attach_property_key_pressed (GtkWidget *widget,
350 gboolean *cancelled);
352 static void compose_exec_ext_editor (Compose *compose);
354 static gint compose_exec_ext_editor_real (const gchar *file,
355 GdkNativeWindow socket_wid);
356 static gboolean compose_ext_editor_kill (Compose *compose);
357 static gboolean compose_input_cb (GIOChannel *source,
358 GIOCondition condition,
360 static void compose_set_ext_editor_sensitive (Compose *compose,
362 static gboolean compose_get_ext_editor_cmd_valid();
363 static gboolean compose_get_ext_editor_uses_socket();
364 static gboolean compose_ext_editor_plug_removed_cb
367 #endif /* G_OS_UNIX */
369 static void compose_undo_state_changed (UndoMain *undostruct,
374 static void compose_create_header_entry (Compose *compose);
375 static void compose_add_header_entry (Compose *compose, const gchar *header,
376 gchar *text, ComposePrefType pref_type);
377 static void compose_remove_header_entries(Compose *compose);
379 static void compose_update_priority_menu_item(Compose * compose);
381 static void compose_spell_menu_changed (void *data);
382 static void compose_dict_changed (void *data);
384 static void compose_add_field_list ( Compose *compose,
385 GList *listAddress );
387 /* callback functions */
389 static void compose_notebook_size_alloc (GtkNotebook *notebook,
390 GtkAllocation *allocation,
392 static gboolean compose_edit_size_alloc (GtkEditable *widget,
393 GtkAllocation *allocation,
394 GtkSHRuler *shruler);
395 static void account_activated (GtkComboBox *optmenu,
397 static void attach_selected (GtkTreeView *tree_view,
398 GtkTreePath *tree_path,
399 GtkTreeViewColumn *column,
401 static gboolean attach_button_pressed (GtkWidget *widget,
402 GdkEventButton *event,
404 static gboolean attach_key_pressed (GtkWidget *widget,
407 static void compose_send_cb (GtkAction *action, gpointer data);
408 static void compose_send_later_cb (GtkAction *action, gpointer data);
410 static void compose_save_cb (GtkAction *action,
413 static void compose_attach_cb (GtkAction *action,
415 static void compose_insert_file_cb (GtkAction *action,
417 static void compose_insert_sig_cb (GtkAction *action,
419 static void compose_replace_sig_cb (GtkAction *action,
422 static void compose_close_cb (GtkAction *action,
424 static void compose_print_cb (GtkAction *action,
427 static void compose_set_encoding_cb (GtkAction *action, GtkRadioAction *current, gpointer data);
429 static void compose_address_cb (GtkAction *action,
431 static void about_show_cb (GtkAction *action,
433 static void compose_template_activate_cb(GtkWidget *widget,
436 static void compose_ext_editor_cb (GtkAction *action,
439 static gint compose_delete_cb (GtkWidget *widget,
443 static void compose_undo_cb (GtkAction *action,
445 static void compose_redo_cb (GtkAction *action,
447 static void compose_cut_cb (GtkAction *action,
449 static void compose_copy_cb (GtkAction *action,
451 static void compose_paste_cb (GtkAction *action,
453 static void compose_paste_as_quote_cb (GtkAction *action,
455 static void compose_paste_no_wrap_cb (GtkAction *action,
457 static void compose_paste_wrap_cb (GtkAction *action,
459 static void compose_allsel_cb (GtkAction *action,
462 static void compose_advanced_action_cb (GtkAction *action,
465 static void compose_grab_focus_cb (GtkWidget *widget,
468 static void compose_changed_cb (GtkTextBuffer *textbuf,
471 static void compose_wrap_cb (GtkAction *action,
473 static void compose_wrap_all_cb (GtkAction *action,
475 static void compose_find_cb (GtkAction *action,
477 static void compose_toggle_autowrap_cb (GtkToggleAction *action,
479 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
482 static void compose_toggle_ruler_cb (GtkToggleAction *action,
484 static void compose_toggle_sign_cb (GtkToggleAction *action,
486 static void compose_toggle_encrypt_cb (GtkToggleAction *action,
488 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data);
489 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn);
490 static void activate_privacy_system (Compose *compose,
491 PrefsAccount *account,
493 static void compose_use_signing(Compose *compose, gboolean use_signing);
494 static void compose_use_encryption(Compose *compose, gboolean use_encryption);
495 static void compose_toggle_return_receipt_cb(GtkToggleAction *action,
497 static void compose_toggle_remove_refs_cb(GtkToggleAction *action,
499 static void compose_set_priority_cb (GtkAction *action, GtkRadioAction *current, gpointer data);
500 static void compose_reply_change_mode (Compose *compose, ComposeMode action);
501 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data);
503 static void compose_attach_drag_received_cb (GtkWidget *widget,
504 GdkDragContext *drag_context,
507 GtkSelectionData *data,
511 static void compose_insert_drag_received_cb (GtkWidget *widget,
512 GdkDragContext *drag_context,
515 GtkSelectionData *data,
519 static void compose_header_drag_received_cb (GtkWidget *widget,
520 GdkDragContext *drag_context,
523 GtkSelectionData *data,
528 static gboolean compose_drag_drop (GtkWidget *widget,
529 GdkDragContext *drag_context,
531 guint time, gpointer user_data);
532 static gboolean completion_set_focus_to_subject
537 static void text_inserted (GtkTextBuffer *buffer,
542 static Compose *compose_generic_reply(MsgInfo *msginfo,
543 ComposeQuoteMode quote_mode,
547 gboolean followup_and_reply_to,
550 static void compose_headerentry_changed_cb (GtkWidget *entry,
551 ComposeHeaderEntry *headerentry);
552 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
554 ComposeHeaderEntry *headerentry);
555 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
556 ComposeHeaderEntry *headerentry);
558 static void compose_show_first_last_header (Compose *compose, gboolean show_first);
560 static void compose_allow_user_actions (Compose *compose, gboolean allow);
562 static void compose_nothing_cb (GtkAction *action, gpointer data)
568 static void compose_check_all (GtkAction *action, gpointer data);
569 static void compose_highlight_all (GtkAction *action, gpointer data);
570 static void compose_check_backwards (GtkAction *action, gpointer data);
571 static void compose_check_forwards_go (GtkAction *action, gpointer data);
574 static PrefsAccount *compose_find_account (MsgInfo *msginfo);
576 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose);
579 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
580 FolderItem *folder_item);
582 static void compose_attach_update_label(Compose *compose);
583 static void compose_set_folder_prefs(Compose *compose, FolderItem *folder,
584 gboolean respect_default_to);
585 static void compose_subject_entry_activated(GtkWidget *widget, gpointer data);
586 static void from_name_activate_cb(GtkWidget *widget, gpointer data);
588 static GtkActionEntry compose_popup_entries[] =
590 {"Compose", NULL, "Compose", NULL, NULL, NULL },
591 {"Compose/Add", NULL, N_("_Add..."), NULL, NULL, G_CALLBACK(compose_attach_cb) },
592 {"Compose/Remove", NULL, N_("_Remove"), NULL, NULL, G_CALLBACK(compose_attach_remove_selected) },
593 {"Compose/---", NULL, "---", NULL, NULL, NULL },
594 {"Compose/Properties", NULL, N_("_Properties..."), NULL, NULL, G_CALLBACK(compose_attach_property) },
597 static GtkActionEntry compose_entries[] =
599 {"Menu", NULL, "Menu", NULL, NULL, NULL },
601 {"Message", NULL, N_("_Message"), NULL, NULL, NULL },
602 {"Edit", NULL, N_("_Edit"), NULL, NULL, NULL },
604 {"Spelling", NULL, N_("_Spelling"), NULL, NULL, NULL },
606 {"Options", NULL, N_("_Options"), NULL, NULL, NULL },
607 {"Tools", NULL, N_("_Tools"), NULL, NULL, NULL },
608 {"Help", NULL, N_("_Help"), NULL, NULL, NULL },
610 {"Message/Send", NULL, N_("S_end"), "<control>Return", NULL, G_CALLBACK(compose_send_cb) },
611 {"Message/SendLater", NULL, N_("Send _later"), "<shift><control>S", NULL, G_CALLBACK(compose_send_later_cb) },
612 {"Message/---", NULL, "---", NULL, NULL, NULL },
614 {"Message/AttachFile", NULL, N_("_Attach file"), "<control>M", NULL, G_CALLBACK(compose_attach_cb) },
615 {"Message/InsertFile", NULL, N_("_Insert file"), "<control>I", NULL, G_CALLBACK(compose_insert_file_cb) },
616 {"Message/InsertSig", NULL, N_("Insert si_gnature"), "<control>G", NULL, G_CALLBACK(compose_insert_sig_cb) },
617 {"Message/ReplaceSig", NULL, N_("_Replace signature"), NULL, NULL, G_CALLBACK(compose_replace_sig_cb) },
618 /* {"Message/---", NULL, "---", NULL, NULL, NULL }, */
619 {"Message/Save", NULL, N_("_Save"), "<control>S", NULL, G_CALLBACK(compose_save_cb) }, /*COMPOSE_KEEP_EDITING*/
620 /* {"Message/---", NULL, "---", NULL, NULL, NULL }, */
621 {"Message/Print", NULL, N_("_Print"), NULL, NULL, G_CALLBACK(compose_print_cb) },
622 /* {"Message/---", NULL, "---", NULL, NULL, NULL }, */
623 {"Message/Close", NULL, N_("_Close"), "<control>W", NULL, G_CALLBACK(compose_close_cb) },
626 {"Edit/Undo", NULL, N_("_Undo"), "<control>Z", NULL, G_CALLBACK(compose_undo_cb) },
627 {"Edit/Redo", NULL, N_("_Redo"), "<control>Y", NULL, G_CALLBACK(compose_redo_cb) },
628 {"Edit/---", NULL, "---", NULL, NULL, NULL },
630 {"Edit/Cut", NULL, N_("Cu_t"), "<control>X", NULL, G_CALLBACK(compose_cut_cb) },
631 {"Edit/Copy", NULL, N_("_Copy"), "<control>C", NULL, G_CALLBACK(compose_copy_cb) },
632 {"Edit/Paste", NULL, N_("_Paste"), "<control>V", NULL, G_CALLBACK(compose_paste_cb) },
634 {"Edit/SpecialPaste", NULL, N_("_Special paste"), NULL, NULL, NULL },
635 {"Edit/SpecialPaste/AsQuotation", NULL, N_("As _quotation"), NULL, NULL, G_CALLBACK(compose_paste_as_quote_cb) },
636 {"Edit/SpecialPaste/Wrapped", NULL, N_("_Wrapped"), NULL, NULL, G_CALLBACK(compose_paste_wrap_cb) },
637 {"Edit/SpecialPaste/Unwrapped", NULL, N_("_Unwrapped"), NULL, NULL, G_CALLBACK(compose_paste_no_wrap_cb) },
639 {"Edit/SelectAll", NULL, N_("Select _all"), "<control>A", NULL, G_CALLBACK(compose_allsel_cb) },
641 {"Edit/Advanced", NULL, N_("A_dvanced"), NULL, NULL, NULL },
642 {"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*/
643 {"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*/
644 {"Edit/Advanced/BackWord", NULL, N_("Move a word backward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD*/
645 {"Edit/Advanced/ForwWord", NULL, N_("Move a word forward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD*/
646 {"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*/
647 {"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*/
648 {"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*/
649 {"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*/
650 {"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*/
651 {"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*/
652 {"Edit/Advanced/DelBackWord", NULL, N_("Delete a word backward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD*/
653 {"Edit/Advanced/DelForwWord", NULL, N_("Delete a word forward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD*/
654 {"Edit/Advanced/DelLine", NULL, N_("Delete line"), "<control>U", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE*/
655 {"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*/
657 /* {"Edit/---", NULL, "---", NULL, NULL, NULL }, */
658 {"Edit/Find", NULL, N_("_Find"), "<control>F", NULL, G_CALLBACK(compose_find_cb) },
660 /* {"Edit/---", NULL, "---", NULL, NULL, NULL }, */
661 {"Edit/WrapPara", NULL, N_("_Wrap current paragraph"), "<control>L", NULL, G_CALLBACK(compose_wrap_cb) }, /* 0 */
662 {"Edit/WrapAllLines", NULL, N_("Wrap all long _lines"), "<control><alt>L", NULL, G_CALLBACK(compose_wrap_all_cb) }, /* 1 */
663 /* {"Edit/---", NULL, "---", NULL, NULL, NULL }, */
664 {"Edit/ExtEditor", NULL, N_("Edit with e_xternal editor"), "<shift><control>X", NULL, G_CALLBACK(compose_ext_editor_cb) },
667 {"Spelling/CheckAllSel", NULL, N_("_Check all or check selection"), NULL, NULL, G_CALLBACK(compose_check_all) },
668 {"Spelling/HighlightAll", NULL, N_("_Highlight all misspelled words"), NULL, NULL, G_CALLBACK(compose_highlight_all) },
669 {"Spelling/CheckBackwards", NULL, N_("Check _backwards misspelled word"), NULL, NULL, G_CALLBACK(compose_check_backwards) },
670 {"Spelling/ForwardNext", NULL, N_("_Forward to next misspelled word"), NULL, NULL, G_CALLBACK(compose_check_forwards_go) },
672 {"Spelling/---", NULL, "---", NULL, NULL, NULL },
673 {"Spelling/Options", NULL, N_("_Options"), NULL, NULL, NULL },
677 {"Options/ReplyMode", NULL, N_("Reply _mode"), NULL, NULL, NULL },
678 {"Options/---", NULL, "---", NULL, NULL, NULL },
679 {"Options/PrivacySystem", NULL, N_("Privacy _System"), NULL, NULL, NULL },
680 {"Options/PrivacySystem/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
682 /* {"Options/---", NULL, "---", NULL, NULL, NULL }, */
683 {"Options/Priority", NULL, N_("_Priority"), NULL, NULL, NULL },
685 {"Options/Encoding", NULL, N_("Character _encoding"), NULL, NULL, NULL },
686 {"Options/Encoding/---", NULL, "---", NULL, NULL, NULL },
687 #define ENC_ACTION(cs_char,c_char,string) \
688 {"Options/Encoding/" cs_char, NULL, N_(string), NULL, NULL, c_char }
690 {"Options/Encoding/Western", NULL, N_("Western European"), NULL, NULL, NULL },
691 {"Options/Encoding/Baltic", NULL, N_("Baltic"), NULL, NULL, NULL },
692 {"Options/Encoding/Hebrew", NULL, N_("Hebrew"), NULL, NULL, NULL },
693 {"Options/Encoding/Arabic", NULL, N_("Arabic"), NULL, NULL, NULL },
694 {"Options/Encoding/Cyrillic", NULL, N_("Cyrillic"), NULL, NULL, NULL },
695 {"Options/Encoding/Japanese", NULL, N_("Japanese"), NULL, NULL, NULL },
696 {"Options/Encoding/Chinese", NULL, N_("Chinese"), NULL, NULL, NULL },
697 {"Options/Encoding/Korean", NULL, N_("Korean"), NULL, NULL, NULL },
698 {"Options/Encoding/Thai", NULL, N_("Thai"), NULL, NULL, NULL },
701 {"Tools/AddressBook", NULL, N_("_Address book"), NULL, NULL, G_CALLBACK(compose_address_cb) },
703 {"Tools/Template", NULL, N_("_Template"), NULL, NULL, NULL },
704 {"Tools/Template/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
705 {"Tools/Actions", NULL, N_("Actio_ns"), NULL, NULL, NULL },
706 {"Tools/Actions/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
709 {"Help/About", NULL, N_("_About"), NULL, NULL, G_CALLBACK(about_show_cb) },
712 static GtkToggleActionEntry compose_toggle_entries[] =
714 {"Edit/AutoWrap", NULL, N_("Aut_o wrapping"), "<shift><control>L", NULL, G_CALLBACK(compose_toggle_autowrap_cb), FALSE }, /* Toggle */
715 {"Edit/AutoIndent", NULL, N_("Auto _indent"), NULL, NULL, G_CALLBACK(compose_toggle_autoindent_cb), FALSE }, /* Toggle */
716 {"Options/Sign", NULL, N_("Si_gn"), NULL, NULL, G_CALLBACK(compose_toggle_sign_cb), FALSE }, /* Toggle */
717 {"Options/Encrypt", NULL, N_("_Encrypt"), NULL, NULL, G_CALLBACK(compose_toggle_encrypt_cb), FALSE }, /* Toggle */
718 {"Options/RequestRetRcpt", NULL, N_("_Request Return Receipt"), NULL, NULL, G_CALLBACK(compose_toggle_return_receipt_cb), FALSE }, /* Toggle */
719 {"Options/RemoveReferences", NULL, N_("Remo_ve references"), NULL, NULL, G_CALLBACK(compose_toggle_remove_refs_cb), FALSE }, /* Toggle */
720 {"Tools/ShowRuler", NULL, N_("Show _ruler"), NULL, NULL, G_CALLBACK(compose_toggle_ruler_cb), FALSE }, /* Toggle */
723 static GtkRadioActionEntry compose_radio_rm_entries[] =
725 {"Options/ReplyMode/Normal", NULL, N_("_Normal"), NULL, NULL, COMPOSE_REPLY }, /* RADIO compose_reply_change_mode_cb */
726 {"Options/ReplyMode/All", NULL, N_("_All"), NULL, NULL, COMPOSE_REPLY_TO_ALL }, /* RADIO compose_reply_change_mode_cb */
727 {"Options/ReplyMode/Sender", NULL, N_("_Sender"), NULL, NULL, COMPOSE_REPLY_TO_SENDER }, /* RADIO compose_reply_change_mode_cb */
728 {"Options/ReplyMode/List", NULL, N_("_Mailing-list"), NULL, NULL, COMPOSE_REPLY_TO_LIST }, /* RADIO compose_reply_change_mode_cb */
731 static GtkRadioActionEntry compose_radio_prio_entries[] =
733 {"Options/Priority/Highest", NULL, N_("_Highest"), NULL, NULL, PRIORITY_HIGHEST }, /* RADIO compose_set_priority_cb */
734 {"Options/Priority/High", NULL, N_("Hi_gh"), NULL, NULL, PRIORITY_HIGH }, /* RADIO compose_set_priority_cb */
735 {"Options/Priority/Normal", NULL, N_("_Normal"), NULL, NULL, PRIORITY_NORMAL }, /* RADIO compose_set_priority_cb */
736 {"Options/Priority/Low", NULL, N_("Lo_w"), NULL, NULL, PRIORITY_LOW }, /* RADIO compose_set_priority_cb */
737 {"Options/Priority/Lowest", NULL, N_("_Lowest"), NULL, NULL, PRIORITY_LOWEST }, /* RADIO compose_set_priority_cb */
740 static GtkRadioActionEntry compose_radio_enc_entries[] =
742 ENC_ACTION(CS_AUTO, C_AUTO, N_("_Automatic")), /* RADIO compose_set_encoding_cb */
743 ENC_ACTION(CS_US_ASCII, C_US_ASCII, N_("7bit ASCII (US-ASC_II)")), /* RADIO compose_set_encoding_cb */
744 ENC_ACTION(CS_UTF_8, C_UTF_8, N_("Unicode (_UTF-8)")), /* RADIO compose_set_encoding_cb */
745 ENC_ACTION("Western/"CS_ISO_8859_1, C_ISO_8859_1, "ISO-8859-_1"), /* RADIO compose_set_encoding_cb */
746 ENC_ACTION("Western/"CS_ISO_8859_15, C_ISO_8859_15, "ISO-8859-15"), /* RADIO compose_set_encoding_cb */
747 ENC_ACTION("Western/"CS_WINDOWS_1252, C_WINDOWS_1252, "Windows-1252"), /* RADIO compose_set_encoding_cb */
748 ENC_ACTION(CS_ISO_8859_2, C_ISO_8859_2, N_("Central European (ISO-8859-_2)")), /* RADIO compose_set_encoding_cb */
749 ENC_ACTION("Baltic/"CS_ISO_8859_13, C_ISO_8859_13, "ISO-8859-13"), /* RADIO compose_set_encoding_cb */
750 ENC_ACTION("Baltic/"CS_ISO_8859_4, C_ISO_8859_14, "ISO-8859-_4"), /* RADIO compose_set_encoding_cb */
751 ENC_ACTION(CS_ISO_8859_7, C_ISO_8859_7, N_("Greek (ISO-8859-_7)")), /* RADIO compose_set_encoding_cb */
752 ENC_ACTION("Hebrew/"CS_ISO_8859_8, C_ISO_8859_8, "ISO-8859-_8"), /* RADIO compose_set_encoding_cb */
753 ENC_ACTION("Hebrew/"CS_WINDOWS_1255, C_WINDOWS_1255, "Windows-1255"), /* RADIO compose_set_encoding_cb */
754 ENC_ACTION("Arabic/"CS_ISO_8859_6, C_ISO_8859_6, "ISO-8859-_6"), /* RADIO compose_set_encoding_cb */
755 ENC_ACTION("Arabic/"CS_WINDOWS_1256, C_WINDOWS_1256, "Windows-1256"), /* RADIO compose_set_encoding_cb */
756 ENC_ACTION(CS_ISO_8859_9, C_ISO_8859_9, N_("Turkish (ISO-8859-_9)")), /* RADIO compose_set_encoding_cb */
757 ENC_ACTION("Cyrillic/"CS_ISO_8859_5, C_ISO_8859_5, "ISO-8859-_5"), /* RADIO compose_set_encoding_cb */
758 ENC_ACTION("Cyrillic/"CS_KOI8_R, C_KOI8_R, "KOI8-_R"), /* RADIO compose_set_encoding_cb */
759 ENC_ACTION("Cyrillic/"CS_MACCYR, C_MACCYR, "_Mac-Cyrillic"), /* RADIO compose_set_encoding_cb */
760 ENC_ACTION("Cyrillic/"CS_KOI8_U, C_KOI8_U, "KOI8-_U"), /* RADIO compose_set_encoding_cb */
761 ENC_ACTION("Cyrillic/"CS_WINDOWS_1251, C_WINDOWS_1251, "Windows-1251"), /* RADIO compose_set_encoding_cb */
762 ENC_ACTION("Japanese/"CS_ISO_2022_JP, C_ISO_2022_JP, "ISO-2022-_JP"), /* RADIO compose_set_encoding_cb */
763 ENC_ACTION("Japanese/"CS_ISO_2022_JP_2, C_ISO_2022_JP_2, "ISO-2022-JP-_2"), /* RADIO compose_set_encoding_cb */
764 ENC_ACTION("Japanese/"CS_EUC_JP, C_EUC_JP, "_EUC-JP"), /* RADIO compose_set_encoding_cb */
765 ENC_ACTION("Japanese/"CS_SHIFT_JIS, C_SHIFT_JIS, "_Shift-JIS"), /* RADIO compose_set_encoding_cb */
766 ENC_ACTION("Chinese/"CS_GB18030, C_GB18030, "_GB18030"), /* RADIO compose_set_encoding_cb */
767 ENC_ACTION("Chinese/"CS_GB2312, C_GB2312, "_GB2312"), /* RADIO compose_set_encoding_cb */
768 ENC_ACTION("Chinese/"CS_GBK, C_GBK, "GB_K"), /* RADIO compose_set_encoding_cb */
769 ENC_ACTION("Chinese/"CS_BIG5, C_BIG5, "_Big5-JP"), /* RADIO compose_set_encoding_cb */
770 ENC_ACTION("Chinese/"CS_EUC_TW, C_EUC_TW, "EUC-_TW"), /* RADIO compose_set_encoding_cb */
771 ENC_ACTION("Korean/"CS_EUC_KR, C_EUC_KR, "_EUC-KR"), /* RADIO compose_set_encoding_cb */
772 ENC_ACTION("Korean/"CS_ISO_2022_KR, C_ISO_2022_KR, "_ISO-2022-KR"), /* RADIO compose_set_encoding_cb */
773 ENC_ACTION("Thai/"CS_TIS_620, C_TIS_620, "_TIS-620-KR"), /* RADIO compose_set_encoding_cb */
774 ENC_ACTION("Thai/"CS_WINDOWS_874, C_WINDOWS_874, "_Windows-874"), /* RADIO compose_set_encoding_cb */
777 static GtkTargetEntry compose_mime_types[] =
779 {"text/uri-list", 0, 0},
780 {"UTF8_STRING", 0, 0},
784 static gboolean compose_put_existing_to_front(MsgInfo *info)
786 const GList *compose_list = compose_get_compose_list();
787 const GList *elem = NULL;
790 for (elem = compose_list; elem != NULL && elem->data != NULL;
792 Compose *c = (Compose*)elem->data;
794 if (!c->targetinfo || !c->targetinfo->msgid ||
798 if (!strcmp(c->targetinfo->msgid, info->msgid)) {
799 gtkut_window_popup(c->window);
807 static GdkColor quote_color1 =
808 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
809 static GdkColor quote_color2 =
810 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
811 static GdkColor quote_color3 =
812 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
814 static GdkColor quote_bgcolor1 =
815 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
816 static GdkColor quote_bgcolor2 =
817 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
818 static GdkColor quote_bgcolor3 =
819 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
821 static GdkColor signature_color = {
828 static GdkColor uri_color = {
835 static void compose_create_tags(GtkTextView *text, Compose *compose)
837 GtkTextBuffer *buffer;
838 GdkColor black = {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
839 #if !GTK_CHECK_VERSION(2, 24, 0)
846 buffer = gtk_text_view_get_buffer(text);
848 if (prefs_common.enable_color) {
849 /* grab the quote colors, converting from an int to a GdkColor */
850 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_col,
852 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_col,
854 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_col,
856 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_bgcol,
858 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_bgcol,
860 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_bgcol,
862 gtkut_convert_int_to_gdk_color(prefs_common.signature_col,
864 gtkut_convert_int_to_gdk_color(prefs_common.uri_col,
867 signature_color = quote_color1 = quote_color2 = quote_color3 =
868 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 = uri_color = black;
871 if (prefs_common.enable_color && prefs_common.enable_bgcolor) {
872 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
873 "foreground-gdk", "e_color1,
874 "paragraph-background-gdk", "e_bgcolor1,
876 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
877 "foreground-gdk", "e_color2,
878 "paragraph-background-gdk", "e_bgcolor2,
880 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
881 "foreground-gdk", "e_color3,
882 "paragraph-background-gdk", "e_bgcolor3,
885 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
886 "foreground-gdk", "e_color1,
888 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
889 "foreground-gdk", "e_color2,
891 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
892 "foreground-gdk", "e_color3,
896 compose->signature_tag = gtk_text_buffer_create_tag(buffer, "signature",
897 "foreground-gdk", &signature_color,
900 compose->uri_tag = gtk_text_buffer_create_tag(buffer, "link",
901 "foreground-gdk", &uri_color,
903 compose->no_wrap_tag = gtk_text_buffer_create_tag(buffer, "no_wrap", NULL);
904 compose->no_join_tag = gtk_text_buffer_create_tag(buffer, "no_join", NULL);
906 #if !GTK_CHECK_VERSION(2, 24, 0)
907 color[0] = quote_color1;
908 color[1] = quote_color2;
909 color[2] = quote_color3;
910 color[3] = quote_bgcolor1;
911 color[4] = quote_bgcolor2;
912 color[5] = quote_bgcolor3;
913 color[6] = signature_color;
914 color[7] = uri_color;
916 cmap = gdk_drawable_get_colormap(gtk_widget_get_window(compose->window));
917 gdk_colormap_alloc_colors(cmap, color, 8, FALSE, TRUE, success);
919 for (i = 0; i < 8; i++) {
920 if (success[i] == FALSE) {
921 g_warning("Compose: color allocation failed.");
922 quote_color1 = quote_color2 = quote_color3 =
923 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 =
924 signature_color = uri_color = black;
930 Compose *compose_new(PrefsAccount *account, const gchar *mailto,
933 return compose_generic_new(account, mailto, NULL, attach_files, NULL);
936 Compose *compose_new_with_folderitem(PrefsAccount *account, FolderItem *item, const gchar *mailto)
938 return compose_generic_new(account, mailto, item, NULL, NULL);
941 Compose *compose_new_with_list( PrefsAccount *account, GList *listAddress )
943 return compose_generic_new( account, NULL, NULL, NULL, listAddress );
946 #define SCROLL_TO_CURSOR(compose) { \
947 GtkTextMark *cmark = gtk_text_buffer_get_insert( \
948 gtk_text_view_get_buffer( \
949 GTK_TEXT_VIEW(compose->text))); \
950 gtk_text_view_scroll_mark_onscreen( \
951 GTK_TEXT_VIEW(compose->text), \
955 static void compose_set_save_to(Compose *compose, const gchar *folderidentifier)
958 if (folderidentifier) {
959 #if !GTK_CHECK_VERSION(2, 24, 0)
960 combobox_unset_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo));
962 combobox_unset_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo));
964 prefs_common.compose_save_to_history = add_history(
965 prefs_common.compose_save_to_history, folderidentifier);
966 #if !GTK_CHECK_VERSION(2, 24, 0)
967 combobox_set_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo),
968 prefs_common.compose_save_to_history);
970 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo),
971 prefs_common.compose_save_to_history);
975 entry = GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(compose->savemsg_combo)));
976 if (folderidentifier)
977 gtk_entry_set_text(GTK_ENTRY(entry), folderidentifier);
979 gtk_entry_set_text(GTK_ENTRY(entry), "");
982 static gchar *compose_get_save_to(Compose *compose)
985 gchar *result = NULL;
986 entry = GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(compose->savemsg_combo)));
987 result = gtk_editable_get_chars(entry, 0, -1);
990 #if !GTK_CHECK_VERSION(2, 24, 0)
991 combobox_unset_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo));
993 combobox_unset_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo));
995 prefs_common.compose_save_to_history = add_history(
996 prefs_common.compose_save_to_history, result);
997 #if !GTK_CHECK_VERSION(2, 24, 0)
998 combobox_set_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo),
999 prefs_common.compose_save_to_history);
1001 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo),
1002 prefs_common.compose_save_to_history);
1008 Compose *compose_generic_new(PrefsAccount *account, const gchar *mailto, FolderItem *item,
1009 GList *attach_files, GList *listAddress )
1012 GtkTextView *textview;
1013 GtkTextBuffer *textbuf;
1015 const gchar *subject_format = NULL;
1016 const gchar *body_format = NULL;
1017 gchar *mailto_from = NULL;
1018 PrefsAccount *mailto_account = NULL;
1019 MsgInfo* dummyinfo = NULL;
1020 gint cursor_pos = -1;
1021 MailField mfield = NO_FIELD_PRESENT;
1025 /* check if mailto defines a from */
1026 if (mailto && *mailto != '\0') {
1027 scan_mailto_url(mailto, &mailto_from, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
1028 /* mailto defines a from, check if we can get account prefs from it,
1029 if not, the account prefs will be guessed using other ways, but we'll keep
1032 mailto_account = account_find_from_address(mailto_from, TRUE);
1033 if (mailto_account == NULL) {
1035 Xstrdup_a(tmp_from, mailto_from, return NULL);
1036 extract_address(tmp_from);
1037 mailto_account = account_find_from_address(tmp_from, TRUE);
1041 account = mailto_account;
1044 /* if no account prefs set from mailto, set if from folder prefs (if any) */
1045 if (!mailto_account && item && item->prefs && item->prefs->enable_default_account)
1046 account = account_find_from_id(item->prefs->default_account);
1048 /* if no account prefs set, fallback to the current one */
1049 if (!account) account = cur_account;
1050 cm_return_val_if_fail(account != NULL, NULL);
1052 compose = compose_create(account, item, COMPOSE_NEW, FALSE);
1054 /* override from name if mailto asked for it */
1056 gtk_entry_set_text(GTK_ENTRY(compose->from_name), mailto_from);
1057 g_free(mailto_from);
1059 /* override from name according to folder properties */
1060 if (item && item->prefs &&
1061 item->prefs->compose_with_format &&
1062 item->prefs->compose_override_from_format &&
1063 *item->prefs->compose_override_from_format != '\0') {
1068 dummyinfo = compose_msginfo_new_from_compose(compose);
1070 /* decode \-escape sequences in the internal representation of the quote format */
1071 tmp = g_malloc(strlen(item->prefs->compose_override_from_format)+1);
1072 pref_get_unescaped_pref(tmp, item->prefs->compose_override_from_format);
1075 quote_fmt_init(dummyinfo, NULL, NULL, FALSE, compose->account, FALSE,
1076 compose->gtkaspell);
1078 quote_fmt_init(dummyinfo, NULL, NULL, FALSE, compose->account, FALSE);
1080 quote_fmt_scan_string(tmp);
1083 buf = quote_fmt_get_buffer();
1085 alertpanel_error(_("New message From format error."));
1087 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1088 quote_fmt_reset_vartable();
1093 compose->replyinfo = NULL;
1094 compose->fwdinfo = NULL;
1096 textview = GTK_TEXT_VIEW(compose->text);
1097 textbuf = gtk_text_view_get_buffer(textview);
1098 compose_create_tags(textview, compose);
1100 undo_block(compose->undostruct);
1102 compose_set_dictionaries_from_folder_prefs(compose, item);
1105 if (account->auto_sig)
1106 compose_insert_sig(compose, FALSE);
1107 gtk_text_buffer_get_start_iter(textbuf, &iter);
1108 gtk_text_buffer_place_cursor(textbuf, &iter);
1110 if (account->protocol != A_NNTP) {
1111 if (mailto && *mailto != '\0') {
1112 mfield = compose_entries_set(compose, mailto, COMPOSE_TO);
1115 compose_set_folder_prefs(compose, item, TRUE);
1117 if (item && item->ret_rcpt) {
1118 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
1121 if (mailto && *mailto != '\0') {
1122 if (!strchr(mailto, '@'))
1123 mfield = compose_entries_set(compose, mailto, COMPOSE_NEWSGROUPS);
1125 mfield = compose_entries_set(compose, mailto, COMPOSE_TO);
1126 } else if (item && FOLDER_CLASS(item->folder) == news_get_class()) {
1127 compose_entry_append(compose, item->path, COMPOSE_NEWSGROUPS, PREF_FOLDER);
1128 mfield = TO_FIELD_PRESENT;
1131 * CLAWS: just don't allow return receipt request, even if the user
1132 * may want to send an email. simple but foolproof.
1134 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", FALSE);
1136 compose_add_field_list( compose, listAddress );
1138 if (item && item->prefs && item->prefs->compose_with_format) {
1139 subject_format = item->prefs->compose_subject_format;
1140 body_format = item->prefs->compose_body_format;
1141 } else if (account->compose_with_format) {
1142 subject_format = account->compose_subject_format;
1143 body_format = account->compose_body_format;
1144 } else if (prefs_common.compose_with_format) {
1145 subject_format = prefs_common.compose_subject_format;
1146 body_format = prefs_common.compose_body_format;
1149 if (subject_format || body_format) {
1152 && *subject_format != '\0' )
1154 gchar *subject = NULL;
1159 dummyinfo = compose_msginfo_new_from_compose(compose);
1161 /* decode \-escape sequences in the internal representation of the quote format */
1162 tmp = g_malloc(strlen(subject_format)+1);
1163 pref_get_unescaped_pref(tmp, subject_format);
1165 subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1167 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account, FALSE,
1168 compose->gtkaspell);
1170 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account, FALSE);
1172 quote_fmt_scan_string(tmp);
1175 buf = quote_fmt_get_buffer();
1177 alertpanel_error(_("New message subject format error."));
1179 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1180 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1181 quote_fmt_reset_vartable();
1185 mfield = SUBJECT_FIELD_PRESENT;
1189 && *body_format != '\0' )
1192 GtkTextBuffer *buffer;
1193 GtkTextIter start, end;
1197 dummyinfo = compose_msginfo_new_from_compose(compose);
1199 text = GTK_TEXT_VIEW(compose->text);
1200 buffer = gtk_text_view_get_buffer(text);
1201 gtk_text_buffer_get_start_iter(buffer, &start);
1202 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
1203 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
1205 compose_quote_fmt(compose, dummyinfo,
1207 NULL, tmp, FALSE, TRUE,
1208 _("The body of the \"New message\" template has an error at line %d."));
1209 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1210 quote_fmt_reset_vartable();
1214 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1215 gtkaspell_highlight_all(compose->gtkaspell);
1217 mfield = BODY_FIELD_PRESENT;
1221 procmsg_msginfo_free( &dummyinfo );
1227 for (curr = attach_files ; curr != NULL ; curr = curr->next) {
1228 ainfo = (AttachInfo *) curr->data;
1229 compose_attach_append(compose, ainfo->file, ainfo->file,
1230 ainfo->content_type, ainfo->charset);
1234 compose_show_first_last_header(compose, TRUE);
1236 /* Set save folder */
1237 if (item && item->prefs && item->prefs->save_copy_to_folder) {
1238 gchar *folderidentifier;
1240 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
1241 folderidentifier = folder_item_get_identifier(item);
1242 compose_set_save_to(compose, folderidentifier);
1243 g_free(folderidentifier);
1246 /* Place cursor according to provided input (mfield) */
1248 case NO_FIELD_PRESENT:
1249 if (compose->header_last)
1250 gtk_widget_grab_focus(compose->header_last->entry);
1252 case TO_FIELD_PRESENT:
1253 buf = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1255 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1258 gtk_widget_grab_focus(compose->subject_entry);
1260 case SUBJECT_FIELD_PRESENT:
1261 textview = GTK_TEXT_VIEW(compose->text);
1264 textbuf = gtk_text_view_get_buffer(textview);
1267 mark = gtk_text_buffer_get_insert(textbuf);
1268 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
1269 gtk_text_buffer_insert(textbuf, &iter, "", -1);
1271 * SUBJECT_FIELD_PRESENT and BODY_FIELD_PRESENT
1272 * only defers where it comes to the variable body
1273 * is not null. If no body is present compose->text
1274 * will be null in which case you cannot place the
1275 * cursor inside the component so. An empty component
1276 * is therefore created before placing the cursor
1278 case BODY_FIELD_PRESENT:
1279 cursor_pos = quote_fmt_get_cursor_pos();
1280 if (cursor_pos == -1)
1281 gtk_widget_grab_focus(compose->header_last->entry);
1283 gtk_widget_grab_focus(compose->text);
1287 undo_unblock(compose->undostruct);
1289 if (prefs_common.auto_exteditor)
1290 compose_exec_ext_editor(compose);
1292 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
1294 SCROLL_TO_CURSOR(compose);
1296 compose->modified = FALSE;
1297 compose_set_title(compose);
1299 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
1304 static void compose_force_encryption(Compose *compose, PrefsAccount *account,
1305 gboolean override_pref, const gchar *system)
1307 const gchar *privacy = NULL;
1309 cm_return_if_fail(compose != NULL);
1310 cm_return_if_fail(account != NULL);
1312 if (override_pref == FALSE && account->default_encrypt_reply == FALSE)
1315 if (account->default_privacy_system && strlen(account->default_privacy_system))
1316 privacy = account->default_privacy_system;
1320 GSList *privacy_avail = privacy_get_system_ids();
1321 if (privacy_avail && g_slist_length(privacy_avail)) {
1322 privacy = (gchar *)(privacy_avail->data);
1325 if (privacy != NULL) {
1327 g_free(compose->privacy_system);
1328 compose->privacy_system = NULL;
1329 g_free(compose->encdata);
1330 compose->encdata = NULL;
1332 if (compose->privacy_system == NULL)
1333 compose->privacy_system = g_strdup(privacy);
1334 else if (*(compose->privacy_system) == '\0') {
1335 g_free(compose->privacy_system);
1336 g_free(compose->encdata);
1337 compose->encdata = NULL;
1338 compose->privacy_system = g_strdup(privacy);
1340 compose_update_privacy_system_menu_item(compose, FALSE);
1341 compose_use_encryption(compose, TRUE);
1345 static void compose_force_signing(Compose *compose, PrefsAccount *account, const gchar *system)
1347 const gchar *privacy = NULL;
1349 if (account->default_privacy_system && strlen(account->default_privacy_system))
1350 privacy = account->default_privacy_system;
1354 GSList *privacy_avail = privacy_get_system_ids();
1355 if (privacy_avail && g_slist_length(privacy_avail)) {
1356 privacy = (gchar *)(privacy_avail->data);
1360 if (privacy != NULL) {
1362 g_free(compose->privacy_system);
1363 compose->privacy_system = NULL;
1364 g_free(compose->encdata);
1365 compose->encdata = NULL;
1367 if (compose->privacy_system == NULL)
1368 compose->privacy_system = g_strdup(privacy);
1369 compose_update_privacy_system_menu_item(compose, FALSE);
1370 compose_use_signing(compose, TRUE);
1374 static Compose *compose_reply_mode(ComposeMode mode, GSList *msginfo_list, gchar *body)
1378 Compose *compose = NULL;
1380 cm_return_val_if_fail(msginfo_list != NULL, NULL);
1382 msginfo = (MsgInfo*)g_slist_nth_data(msginfo_list, 0);
1383 cm_return_val_if_fail(msginfo != NULL, NULL);
1385 list_len = g_slist_length(msginfo_list);
1389 case COMPOSE_REPLY_TO_ADDRESS:
1390 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1391 FALSE, prefs_common.default_reply_list, FALSE, body);
1393 case COMPOSE_REPLY_WITH_QUOTE:
1394 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1395 FALSE, prefs_common.default_reply_list, FALSE, body);
1397 case COMPOSE_REPLY_WITHOUT_QUOTE:
1398 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1399 FALSE, prefs_common.default_reply_list, FALSE, NULL);
1401 case COMPOSE_REPLY_TO_SENDER:
1402 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1403 FALSE, FALSE, TRUE, body);
1405 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1406 compose = compose_followup_and_reply_to(msginfo,
1407 COMPOSE_QUOTE_CHECK,
1408 FALSE, FALSE, body);
1410 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1411 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1412 FALSE, FALSE, TRUE, body);
1414 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1415 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1416 FALSE, FALSE, TRUE, NULL);
1418 case COMPOSE_REPLY_TO_ALL:
1419 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1420 TRUE, FALSE, FALSE, body);
1422 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1423 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1424 TRUE, FALSE, FALSE, body);
1426 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1427 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1428 TRUE, FALSE, FALSE, NULL);
1430 case COMPOSE_REPLY_TO_LIST:
1431 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1432 FALSE, TRUE, FALSE, body);
1434 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1435 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1436 FALSE, TRUE, FALSE, body);
1438 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1439 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1440 FALSE, TRUE, FALSE, NULL);
1442 case COMPOSE_FORWARD:
1443 if (prefs_common.forward_as_attachment) {
1444 compose = compose_reply_mode(COMPOSE_FORWARD_AS_ATTACH, msginfo_list, body);
1447 compose = compose_reply_mode(COMPOSE_FORWARD_INLINE, msginfo_list, body);
1451 case COMPOSE_FORWARD_INLINE:
1452 /* check if we reply to more than one Message */
1453 if (list_len == 1) {
1454 compose = compose_forward(NULL, msginfo, FALSE, body, FALSE, FALSE);
1457 /* more messages FALL THROUGH */
1458 case COMPOSE_FORWARD_AS_ATTACH:
1459 compose = compose_forward_multiple(NULL, msginfo_list);
1461 case COMPOSE_REDIRECT:
1462 compose = compose_redirect(NULL, msginfo, FALSE);
1465 g_warning("compose_reply_mode(): invalid Compose Mode: %d", mode);
1468 if (compose == NULL) {
1469 alertpanel_error(_("Unable to reply. The original email probably doesn't exist."));
1473 compose->rmode = mode;
1474 switch (compose->rmode) {
1476 case COMPOSE_REPLY_WITH_QUOTE:
1477 case COMPOSE_REPLY_WITHOUT_QUOTE:
1478 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1479 debug_print("reply mode Normal\n");
1480 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/Normal", TRUE);
1481 compose_reply_change_mode(compose, COMPOSE_REPLY); /* force update */
1483 case COMPOSE_REPLY_TO_SENDER:
1484 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1485 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1486 debug_print("reply mode Sender\n");
1487 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/Sender", TRUE);
1489 case COMPOSE_REPLY_TO_ALL:
1490 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1491 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1492 debug_print("reply mode All\n");
1493 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/All", TRUE);
1495 case COMPOSE_REPLY_TO_LIST:
1496 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1497 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1498 debug_print("reply mode List\n");
1499 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/List", TRUE);
1501 case COMPOSE_REPLY_TO_ADDRESS:
1502 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", FALSE);
1510 static Compose *compose_reply(MsgInfo *msginfo,
1511 ComposeQuoteMode quote_mode,
1517 return compose_generic_reply(msginfo, quote_mode, to_all, to_ml,
1518 to_sender, FALSE, body);
1521 static Compose *compose_followup_and_reply_to(MsgInfo *msginfo,
1522 ComposeQuoteMode quote_mode,
1527 return compose_generic_reply(msginfo, quote_mode, to_all, FALSE,
1528 to_sender, TRUE, body);
1531 static void compose_extract_original_charset(Compose *compose)
1533 MsgInfo *info = NULL;
1534 if (compose->replyinfo) {
1535 info = compose->replyinfo;
1536 } else if (compose->fwdinfo) {
1537 info = compose->fwdinfo;
1538 } else if (compose->targetinfo) {
1539 info = compose->targetinfo;
1542 MimeInfo *mimeinfo = procmime_scan_message_short(info);
1543 MimeInfo *partinfo = mimeinfo;
1544 while (partinfo && partinfo->type != MIMETYPE_TEXT)
1545 partinfo = procmime_mimeinfo_next(partinfo);
1547 compose->orig_charset =
1548 g_strdup(procmime_mimeinfo_get_parameter(
1549 partinfo, "charset"));
1551 procmime_mimeinfo_free_all(&mimeinfo);
1555 #define SIGNAL_BLOCK(buffer) { \
1556 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1557 G_CALLBACK(compose_changed_cb), \
1559 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1560 G_CALLBACK(text_inserted), \
1564 #define SIGNAL_UNBLOCK(buffer) { \
1565 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1566 G_CALLBACK(compose_changed_cb), \
1568 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1569 G_CALLBACK(text_inserted), \
1573 static Compose *compose_generic_reply(MsgInfo *msginfo,
1574 ComposeQuoteMode quote_mode,
1575 gboolean to_all, gboolean to_ml,
1577 gboolean followup_and_reply_to,
1581 PrefsAccount *account = NULL;
1582 GtkTextView *textview;
1583 GtkTextBuffer *textbuf;
1584 gboolean quote = FALSE;
1585 const gchar *qmark = NULL;
1586 const gchar *body_fmt = NULL;
1587 gchar *s_system = NULL;
1589 cm_return_val_if_fail(msginfo != NULL, NULL);
1590 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
1592 account = account_get_reply_account(msginfo, prefs_common.reply_account_autosel);
1594 cm_return_val_if_fail(account != NULL, NULL);
1596 compose = compose_create(account, msginfo->folder, COMPOSE_REPLY, FALSE);
1598 compose->updating = TRUE;
1600 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
1601 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", TRUE);
1603 compose->replyinfo = procmsg_msginfo_get_full_info(msginfo);
1604 if (!compose->replyinfo)
1605 compose->replyinfo = procmsg_msginfo_copy(msginfo);
1607 compose_extract_original_charset(compose);
1609 if (msginfo->folder && msginfo->folder->ret_rcpt)
1610 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
1612 /* Set save folder */
1613 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1614 gchar *folderidentifier;
1616 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1617 folderidentifier = folder_item_get_identifier(msginfo->folder);
1618 compose_set_save_to(compose, folderidentifier);
1619 g_free(folderidentifier);
1622 if (compose_parse_header(compose, msginfo) < 0) {
1623 compose->updating = FALSE;
1624 compose_destroy(compose);
1628 /* override from name according to folder properties */
1629 if (msginfo->folder && msginfo->folder->prefs &&
1630 msginfo->folder->prefs->reply_with_format &&
1631 msginfo->folder->prefs->reply_override_from_format &&
1632 *msginfo->folder->prefs->reply_override_from_format != '\0') {
1637 /* decode \-escape sequences in the internal representation of the quote format */
1638 tmp = g_malloc(strlen(msginfo->folder->prefs->reply_override_from_format)+1);
1639 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->reply_override_from_format);
1642 quote_fmt_init(compose->replyinfo, NULL, NULL, FALSE, compose->account, FALSE,
1643 compose->gtkaspell);
1645 quote_fmt_init(compose->replyinfo, NULL, NULL, FALSE, compose->account, FALSE);
1647 quote_fmt_scan_string(tmp);
1650 buf = quote_fmt_get_buffer();
1652 alertpanel_error(_("The \"From\" field of the \"Reply\" template contains an invalid email address."));
1654 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1655 quote_fmt_reset_vartable();
1660 textview = (GTK_TEXT_VIEW(compose->text));
1661 textbuf = gtk_text_view_get_buffer(textview);
1662 compose_create_tags(textview, compose);
1664 undo_block(compose->undostruct);
1666 compose_set_dictionaries_from_folder_prefs(compose, msginfo->folder);
1667 gtkaspell_block_check(compose->gtkaspell);
1670 if (quote_mode == COMPOSE_QUOTE_FORCED ||
1671 (quote_mode == COMPOSE_QUOTE_CHECK && prefs_common.reply_with_quote)) {
1672 /* use the reply format of folder (if enabled), or the account's one
1673 (if enabled) or fallback to the global reply format, which is always
1674 enabled (even if empty), and use the relevant quotemark */
1676 if (msginfo->folder && msginfo->folder->prefs &&
1677 msginfo->folder->prefs->reply_with_format) {
1678 qmark = msginfo->folder->prefs->reply_quotemark;
1679 body_fmt = msginfo->folder->prefs->reply_body_format;
1681 } else if (account->reply_with_format) {
1682 qmark = account->reply_quotemark;
1683 body_fmt = account->reply_body_format;
1686 qmark = prefs_common.quotemark;
1687 if (prefs_common.quotefmt && *prefs_common.quotefmt)
1688 body_fmt = gettext(prefs_common.quotefmt);
1695 /* empty quotemark is not allowed */
1696 if (qmark == NULL || *qmark == '\0')
1698 compose_quote_fmt(compose, compose->replyinfo,
1699 body_fmt, qmark, body, FALSE, TRUE,
1700 _("The body of the \"Reply\" template has an error at line %d."));
1701 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1702 quote_fmt_reset_vartable();
1705 if (MSG_IS_ENCRYPTED(compose->replyinfo->flags)) {
1706 compose_force_encryption(compose, account, FALSE, s_system);
1709 privacy_msginfo_get_signed_state(compose->replyinfo, &s_system);
1710 if (MSG_IS_SIGNED(compose->replyinfo->flags) && account->default_sign_reply) {
1711 compose_force_signing(compose, account, s_system);
1715 SIGNAL_BLOCK(textbuf);
1717 if (account->auto_sig)
1718 compose_insert_sig(compose, FALSE);
1720 compose_wrap_all(compose);
1723 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1724 gtkaspell_highlight_all(compose->gtkaspell);
1725 gtkaspell_unblock_check(compose->gtkaspell);
1727 SIGNAL_UNBLOCK(textbuf);
1729 gtk_widget_grab_focus(compose->text);
1731 undo_unblock(compose->undostruct);
1733 if (prefs_common.auto_exteditor)
1734 compose_exec_ext_editor(compose);
1736 compose->modified = FALSE;
1737 compose_set_title(compose);
1739 compose->updating = FALSE;
1740 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
1741 SCROLL_TO_CURSOR(compose);
1743 if (compose->deferred_destroy) {
1744 compose_destroy(compose);
1752 #define INSERT_FW_HEADER(var, hdr) \
1753 if (msginfo->var && *msginfo->var) { \
1754 gtk_stext_insert(text, NULL, NULL, NULL, hdr, -1); \
1755 gtk_stext_insert(text, NULL, NULL, NULL, msginfo->var, -1); \
1756 gtk_stext_insert(text, NULL, NULL, NULL, "\n", 1); \
1759 Compose *compose_forward(PrefsAccount *account, MsgInfo *msginfo,
1760 gboolean as_attach, const gchar *body,
1761 gboolean no_extedit,
1765 GtkTextView *textview;
1766 GtkTextBuffer *textbuf;
1767 gint cursor_pos = -1;
1770 cm_return_val_if_fail(msginfo != NULL, NULL);
1771 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
1773 if (!account && !(account = compose_find_account(msginfo)))
1774 account = cur_account;
1776 if (!prefs_common.forward_as_attachment)
1777 mode = COMPOSE_FORWARD_INLINE;
1779 mode = COMPOSE_FORWARD;
1780 compose = compose_create(account, msginfo->folder, mode, batch);
1782 compose->updating = TRUE;
1783 compose->fwdinfo = procmsg_msginfo_get_full_info(msginfo);
1784 if (!compose->fwdinfo)
1785 compose->fwdinfo = procmsg_msginfo_copy(msginfo);
1787 compose_extract_original_charset(compose);
1789 if (msginfo->subject && *msginfo->subject) {
1790 gchar *buf, *buf2, *p;
1792 buf = p = g_strdup(msginfo->subject);
1793 p += subject_get_prefix_length(p);
1794 memmove(buf, p, strlen(p) + 1);
1796 buf2 = g_strdup_printf("Fw: %s", buf);
1797 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
1803 /* override from name according to folder properties */
1804 if (msginfo->folder && msginfo->folder->prefs &&
1805 msginfo->folder->prefs->forward_with_format &&
1806 msginfo->folder->prefs->forward_override_from_format &&
1807 *msginfo->folder->prefs->forward_override_from_format != '\0') {
1811 MsgInfo *full_msginfo = NULL;
1814 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1816 full_msginfo = procmsg_msginfo_copy(msginfo);
1818 /* decode \-escape sequences in the internal representation of the quote format */
1819 tmp = g_malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
1820 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
1823 gtkaspell_block_check(compose->gtkaspell);
1824 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE,
1825 compose->gtkaspell);
1827 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE);
1829 quote_fmt_scan_string(tmp);
1832 buf = quote_fmt_get_buffer();
1834 alertpanel_error(_("The \"From\" field of the \"Forward\" template contains an invalid email address."));
1836 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1837 quote_fmt_reset_vartable();
1840 procmsg_msginfo_free(&full_msginfo);
1843 textview = GTK_TEXT_VIEW(compose->text);
1844 textbuf = gtk_text_view_get_buffer(textview);
1845 compose_create_tags(textview, compose);
1847 undo_block(compose->undostruct);
1851 msgfile = procmsg_get_message_file(msginfo);
1852 if (!is_file_exist(msgfile))
1853 g_warning("%s: file does not exist", msgfile);
1855 compose_attach_append(compose, msgfile, msgfile,
1856 "message/rfc822", NULL);
1860 const gchar *qmark = NULL;
1861 const gchar *body_fmt = NULL;
1862 MsgInfo *full_msginfo;
1864 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1866 full_msginfo = procmsg_msginfo_copy(msginfo);
1868 /* use the forward format of folder (if enabled), or the account's one
1869 (if enabled) or fallback to the global forward format, which is always
1870 enabled (even if empty), and use the relevant quotemark */
1871 if (msginfo->folder && msginfo->folder->prefs &&
1872 msginfo->folder->prefs->forward_with_format) {
1873 qmark = msginfo->folder->prefs->forward_quotemark;
1874 body_fmt = msginfo->folder->prefs->forward_body_format;
1876 } else if (account->forward_with_format) {
1877 qmark = account->forward_quotemark;
1878 body_fmt = account->forward_body_format;
1881 qmark = prefs_common.fw_quotemark;
1882 if (prefs_common.fw_quotefmt && *prefs_common.fw_quotefmt)
1883 body_fmt = gettext(prefs_common.fw_quotefmt);
1888 /* empty quotemark is not allowed */
1889 if (qmark == NULL || *qmark == '\0')
1892 compose_quote_fmt(compose, full_msginfo,
1893 body_fmt, qmark, body, FALSE, TRUE,
1894 _("The body of the \"Forward\" template has an error at line %d."));
1895 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1896 quote_fmt_reset_vartable();
1897 compose_attach_parts(compose, msginfo);
1899 procmsg_msginfo_free(&full_msginfo);
1902 SIGNAL_BLOCK(textbuf);
1904 if (account->auto_sig)
1905 compose_insert_sig(compose, FALSE);
1907 compose_wrap_all(compose);
1910 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1911 gtkaspell_highlight_all(compose->gtkaspell);
1912 gtkaspell_unblock_check(compose->gtkaspell);
1914 SIGNAL_UNBLOCK(textbuf);
1916 cursor_pos = quote_fmt_get_cursor_pos();
1917 if (cursor_pos == -1)
1918 gtk_widget_grab_focus(compose->header_last->entry);
1920 gtk_widget_grab_focus(compose->text);
1922 if (!no_extedit && prefs_common.auto_exteditor)
1923 compose_exec_ext_editor(compose);
1926 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1927 gchar *folderidentifier;
1929 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1930 folderidentifier = folder_item_get_identifier(msginfo->folder);
1931 compose_set_save_to(compose, folderidentifier);
1932 g_free(folderidentifier);
1935 undo_unblock(compose->undostruct);
1937 compose->modified = FALSE;
1938 compose_set_title(compose);
1940 compose->updating = FALSE;
1941 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
1942 SCROLL_TO_CURSOR(compose);
1944 if (compose->deferred_destroy) {
1945 compose_destroy(compose);
1949 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
1954 #undef INSERT_FW_HEADER
1956 static Compose *compose_forward_multiple(PrefsAccount *account, GSList *msginfo_list)
1959 GtkTextView *textview;
1960 GtkTextBuffer *textbuf;
1964 gboolean single_mail = TRUE;
1966 cm_return_val_if_fail(msginfo_list != NULL, NULL);
1968 if (g_slist_length(msginfo_list) > 1)
1969 single_mail = FALSE;
1971 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next)
1972 if (((MsgInfo *)msginfo->data)->folder == NULL)
1975 /* guess account from first selected message */
1977 !(account = compose_find_account(msginfo_list->data)))
1978 account = cur_account;
1980 cm_return_val_if_fail(account != NULL, NULL);
1982 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
1983 if (msginfo->data) {
1984 MSG_UNSET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_REPLIED);
1985 MSG_SET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_FORWARDED);
1989 if (msginfo_list == NULL || msginfo_list->data == NULL) {
1990 g_warning("no msginfo_list");
1994 compose = compose_create(account, ((MsgInfo *)msginfo_list->data)->folder, COMPOSE_FORWARD, FALSE);
1996 compose->updating = TRUE;
1998 /* override from name according to folder properties */
1999 if (msginfo_list->data) {
2000 MsgInfo *msginfo = msginfo_list->data;
2002 if (msginfo->folder && msginfo->folder->prefs &&
2003 msginfo->folder->prefs->forward_with_format &&
2004 msginfo->folder->prefs->forward_override_from_format &&
2005 *msginfo->folder->prefs->forward_override_from_format != '\0') {
2010 /* decode \-escape sequences in the internal representation of the quote format */
2011 tmp = g_malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
2012 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
2015 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
2016 compose->gtkaspell);
2018 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
2020 quote_fmt_scan_string(tmp);
2023 buf = quote_fmt_get_buffer();
2025 alertpanel_error(_("The \"From\" field of the \"Forward\" template contains an invalid email address."));
2027 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
2028 quote_fmt_reset_vartable();
2034 textview = GTK_TEXT_VIEW(compose->text);
2035 textbuf = gtk_text_view_get_buffer(textview);
2036 compose_create_tags(textview, compose);
2038 undo_block(compose->undostruct);
2039 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
2040 msgfile = procmsg_get_message_file((MsgInfo *)msginfo->data);
2042 if (!is_file_exist(msgfile))
2043 g_warning("%s: file does not exist", msgfile);
2045 compose_attach_append(compose, msgfile, msgfile,
2046 "message/rfc822", NULL);
2051 MsgInfo *info = (MsgInfo *)msginfo_list->data;
2052 if (info->subject && *info->subject) {
2053 gchar *buf, *buf2, *p;
2055 buf = p = g_strdup(info->subject);
2056 p += subject_get_prefix_length(p);
2057 memmove(buf, p, strlen(p) + 1);
2059 buf2 = g_strdup_printf("Fw: %s", buf);
2060 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
2066 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2067 _("Fw: multiple emails"));
2070 SIGNAL_BLOCK(textbuf);
2072 if (account->auto_sig)
2073 compose_insert_sig(compose, FALSE);
2075 compose_wrap_all(compose);
2077 SIGNAL_UNBLOCK(textbuf);
2079 gtk_text_buffer_get_start_iter(textbuf, &iter);
2080 gtk_text_buffer_place_cursor(textbuf, &iter);
2082 if (prefs_common.auto_exteditor)
2083 compose_exec_ext_editor(compose);
2085 gtk_widget_grab_focus(compose->header_last->entry);
2086 undo_unblock(compose->undostruct);
2087 compose->modified = FALSE;
2088 compose_set_title(compose);
2090 compose->updating = FALSE;
2091 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2092 SCROLL_TO_CURSOR(compose);
2094 if (compose->deferred_destroy) {
2095 compose_destroy(compose);
2099 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2104 static gboolean compose_is_sig_separator(Compose *compose, GtkTextBuffer *textbuf, GtkTextIter *iter)
2106 GtkTextIter start = *iter;
2107 GtkTextIter end_iter;
2108 int start_pos = gtk_text_iter_get_offset(&start);
2110 if (!compose->account->sig_sep)
2113 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
2114 start_pos+strlen(compose->account->sig_sep));
2116 /* check sig separator */
2117 str = gtk_text_iter_get_text(&start, &end_iter);
2118 if (!strcmp(str, compose->account->sig_sep)) {
2120 /* check end of line (\n) */
2121 gtk_text_buffer_get_iter_at_offset(textbuf, &start,
2122 start_pos+strlen(compose->account->sig_sep));
2123 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
2124 start_pos+strlen(compose->account->sig_sep)+1);
2125 tmp = gtk_text_iter_get_text(&start, &end_iter);
2126 if (!strcmp(tmp,"\n")) {
2138 static gboolean compose_update_folder_hook(gpointer source, gpointer data)
2140 FolderUpdateData *hookdata = (FolderUpdateData *)source;
2141 Compose *compose = (Compose *)data;
2142 FolderItem *old_item = NULL;
2143 FolderItem *new_item = NULL;
2144 gchar *old_id, *new_id;
2146 if (!(hookdata->update_flags & FOLDER_REMOVE_FOLDERITEM)
2147 && !(hookdata->update_flags & FOLDER_MOVE_FOLDERITEM))
2150 old_item = hookdata->item;
2151 new_item = hookdata->item2;
2153 old_id = folder_item_get_identifier(old_item);
2154 new_id = new_item ? folder_item_get_identifier(new_item) : g_strdup("NULL");
2156 if (compose->targetinfo && compose->targetinfo->folder == old_item) {
2157 debug_print("updating targetinfo folder: %s -> %s\n", old_id, new_id);
2158 compose->targetinfo->folder = new_item;
2161 if (compose->replyinfo && compose->replyinfo->folder == old_item) {
2162 debug_print("updating replyinfo folder: %s -> %s\n", old_id, new_id);
2163 compose->replyinfo->folder = new_item;
2166 if (compose->fwdinfo && compose->fwdinfo->folder == old_item) {
2167 debug_print("updating fwdinfo folder: %s -> %s\n", old_id, new_id);
2168 compose->fwdinfo->folder = new_item;
2176 static void compose_colorize_signature(Compose *compose)
2178 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
2180 GtkTextIter end_iter;
2181 gtk_text_buffer_get_start_iter(buffer, &iter);
2182 while (gtk_text_iter_forward_line(&iter))
2183 if (compose_is_sig_separator(compose, buffer, &iter)) {
2184 gtk_text_buffer_get_end_iter(buffer, &end_iter);
2185 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &end_iter);
2189 #define BLOCK_WRAP() { \
2190 prev_autowrap = compose->autowrap; \
2191 buffer = gtk_text_view_get_buffer( \
2192 GTK_TEXT_VIEW(compose->text)); \
2193 compose->autowrap = FALSE; \
2195 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2196 G_CALLBACK(compose_changed_cb), \
2198 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2199 G_CALLBACK(text_inserted), \
2202 #define UNBLOCK_WRAP() { \
2203 compose->autowrap = prev_autowrap; \
2204 if (compose->autowrap) { \
2205 gint old = compose->draft_timeout_tag; \
2206 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN; \
2207 compose_wrap_all(compose); \
2208 compose->draft_timeout_tag = old; \
2211 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2212 G_CALLBACK(compose_changed_cb), \
2214 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2215 G_CALLBACK(text_inserted), \
2219 Compose *compose_reedit(MsgInfo *msginfo, gboolean batch)
2221 Compose *compose = NULL;
2222 PrefsAccount *account = NULL;
2223 GtkTextView *textview;
2224 GtkTextBuffer *textbuf;
2228 gboolean use_signing = FALSE;
2229 gboolean use_encryption = FALSE;
2230 gchar *privacy_system = NULL;
2231 int priority = PRIORITY_NORMAL;
2232 MsgInfo *replyinfo = NULL, *fwdinfo = NULL;
2233 gboolean autowrap = prefs_common.autowrap;
2234 gboolean autoindent = prefs_common.auto_indent;
2235 HeaderEntry *manual_headers = NULL;
2237 cm_return_val_if_fail(msginfo != NULL, NULL);
2238 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
2240 if (compose_put_existing_to_front(msginfo)) {
2244 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2245 folder_has_parent_of_type(msginfo->folder, F_DRAFT) ||
2246 folder_has_parent_of_type(msginfo->folder, F_OUTBOX)) {
2247 gchar *queueheader_buf = NULL;
2250 /* Select Account from queue headers */
2251 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2252 "X-Claws-Account-Id:")) {
2253 id = atoi(&queueheader_buf[strlen("X-Claws-Account-Id:")]);
2254 account = account_find_from_id(id);
2255 g_free(queueheader_buf);
2257 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2258 "X-Sylpheed-Account-Id:")) {
2259 id = atoi(&queueheader_buf[strlen("X-Sylpheed-Account-Id:")]);
2260 account = account_find_from_id(id);
2261 g_free(queueheader_buf);
2263 if (!account && !procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2265 id = atoi(&queueheader_buf[strlen("NAID:")]);
2266 account = account_find_from_id(id);
2267 g_free(queueheader_buf);
2269 if (!account && !procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2271 id = atoi(&queueheader_buf[strlen("MAID:")]);
2272 account = account_find_from_id(id);
2273 g_free(queueheader_buf);
2275 if (!account && !procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2277 account = account_find_from_address(queueheader_buf, FALSE);
2278 g_free(queueheader_buf);
2280 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2282 param = atoi(&queueheader_buf[strlen("X-Claws-Sign:")]);
2283 use_signing = param;
2284 g_free(queueheader_buf);
2286 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2287 "X-Sylpheed-Sign:")) {
2288 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Sign:")]);
2289 use_signing = param;
2290 g_free(queueheader_buf);
2292 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2293 "X-Claws-Encrypt:")) {
2294 param = atoi(&queueheader_buf[strlen("X-Claws-Encrypt:")]);
2295 use_encryption = param;
2296 g_free(queueheader_buf);
2298 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2299 "X-Sylpheed-Encrypt:")) {
2300 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Encrypt:")]);
2301 use_encryption = param;
2302 g_free(queueheader_buf);
2304 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2305 "X-Claws-Auto-Wrapping:")) {
2306 param = atoi(&queueheader_buf[strlen("X-Claws-Auto-Wrapping:")]);
2308 g_free(queueheader_buf);
2310 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2311 "X-Claws-Auto-Indent:")) {
2312 param = atoi(&queueheader_buf[strlen("X-Claws-Auto-Indent:")]);
2314 g_free(queueheader_buf);
2316 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2317 "X-Claws-Privacy-System:")) {
2318 privacy_system = g_strdup(&queueheader_buf[strlen("X-Claws-Privacy-System:")]);
2319 g_free(queueheader_buf);
2321 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2322 "X-Sylpheed-Privacy-System:")) {
2323 privacy_system = g_strdup(&queueheader_buf[strlen("X-Sylpheed-Privacy-System:")]);
2324 g_free(queueheader_buf);
2326 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2328 param = atoi(&queueheader_buf[strlen("X-Priority: ")]); /* mind the space */
2330 g_free(queueheader_buf);
2332 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2334 gchar **tokens = g_strsplit(&queueheader_buf[strlen("RMID:")], "\t", 0);
2335 if (tokens && tokens[0] && tokens[1] && tokens[2]) {
2336 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2337 if (orig_item != NULL) {
2338 replyinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2342 g_free(queueheader_buf);
2344 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2346 gchar **tokens = g_strsplit(&queueheader_buf[strlen("FMID:")], "\t", 0);
2347 if (tokens && tokens[0] && tokens[1] && tokens[2]) {
2348 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2349 if (orig_item != NULL) {
2350 fwdinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2354 g_free(queueheader_buf);
2356 /* Get manual headers */
2357 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2358 "X-Claws-Manual-Headers:")) {
2359 gchar *listmh = g_strdup(&queueheader_buf[strlen("X-Claws-Manual-Headers:")]);
2360 if (listmh && *listmh != '\0') {
2361 debug_print("Got manual headers: %s\n", listmh);
2362 manual_headers = procheader_entries_from_str(listmh);
2365 g_free(queueheader_buf);
2368 account = msginfo->folder->folder->account;
2371 if (!account && prefs_common.reedit_account_autosel) {
2373 if (!procheader_get_header_from_msginfo(msginfo, &from, "FROM:")) {
2374 extract_address(from);
2375 account = account_find_from_address(from, FALSE);
2380 account = cur_account;
2382 cm_return_val_if_fail(account != NULL, NULL);
2384 compose = compose_create(account, msginfo->folder, COMPOSE_REEDIT, batch);
2386 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", autowrap);
2387 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", autoindent);
2388 compose->autowrap = autowrap;
2389 compose->replyinfo = replyinfo;
2390 compose->fwdinfo = fwdinfo;
2392 compose->updating = TRUE;
2393 compose->priority = priority;
2395 if (privacy_system != NULL) {
2396 compose->privacy_system = privacy_system;
2397 compose_use_signing(compose, use_signing);
2398 compose_use_encryption(compose, use_encryption);
2399 compose_update_privacy_system_menu_item(compose, FALSE);
2401 activate_privacy_system(compose, account, FALSE);
2404 compose->targetinfo = procmsg_msginfo_copy(msginfo);
2406 compose_extract_original_charset(compose);
2408 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2409 folder_has_parent_of_type(msginfo->folder, F_DRAFT) ||
2410 folder_has_parent_of_type(msginfo->folder, F_OUTBOX)) {
2411 gchar *queueheader_buf = NULL;
2413 /* Set message save folder */
2414 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf, "SCF:")) {
2415 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
2416 compose_set_save_to(compose, &queueheader_buf[4]);
2417 g_free(queueheader_buf);
2419 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf, "RRCPT:")) {
2420 gint active = atoi(&queueheader_buf[strlen("RRCPT:")]);
2422 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
2424 g_free(queueheader_buf);
2428 if (compose_parse_header(compose, msginfo) < 0) {
2429 compose->updating = FALSE;
2430 compose_destroy(compose);
2433 compose_reedit_set_entry(compose, msginfo);
2435 textview = GTK_TEXT_VIEW(compose->text);
2436 textbuf = gtk_text_view_get_buffer(textview);
2437 compose_create_tags(textview, compose);
2439 mark = gtk_text_buffer_get_insert(textbuf);
2440 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
2442 g_signal_handlers_block_by_func(G_OBJECT(textbuf),
2443 G_CALLBACK(compose_changed_cb),
2446 if (MSG_IS_ENCRYPTED(msginfo->flags)) {
2447 fp = procmime_get_first_encrypted_text_content(msginfo);
2449 compose_force_encryption(compose, account, TRUE, NULL);
2452 fp = procmime_get_first_text_content(msginfo);
2455 g_warning("Can't get text part");
2459 gchar buf[BUFFSIZE];
2460 gboolean prev_autowrap;
2461 GtkTextBuffer *buffer;
2463 while (fgets(buf, sizeof(buf), fp) != NULL) {
2465 gtk_text_buffer_insert(textbuf, &iter, buf, -1);
2471 compose_attach_parts(compose, msginfo);
2473 compose_colorize_signature(compose);
2475 g_signal_handlers_unblock_by_func(G_OBJECT(textbuf),
2476 G_CALLBACK(compose_changed_cb),
2479 if (manual_headers != NULL) {
2480 if (compose_parse_manual_headers(compose, msginfo, manual_headers) < 0) {
2481 procheader_entries_free(manual_headers);
2482 compose->updating = FALSE;
2483 compose_destroy(compose);
2486 procheader_entries_free(manual_headers);
2489 gtk_widget_grab_focus(compose->text);
2491 if (prefs_common.auto_exteditor) {
2492 compose_exec_ext_editor(compose);
2494 compose->modified = FALSE;
2495 compose_set_title(compose);
2497 compose->updating = FALSE;
2498 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2499 SCROLL_TO_CURSOR(compose);
2501 if (compose->deferred_destroy) {
2502 compose_destroy(compose);
2506 compose->sig_str = account_get_signature_str(compose->account);
2508 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2513 Compose *compose_redirect(PrefsAccount *account, MsgInfo *msginfo,
2520 cm_return_val_if_fail(msginfo != NULL, NULL);
2523 account = account_get_reply_account(msginfo,
2524 prefs_common.reply_account_autosel);
2525 cm_return_val_if_fail(account != NULL, NULL);
2527 compose = compose_create(account, msginfo->folder, COMPOSE_REDIRECT, batch);
2529 compose->updating = TRUE;
2531 compose_create_tags(GTK_TEXT_VIEW(compose->text), compose);
2532 compose->replyinfo = NULL;
2533 compose->fwdinfo = NULL;
2535 compose_show_first_last_header(compose, TRUE);
2537 gtk_widget_grab_focus(compose->header_last->entry);
2539 filename = procmsg_get_message_file(msginfo);
2541 if (filename == NULL) {
2542 compose->updating = FALSE;
2543 compose_destroy(compose);
2548 compose->redirect_filename = filename;
2550 /* Set save folder */
2551 item = msginfo->folder;
2552 if (item && item->prefs && item->prefs->save_copy_to_folder) {
2553 gchar *folderidentifier;
2555 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
2556 folderidentifier = folder_item_get_identifier(item);
2557 compose_set_save_to(compose, folderidentifier);
2558 g_free(folderidentifier);
2561 compose_attach_parts(compose, msginfo);
2563 if (msginfo->subject)
2564 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2566 gtk_editable_set_editable(GTK_EDITABLE(compose->subject_entry), FALSE);
2568 compose_quote_fmt(compose, msginfo, "%M", NULL, NULL, FALSE, FALSE,
2569 _("The body of the \"Redirect\" template has an error at line %d."));
2570 quote_fmt_reset_vartable();
2571 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), FALSE);
2573 compose_colorize_signature(compose);
2576 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Add", FALSE);
2577 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", FALSE);
2578 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", FALSE);
2580 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/Save", FALSE);
2581 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertFile", FALSE);
2582 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/AttachFile", FALSE);
2583 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertSig", FALSE);
2584 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/ReplaceSig", FALSE);
2585 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", FALSE);
2586 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", FALSE);
2587 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/ShowRuler", FALSE);
2588 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Actions", FALSE);
2590 if (compose->toolbar->draft_btn)
2591 gtk_widget_set_sensitive(compose->toolbar->draft_btn, FALSE);
2592 if (compose->toolbar->insert_btn)
2593 gtk_widget_set_sensitive(compose->toolbar->insert_btn, FALSE);
2594 if (compose->toolbar->attach_btn)
2595 gtk_widget_set_sensitive(compose->toolbar->attach_btn, FALSE);
2596 if (compose->toolbar->sig_btn)
2597 gtk_widget_set_sensitive(compose->toolbar->sig_btn, FALSE);
2598 if (compose->toolbar->exteditor_btn)
2599 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, FALSE);
2600 if (compose->toolbar->linewrap_current_btn)
2601 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, FALSE);
2602 if (compose->toolbar->linewrap_all_btn)
2603 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, FALSE);
2605 compose->modified = FALSE;
2606 compose_set_title(compose);
2607 compose->updating = FALSE;
2608 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2609 SCROLL_TO_CURSOR(compose);
2611 if (compose->deferred_destroy) {
2612 compose_destroy(compose);
2616 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2621 const GList *compose_get_compose_list(void)
2623 return compose_list;
2626 void compose_entry_append(Compose *compose, const gchar *address,
2627 ComposeEntryType type, ComposePrefType pref_type)
2629 const gchar *header;
2631 gboolean in_quote = FALSE;
2632 if (!address || *address == '\0') return;
2639 header = N_("Bcc:");
2641 case COMPOSE_REPLYTO:
2642 header = N_("Reply-To:");
2644 case COMPOSE_NEWSGROUPS:
2645 header = N_("Newsgroups:");
2647 case COMPOSE_FOLLOWUPTO:
2648 header = N_( "Followup-To:");
2650 case COMPOSE_INREPLYTO:
2651 header = N_( "In-Reply-To:");
2658 header = prefs_common_translated_header_name(header);
2660 cur = begin = (gchar *)address;
2662 /* we separate the line by commas, but not if we're inside a quoted
2664 while (*cur != '\0') {
2666 in_quote = !in_quote;
2667 if (*cur == ',' && !in_quote) {
2668 gchar *tmp = g_strdup(begin);
2670 tmp[cur-begin]='\0';
2673 while (*tmp == ' ' || *tmp == '\t')
2675 compose_add_header_entry(compose, header, tmp, pref_type);
2676 compose_entry_indicate(compose, tmp);
2683 gchar *tmp = g_strdup(begin);
2685 tmp[cur-begin]='\0';
2686 while (*tmp == ' ' || *tmp == '\t')
2688 compose_add_header_entry(compose, header, tmp, pref_type);
2689 compose_entry_indicate(compose, tmp);
2694 static void compose_entry_indicate(Compose *compose, const gchar *mailto)
2699 for (h_list = compose->header_list; h_list != NULL; h_list = h_list->next) {
2700 entry = GTK_ENTRY(((ComposeHeaderEntry *)h_list->data)->entry);
2701 if (gtk_entry_get_text(entry) &&
2702 !g_utf8_collate(gtk_entry_get_text(entry), mailto)) {
2703 gtk_widget_modify_base(
2704 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2705 GTK_STATE_NORMAL, &default_header_bgcolor);
2706 gtk_widget_modify_text(
2707 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2708 GTK_STATE_NORMAL, &default_header_color);
2713 void compose_toolbar_cb(gint action, gpointer data)
2715 ToolbarItem *toolbar_item = (ToolbarItem*)data;
2716 Compose *compose = (Compose*)toolbar_item->parent;
2718 cm_return_if_fail(compose != NULL);
2722 compose_send_cb(NULL, compose);
2725 compose_send_later_cb(NULL, compose);
2728 compose_draft(compose, COMPOSE_QUIT_EDITING);
2731 compose_insert_file_cb(NULL, compose);
2734 compose_attach_cb(NULL, compose);
2737 compose_insert_sig(compose, FALSE);
2740 compose_insert_sig(compose, TRUE);
2743 compose_ext_editor_cb(NULL, compose);
2745 case A_LINEWRAP_CURRENT:
2746 compose_beautify_paragraph(compose, NULL, TRUE);
2748 case A_LINEWRAP_ALL:
2749 compose_wrap_all_full(compose, TRUE);
2752 compose_address_cb(NULL, compose);
2755 case A_CHECK_SPELLING:
2756 compose_check_all(NULL, compose);
2764 static MailField compose_entries_set(Compose *compose, const gchar *mailto, ComposeEntryType to_type)
2769 gchar *subject = NULL;
2773 gchar **attach = NULL;
2774 gchar *inreplyto = NULL;
2775 MailField mfield = NO_FIELD_PRESENT;
2777 /* get mailto parts but skip from */
2778 scan_mailto_url(mailto, NULL, &to, &cc, &bcc, &subject, &body, &attach, &inreplyto);
2781 compose_entry_append(compose, to, to_type, PREF_MAILTO);
2782 mfield = TO_FIELD_PRESENT;
2785 compose_entry_append(compose, cc, COMPOSE_CC, PREF_MAILTO);
2787 compose_entry_append(compose, bcc, COMPOSE_BCC, PREF_MAILTO);
2789 if (!g_utf8_validate (subject, -1, NULL)) {
2790 temp = g_locale_to_utf8 (subject, -1, NULL, &len, NULL);
2791 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), temp);
2794 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), subject);
2796 mfield = SUBJECT_FIELD_PRESENT;
2799 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2800 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2803 gboolean prev_autowrap = compose->autowrap;
2805 compose->autowrap = FALSE;
2807 mark = gtk_text_buffer_get_insert(buffer);
2808 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2810 if (!g_utf8_validate (body, -1, NULL)) {
2811 temp = g_locale_to_utf8 (body, -1, NULL, &len, NULL);
2812 gtk_text_buffer_insert(buffer, &iter, temp, -1);
2815 gtk_text_buffer_insert(buffer, &iter, body, -1);
2817 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
2819 compose->autowrap = prev_autowrap;
2820 if (compose->autowrap)
2821 compose_wrap_all(compose);
2822 mfield = BODY_FIELD_PRESENT;
2826 gint i = 0, att = 0;
2827 gchar *warn_files = NULL;
2828 while (attach[i] != NULL) {
2829 gchar *utf8_filename = conv_filename_to_utf8(attach[i]);
2830 if (utf8_filename) {
2831 if (compose_attach_append(compose, attach[i], utf8_filename, NULL, NULL)) {
2832 gchar *tmp = g_strdup_printf("%s%s\n",
2833 warn_files?warn_files:"",
2839 g_free(utf8_filename);
2841 alertpanel_error(_("Couldn't attach a file (charset conversion failed)."));
2846 alertpanel_notice(ngettext(
2847 "The following file has been attached: \n%s",
2848 "The following files have been attached: \n%s", att), warn_files);
2853 compose_entry_append(compose, inreplyto, COMPOSE_INREPLYTO, PREF_MAILTO);
2866 static gint compose_parse_header(Compose *compose, MsgInfo *msginfo)
2868 static HeaderEntry hentry[] = {
2869 {"Reply-To:", NULL, TRUE },
2870 {"Cc:", NULL, TRUE },
2871 {"References:", NULL, FALSE },
2872 {"Bcc:", NULL, TRUE },
2873 {"Newsgroups:", NULL, TRUE },
2874 {"Followup-To:", NULL, TRUE },
2875 {"List-Post:", NULL, FALSE },
2876 {"X-Priority:", NULL, FALSE },
2877 {NULL, NULL, FALSE }
2894 cm_return_val_if_fail(msginfo != NULL, -1);
2896 if ((fp = procmsg_open_message(msginfo)) == NULL) return -1;
2897 procheader_get_header_fields(fp, hentry);
2900 if (hentry[H_REPLY_TO].body != NULL) {
2901 if (hentry[H_REPLY_TO].body[0] != '\0') {
2903 conv_unmime_header(hentry[H_REPLY_TO].body,
2906 g_free(hentry[H_REPLY_TO].body);
2907 hentry[H_REPLY_TO].body = NULL;
2909 if (hentry[H_CC].body != NULL) {
2910 compose->cc = conv_unmime_header(hentry[H_CC].body, NULL, TRUE);
2911 g_free(hentry[H_CC].body);
2912 hentry[H_CC].body = NULL;
2914 if (hentry[H_REFERENCES].body != NULL) {
2915 if (compose->mode == COMPOSE_REEDIT)
2916 compose->references = hentry[H_REFERENCES].body;
2918 compose->references = compose_parse_references
2919 (hentry[H_REFERENCES].body, msginfo->msgid);
2920 g_free(hentry[H_REFERENCES].body);
2922 hentry[H_REFERENCES].body = NULL;
2924 if (hentry[H_BCC].body != NULL) {
2925 if (compose->mode == COMPOSE_REEDIT)
2927 conv_unmime_header(hentry[H_BCC].body, NULL, TRUE);
2928 g_free(hentry[H_BCC].body);
2929 hentry[H_BCC].body = NULL;
2931 if (hentry[H_NEWSGROUPS].body != NULL) {
2932 compose->newsgroups = hentry[H_NEWSGROUPS].body;
2933 hentry[H_NEWSGROUPS].body = NULL;
2935 if (hentry[H_FOLLOWUP_TO].body != NULL) {
2936 if (hentry[H_FOLLOWUP_TO].body[0] != '\0') {
2937 compose->followup_to =
2938 conv_unmime_header(hentry[H_FOLLOWUP_TO].body,
2941 g_free(hentry[H_FOLLOWUP_TO].body);
2942 hentry[H_FOLLOWUP_TO].body = NULL;
2944 if (hentry[H_LIST_POST].body != NULL) {
2945 gchar *to = NULL, *start = NULL;
2947 extract_address(hentry[H_LIST_POST].body);
2948 if (hentry[H_LIST_POST].body[0] != '\0') {
2949 start = strstr(hentry[H_LIST_POST].body, "mailto:");
2951 scan_mailto_url(start ? start : hentry[H_LIST_POST].body,
2952 NULL, &to, NULL, NULL, NULL, NULL, NULL, NULL);
2955 g_free(compose->ml_post);
2956 compose->ml_post = to;
2959 g_free(hentry[H_LIST_POST].body);
2960 hentry[H_LIST_POST].body = NULL;
2963 /* CLAWS - X-Priority */
2964 if (compose->mode == COMPOSE_REEDIT)
2965 if (hentry[H_X_PRIORITY].body != NULL) {
2968 priority = atoi(hentry[H_X_PRIORITY].body);
2969 g_free(hentry[H_X_PRIORITY].body);
2971 hentry[H_X_PRIORITY].body = NULL;
2973 if (priority < PRIORITY_HIGHEST ||
2974 priority > PRIORITY_LOWEST)
2975 priority = PRIORITY_NORMAL;
2977 compose->priority = priority;
2980 if (compose->mode == COMPOSE_REEDIT) {
2981 if (msginfo->inreplyto && *msginfo->inreplyto)
2982 compose->inreplyto = g_strdup(msginfo->inreplyto);
2984 if (msginfo->msgid && *msginfo->msgid &&
2985 compose->folder != NULL &&
2986 compose->folder->stype == F_DRAFT)
2987 compose->msgid = g_strdup(msginfo->msgid);
2989 if (msginfo->msgid && *msginfo->msgid)
2990 compose->inreplyto = g_strdup(msginfo->msgid);
2992 if (!compose->references) {
2993 if (msginfo->msgid && *msginfo->msgid) {
2994 if (msginfo->inreplyto && *msginfo->inreplyto)
2995 compose->references =
2996 g_strdup_printf("<%s>\n\t<%s>",
3000 compose->references =
3001 g_strconcat("<", msginfo->msgid, ">",
3003 } else if (msginfo->inreplyto && *msginfo->inreplyto) {
3004 compose->references =
3005 g_strconcat("<", msginfo->inreplyto, ">",
3014 static gint compose_parse_manual_headers(Compose *compose, MsgInfo *msginfo, HeaderEntry *entries)
3019 cm_return_val_if_fail(msginfo != NULL, -1);
3021 if ((fp = procmsg_open_message(msginfo)) == NULL) return -1;
3022 procheader_get_header_fields(fp, entries);
3026 while (he != NULL && he->name != NULL) {
3028 GtkListStore *model = NULL;
3030 debug_print("Adding manual header: %s with value %s\n", he->name, he->body);
3031 model = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(compose->header_last->combo)));
3032 COMBOBOX_ADD(model, he->name, COMPOSE_TO);
3033 gtk_combo_box_set_active_iter(GTK_COMBO_BOX(compose->header_last->combo), &iter);
3034 gtk_entry_set_text(GTK_ENTRY(compose->header_last->entry), he->body);
3041 static gchar *compose_parse_references(const gchar *ref, const gchar *msgid)
3043 GSList *ref_id_list, *cur;
3047 ref_id_list = references_list_append(NULL, ref);
3048 if (!ref_id_list) return NULL;
3049 if (msgid && *msgid)
3050 ref_id_list = g_slist_append(ref_id_list, g_strdup(msgid));
3055 for (cur = ref_id_list; cur != NULL; cur = cur->next)
3056 /* "<" + Message-ID + ">" + CR+LF+TAB */
3057 len += strlen((gchar *)cur->data) + 5;
3059 if (len > MAX_REFERENCES_LEN) {
3060 /* remove second message-ID */
3061 if (ref_id_list && ref_id_list->next &&
3062 ref_id_list->next->next) {
3063 g_free(ref_id_list->next->data);
3064 ref_id_list = g_slist_remove
3065 (ref_id_list, ref_id_list->next->data);
3067 slist_free_strings_full(ref_id_list);
3074 new_ref = g_string_new("");
3075 for (cur = ref_id_list; cur != NULL; cur = cur->next) {
3076 if (new_ref->len > 0)
3077 g_string_append(new_ref, "\n\t");
3078 g_string_append_printf(new_ref, "<%s>", (gchar *)cur->data);
3081 slist_free_strings_full(ref_id_list);
3083 new_ref_str = new_ref->str;
3084 g_string_free(new_ref, FALSE);
3089 static gchar *compose_quote_fmt(Compose *compose, MsgInfo *msginfo,
3090 const gchar *fmt, const gchar *qmark,
3091 const gchar *body, gboolean rewrap,
3092 gboolean need_unescape,
3093 const gchar *err_msg)
3095 MsgInfo* dummyinfo = NULL;
3096 gchar *quote_str = NULL;
3098 gboolean prev_autowrap;
3099 const gchar *trimmed_body = body;
3100 gint cursor_pos = -1;
3101 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3102 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3107 SIGNAL_BLOCK(buffer);
3110 dummyinfo = compose_msginfo_new_from_compose(compose);
3111 msginfo = dummyinfo;
3114 if (qmark != NULL) {
3116 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
3117 compose->gtkaspell);
3119 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
3121 quote_fmt_scan_string(qmark);
3124 buf = quote_fmt_get_buffer();
3126 alertpanel_error(_("The \"Quotation mark\" of the template is invalid."));
3128 Xstrdup_a(quote_str, buf, goto error)
3131 if (fmt && *fmt != '\0') {
3134 while (*trimmed_body == '\n')
3138 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE,
3139 compose->gtkaspell);
3141 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE);
3143 if (need_unescape) {
3146 /* decode \-escape sequences in the internal representation of the quote format */
3147 tmp = g_malloc(strlen(fmt)+1);
3148 pref_get_unescaped_pref(tmp, fmt);
3149 quote_fmt_scan_string(tmp);
3153 quote_fmt_scan_string(fmt);
3157 buf = quote_fmt_get_buffer();
3159 gint line = quote_fmt_get_line();
3160 alertpanel_error(err_msg, line);
3166 prev_autowrap = compose->autowrap;
3167 compose->autowrap = FALSE;
3169 mark = gtk_text_buffer_get_insert(buffer);
3170 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3171 if (g_utf8_validate(buf, -1, NULL)) {
3172 gtk_text_buffer_insert(buffer, &iter, buf, -1);
3174 gchar *tmpout = NULL;
3175 tmpout = conv_codeset_strdup
3176 (buf, conv_get_locale_charset_str_no_utf8(),
3178 if (!tmpout || !g_utf8_validate(tmpout, -1, NULL)) {
3180 tmpout = g_malloc(strlen(buf)*2+1);
3181 conv_localetodisp(tmpout, strlen(buf)*2+1, buf);
3183 gtk_text_buffer_insert(buffer, &iter, tmpout, -1);
3187 cursor_pos = quote_fmt_get_cursor_pos();
3188 if (cursor_pos == -1)
3189 cursor_pos = gtk_text_iter_get_offset(&iter);
3190 compose->set_cursor_pos = cursor_pos;
3192 gtk_text_buffer_get_start_iter(buffer, &iter);
3193 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
3194 gtk_text_buffer_place_cursor(buffer, &iter);
3196 compose->autowrap = prev_autowrap;
3197 if (compose->autowrap && rewrap)
3198 compose_wrap_all(compose);
3205 SIGNAL_UNBLOCK(buffer);
3207 procmsg_msginfo_free( &dummyinfo );
3212 /* if ml_post is of type addr@host and from is of type
3213 * addr-anything@host, return TRUE
3215 static gboolean is_subscription(const gchar *ml_post, const gchar *from)
3217 gchar *left_ml = NULL;
3218 gchar *right_ml = NULL;
3219 gchar *left_from = NULL;
3220 gchar *right_from = NULL;
3221 gboolean result = FALSE;
3223 if (!ml_post || !from)
3226 left_ml = g_strdup(ml_post);
3227 if (strstr(left_ml, "@")) {
3228 right_ml = strstr(left_ml, "@")+1;
3229 *(strstr(left_ml, "@")) = '\0';
3232 left_from = g_strdup(from);
3233 if (strstr(left_from, "@")) {
3234 right_from = strstr(left_from, "@")+1;
3235 *(strstr(left_from, "@")) = '\0';
3238 if (right_ml && right_from
3239 && !strncmp(left_from, left_ml, strlen(left_ml))
3240 && !strcmp(right_from, right_ml)) {
3249 static void compose_set_folder_prefs(Compose *compose, FolderItem *folder,
3250 gboolean respect_default_to)
3254 if (!folder || !folder->prefs)
3257 if (respect_default_to && folder->prefs->enable_default_to) {
3258 compose_entry_append(compose, folder->prefs->default_to,
3259 COMPOSE_TO, PREF_FOLDER);
3260 compose_entry_indicate(compose, folder->prefs->default_to);
3262 if (folder->prefs->enable_default_cc) {
3263 compose_entry_append(compose, folder->prefs->default_cc,
3264 COMPOSE_CC, PREF_FOLDER);
3265 compose_entry_indicate(compose, folder->prefs->default_cc);
3267 if (folder->prefs->enable_default_bcc) {
3268 compose_entry_append(compose, folder->prefs->default_bcc,
3269 COMPOSE_BCC, PREF_FOLDER);
3270 compose_entry_indicate(compose, folder->prefs->default_bcc);
3272 if (folder->prefs->enable_default_replyto) {
3273 compose_entry_append(compose, folder->prefs->default_replyto,
3274 COMPOSE_REPLYTO, PREF_FOLDER);
3275 compose_entry_indicate(compose, folder->prefs->default_replyto);
3279 static void compose_reply_set_subject(Compose *compose, MsgInfo *msginfo)
3284 if (!compose || !msginfo)
3287 if (msginfo->subject && *msginfo->subject) {
3288 buf = p = g_strdup(msginfo->subject);
3289 p += subject_get_prefix_length(p);
3290 memmove(buf, p, strlen(p) + 1);
3292 buf2 = g_strdup_printf("Re: %s", buf);
3293 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
3298 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), "Re: ");
3301 static void compose_reply_set_entry(Compose *compose, MsgInfo *msginfo,
3302 gboolean to_all, gboolean to_ml,
3304 gboolean followup_and_reply_to)
3306 GSList *cc_list = NULL;
3309 gchar *replyto = NULL;
3310 gchar *ac_email = NULL;
3312 gboolean reply_to_ml = FALSE;
3313 gboolean default_reply_to = FALSE;
3315 cm_return_if_fail(compose->account != NULL);
3316 cm_return_if_fail(msginfo != NULL);
3318 reply_to_ml = to_ml && compose->ml_post;
3320 default_reply_to = msginfo->folder &&
3321 msginfo->folder->prefs->enable_default_reply_to;
3323 if (compose->account->protocol != A_NNTP) {
3324 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
3326 if (reply_to_ml && !default_reply_to) {
3328 gboolean is_subscr = is_subscription(compose->ml_post,
3331 /* normal answer to ml post with a reply-to */
3332 compose_entry_append(compose,
3334 COMPOSE_TO, PREF_ML);
3335 if (compose->replyto)
3336 compose_entry_append(compose,
3338 COMPOSE_CC, PREF_ML);
3340 /* answer to subscription confirmation */
3341 if (compose->replyto)
3342 compose_entry_append(compose,
3344 COMPOSE_TO, PREF_ML);
3345 else if (msginfo->from)
3346 compose_entry_append(compose,
3348 COMPOSE_TO, PREF_ML);
3351 else if (!(to_all || to_sender) && default_reply_to) {
3352 compose_entry_append(compose,
3353 msginfo->folder->prefs->default_reply_to,
3354 COMPOSE_TO, PREF_FOLDER);
3355 compose_entry_indicate(compose,
3356 msginfo->folder->prefs->default_reply_to);
3362 compose_entry_append(compose, msginfo->from,
3363 COMPOSE_TO, PREF_NONE);
3365 Xstrdup_a(tmp1, msginfo->from, return);
3366 extract_address(tmp1);
3367 compose_entry_append(compose,
3368 (!account_find_from_address(tmp1, FALSE))
3371 COMPOSE_TO, PREF_NONE);
3372 if (compose->replyto)
3373 compose_entry_append(compose,
3375 COMPOSE_CC, PREF_NONE);
3377 if (!folder_has_parent_of_type(msginfo->folder, F_QUEUE) &&
3378 !folder_has_parent_of_type(msginfo->folder, F_OUTBOX) &&
3379 !folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
3380 if (compose->replyto) {
3381 compose_entry_append(compose,
3383 COMPOSE_TO, PREF_NONE);
3385 compose_entry_append(compose,
3386 msginfo->from ? msginfo->from : "",
3387 COMPOSE_TO, PREF_NONE);
3390 /* replying to own mail, use original recp */
3391 compose_entry_append(compose,
3392 msginfo->to ? msginfo->to : "",
3393 COMPOSE_TO, PREF_NONE);
3394 compose_entry_append(compose,
3395 msginfo->cc ? msginfo->cc : "",
3396 COMPOSE_CC, PREF_NONE);
3401 if (to_sender || (compose->followup_to &&
3402 !strncmp(compose->followup_to, "poster", 6)))
3403 compose_entry_append
3405 (compose->replyto ? compose->replyto :
3406 msginfo->from ? msginfo->from : ""),
3407 COMPOSE_TO, PREF_NONE);
3409 else if (followup_and_reply_to || to_all) {
3410 compose_entry_append
3412 (compose->replyto ? compose->replyto :
3413 msginfo->from ? msginfo->from : ""),
3414 COMPOSE_TO, PREF_NONE);
3416 compose_entry_append
3418 compose->followup_to ? compose->followup_to :
3419 compose->newsgroups ? compose->newsgroups : "",
3420 COMPOSE_NEWSGROUPS, PREF_NONE);
3422 compose_entry_append
3424 msginfo->cc ? msginfo->cc : "",
3425 COMPOSE_CC, PREF_NONE);
3428 compose_entry_append
3430 compose->followup_to ? compose->followup_to :
3431 compose->newsgroups ? compose->newsgroups : "",
3432 COMPOSE_NEWSGROUPS, PREF_NONE);
3434 compose_reply_set_subject(compose, msginfo);
3436 if (to_ml && compose->ml_post) return;
3437 if (!to_all || compose->account->protocol == A_NNTP) return;
3439 if (compose->replyto) {
3440 Xstrdup_a(replyto, compose->replyto, return);
3441 extract_address(replyto);
3443 if (msginfo->from) {
3444 Xstrdup_a(from, msginfo->from, return);
3445 extract_address(from);
3448 if (replyto && from)
3449 cc_list = address_list_append_with_comments(cc_list, from);
3450 if (to_all && msginfo->folder &&
3451 msginfo->folder->prefs->enable_default_reply_to)
3452 cc_list = address_list_append_with_comments(cc_list,
3453 msginfo->folder->prefs->default_reply_to);
3454 cc_list = address_list_append_with_comments(cc_list, msginfo->to);
3455 cc_list = address_list_append_with_comments(cc_list, compose->cc);
3457 ac_email = g_utf8_strdown(compose->account->address, -1);
3460 for (cur = cc_list; cur != NULL; cur = cur->next) {
3461 gchar *addr = g_utf8_strdown(cur->data, -1);
3462 extract_address(addr);
3464 if (strcmp(ac_email, addr))
3465 compose_entry_append(compose, (gchar *)cur->data,
3466 COMPOSE_CC, PREF_NONE);
3468 debug_print("Cc address same as compose account's, ignoring\n");
3473 slist_free_strings_full(cc_list);
3479 #define SET_ENTRY(entry, str) \
3482 gtk_entry_set_text(GTK_ENTRY(compose->entry), str); \
3485 #define SET_ADDRESS(type, str) \
3488 compose_entry_append(compose, str, type, PREF_NONE); \
3491 static void compose_reedit_set_entry(Compose *compose, MsgInfo *msginfo)
3493 cm_return_if_fail(msginfo != NULL);
3495 SET_ENTRY(subject_entry, msginfo->subject);
3496 SET_ENTRY(from_name, msginfo->from);
3497 SET_ADDRESS(COMPOSE_TO, msginfo->to);
3498 SET_ADDRESS(COMPOSE_CC, compose->cc);
3499 SET_ADDRESS(COMPOSE_BCC, compose->bcc);
3500 SET_ADDRESS(COMPOSE_REPLYTO, compose->replyto);
3501 SET_ADDRESS(COMPOSE_NEWSGROUPS, compose->newsgroups);
3502 SET_ADDRESS(COMPOSE_FOLLOWUPTO, compose->followup_to);
3504 compose_update_priority_menu_item(compose);
3505 compose_update_privacy_system_menu_item(compose, FALSE);
3506 compose_show_first_last_header(compose, TRUE);
3512 static void compose_insert_sig(Compose *compose, gboolean replace)
3514 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3515 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3517 GtkTextIter iter, iter_end;
3518 gint cur_pos, ins_pos;
3519 gboolean prev_autowrap;
3520 gboolean found = FALSE;
3521 gboolean exists = FALSE;
3523 cm_return_if_fail(compose->account != NULL);
3527 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3528 G_CALLBACK(compose_changed_cb),
3531 mark = gtk_text_buffer_get_insert(buffer);
3532 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3533 cur_pos = gtk_text_iter_get_offset (&iter);
3536 gtk_text_buffer_get_end_iter(buffer, &iter);
3538 exists = (compose->sig_str != NULL);
3541 GtkTextIter first_iter, start_iter, end_iter;
3543 gtk_text_buffer_get_start_iter(buffer, &first_iter);
3545 if (!exists || compose->sig_str[0] == '\0')
3548 found = gtk_text_iter_forward_to_tag_toggle(&first_iter,
3549 compose->signature_tag);
3552 /* include previous \n\n */
3553 gtk_text_iter_backward_chars(&first_iter, 1);
3554 start_iter = first_iter;
3555 end_iter = first_iter;
3557 found = gtk_text_iter_forward_to_tag_toggle(&end_iter,
3558 compose->signature_tag);
3559 found &= gtk_text_iter_forward_to_tag_toggle(&end_iter,
3560 compose->signature_tag);
3562 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
3568 g_free(compose->sig_str);
3569 compose->sig_str = account_get_signature_str(compose->account);
3571 cur_pos = gtk_text_iter_get_offset(&iter);
3573 if (!compose->sig_str || (replace && !compose->account->auto_sig)) {
3574 g_free(compose->sig_str);
3575 compose->sig_str = NULL;
3577 if (compose->sig_inserted == FALSE)
3578 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
3579 compose->sig_inserted = TRUE;
3581 cur_pos = gtk_text_iter_get_offset(&iter);
3582 gtk_text_buffer_insert(buffer, &iter, compose->sig_str, -1);
3584 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cur_pos);
3585 gtk_text_iter_forward_chars(&iter, 1);
3586 gtk_text_buffer_get_end_iter(buffer, &iter_end);
3587 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &iter_end);
3589 if (cur_pos > gtk_text_buffer_get_char_count (buffer))
3590 cur_pos = gtk_text_buffer_get_char_count (buffer);
3593 /* put the cursor where it should be
3594 * either where the quote_fmt says, either where it was */
3595 if (compose->set_cursor_pos < 0)
3596 gtk_text_buffer_get_iter_at_offset(buffer, &iter, ins_pos);
3598 gtk_text_buffer_get_iter_at_offset(buffer, &iter,
3599 compose->set_cursor_pos);
3601 compose->set_cursor_pos = -1;
3602 gtk_text_buffer_place_cursor(buffer, &iter);
3603 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3604 G_CALLBACK(compose_changed_cb),
3610 static ComposeInsertResult compose_insert_file(Compose *compose, const gchar *file)
3613 GtkTextBuffer *buffer;
3616 const gchar *cur_encoding;
3617 gchar buf[BUFFSIZE];
3620 gboolean prev_autowrap;
3623 GString *file_contents = NULL;
3624 ComposeInsertResult result = COMPOSE_INSERT_SUCCESS;
3626 cm_return_val_if_fail(file != NULL, COMPOSE_INSERT_NO_FILE);
3628 /* get the size of the file we are about to insert */
3629 ret = g_stat(file, &file_stat);
3631 gchar *shortfile = g_path_get_basename(file);
3632 alertpanel_error(_("Could not get size of file '%s'."), shortfile);
3634 return COMPOSE_INSERT_NO_FILE;
3635 } else if (prefs_common.warn_large_insert == TRUE) {
3637 /* ask user for confirmation if the file is large */
3638 if (prefs_common.warn_large_insert_size < 0 ||
3639 file_stat.st_size > (prefs_common.warn_large_insert_size * 1024)) {
3643 msg = g_strdup_printf(_("You are about to insert a file of %s "
3644 "in the message body. Are you sure you want to do that?"),
3645 to_human_readable(file_stat.st_size));
3646 aval = alertpanel_full(_("Are you sure?"), msg, GTK_STOCK_CANCEL,
3647 g_strconcat("+", _("_Insert"), NULL), NULL, TRUE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
3650 /* do we ask for confirmation next time? */
3651 if (aval & G_ALERTDISABLE) {
3652 /* no confirmation next time, disable feature in preferences */
3653 aval &= ~G_ALERTDISABLE;
3654 prefs_common.warn_large_insert = FALSE;
3657 /* abort file insertion if user canceled action */
3658 if (aval != G_ALERTALTERNATE) {
3659 return COMPOSE_INSERT_NO_FILE;
3665 if ((fp = g_fopen(file, "rb")) == NULL) {
3666 FILE_OP_ERROR(file, "fopen");
3667 return COMPOSE_INSERT_READ_ERROR;
3670 prev_autowrap = compose->autowrap;
3671 compose->autowrap = FALSE;
3673 text = GTK_TEXT_VIEW(compose->text);
3674 buffer = gtk_text_view_get_buffer(text);
3675 mark = gtk_text_buffer_get_insert(buffer);
3676 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3678 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3679 G_CALLBACK(text_inserted),
3682 cur_encoding = conv_get_locale_charset_str_no_utf8();
3684 file_contents = g_string_new("");
3685 while (fgets(buf, sizeof(buf), fp) != NULL) {
3688 if (g_utf8_validate(buf, -1, NULL) == TRUE)
3689 str = g_strdup(buf);
3691 codeconv_set_strict(TRUE);
3692 str = conv_codeset_strdup
3693 (buf, cur_encoding, CS_INTERNAL);
3694 codeconv_set_strict(FALSE);
3697 result = COMPOSE_INSERT_INVALID_CHARACTER;
3703 /* strip <CR> if DOS/Windows file,
3704 replace <CR> with <LF> if Macintosh file. */
3707 if (len > 0 && str[len - 1] != '\n') {
3709 if (str[len] == '\r') str[len] = '\n';
3712 file_contents = g_string_append(file_contents, str);
3716 if (result == COMPOSE_INSERT_SUCCESS) {
3717 gtk_text_buffer_insert(buffer, &iter, file_contents->str, -1);
3719 compose_changed_cb(NULL, compose);
3720 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3721 G_CALLBACK(text_inserted),
3723 compose->autowrap = prev_autowrap;
3724 if (compose->autowrap)
3725 compose_wrap_all(compose);
3728 g_string_free(file_contents, TRUE);
3734 static gboolean compose_attach_append(Compose *compose, const gchar *file,
3735 const gchar *filename,
3736 const gchar *content_type,
3737 const gchar *charset)
3745 GtkListStore *store;
3747 gboolean has_binary = FALSE;
3749 if (!is_file_exist(file)) {
3750 gchar *file_from_uri = g_filename_from_uri(file, NULL, NULL);
3751 gboolean result = FALSE;
3752 if (file_from_uri && is_file_exist(file_from_uri)) {
3753 result = compose_attach_append(
3754 compose, file_from_uri,
3755 filename, content_type,
3758 g_free(file_from_uri);
3761 alertpanel_error("File %s doesn't exist or permission denied\n", filename);
3764 if ((size = get_file_size(file)) < 0) {
3765 alertpanel_error("Can't get file size of %s\n", filename);
3769 /* In batch mode, we allow 0-length files to be attached no questions asked */
3770 if (size == 0 && !compose->batch) {
3771 gchar * msg = g_strdup_printf(_("File %s is empty."), filename);
3772 AlertValue aval = alertpanel_full(_("Empty file"), msg,
3773 GTK_STOCK_CANCEL, g_strconcat("+", _("_Attach anyway"), NULL), NULL, FALSE,
3774 NULL, ALERT_WARNING, G_ALERTDEFAULT);
3777 if (aval != G_ALERTALTERNATE) {
3781 if ((fp = g_fopen(file, "rb")) == NULL) {
3782 alertpanel_error(_("Can't read %s."), filename);
3787 ainfo = g_new0(AttachInfo, 1);
3788 auto_ainfo = g_auto_pointer_new_with_free
3789 (ainfo, (GFreeFunc) compose_attach_info_free);
3790 ainfo->file = g_strdup(file);
3793 ainfo->content_type = g_strdup(content_type);
3794 if (!g_ascii_strcasecmp(content_type, "message/rfc822")) {
3796 MsgFlags flags = {0, 0};
3798 if (procmime_get_encoding_for_text_file(file, &has_binary) == ENC_7BIT)
3799 ainfo->encoding = ENC_7BIT;
3801 ainfo->encoding = ENC_8BIT;
3803 msginfo = procheader_parse_file(file, flags, FALSE, FALSE);
3804 if (msginfo && msginfo->subject)
3805 name = g_strdup(msginfo->subject);
3807 name = g_path_get_basename(filename ? filename : file);
3809 ainfo->name = g_strdup_printf(_("Message: %s"), name);
3811 procmsg_msginfo_free(&msginfo);
3813 if (!g_ascii_strncasecmp(content_type, "text/", 5)) {
3814 ainfo->charset = g_strdup(charset);
3815 ainfo->encoding = procmime_get_encoding_for_text_file(file, &has_binary);
3817 ainfo->encoding = ENC_BASE64;
3819 name = g_path_get_basename(filename ? filename : file);
3820 ainfo->name = g_strdup(name);
3824 ainfo->content_type = procmime_get_mime_type(file);
3825 if (!ainfo->content_type) {
3826 ainfo->content_type =
3827 g_strdup("application/octet-stream");
3828 ainfo->encoding = ENC_BASE64;
3829 } else if (!g_ascii_strncasecmp(ainfo->content_type, "text/", 5))
3831 procmime_get_encoding_for_text_file(file, &has_binary);
3833 ainfo->encoding = ENC_BASE64;
3834 name = g_path_get_basename(filename ? filename : file);
3835 ainfo->name = g_strdup(name);
3839 if (ainfo->name != NULL
3840 && !strcmp(ainfo->name, ".")) {
3841 g_free(ainfo->name);
3845 if (!strcmp(ainfo->content_type, "unknown") || has_binary) {
3846 g_free(ainfo->content_type);
3847 ainfo->content_type = g_strdup("application/octet-stream");
3848 g_free(ainfo->charset);
3849 ainfo->charset = NULL;
3852 ainfo->size = (goffset)size;
3853 size_text = to_human_readable((goffset)size);
3855 store = GTK_LIST_STORE(gtk_tree_view_get_model
3856 (GTK_TREE_VIEW(compose->attach_clist)));
3858 gtk_list_store_append(store, &iter);
3859 gtk_list_store_set(store, &iter,
3860 COL_MIMETYPE, ainfo->content_type,
3861 COL_SIZE, size_text,
3862 COL_NAME, ainfo->name,
3863 COL_CHARSET, ainfo->charset,
3865 COL_AUTODATA, auto_ainfo,
3868 g_auto_pointer_free(auto_ainfo);
3869 compose_attach_update_label(compose);
3873 static void compose_use_signing(Compose *compose, gboolean use_signing)
3875 compose->use_signing = use_signing;
3876 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", use_signing);
3879 static void compose_use_encryption(Compose *compose, gboolean use_encryption)
3881 compose->use_encryption = use_encryption;
3882 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", use_encryption);
3885 #define NEXT_PART_NOT_CHILD(info) \
3887 node = info->node; \
3888 while (node->children) \
3889 node = g_node_last_child(node); \
3890 info = procmime_mimeinfo_next((MimeInfo *)node->data); \
3893 static void compose_attach_parts(Compose *compose, MsgInfo *msginfo)
3897 MimeInfo *firsttext = NULL;
3898 MimeInfo *encrypted = NULL;
3901 const gchar *partname = NULL;
3903 mimeinfo = procmime_scan_message(msginfo);
3904 if (!mimeinfo) return;
3906 if (mimeinfo->node->children == NULL) {
3907 procmime_mimeinfo_free_all(&mimeinfo);
3911 /* find first content part */
3912 child = (MimeInfo *) mimeinfo->node->children->data;
3913 while (child && child->node->children && (child->type == MIMETYPE_MULTIPART))
3914 child = (MimeInfo *)child->node->children->data;
3917 if (child->type == MIMETYPE_TEXT) {
3919 debug_print("First text part found\n");
3920 } else if (compose->mode == COMPOSE_REEDIT &&
3921 child->type == MIMETYPE_APPLICATION &&
3922 !g_ascii_strcasecmp(child->subtype, "pgp-encrypted")) {
3923 encrypted = (MimeInfo *)child->node->parent->data;
3926 child = (MimeInfo *) mimeinfo->node->children->data;
3927 while (child != NULL) {
3930 if (child == encrypted) {
3931 /* skip this part of tree */
3932 NEXT_PART_NOT_CHILD(child);
3936 if (child->type == MIMETYPE_MULTIPART) {
3937 /* get the actual content */
3938 child = procmime_mimeinfo_next(child);
3942 if (child == firsttext) {
3943 child = procmime_mimeinfo_next(child);
3947 outfile = procmime_get_tmp_file_name(child);
3948 if ((err = procmime_get_part(outfile, child)) < 0)
3949 g_warning("Can't get the part of multipart message. (%s)", g_strerror(-err));
3951 gchar *content_type;
3953 content_type = procmime_get_content_type_str(child->type, child->subtype);
3955 /* if we meet a pgp signature, we don't attach it, but
3956 * we force signing. */
3957 if ((strcmp(content_type, "application/pgp-signature") &&
3958 strcmp(content_type, "application/pkcs7-signature") &&
3959 strcmp(content_type, "application/x-pkcs7-signature"))
3960 || compose->mode == COMPOSE_REDIRECT) {
3961 partname = procmime_mimeinfo_get_parameter(child, "filename");
3962 if (partname == NULL)
3963 partname = procmime_mimeinfo_get_parameter(child, "name");
3964 if (partname == NULL)
3966 compose_attach_append(compose, outfile,
3967 partname, content_type,
3968 procmime_mimeinfo_get_parameter(child, "charset"));
3970 compose_force_signing(compose, compose->account, NULL);
3972 g_free(content_type);
3975 NEXT_PART_NOT_CHILD(child);
3977 procmime_mimeinfo_free_all(&mimeinfo);
3980 #undef NEXT_PART_NOT_CHILD
3985 WAIT_FOR_INDENT_CHAR,
3986 WAIT_FOR_INDENT_CHAR_OR_SPACE,
3989 /* return indent length, we allow:
3990 indent characters followed by indent characters or spaces/tabs,
3991 alphabets and numbers immediately followed by indent characters,
3992 and the repeating sequences of the above
3993 If quote ends with multiple spaces, only the first one is included. */
3994 static gchar *compose_get_quote_str(GtkTextBuffer *buffer,
3995 const GtkTextIter *start, gint *len)
3997 GtkTextIter iter = *start;
4001 IndentState state = WAIT_FOR_INDENT_CHAR;
4004 gint alnum_count = 0;
4005 gint space_count = 0;
4008 if (prefs_common.quote_chars == NULL) {
4012 while (!gtk_text_iter_ends_line(&iter)) {
4013 wc = gtk_text_iter_get_char(&iter);
4014 if (g_unichar_iswide(wc))
4016 clen = g_unichar_to_utf8(wc, ch);
4020 is_indent = strchr(prefs_common.quote_chars, ch[0]) ? TRUE : FALSE;
4021 is_space = g_unichar_isspace(wc);
4023 if (state == WAIT_FOR_INDENT_CHAR) {
4024 if (!is_indent && !g_unichar_isalnum(wc))
4027 quote_len += alnum_count + space_count + 1;
4028 alnum_count = space_count = 0;
4029 state = WAIT_FOR_INDENT_CHAR_OR_SPACE;
4032 } else if (state == WAIT_FOR_INDENT_CHAR_OR_SPACE) {
4033 if (!is_indent && !is_space && !g_unichar_isalnum(wc))
4037 else if (is_indent) {
4038 quote_len += alnum_count + space_count + 1;
4039 alnum_count = space_count = 0;
4042 state = WAIT_FOR_INDENT_CHAR;
4046 gtk_text_iter_forward_char(&iter);
4049 if (quote_len > 0 && space_count > 0)
4055 if (quote_len > 0) {
4057 gtk_text_iter_forward_chars(&iter, quote_len);
4058 return gtk_text_buffer_get_text(buffer, start, &iter, FALSE);
4064 /* return >0 if the line is itemized */
4065 static int compose_itemized_length(GtkTextBuffer *buffer,
4066 const GtkTextIter *start)
4068 GtkTextIter iter = *start;
4073 if (gtk_text_iter_ends_line(&iter))
4078 wc = gtk_text_iter_get_char(&iter);
4079 if (!g_unichar_isspace(wc))
4081 gtk_text_iter_forward_char(&iter);
4082 if (gtk_text_iter_ends_line(&iter))
4086 clen = g_unichar_to_utf8(wc, ch);
4087 if (!((clen == 1 && strchr("*-+", ch[0])) ||
4089 wc == 0x2022 || /* BULLET */
4090 wc == 0x2023 || /* TRIANGULAR BULLET */
4091 wc == 0x2043 || /* HYPHEN BULLET */
4092 wc == 0x204c || /* BLACK LEFTWARDS BULLET */
4093 wc == 0x204d || /* BLACK RIGHTWARDS BULLET */
4094 wc == 0x2219 || /* BULLET OPERATOR */
4095 wc == 0x25d8 || /* INVERSE BULLET */
4096 wc == 0x25e6 || /* WHITE BULLET */
4097 wc == 0x2619 || /* REVERSED ROTATED FLORAL HEART BULLET */
4098 wc == 0x2765 || /* ROTATED HEAVY BLACK HEART BULLET */
4099 wc == 0x2767 || /* ROTATED FLORAL HEART BULLET */
4100 wc == 0x29be || /* CIRCLED WHITE BULLET */
4101 wc == 0x29bf /* CIRCLED BULLET */
4105 gtk_text_iter_forward_char(&iter);
4106 if (gtk_text_iter_ends_line(&iter))
4108 wc = gtk_text_iter_get_char(&iter);
4109 if (g_unichar_isspace(wc)) {
4115 /* return the string at the start of the itemization */
4116 static gchar * compose_get_itemized_chars(GtkTextBuffer *buffer,
4117 const GtkTextIter *start)
4119 GtkTextIter iter = *start;
4122 GString *item_chars = g_string_new("");
4125 if (gtk_text_iter_ends_line(&iter))
4130 wc = gtk_text_iter_get_char(&iter);
4131 if (!g_unichar_isspace(wc))
4133 gtk_text_iter_forward_char(&iter);
4134 if (gtk_text_iter_ends_line(&iter))
4136 g_string_append_unichar(item_chars, wc);
4139 str = item_chars->str;
4140 g_string_free(item_chars, FALSE);
4144 /* return the number of spaces at a line's start */
4145 static int compose_left_offset_length(GtkTextBuffer *buffer,
4146 const GtkTextIter *start)
4148 GtkTextIter iter = *start;
4151 if (gtk_text_iter_ends_line(&iter))
4155 wc = gtk_text_iter_get_char(&iter);
4156 if (!g_unichar_isspace(wc))
4159 gtk_text_iter_forward_char(&iter);
4160 if (gtk_text_iter_ends_line(&iter))
4164 gtk_text_iter_forward_char(&iter);
4165 if (gtk_text_iter_ends_line(&iter))
4170 static gboolean compose_get_line_break_pos(GtkTextBuffer *buffer,
4171 const GtkTextIter *start,
4172 GtkTextIter *break_pos,
4176 GtkTextIter iter = *start, line_end = *start;
4177 PangoLogAttr *attrs;
4184 gboolean can_break = FALSE;
4185 gboolean do_break = FALSE;
4186 gboolean was_white = FALSE;
4187 gboolean prev_dont_break = FALSE;
4189 gtk_text_iter_forward_to_line_end(&line_end);
4190 str = gtk_text_buffer_get_text(buffer, &iter, &line_end, FALSE);
4191 len = g_utf8_strlen(str, -1);
4195 g_warning("compose_get_line_break_pos: len = 0!");
4199 /* g_print("breaking line: %d: %s (len = %d)\n",
4200 gtk_text_iter_get_line(&iter), str, len); */
4202 attrs = g_new(PangoLogAttr, len + 1);
4204 pango_default_break(str, -1, NULL, attrs, len + 1);
4208 /* skip quote and leading spaces */
4209 for (i = 0; *p != '\0' && i < len; i++) {
4212 wc = g_utf8_get_char(p);
4213 if (i >= quote_len && !g_unichar_isspace(wc))
4215 if (g_unichar_iswide(wc))
4217 else if (*p == '\t')
4221 p = g_utf8_next_char(p);
4224 for (; *p != '\0' && i < len; i++) {
4225 PangoLogAttr *attr = attrs + i;
4229 if (attr->is_line_break && can_break && was_white && !prev_dont_break)
4232 was_white = attr->is_white;
4234 /* don't wrap URI */
4235 if ((uri_len = get_uri_len(p)) > 0) {
4237 if (pos > 0 && col > max_col) {
4247 wc = g_utf8_get_char(p);
4248 if (g_unichar_iswide(wc)) {
4250 if (prev_dont_break && can_break && attr->is_line_break)
4252 } else if (*p == '\t')
4256 if (pos > 0 && col > max_col) {
4261 if (*p == '-' || *p == '/')
4262 prev_dont_break = TRUE;
4264 prev_dont_break = FALSE;
4266 p = g_utf8_next_char(p);
4270 /* debug_print("compose_get_line_break_pos(): do_break = %d, pos = %d, col = %d\n", do_break, pos, col); */
4275 *break_pos = *start;
4276 gtk_text_iter_set_line_offset(break_pos, pos);
4281 static gboolean compose_join_next_line(Compose *compose,
4282 GtkTextBuffer *buffer,
4284 const gchar *quote_str)
4286 GtkTextIter iter_ = *iter, cur, prev, next, end;
4287 PangoLogAttr attrs[3];
4289 gchar *next_quote_str;
4292 gboolean keep_cursor = FALSE;
4294 if (!gtk_text_iter_forward_line(&iter_) ||
4295 gtk_text_iter_ends_line(&iter_)) {
4298 next_quote_str = compose_get_quote_str(buffer, &iter_, "e_len);
4300 if ((quote_str || next_quote_str) &&
4301 strcmp2(quote_str, next_quote_str) != 0) {
4302 g_free(next_quote_str);
4305 g_free(next_quote_str);
4308 if (quote_len > 0) {
4309 gtk_text_iter_forward_chars(&end, quote_len);
4310 if (gtk_text_iter_ends_line(&end)) {
4315 /* don't join itemized lines */
4316 if (compose_itemized_length(buffer, &end) > 0) {
4320 /* don't join signature separator */
4321 if (compose_is_sig_separator(compose, buffer, &iter_)) {
4324 /* delete quote str */
4326 gtk_text_buffer_delete(buffer, &iter_, &end);
4328 /* don't join line breaks put by the user */
4330 gtk_text_iter_backward_char(&cur);
4331 if (gtk_text_iter_has_tag(&cur, compose->no_join_tag)) {
4332 gtk_text_iter_forward_char(&cur);
4336 gtk_text_iter_forward_char(&cur);
4337 /* delete linebreak and extra spaces */
4338 while (gtk_text_iter_backward_char(&cur)) {
4339 wc1 = gtk_text_iter_get_char(&cur);
4340 if (!g_unichar_isspace(wc1))
4345 while (!gtk_text_iter_ends_line(&cur)) {
4346 wc1 = gtk_text_iter_get_char(&cur);
4347 if (!g_unichar_isspace(wc1))
4349 gtk_text_iter_forward_char(&cur);
4352 if (!gtk_text_iter_equal(&prev, &next)) {
4355 mark = gtk_text_buffer_get_insert(buffer);
4356 gtk_text_buffer_get_iter_at_mark(buffer, &cur, mark);
4357 if (gtk_text_iter_equal(&prev, &cur))
4359 gtk_text_buffer_delete(buffer, &prev, &next);
4363 /* insert space if required */
4364 gtk_text_iter_backward_char(&prev);
4365 wc1 = gtk_text_iter_get_char(&prev);
4366 wc2 = gtk_text_iter_get_char(&next);
4367 gtk_text_iter_forward_char(&next);
4368 str = gtk_text_buffer_get_text(buffer, &prev, &next, FALSE);
4369 pango_default_break(str, -1, NULL, attrs, 3);
4370 if (!attrs[1].is_line_break ||
4371 (!g_unichar_iswide(wc1) || !g_unichar_iswide(wc2))) {
4372 gtk_text_buffer_insert(buffer, &iter_, " ", 1);
4374 gtk_text_iter_backward_char(&iter_);
4375 gtk_text_buffer_place_cursor(buffer, &iter_);
4384 #define ADD_TXT_POS(bp_, ep_, pti_) \
4385 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
4386 last = last->next; \
4387 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
4388 last->next = NULL; \
4390 g_warning("alloc error scanning URIs"); \
4393 static gboolean compose_beautify_paragraph(Compose *compose, GtkTextIter *par_iter, gboolean force)
4395 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4396 GtkTextBuffer *buffer;
4397 GtkTextIter iter, break_pos, end_of_line;
4398 gchar *quote_str = NULL;
4400 gboolean wrap_quote = force || prefs_common.linewrap_quote;
4401 gboolean prev_autowrap = compose->autowrap;
4402 gint startq_offset = -1, noq_offset = -1;
4403 gint uri_start = -1, uri_stop = -1;
4404 gint nouri_start = -1, nouri_stop = -1;
4405 gint num_blocks = 0;
4406 gint quotelevel = -1;
4407 gboolean modified = force;
4408 gboolean removed = FALSE;
4409 gboolean modified_before_remove = FALSE;
4411 gboolean start = TRUE;
4412 gint itemized_len = 0, rem_item_len = 0;
4413 gchar *itemized_chars = NULL;
4414 gboolean item_continuation = FALSE;
4419 if (compose->draft_timeout_tag == COMPOSE_DRAFT_TIMEOUT_FORBIDDEN) {
4423 compose->autowrap = FALSE;
4425 buffer = gtk_text_view_get_buffer(text);
4426 undo_wrapping(compose->undostruct, TRUE);
4431 mark = gtk_text_buffer_get_insert(buffer);
4432 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
4436 if (compose->draft_timeout_tag == COMPOSE_DRAFT_TIMEOUT_FORBIDDEN) {
4437 if (gtk_text_iter_ends_line(&iter)) {
4438 while (gtk_text_iter_ends_line(&iter) &&
4439 gtk_text_iter_forward_line(&iter))
4442 while (gtk_text_iter_backward_line(&iter)) {
4443 if (gtk_text_iter_ends_line(&iter)) {
4444 gtk_text_iter_forward_line(&iter);
4450 /* move to line start */
4451 gtk_text_iter_set_line_offset(&iter, 0);
4454 itemized_len = compose_itemized_length(buffer, &iter);
4456 if (!itemized_len) {
4457 itemized_len = compose_left_offset_length(buffer, &iter);
4458 item_continuation = TRUE;
4462 itemized_chars = compose_get_itemized_chars(buffer, &iter);
4464 /* go until paragraph end (empty line) */
4465 while (start || !gtk_text_iter_ends_line(&iter)) {
4466 gchar *scanpos = NULL;
4467 /* parse table - in order of priority */
4469 const gchar *needle; /* token */
4471 /* token search function */
4472 gchar *(*search) (const gchar *haystack,
4473 const gchar *needle);
4474 /* part parsing function */
4475 gboolean (*parse) (const gchar *start,
4476 const gchar *scanpos,
4480 /* part to URI function */
4481 gchar *(*build_uri) (const gchar *bp,
4485 static struct table parser[] = {
4486 {"http://", strcasestr, get_uri_part, make_uri_string},
4487 {"https://", strcasestr, get_uri_part, make_uri_string},
4488 {"ftp://", strcasestr, get_uri_part, make_uri_string},
4489 {"sftp://", strcasestr, get_uri_part, make_uri_string},
4490 {"gopher://",strcasestr, get_uri_part, make_uri_string},
4491 {"www.", strcasestr, get_uri_part, make_http_string},
4492 {"mailto:", strcasestr, get_uri_part, make_uri_string},
4493 {"@", strcasestr, get_email_part, make_email_string}
4495 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
4496 gint last_index = PARSE_ELEMS;
4498 gchar *o_walk = NULL, *walk = NULL, *bp = NULL, *ep = NULL;
4502 if (!prev_autowrap && num_blocks == 0) {
4504 g_signal_handlers_block_by_func(G_OBJECT(buffer),
4505 G_CALLBACK(text_inserted),
4508 if (gtk_text_iter_has_tag(&iter, compose->no_wrap_tag) && !force)
4511 uri_start = uri_stop = -1;
4513 quote_str = compose_get_quote_str(buffer, &iter, "e_len);
4516 /* debug_print("compose_beautify_paragraph(): quote_str = '%s'\n", quote_str); */
4517 if (startq_offset == -1)
4518 startq_offset = gtk_text_iter_get_offset(&iter);
4519 quotelevel = get_quote_level(quote_str, prefs_common.quote_chars);
4520 if (quotelevel > 2) {
4521 /* recycle colors */
4522 if (prefs_common.recycle_quote_colors)
4531 if (startq_offset == -1)
4532 noq_offset = gtk_text_iter_get_offset(&iter);
4536 if (prev_autowrap == FALSE && !force && !wrap_quote) {
4539 if (gtk_text_iter_ends_line(&iter)) {
4541 } else if (compose_get_line_break_pos(buffer, &iter, &break_pos,
4542 prefs_common.linewrap_len,
4544 GtkTextIter prev, next, cur;
4545 if (prev_autowrap != FALSE || force) {
4546 compose->automatic_break = TRUE;
4548 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4549 compose->automatic_break = FALSE;
4550 if (itemized_len && compose->autoindent) {
4551 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4552 if (!item_continuation)
4553 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4555 } else if (quote_str && wrap_quote) {
4556 compose->automatic_break = TRUE;
4558 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4559 compose->automatic_break = FALSE;
4560 if (itemized_len && compose->autoindent) {
4561 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4562 if (!item_continuation)
4563 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4567 /* remove trailing spaces */
4569 rem_item_len = itemized_len;
4570 while (compose->autoindent && rem_item_len-- > 0)
4571 gtk_text_iter_backward_char(&cur);
4572 gtk_text_iter_backward_char(&cur);
4575 while (!gtk_text_iter_starts_line(&cur)) {
4578 gtk_text_iter_backward_char(&cur);
4579 wc = gtk_text_iter_get_char(&cur);
4580 if (!g_unichar_isspace(wc))
4584 if (!gtk_text_iter_equal(&prev, &next)) {
4585 gtk_text_buffer_delete(buffer, &prev, &next);
4587 gtk_text_iter_forward_char(&break_pos);
4591 gtk_text_buffer_insert(buffer, &break_pos,
4595 modified |= compose_join_next_line(compose, buffer, &iter, quote_str);
4597 /* move iter to current line start */
4598 gtk_text_iter_set_line_offset(&iter, 0);
4605 /* move iter to next line start */
4611 if (!prev_autowrap && num_blocks > 0) {
4613 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
4614 G_CALLBACK(text_inserted),
4618 while (!gtk_text_iter_ends_line(&end_of_line)) {
4619 gtk_text_iter_forward_char(&end_of_line);
4621 o_walk = walk = gtk_text_buffer_get_text(buffer, &iter, &end_of_line, FALSE);
4623 nouri_start = gtk_text_iter_get_offset(&iter);
4624 nouri_stop = gtk_text_iter_get_offset(&end_of_line);
4626 walk_pos = gtk_text_iter_get_offset(&iter);
4627 /* FIXME: this looks phony. scanning for anything in the parse table */
4628 for (n = 0; n < PARSE_ELEMS; n++) {
4631 tmp = parser[n].search(walk, parser[n].needle);
4633 if (scanpos == NULL || tmp < scanpos) {
4642 /* check if URI can be parsed */
4643 if (parser[last_index].parse(walk, scanpos, (const gchar **)&bp,
4644 (const gchar **)&ep, FALSE)
4645 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
4649 strlen(parser[last_index].needle);
4652 uri_start = walk_pos + (bp - o_walk);
4653 uri_stop = walk_pos + (ep - o_walk);
4657 gtk_text_iter_forward_line(&iter);
4660 if (startq_offset != -1) {
4661 GtkTextIter startquote, endquote;
4662 gtk_text_buffer_get_iter_at_offset(
4663 buffer, &startquote, startq_offset);
4666 switch (quotelevel) {
4668 if (!gtk_text_iter_has_tag(&startquote, compose->quote0_tag) ||
4669 !gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) {
4670 gtk_text_buffer_apply_tag_by_name(
4671 buffer, "quote0", &startquote, &endquote);
4672 gtk_text_buffer_remove_tag_by_name(
4673 buffer, "quote1", &startquote, &endquote);
4674 gtk_text_buffer_remove_tag_by_name(
4675 buffer, "quote2", &startquote, &endquote);
4680 if (!gtk_text_iter_has_tag(&startquote, compose->quote1_tag) ||
4681 !gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) {
4682 gtk_text_buffer_apply_tag_by_name(
4683 buffer, "quote1", &startquote, &endquote);
4684 gtk_text_buffer_remove_tag_by_name(
4685 buffer, "quote0", &startquote, &endquote);
4686 gtk_text_buffer_remove_tag_by_name(
4687 buffer, "quote2", &startquote, &endquote);
4692 if (!gtk_text_iter_has_tag(&startquote, compose->quote2_tag) ||
4693 !gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag)) {
4694 gtk_text_buffer_apply_tag_by_name(
4695 buffer, "quote2", &startquote, &endquote);
4696 gtk_text_buffer_remove_tag_by_name(
4697 buffer, "quote0", &startquote, &endquote);
4698 gtk_text_buffer_remove_tag_by_name(
4699 buffer, "quote1", &startquote, &endquote);
4705 } else if (noq_offset != -1) {
4706 GtkTextIter startnoquote, endnoquote;
4707 gtk_text_buffer_get_iter_at_offset(
4708 buffer, &startnoquote, noq_offset);
4711 if ((gtk_text_iter_has_tag(&startnoquote, compose->quote0_tag)
4712 && gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) ||
4713 (gtk_text_iter_has_tag(&startnoquote, compose->quote1_tag)
4714 && gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) ||
4715 (gtk_text_iter_has_tag(&startnoquote, compose->quote2_tag)
4716 && gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag))) {
4717 gtk_text_buffer_remove_tag_by_name(
4718 buffer, "quote0", &startnoquote, &endnoquote);
4719 gtk_text_buffer_remove_tag_by_name(
4720 buffer, "quote1", &startnoquote, &endnoquote);
4721 gtk_text_buffer_remove_tag_by_name(
4722 buffer, "quote2", &startnoquote, &endnoquote);
4728 if (uri_start != nouri_start && uri_stop != nouri_stop) {
4729 GtkTextIter nouri_start_iter, nouri_end_iter;
4730 gtk_text_buffer_get_iter_at_offset(
4731 buffer, &nouri_start_iter, nouri_start);
4732 gtk_text_buffer_get_iter_at_offset(
4733 buffer, &nouri_end_iter, nouri_stop);
4734 if (gtk_text_iter_has_tag(&nouri_start_iter, compose->uri_tag) &&
4735 gtk_text_iter_has_tag(&nouri_end_iter, compose->uri_tag)) {
4736 gtk_text_buffer_remove_tag_by_name(
4737 buffer, "link", &nouri_start_iter, &nouri_end_iter);
4738 modified_before_remove = modified;
4743 if (uri_start >= 0 && uri_stop > 0) {
4744 GtkTextIter uri_start_iter, uri_end_iter, back;
4745 gtk_text_buffer_get_iter_at_offset(
4746 buffer, &uri_start_iter, uri_start);
4747 gtk_text_buffer_get_iter_at_offset(
4748 buffer, &uri_end_iter, uri_stop);
4749 back = uri_end_iter;
4750 gtk_text_iter_backward_char(&back);
4751 if (!gtk_text_iter_has_tag(&uri_start_iter, compose->uri_tag) ||
4752 !gtk_text_iter_has_tag(&back, compose->uri_tag)) {
4753 gtk_text_buffer_apply_tag_by_name(
4754 buffer, "link", &uri_start_iter, &uri_end_iter);
4756 if (removed && !modified_before_remove) {
4762 /* debug_print("not modified, out after %d lines\n", lines); */
4766 /* debug_print("modified, out after %d lines\n", lines); */
4768 g_free(itemized_chars);
4771 undo_wrapping(compose->undostruct, FALSE);
4772 compose->autowrap = prev_autowrap;
4777 void compose_action_cb(void *data)
4779 Compose *compose = (Compose *)data;
4780 compose_wrap_all(compose);
4783 static void compose_wrap_all(Compose *compose)
4785 compose_wrap_all_full(compose, FALSE);
4788 static void compose_wrap_all_full(Compose *compose, gboolean force)
4790 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4791 GtkTextBuffer *buffer;
4793 gboolean modified = TRUE;
4795 buffer = gtk_text_view_get_buffer(text);
4797 gtk_text_buffer_get_start_iter(buffer, &iter);
4799 undo_wrapping(compose->undostruct, TRUE);
4801 while (!gtk_text_iter_is_end(&iter) && modified)
4802 modified = compose_beautify_paragraph(compose, &iter, force);
4804 undo_wrapping(compose->undostruct, FALSE);
4808 static void compose_set_title(Compose *compose)
4814 edited = compose->modified ? _(" [Edited]") : "";
4816 subject = gtk_editable_get_chars(
4817 GTK_EDITABLE(compose->subject_entry), 0, -1);
4819 #ifndef GENERIC_UMPC
4820 if (subject && strlen(subject))
4821 str = g_strdup_printf(_("%s - Compose message%s"),
4824 str = g_strdup_printf(_("[no subject] - Compose message%s"), edited);
4826 str = g_strdup(_("Compose message"));
4829 gtk_window_set_title(GTK_WINDOW(compose->window), str);
4835 * compose_current_mail_account:
4837 * Find a current mail account (the currently selected account, or the
4838 * default account, if a news account is currently selected). If a
4839 * mail account cannot be found, display an error message.
4841 * Return value: Mail account, or NULL if not found.
4843 static PrefsAccount *
4844 compose_current_mail_account(void)
4848 if (cur_account && cur_account->protocol != A_NNTP)
4851 ac = account_get_default();
4852 if (!ac || ac->protocol == A_NNTP) {
4853 alertpanel_error(_("Account for sending mail is not specified.\n"
4854 "Please select a mail account before sending."));
4861 #define QUOTE_IF_REQUIRED(out, str) \
4863 if (*str != '"' && strpbrk(str, ",.:;[]<>()@\\\"")) { \
4867 len = strlen(str) + 3; \
4868 if ((__tmp = alloca(len)) == NULL) { \
4869 g_warning("can't allocate memory"); \
4870 g_string_free(header, TRUE); \
4873 g_snprintf(__tmp, len, "\"%s\"", str); \
4878 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4879 g_warning("can't allocate memory"); \
4880 g_string_free(header, TRUE); \
4883 strcpy(__tmp, str); \
4889 #define QUOTE_IF_REQUIRED_NORMAL(out, str, errret) \
4891 if (*str != '"' && strpbrk(str, ",.:;[]<>()@\\\"")) { \
4895 len = strlen(str) + 3; \
4896 if ((__tmp = alloca(len)) == NULL) { \
4897 g_warning("can't allocate memory"); \
4900 g_snprintf(__tmp, len, "\"%s\"", str); \
4905 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4906 g_warning("can't allocate memory"); \
4909 strcpy(__tmp, str); \
4915 static void compose_select_account(Compose *compose, PrefsAccount *account,
4918 gchar *from = NULL, *header = NULL;
4919 ComposeHeaderEntry *header_entry;
4920 #if GTK_CHECK_VERSION(2, 24, 0)
4924 cm_return_if_fail(account != NULL);
4926 compose->account = account;
4927 if (account->name && *account->name) {
4929 QUOTE_IF_REQUIRED_NORMAL(buf, account->name, return);
4930 qbuf = escape_internal_quotes(buf, '"');
4931 from = g_strdup_printf("%s <%s>",
4932 qbuf, account->address);
4935 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4937 from = g_strdup_printf("<%s>",
4939 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4944 compose_set_title(compose);
4946 if (account->default_sign && compose->mode != COMPOSE_REDIRECT)
4947 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", TRUE);
4949 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", FALSE);
4950 if (account->default_encrypt && compose->mode != COMPOSE_REDIRECT)
4951 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", TRUE);
4953 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", FALSE);
4955 activate_privacy_system(compose, account, FALSE);
4957 if (!init && compose->mode != COMPOSE_REDIRECT) {
4958 undo_block(compose->undostruct);
4959 compose_insert_sig(compose, TRUE);
4960 undo_unblock(compose->undostruct);
4963 header_entry = (ComposeHeaderEntry *) compose->header_list->data;
4964 #if !GTK_CHECK_VERSION(2, 24, 0)
4965 header = gtk_combo_box_get_active_text(GTK_COMBO_BOX(header_entry->combo));
4967 if (gtk_combo_box_get_active_iter(GTK_COMBO_BOX(header_entry->combo), &iter))
4968 gtk_tree_model_get(gtk_combo_box_get_model(GTK_COMBO_BOX(
4969 header_entry->combo)), &iter, COMBOBOX_TEXT, &header, -1);
4972 if (header && !strlen(gtk_entry_get_text(GTK_ENTRY(header_entry->entry)))) {
4973 if (account->protocol == A_NNTP) {
4974 if (!strcmp(header, _("To:")))
4975 combobox_select_by_text(
4976 GTK_COMBO_BOX(header_entry->combo),
4979 if (!strcmp(header, _("Newsgroups:")))
4980 combobox_select_by_text(
4981 GTK_COMBO_BOX(header_entry->combo),
4989 /* use account's dict info if set */
4990 if (compose->gtkaspell) {
4991 if (account->enable_default_dictionary)
4992 gtkaspell_change_dict(compose->gtkaspell,
4993 account->default_dictionary, FALSE);
4994 if (account->enable_default_alt_dictionary)
4995 gtkaspell_change_alt_dict(compose->gtkaspell,
4996 account->default_alt_dictionary);
4997 if (account->enable_default_dictionary
4998 || account->enable_default_alt_dictionary)
4999 compose_spell_menu_changed(compose);
5004 gboolean compose_check_for_valid_recipient(Compose *compose) {
5005 gchar *recipient_headers_mail[] = {"To:", "Cc:", "Bcc:", NULL};
5006 gchar *recipient_headers_news[] = {"Newsgroups:", NULL};
5007 gboolean recipient_found = FALSE;
5011 /* free to and newsgroup list */
5012 slist_free_strings_full(compose->to_list);
5013 compose->to_list = NULL;
5015 slist_free_strings_full(compose->newsgroup_list);
5016 compose->newsgroup_list = NULL;
5018 /* search header entries for to and newsgroup entries */
5019 for (list = compose->header_list; list; list = list->next) {
5022 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5023 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5026 if (entry[0] != '\0') {
5027 for (strptr = recipient_headers_mail; *strptr != NULL; strptr++) {
5028 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
5029 compose->to_list = address_list_append(compose->to_list, entry);
5030 recipient_found = TRUE;
5033 for (strptr = recipient_headers_news; *strptr != NULL; strptr++) {
5034 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
5035 compose->newsgroup_list = newsgroup_list_append(compose->newsgroup_list, entry);
5036 recipient_found = TRUE;
5043 return recipient_found;
5046 static gboolean compose_check_for_set_recipients(Compose *compose)
5048 if (compose->account->set_autocc && compose->account->auto_cc) {
5049 gboolean found_other = FALSE;
5051 /* search header entries for to and newsgroup entries */
5052 for (list = compose->header_list; list; list = list->next) {
5055 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5056 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5059 if (strcmp(entry, compose->account->auto_cc)
5060 || strcmp(header, prefs_common_translated_header_name("Cc:"))) {
5071 if (compose->batch) {
5072 gtk_widget_show_all(compose->window);
5074 text = g_strdup_printf(_("The only recipient is the default '%s' address. Send anyway?"),
5075 prefs_common_translated_header_name("Cc"));
5076 aval = alertpanel(_("Send"),
5078 GTK_STOCK_CANCEL, g_strconcat("+", _("_Send"), NULL), NULL);
5080 if (aval != G_ALERTALTERNATE)
5084 if (compose->account->set_autobcc && compose->account->auto_bcc) {
5085 gboolean found_other = FALSE;
5087 /* search header entries for to and newsgroup entries */
5088 for (list = compose->header_list; list; list = list->next) {
5091 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5092 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5095 if (strcmp(entry, compose->account->auto_bcc)
5096 || strcmp(header, prefs_common_translated_header_name("Bcc:"))) {
5108 if (compose->batch) {
5109 gtk_widget_show_all(compose->window);
5111 text = g_strdup_printf(_("The only recipient is the default '%s' address. Send anyway?"),
5112 prefs_common_translated_header_name("Bcc"));
5113 aval = alertpanel(_("Send"),
5115 GTK_STOCK_CANCEL, g_strconcat("+", _("_Send"), NULL), NULL);
5117 if (aval != G_ALERTALTERNATE)
5124 static gboolean compose_check_entries(Compose *compose, gboolean check_everything)
5128 if (compose_check_for_valid_recipient(compose) == FALSE) {
5129 if (compose->batch) {
5130 gtk_widget_show_all(compose->window);
5132 alertpanel_error(_("Recipient is not specified."));
5136 if (compose_check_for_set_recipients(compose) == FALSE) {
5140 if (!compose->batch && prefs_common.warn_empty_subj == TRUE) {
5141 str = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5142 if (*str == '\0' && check_everything == TRUE &&
5143 compose->mode != COMPOSE_REDIRECT) {
5145 gchar *button_label;
5148 if (compose->sending)
5149 button_label = g_strconcat("+", _("_Send"), NULL);
5151 button_label = g_strconcat("+", _("_Queue"), NULL);
5152 message = g_strdup_printf(_("Subject is empty. %s"),
5153 compose->sending?_("Send it anyway?"):
5154 _("Queue it anyway?"));
5156 aval = alertpanel_full(compose->sending?_("Send"):_("Send later"), message,
5157 GTK_STOCK_CANCEL, button_label, NULL, TRUE, NULL,
5158 ALERT_QUESTION, G_ALERTDEFAULT);
5160 if (aval & G_ALERTDISABLE) {
5161 aval &= ~G_ALERTDISABLE;
5162 prefs_common.warn_empty_subj = FALSE;
5164 if (aval != G_ALERTALTERNATE)
5169 if (!compose->batch && prefs_common.warn_sending_many_recipients_num > 0
5170 && check_everything == TRUE) {
5174 /* count To and Cc recipients */
5175 for (list = compose->header_list; list; list = list->next) {
5179 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5180 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5183 if ((entry[0] != '\0')
5184 && (strcmp(header, prefs_common_translated_header_name("To:"))
5185 || strcmp(header, prefs_common_translated_header_name("Cc:")))) {
5191 if (cnt > prefs_common.warn_sending_many_recipients_num) {
5193 gchar *button_label;
5196 if (compose->sending)
5197 button_label = g_strconcat("+", _("_Send"), NULL);
5199 button_label = g_strconcat("+", _("_Queue"), NULL);
5200 message = g_strdup_printf(_("Sending to %d recipients. %s"), cnt,
5201 compose->sending?_("Send it anyway?"):
5202 _("Queue it anyway?"));
5204 aval = alertpanel_full(compose->sending?_("Send"):_("Send later"), message,
5205 GTK_STOCK_CANCEL, button_label, NULL, TRUE, NULL,
5206 ALERT_QUESTION, G_ALERTDEFAULT);
5208 if (aval & G_ALERTDISABLE) {
5209 aval &= ~G_ALERTDISABLE;
5210 prefs_common.warn_sending_many_recipients_num = 0;
5212 if (aval != G_ALERTALTERNATE)
5217 if (check_everything && hooks_invoke(COMPOSE_CHECK_BEFORE_SEND_HOOKLIST, compose))
5223 gint compose_send(Compose *compose)
5226 FolderItem *folder = NULL;
5228 gchar *msgpath = NULL;
5229 gboolean discard_window = FALSE;
5230 gchar *errstr = NULL;
5231 gchar *tmsgid = NULL;
5232 MainWindow *mainwin = mainwindow_get_mainwindow();
5233 gboolean queued_removed = FALSE;
5235 if (prefs_common.send_dialog_invisible
5236 || compose->batch == TRUE)
5237 discard_window = TRUE;
5239 compose_allow_user_actions (compose, FALSE);
5240 compose->sending = TRUE;
5242 if (compose_check_entries(compose, TRUE) == FALSE) {
5243 if (compose->batch) {
5244 gtk_widget_show_all(compose->window);
5250 val = compose_queue(compose, &msgnum, &folder, &msgpath, TRUE);
5253 if (compose->batch) {
5254 gtk_widget_show_all(compose->window);
5257 alertpanel_error(_("Could not queue message for sending:\n\n"
5258 "Charset conversion failed."));
5259 } else if (val == -5) {
5260 alertpanel_error(_("Could not queue message for sending:\n\n"
5261 "Couldn't get recipient encryption key."));
5262 } else if (val == -6) {
5264 } else if (val == -3) {
5265 if (privacy_peek_error())
5266 alertpanel_error(_("Could not queue message for sending:\n\n"
5267 "Signature failed: %s"), privacy_get_error());
5268 } else if (val == -2 && errno != 0) {
5269 alertpanel_error(_("Could not queue message for sending:\n\n%s."), g_strerror(errno));
5271 alertpanel_error(_("Could not queue message for sending."));
5276 tmsgid = compose->msgid ? g_strdup(compose->msgid) : NULL;
5277 if (discard_window) {
5278 compose->sending = FALSE;
5279 compose_close(compose);
5280 /* No more compose access in the normal codepath
5281 * after this point! */
5286 alertpanel_error(_("The message was queued but could not be "
5287 "sent.\nUse \"Send queued messages\" from "
5288 "the main window to retry."));
5289 if (!discard_window) {
5296 if (msgpath == NULL) {
5297 msgpath = folder_item_fetch_msg(folder, msgnum);
5298 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5301 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5302 claws_unlink(msgpath);
5305 if (!discard_window) {
5307 if (!queued_removed)
5308 folder_item_remove_msg(folder, msgnum);
5309 folder_item_scan(folder);
5311 /* make sure we delete that */
5312 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5314 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5315 folder_item_remove_msg(folder, tmp->msgnum);
5316 procmsg_msginfo_free(&tmp);
5323 if (!queued_removed)
5324 folder_item_remove_msg(folder, msgnum);
5325 folder_item_scan(folder);
5327 /* make sure we delete that */
5328 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5330 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5331 folder_item_remove_msg(folder, tmp->msgnum);
5332 procmsg_msginfo_free(&tmp);
5335 if (!discard_window) {
5336 compose->sending = FALSE;
5337 compose_allow_user_actions (compose, TRUE);
5338 compose_close(compose);
5342 alertpanel_error_log(_("%s\nUse \"Send queued messages\" from "
5343 "the main window to retry."), errstr);
5346 alertpanel_error_log(_("The message was queued but could not be "
5347 "sent.\nUse \"Send queued messages\" from "
5348 "the main window to retry."));
5350 if (!discard_window) {
5359 toolbar_main_set_sensitive(mainwin);
5360 main_window_set_menu_sensitive(mainwin);
5366 compose_allow_user_actions (compose, TRUE);
5367 compose->sending = FALSE;
5368 compose->modified = TRUE;
5369 toolbar_main_set_sensitive(mainwin);
5370 main_window_set_menu_sensitive(mainwin);
5375 static gboolean compose_use_attach(Compose *compose)
5377 GtkTreeModel *model = gtk_tree_view_get_model
5378 (GTK_TREE_VIEW(compose->attach_clist));
5379 return gtk_tree_model_iter_n_children(model, NULL) > 0;
5382 static gint compose_redirect_write_headers_from_headerlist(Compose *compose,
5385 gchar buf[BUFFSIZE];
5387 gboolean first_to_address;
5388 gboolean first_cc_address;
5390 ComposeHeaderEntry *headerentry;
5391 const gchar *headerentryname;
5392 const gchar *cc_hdr;
5393 const gchar *to_hdr;
5394 gboolean err = FALSE;
5396 debug_print("Writing redirect header\n");
5398 cc_hdr = prefs_common_translated_header_name("Cc:");
5399 to_hdr = prefs_common_translated_header_name("To:");
5401 first_to_address = TRUE;
5402 for (list = compose->header_list; list; list = list->next) {
5403 headerentry = ((ComposeHeaderEntry *)list->data);
5404 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5406 if (g_utf8_collate(headerentryname, to_hdr) == 0) {
5407 const gchar *entstr = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5408 Xstrdup_a(str, entstr, return -1);
5410 if (str[0] != '\0') {
5411 compose_convert_header
5412 (compose, buf, sizeof(buf), str,
5413 strlen("Resent-To") + 2, TRUE);
5415 if (first_to_address) {
5416 err |= (fprintf(fp, "Resent-To: ") < 0);
5417 first_to_address = FALSE;
5419 err |= (fprintf(fp, ",") < 0);
5421 err |= (fprintf(fp, "%s", buf) < 0);
5425 if (!first_to_address) {
5426 err |= (fprintf(fp, "\n") < 0);
5429 first_cc_address = TRUE;
5430 for (list = compose->header_list; list; list = list->next) {
5431 headerentry = ((ComposeHeaderEntry *)list->data);
5432 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5434 if (g_utf8_collate(headerentryname, cc_hdr) == 0) {
5435 const gchar *strg = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5436 Xstrdup_a(str, strg, return -1);
5438 if (str[0] != '\0') {
5439 compose_convert_header
5440 (compose, buf, sizeof(buf), str,
5441 strlen("Resent-Cc") + 2, TRUE);
5443 if (first_cc_address) {
5444 err |= (fprintf(fp, "Resent-Cc: ") < 0);
5445 first_cc_address = FALSE;
5447 err |= (fprintf(fp, ",") < 0);
5449 err |= (fprintf(fp, "%s", buf) < 0);
5453 if (!first_cc_address) {
5454 err |= (fprintf(fp, "\n") < 0);
5457 return (err ? -1:0);
5460 static gint compose_redirect_write_headers(Compose *compose, FILE *fp)
5462 gchar date[RFC822_DATE_BUFFSIZE];
5463 gchar buf[BUFFSIZE];
5465 const gchar *entstr;
5466 /* struct utsname utsbuf; */
5467 gboolean err = FALSE;
5469 cm_return_val_if_fail(fp != NULL, -1);
5470 cm_return_val_if_fail(compose->account != NULL, -1);
5471 cm_return_val_if_fail(compose->account->address != NULL, -1);
5474 if (prefs_common.hide_timezone)
5475 get_rfc822_date_hide_tz(date, sizeof(date));
5477 get_rfc822_date(date, sizeof(date));
5478 err |= (fprintf(fp, "Resent-Date: %s\n", date) < 0);
5481 if (compose->account->name && *compose->account->name) {
5482 compose_convert_header
5483 (compose, buf, sizeof(buf), compose->account->name,
5484 strlen("From: "), TRUE);
5485 err |= (fprintf(fp, "Resent-From: %s <%s>\n",
5486 buf, compose->account->address) < 0);
5488 err |= (fprintf(fp, "Resent-From: %s\n", compose->account->address) < 0);
5491 entstr = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5492 if (*entstr != '\0') {
5493 Xstrdup_a(str, entstr, return -1);
5496 compose_convert_header(compose, buf, sizeof(buf), str,
5497 strlen("Subject: "), FALSE);
5498 err |= (fprintf(fp, "Subject: %s\n", buf) < 0);
5502 /* Resent-Message-ID */
5503 if (compose->account->gen_msgid) {
5504 gchar *addr = prefs_account_generate_msgid(compose->account);
5505 err |= (fprintf(fp, "Resent-Message-ID: <%s>\n", addr) < 0);
5507 g_free(compose->msgid);
5508 compose->msgid = addr;
5510 compose->msgid = NULL;
5513 if (compose_redirect_write_headers_from_headerlist(compose, fp))
5516 /* separator between header and body */
5517 err |= (fputs("\n", fp) == EOF);
5519 return (err ? -1:0);
5522 static gint compose_redirect_write_to_file(Compose *compose, FILE *fdest)
5527 gchar rewrite_buf[BUFFSIZE];
5529 gboolean skip = FALSE;
5530 gboolean err = FALSE;
5531 gchar *not_included[]={
5532 "Return-Path:", "Delivered-To:", "Received:",
5533 "Subject:", "X-UIDL:", "AF:",
5534 "NF:", "PS:", "SRH:",
5535 "SFN:", "DSR:", "MID:",
5536 "CFG:", "PT:", "S:",
5537 "RQ:", "SSV:", "NSV:",
5538 "SSH:", "R:", "MAID:",
5539 "NAID:", "RMID:", "FMID:",
5540 "SCF:", "RRCPT:", "NG:",
5541 "X-Claws-Privacy", "X-Claws-Sign:", "X-Claws-Encrypt",
5542 "X-Claws-End-Special-Headers:", "X-Claws-Account-Id:",
5543 "X-Sylpheed-Privacy", "X-Sylpheed-Sign:", "X-Sylpheed-Encrypt",
5544 "X-Sylpheed-End-Special-Headers:", "X-Sylpheed-Account-Id:",
5545 "X-Claws-Auto-Wrapping:", "X-Claws-Auto-Indent:",
5550 if ((fp = g_fopen(compose->redirect_filename, "rb")) == NULL) {
5551 FILE_OP_ERROR(compose->redirect_filename, "fopen");
5555 while ((ret = procheader_get_one_field_asis(&buf, fp)) != -1) {
5557 for (i = 0; not_included[i] != NULL; i++) {
5558 if (g_ascii_strncasecmp(buf, not_included[i],
5559 strlen(not_included[i])) == 0) {
5569 if (fputs(buf, fdest) == -1) {
5575 if (!prefs_common.redirect_keep_from) {
5576 if (g_ascii_strncasecmp(buf, "From:",
5577 strlen("From:")) == 0) {
5578 err |= (fputs(" (by way of ", fdest) == EOF);
5579 if (compose->account->name
5580 && *compose->account->name) {
5581 gchar buffer[BUFFSIZE];
5583 compose_convert_header
5584 (compose, buffer, sizeof(buffer),
5585 compose->account->name,
5588 err |= (fprintf(fdest, "%s <%s>",
5590 compose->account->address) < 0);
5592 err |= (fprintf(fdest, "%s",
5593 compose->account->address) < 0);
5594 err |= (fputs(")", fdest) == EOF);
5600 if (fputs("\n", fdest) == -1)
5607 if (compose_redirect_write_headers(compose, fdest))
5610 while ((len = fread(rewrite_buf, sizeof(gchar), sizeof(rewrite_buf), fp)) > 0) {
5611 if (fwrite(rewrite_buf, sizeof(gchar), len, fdest) != len)
5625 static gint compose_write_to_file(Compose *compose, FILE *fp, gint action, gboolean attach_parts)
5627 GtkTextBuffer *buffer;
5628 GtkTextIter start, end, tmp;
5629 gchar *chars, *tmp_enc_file, *content;
5631 const gchar *out_codeset;
5632 EncodingType encoding = ENC_UNKNOWN;
5633 MimeInfo *mimemsg, *mimetext;
5635 const gchar *src_codeset = CS_INTERNAL;
5636 gchar *from_addr = NULL;
5637 gchar *from_name = NULL;
5640 if (action == COMPOSE_WRITE_FOR_SEND) {
5641 attach_parts = TRUE;
5643 /* We're sending the message, generate a Message-ID
5645 if (compose->msgid == NULL &&
5646 compose->account->gen_msgid) {
5647 compose->msgid = prefs_account_generate_msgid(compose->account);
5651 /* create message MimeInfo */
5652 mimemsg = procmime_mimeinfo_new();
5653 mimemsg->type = MIMETYPE_MESSAGE;
5654 mimemsg->subtype = g_strdup("rfc822");
5655 mimemsg->content = MIMECONTENT_MEM;
5656 mimemsg->tmp = TRUE; /* must free content later */
5657 mimemsg->data.mem = compose_get_header(compose);
5659 /* Create text part MimeInfo */
5660 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5661 gtk_text_buffer_get_end_iter(buffer, &end);
5664 /* We make sure that there is a newline at the end. */
5665 if (action == COMPOSE_WRITE_FOR_SEND && gtk_text_iter_backward_char(&tmp)) {
5666 chars = gtk_text_buffer_get_text(buffer, &tmp, &end, FALSE);
5667 if (*chars != '\n') {
5668 gtk_text_buffer_insert(buffer, &end, "\n", 1);
5672 /* get all composed text */
5673 gtk_text_buffer_get_start_iter(buffer, &start);
5674 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5676 out_codeset = conv_get_charset_str(compose->out_encoding);
5678 if (!out_codeset && is_ascii_str(chars)) {
5679 out_codeset = CS_US_ASCII;
5680 } else if (prefs_common.outgoing_fallback_to_ascii &&
5681 is_ascii_str(chars)) {
5682 out_codeset = CS_US_ASCII;
5683 encoding = ENC_7BIT;
5687 gchar *test_conv_global_out = NULL;
5688 gchar *test_conv_reply = NULL;
5690 /* automatic mode. be automatic. */
5691 codeconv_set_strict(TRUE);
5693 out_codeset = conv_get_outgoing_charset_str();
5695 debug_print("trying to convert to %s\n", out_codeset);
5696 test_conv_global_out = conv_codeset_strdup(chars, src_codeset, out_codeset);
5699 if (!test_conv_global_out && compose->orig_charset
5700 && strcmp(compose->orig_charset, CS_US_ASCII)) {
5701 out_codeset = compose->orig_charset;
5702 debug_print("failure; trying to convert to %s\n", out_codeset);
5703 test_conv_reply = conv_codeset_strdup(chars, src_codeset, out_codeset);
5706 if (!test_conv_global_out && !test_conv_reply) {
5708 out_codeset = CS_INTERNAL;
5709 debug_print("failure; finally using %s\n", out_codeset);
5711 g_free(test_conv_global_out);
5712 g_free(test_conv_reply);
5713 codeconv_set_strict(FALSE);
5716 if (encoding == ENC_UNKNOWN) {
5717 if (prefs_common.encoding_method == CTE_BASE64)
5718 encoding = ENC_BASE64;
5719 else if (prefs_common.encoding_method == CTE_QUOTED_PRINTABLE)
5720 encoding = ENC_QUOTED_PRINTABLE;
5721 else if (prefs_common.encoding_method == CTE_8BIT)
5722 encoding = ENC_8BIT;
5724 encoding = procmime_get_encoding_for_charset(out_codeset);
5727 debug_print("src encoding = %s, out encoding = %s, transfer encoding = %s\n",
5728 src_codeset, out_codeset, procmime_get_encoding_str(encoding));
5730 if (action == COMPOSE_WRITE_FOR_SEND) {
5731 codeconv_set_strict(TRUE);
5732 buf = conv_codeset_strdup(chars, src_codeset, out_codeset);
5733 codeconv_set_strict(FALSE);
5738 msg = g_strdup_printf(_("Can't convert the character encoding of the message \n"
5739 "to the specified %s charset.\n"
5740 "Send it as %s?"), out_codeset, src_codeset);
5741 aval = alertpanel_full(_("Error"), msg, GTK_STOCK_CANCEL,
5742 g_strconcat("+", _("_Send"), NULL), NULL, FALSE,
5743 NULL, ALERT_ERROR, G_ALERTDEFAULT);
5746 if (aval != G_ALERTALTERNATE) {
5751 out_codeset = src_codeset;
5757 out_codeset = src_codeset;
5762 if (prefs_common.rewrite_first_from && (encoding == ENC_8BIT || encoding == ENC_7BIT)) {
5763 if (!strncmp(buf, "From ", sizeof("From ")-1) ||
5764 strstr(buf, "\nFrom ") != NULL) {
5765 encoding = ENC_QUOTED_PRINTABLE;
5769 mimetext = procmime_mimeinfo_new();
5770 mimetext->content = MIMECONTENT_MEM;
5771 mimetext->tmp = TRUE; /* must free content later */
5772 /* dup'ed because procmime_encode_content can turn it into a tmpfile
5773 * and free the data, which we need later. */
5774 mimetext->data.mem = g_strdup(buf);
5775 mimetext->type = MIMETYPE_TEXT;
5776 mimetext->subtype = g_strdup("plain");
5777 g_hash_table_insert(mimetext->typeparameters, g_strdup("charset"),
5778 g_strdup(out_codeset));
5780 /* protect trailing spaces when signing message */
5781 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5782 privacy_system_can_sign(compose->privacy_system)) {
5783 encoding = ENC_QUOTED_PRINTABLE;
5787 debug_print("main text: %Id bytes encoded as %s in %d\n",
5789 debug_print("main text: %zd bytes encoded as %s in %d\n",
5791 strlen(buf), out_codeset, encoding);
5793 /* check for line length limit */
5794 if (action == COMPOSE_WRITE_FOR_SEND &&
5795 encoding != ENC_QUOTED_PRINTABLE && encoding != ENC_BASE64 &&
5796 check_line_length(buf, 1000, &line) < 0) {
5799 msg = g_strdup_printf
5800 (_("Line %d exceeds the line length limit (998 bytes).\n"
5801 "The contents of the message might be broken on the way to the delivery.\n"
5803 "Send it anyway?"), line + 1);
5804 aval = alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, GTK_STOCK_OK, NULL);
5806 if (aval != G_ALERTALTERNATE) {
5812 if (encoding != ENC_UNKNOWN)
5813 procmime_encode_content(mimetext, encoding);
5815 /* append attachment parts */
5816 if (compose_use_attach(compose) && attach_parts) {
5817 MimeInfo *mimempart;
5818 gchar *boundary = NULL;
5819 mimempart = procmime_mimeinfo_new();
5820 mimempart->content = MIMECONTENT_EMPTY;
5821 mimempart->type = MIMETYPE_MULTIPART;
5822 mimempart->subtype = g_strdup("mixed");
5826 boundary = generate_mime_boundary(NULL);
5827 } while (strstr(buf, boundary) != NULL);
5829 g_hash_table_insert(mimempart->typeparameters, g_strdup("boundary"),
5832 mimetext->disposition = DISPOSITIONTYPE_INLINE;
5834 g_node_append(mimempart->node, mimetext->node);
5835 g_node_append(mimemsg->node, mimempart->node);
5837 if (compose_add_attachments(compose, mimempart) < 0)
5840 g_node_append(mimemsg->node, mimetext->node);
5844 if (strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) != 0) {
5845 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
5846 /* extract name and address */
5847 if (strstr(spec, " <") && strstr(spec, ">")) {
5848 from_addr = g_strdup(strrchr(spec, '<')+1);
5849 *(strrchr(from_addr, '>')) = '\0';
5850 from_name = g_strdup(spec);
5851 *(strrchr(from_name, '<')) = '\0';
5858 /* sign message if sending */
5859 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5860 privacy_system_can_sign(compose->privacy_system))
5861 if (!privacy_sign(compose->privacy_system, mimemsg,
5862 compose->account, from_addr)) {
5870 if (compose->use_encryption) {
5871 if (compose->encdata != NULL &&
5872 strcmp(compose->encdata, "_DONT_ENCRYPT_")) {
5874 /* First, write an unencrypted copy and save it to outbox, if
5875 * user wants that. */
5876 if (compose->account->save_encrypted_as_clear_text) {
5877 debug_print("saving sent message unencrypted...\n");
5878 FILE *tmpfp = get_tmpfile_in_dir(get_mime_tmp_dir(), &tmp_enc_file);
5882 /* fp now points to a file with headers written,
5883 * let's make a copy. */
5885 content = file_read_stream_to_str(fp);
5887 str_write_to_file(content, tmp_enc_file);
5890 /* Now write the unencrypted body. */
5891 if ((tmpfp = g_fopen(tmp_enc_file, "a")) != NULL) {
5892 procmime_write_mimeinfo(mimemsg, tmpfp);
5895 outbox = folder_find_item_from_identifier(compose_get_save_to(compose));
5897 outbox = folder_get_default_outbox();
5899 procmsg_save_to_outbox(outbox, tmp_enc_file, TRUE);
5900 claws_unlink(tmp_enc_file);
5902 g_warning("Can't open file '%s'", tmp_enc_file);
5905 g_warning("couldn't get tempfile");
5908 if (!privacy_encrypt(compose->privacy_system, mimemsg, compose->encdata)) {
5909 debug_print("Couldn't encrypt mime structure: %s.\n",
5910 privacy_get_error());
5911 alertpanel_error(_("Couldn't encrypt the email: %s"),
5912 privacy_get_error());
5917 procmime_write_mimeinfo(mimemsg, fp);
5919 procmime_mimeinfo_free_all(&mimemsg);
5924 static gint compose_write_body_to_file(Compose *compose, const gchar *file)
5926 GtkTextBuffer *buffer;
5927 GtkTextIter start, end;
5932 if ((fp = g_fopen(file, "wb")) == NULL) {
5933 FILE_OP_ERROR(file, "fopen");
5937 /* chmod for security */
5938 if (change_file_mode_rw(fp, file) < 0) {
5939 FILE_OP_ERROR(file, "chmod");
5940 g_warning("can't change file mode");
5943 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5944 gtk_text_buffer_get_start_iter(buffer, &start);
5945 gtk_text_buffer_get_end_iter(buffer, &end);
5946 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5948 chars = conv_codeset_strdup
5949 (tmp, CS_INTERNAL, conv_get_locale_charset_str());
5958 len = strlen(chars);
5959 if (fwrite(chars, sizeof(gchar), len, fp) != len) {
5960 FILE_OP_ERROR(file, "fwrite");
5969 if (fclose(fp) == EOF) {
5970 FILE_OP_ERROR(file, "fclose");
5977 static gint compose_remove_reedit_target(Compose *compose, gboolean force)
5980 MsgInfo *msginfo = compose->targetinfo;
5982 cm_return_val_if_fail(compose->mode == COMPOSE_REEDIT, -1);
5983 if (!msginfo) return -1;
5985 if (!force && MSG_IS_LOCKED(msginfo->flags))
5988 item = msginfo->folder;
5989 cm_return_val_if_fail(item != NULL, -1);
5991 if (procmsg_msg_exist(msginfo) &&
5992 (folder_has_parent_of_type(item, F_QUEUE) ||
5993 folder_has_parent_of_type(item, F_DRAFT)
5994 || msginfo == compose->autosaved_draft)) {
5995 if (folder_item_remove_msg(item, msginfo->msgnum) < 0) {
5996 g_warning("can't remove the old message");
5999 debug_print("removed reedit target %d\n", msginfo->msgnum);
6006 static void compose_remove_draft(Compose *compose)
6009 MsgInfo *msginfo = compose->targetinfo;
6010 drafts = account_get_special_folder(compose->account, F_DRAFT);
6012 if (procmsg_msg_exist(msginfo)) {
6013 folder_item_remove_msg(drafts, msginfo->msgnum);
6018 gint compose_queue(Compose *compose, gint *msgnum, FolderItem **item, gchar **msgpath,
6019 gboolean remove_reedit_target)
6021 return compose_queue_sub (compose, msgnum, item, msgpath, FALSE, remove_reedit_target);
6024 static gboolean compose_warn_encryption(Compose *compose)
6026 const gchar *warning = privacy_get_encrypt_warning(compose->privacy_system);
6027 AlertValue val = G_ALERTALTERNATE;
6029 if (warning == NULL)
6032 val = alertpanel_full(_("Encryption warning"), warning,
6033 GTK_STOCK_CANCEL, g_strconcat("+", _("C_ontinue"), NULL), NULL,
6034 TRUE, NULL, ALERT_WARNING, G_ALERTALTERNATE);
6035 if (val & G_ALERTDISABLE) {
6036 val &= ~G_ALERTDISABLE;
6037 if (val == G_ALERTALTERNATE)
6038 privacy_inhibit_encrypt_warning(compose->privacy_system,
6042 if (val == G_ALERTALTERNATE) {
6049 static gint compose_queue_sub(Compose *compose, gint *msgnum, FolderItem **item,
6050 gchar **msgpath, gboolean perform_checks,
6051 gboolean remove_reedit_target)
6058 PrefsAccount *mailac = NULL, *newsac = NULL;
6059 gboolean err = FALSE;
6061 debug_print("queueing message...\n");
6062 cm_return_val_if_fail(compose->account != NULL, -1);
6064 if (compose_check_entries(compose, perform_checks) == FALSE) {
6065 if (compose->batch) {
6066 gtk_widget_show_all(compose->window);
6071 if (!compose->to_list && !compose->newsgroup_list) {
6072 g_warning("can't get recipient list.");
6076 if (compose->to_list) {
6077 if (compose->account->protocol != A_NNTP)
6078 mailac = compose->account;
6079 else if (cur_account && cur_account->protocol != A_NNTP)
6080 mailac = cur_account;
6081 else if (!(mailac = compose_current_mail_account())) {
6082 alertpanel_error(_("No account for sending mails available!"));
6087 if (compose->newsgroup_list) {
6088 if (compose->account->protocol == A_NNTP)
6089 newsac = compose->account;
6091 alertpanel_error(_("Selected account isn't NNTP: Posting is impossible."));
6096 /* write queue header */
6097 tmp = g_strdup_printf("%s%cqueue.%p%08x", get_tmp_dir(),
6098 G_DIR_SEPARATOR, compose, (guint) rand());
6099 debug_print("queuing to %s\n", tmp);
6100 if ((fp = g_fopen(tmp, "w+b")) == NULL) {
6101 FILE_OP_ERROR(tmp, "fopen");
6106 if (change_file_mode_rw(fp, tmp) < 0) {
6107 FILE_OP_ERROR(tmp, "chmod");
6108 g_warning("can't change file mode");
6111 /* queueing variables */
6112 err |= (fprintf(fp, "AF:\n") < 0);
6113 err |= (fprintf(fp, "NF:0\n") < 0);
6114 err |= (fprintf(fp, "PS:10\n") < 0);
6115 err |= (fprintf(fp, "SRH:1\n") < 0);
6116 err |= (fprintf(fp, "SFN:\n") < 0);
6117 err |= (fprintf(fp, "DSR:\n") < 0);
6119 err |= (fprintf(fp, "MID:<%s>\n", compose->msgid) < 0);
6121 err |= (fprintf(fp, "MID:\n") < 0);
6122 err |= (fprintf(fp, "CFG:\n") < 0);
6123 err |= (fprintf(fp, "PT:0\n") < 0);
6124 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
6125 err |= (fprintf(fp, "RQ:\n") < 0);
6127 err |= (fprintf(fp, "SSV:%s\n", mailac->smtp_server) < 0);
6129 err |= (fprintf(fp, "SSV:\n") < 0);
6131 err |= (fprintf(fp, "NSV:%s\n", newsac->nntp_server) < 0);
6133 err |= (fprintf(fp, "NSV:\n") < 0);
6134 err |= (fprintf(fp, "SSH:\n") < 0);
6135 /* write recepient list */
6136 if (compose->to_list) {
6137 err |= (fprintf(fp, "R:<%s>", (gchar *)compose->to_list->data) < 0);
6138 for (cur = compose->to_list->next; cur != NULL;
6140 err |= (fprintf(fp, ",<%s>", (gchar *)cur->data) < 0);
6141 err |= (fprintf(fp, "\n") < 0);
6143 /* write newsgroup list */
6144 if (compose->newsgroup_list) {
6145 err |= (fprintf(fp, "NG:") < 0);
6146 err |= (fprintf(fp, "%s", (gchar *)compose->newsgroup_list->data) < 0);
6147 for (cur = compose->newsgroup_list->next; cur != NULL; cur = cur->next)
6148 err |= (fprintf(fp, ",%s", (gchar *)cur->data) < 0);
6149 err |= (fprintf(fp, "\n") < 0);
6151 /* Sylpheed account IDs */
6153 err |= (fprintf(fp, "MAID:%d\n", mailac->account_id) < 0);
6155 err |= (fprintf(fp, "NAID:%d\n", newsac->account_id) < 0);
6158 if (compose->privacy_system != NULL) {
6159 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
6160 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
6161 if (compose->use_encryption) {
6162 if (!compose_warn_encryption(compose)) {
6168 if (mailac && mailac->encrypt_to_self) {
6169 GSList *tmp_list = g_slist_copy(compose->to_list);
6170 tmp_list = g_slist_append(tmp_list, compose->account->address);
6171 compose->encdata = privacy_get_encrypt_data(compose->privacy_system, tmp_list);
6172 g_slist_free(tmp_list);
6174 compose->encdata = privacy_get_encrypt_data(compose->privacy_system, compose->to_list);
6176 if (compose->encdata != NULL) {
6177 if (strcmp(compose->encdata, "_DONT_ENCRYPT_")) {
6178 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
6179 err |= (fprintf(fp, "X-Claws-Encrypt-Data:%s\n",
6180 compose->encdata) < 0);
6181 } /* else we finally dont want to encrypt */
6183 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
6184 /* and if encdata was null, it means there's been a problem in
6187 g_warning("failed to write queue message");
6196 /* Save copy folder */
6197 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
6198 gchar *savefolderid;
6200 savefolderid = compose_get_save_to(compose);
6201 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
6202 g_free(savefolderid);
6204 /* Save copy folder */
6205 if (compose->return_receipt) {
6206 err |= (fprintf(fp, "RRCPT:1\n") < 0);
6208 /* Message-ID of message replying to */
6209 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
6210 gchar *folderid = NULL;
6212 if (compose->replyinfo->folder)
6213 folderid = folder_item_get_identifier(compose->replyinfo->folder);
6214 if (folderid == NULL)
6215 folderid = g_strdup("NULL");
6217 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
6220 /* Message-ID of message forwarding to */
6221 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
6222 gchar *folderid = NULL;
6224 if (compose->fwdinfo->folder)
6225 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
6226 if (folderid == NULL)
6227 folderid = g_strdup("NULL");
6229 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
6233 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
6234 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
6236 /* end of headers */
6237 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
6239 if (compose->redirect_filename != NULL) {
6240 if (compose_redirect_write_to_file(compose, fp) < 0) {
6248 if ((result = compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_SEND, TRUE)) < 0) {
6252 return result - 1; /* -2 for a generic error, -3 for signing error, -4 for encoding */
6256 g_warning("failed to write queue message");
6262 if (fclose(fp) == EOF) {
6263 FILE_OP_ERROR(tmp, "fclose");
6269 if (item && *item) {
6272 queue = account_get_special_folder(compose->account, F_QUEUE);
6275 g_warning("can't find queue folder");
6280 folder_item_scan(queue);
6281 if ((num = folder_item_add_msg(queue, tmp, NULL, FALSE)) < 0) {
6282 g_warning("can't queue the message");
6288 if (msgpath == NULL) {
6294 if (compose->mode == COMPOSE_REEDIT && remove_reedit_target) {
6295 compose_remove_reedit_target(compose, FALSE);
6298 if ((msgnum != NULL) && (item != NULL)) {
6306 static int compose_add_attachments(Compose *compose, MimeInfo *parent)
6309 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
6312 gchar *type, *subtype;
6313 GtkTreeModel *model;
6316 model = gtk_tree_view_get_model(tree_view);
6318 if (!gtk_tree_model_get_iter_first(model, &iter))
6321 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
6323 if (!is_file_exist(ainfo->file)) {
6324 gchar *msg = g_strdup_printf(_("Attachment %s doesn't exist anymore. Ignore?"), ainfo->file);
6325 AlertValue val = alertpanel_full(_("Warning"), msg, _("Cancel sending"), _("Ignore attachment"),
6326 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
6328 if (val == G_ALERTDEFAULT) {
6333 if (g_stat(ainfo->file, &statbuf) < 0)
6336 mimepart = procmime_mimeinfo_new();
6337 mimepart->content = MIMECONTENT_FILE;
6338 mimepart->data.filename = g_strdup(ainfo->file);
6339 mimepart->tmp = FALSE; /* or we destroy our attachment */
6340 mimepart->offset = 0;
6341 mimepart->length = statbuf.st_size;
6343 type = g_strdup(ainfo->content_type);
6345 if (!strchr(type, '/')) {
6347 type = g_strdup("application/octet-stream");
6350 subtype = strchr(type, '/') + 1;
6351 *(subtype - 1) = '\0';
6352 mimepart->type = procmime_get_media_type(type);
6353 mimepart->subtype = g_strdup(subtype);
6356 if (mimepart->type == MIMETYPE_MESSAGE &&
6357 !g_ascii_strcasecmp(mimepart->subtype, "rfc822")) {
6358 mimepart->disposition = DISPOSITIONTYPE_INLINE;
6359 } else if (mimepart->type == MIMETYPE_TEXT) {
6360 if (!ainfo->name && g_ascii_strcasecmp(mimepart->subtype, "plain")) {
6361 /* Text parts with no name come from multipart/alternative
6362 * forwards. Make sure the recipient won't look at the
6363 * original HTML part by mistake. */
6364 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6365 ainfo->name = g_strdup_printf(_("Original %s part"),
6369 g_hash_table_insert(mimepart->typeparameters,
6370 g_strdup("charset"), g_strdup(ainfo->charset));
6372 if (ainfo->name && mimepart->type != MIMETYPE_MESSAGE) {
6373 if (mimepart->type == MIMETYPE_APPLICATION &&
6374 !strcmp2(mimepart->subtype, "octet-stream"))
6375 g_hash_table_insert(mimepart->typeparameters,
6376 g_strdup("name"), g_strdup(ainfo->name));
6377 g_hash_table_insert(mimepart->dispositionparameters,
6378 g_strdup("filename"), g_strdup(ainfo->name));
6379 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6382 if (mimepart->type == MIMETYPE_MESSAGE
6383 || mimepart->type == MIMETYPE_MULTIPART)
6384 ainfo->encoding = ENC_BINARY;
6385 else if (compose->use_signing) {
6386 if (ainfo->encoding == ENC_7BIT)
6387 ainfo->encoding = ENC_QUOTED_PRINTABLE;
6388 else if (ainfo->encoding == ENC_8BIT)
6389 ainfo->encoding = ENC_BASE64;
6392 procmime_encode_content(mimepart, ainfo->encoding);
6394 g_node_append(parent->node, mimepart->node);
6395 } while (gtk_tree_model_iter_next(model, &iter));
6400 static gchar *compose_quote_list_of_addresses(gchar *str)
6402 GSList *list = NULL, *item = NULL;
6403 gchar *qname = NULL, *faddr = NULL, *result = NULL;
6405 list = address_list_append_with_comments(list, str);
6406 for (item = list; item != NULL; item = item->next) {
6407 gchar *spec = item->data;
6408 gchar *endofname = strstr(spec, " <");
6409 if (endofname != NULL) {
6412 QUOTE_IF_REQUIRED_NORMAL(qname, spec, return NULL);
6413 qqname = escape_internal_quotes(qname, '"');
6415 if (*qname != *spec || qqname != qname) { /* has been quoted, compute new */
6416 gchar *addr = g_strdup(endofname);
6417 gchar *name = (qqname != qname)? qqname: g_strdup(qname);
6418 faddr = g_strconcat(name, addr, NULL);
6421 debug_print("new auto-quoted address: '%s'\n", faddr);
6425 result = g_strdup((faddr != NULL)? faddr: spec);
6427 result = g_strconcat(result,
6429 (faddr != NULL)? faddr: spec,
6432 if (faddr != NULL) {
6437 slist_free_strings_full(list);
6442 #define IS_IN_CUSTOM_HEADER(header) \
6443 (compose->account->add_customhdr && \
6444 custom_header_find(compose->account->customhdr_list, header) != NULL)
6446 static const gchar *compose_untranslated_header_name(gchar *header_name)
6448 /* return the untranslated header name, if header_name is a known
6449 header name, in either its translated or untranslated form, with
6450 or without trailing colon. otherwise, returns header_name. */
6451 gchar *translated_header_name;
6452 gchar *translated_header_name_wcolon;
6453 const gchar *untranslated_header_name;
6454 const gchar *untranslated_header_name_wcolon;
6457 cm_return_val_if_fail(header_name != NULL, NULL);
6459 for (i = 0; HEADERS[i].header_name != NULL; i++) {
6460 untranslated_header_name = HEADERS[i].header_name;
6461 untranslated_header_name_wcolon = HEADERS[i].header_name_w_colon;
6463 translated_header_name = gettext(untranslated_header_name);
6464 translated_header_name_wcolon = gettext(untranslated_header_name_wcolon);
6466 if (!strcmp(header_name, untranslated_header_name) ||
6467 !strcmp(header_name, translated_header_name)) {
6468 return untranslated_header_name;
6470 if (!strcmp(header_name, untranslated_header_name_wcolon) ||
6471 !strcmp(header_name, translated_header_name_wcolon)) {
6472 return untranslated_header_name_wcolon;
6476 debug_print("compose_untranslated_header_name: unknown header '%s'\n", header_name);
6480 static void compose_add_headerfield_from_headerlist(Compose *compose,
6482 const gchar *fieldname,
6483 const gchar *seperator)
6485 gchar *str, *fieldname_w_colon;
6486 gboolean add_field = FALSE;
6488 ComposeHeaderEntry *headerentry;
6489 const gchar *headerentryname;
6490 const gchar *trans_fieldname;
6493 if (IS_IN_CUSTOM_HEADER(fieldname))
6496 debug_print("Adding %s-fields\n", fieldname);
6498 fieldstr = g_string_sized_new(64);
6500 fieldname_w_colon = g_strconcat(fieldname, ":", NULL);
6501 trans_fieldname = prefs_common_translated_header_name(fieldname_w_colon);
6503 for (list = compose->header_list; list; list = list->next) {
6504 headerentry = ((ComposeHeaderEntry *)list->data);
6505 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
6507 if (!g_utf8_collate(trans_fieldname, headerentryname)) {
6508 gchar * ustr = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
6510 str = compose_quote_list_of_addresses(ustr);
6512 if (str != NULL && str[0] != '\0') {
6514 g_string_append(fieldstr, seperator);
6515 g_string_append(fieldstr, str);
6524 buf = g_new0(gchar, fieldstr->len * 4 + 256);
6525 compose_convert_header
6526 (compose, buf, fieldstr->len * 4 + 256, fieldstr->str,
6527 strlen(fieldname) + 2, TRUE);
6528 g_string_append_printf(header, "%s: %s\n", fieldname, buf);
6532 g_free(fieldname_w_colon);
6533 g_string_free(fieldstr, TRUE);
6538 static gchar *compose_get_manual_headers_info(Compose *compose)
6540 GString *sh_header = g_string_new(" ");
6542 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6544 for (list = compose->header_list; list; list = list->next) {
6545 ComposeHeaderEntry *headerentry;
6548 gchar *headername_wcolon;
6549 const gchar *headername_trans;
6551 gboolean standard_header = FALSE;
6553 headerentry = ((ComposeHeaderEntry *)list->data);
6555 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6557 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6562 if (!strstr(tmp, ":")) {
6563 headername_wcolon = g_strconcat(tmp, ":", NULL);
6564 headername = g_strdup(tmp);
6566 headername_wcolon = g_strdup(tmp);
6567 headername = g_strdup(strtok(tmp, ":"));
6571 string = std_headers;
6572 while (*string != NULL) {
6573 headername_trans = prefs_common_translated_header_name(*string);
6574 if (!strcmp(headername_trans, headername_wcolon))
6575 standard_header = TRUE;
6578 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
6579 g_string_append_printf(sh_header, "%s ", headername);
6581 g_free(headername_wcolon);
6583 g_string_truncate(sh_header, strlen(sh_header->str) - 1); /* remove last space */
6584 return g_string_free(sh_header, FALSE);
6587 static gchar *compose_get_header(Compose *compose)
6589 gchar date[RFC822_DATE_BUFFSIZE];
6590 gchar buf[BUFFSIZE];
6591 const gchar *entry_str;
6595 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6597 gchar *from_name = NULL, *from_address = NULL;
6600 cm_return_val_if_fail(compose->account != NULL, NULL);
6601 cm_return_val_if_fail(compose->account->address != NULL, NULL);
6603 header = g_string_sized_new(64);
6606 if (prefs_common.hide_timezone)
6607 get_rfc822_date_hide_tz(date, sizeof(date));
6609 get_rfc822_date(date, sizeof(date));
6610 g_string_append_printf(header, "Date: %s\n", date);
6614 if (compose->account->name && *compose->account->name) {
6616 QUOTE_IF_REQUIRED(buf, compose->account->name);
6617 tmp = g_strdup_printf("%s <%s>",
6618 buf, compose->account->address);
6620 tmp = g_strdup_printf("%s",
6621 compose->account->address);
6623 if (!strcmp(gtk_entry_get_text(GTK_ENTRY(compose->from_name)), tmp)
6624 || strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) == 0) {
6626 from_name = compose->account->name ? g_strdup(compose->account->name):NULL;
6627 from_address = g_strdup(compose->account->address);
6629 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
6630 /* extract name and address */
6631 if (strstr(spec, " <") && strstr(spec, ">")) {
6632 from_address = g_strdup(strrchr(spec, '<')+1);
6633 *(strrchr(from_address, '>')) = '\0';
6634 from_name = g_strdup(spec);
6635 *(strrchr(from_name, '<')) = '\0';
6638 from_address = g_strdup(spec);
6645 if (from_name && *from_name) {
6647 compose_convert_header
6648 (compose, buf, sizeof(buf), from_name,
6649 strlen("From: "), TRUE);
6650 QUOTE_IF_REQUIRED(name, buf);
6651 qname = escape_internal_quotes(name, '"');
6653 g_string_append_printf(header, "From: %s <%s>\n",
6654 qname, from_address);
6655 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To") &&
6656 compose->return_receipt) {
6657 compose_convert_header(compose, buf, sizeof(buf), from_name,
6658 strlen("Disposition-Notification-To: "),
6660 g_string_append_printf(header, "Disposition-Notification-To: %s <%s>\n", buf, from_address);
6665 g_string_append_printf(header, "From: %s\n", from_address);
6666 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To") &&
6667 compose->return_receipt)
6668 g_string_append_printf(header, "Disposition-Notification-To: %s\n", from_address);
6672 g_free(from_address);
6675 compose_add_headerfield_from_headerlist(compose, header, "To", ", ");
6678 compose_add_headerfield_from_headerlist(compose, header, "Newsgroups", ",");
6681 compose_add_headerfield_from_headerlist(compose, header, "Cc", ", ");
6685 * If this account is a NNTP account remove Bcc header from
6686 * message body since it otherwise will be publicly shown
6688 if (compose->account->protocol != A_NNTP)
6689 compose_add_headerfield_from_headerlist(compose, header, "Bcc", ", ");
6692 str = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
6694 if (*str != '\0' && !IS_IN_CUSTOM_HEADER("Subject")) {
6697 compose_convert_header(compose, buf, sizeof(buf), str,
6698 strlen("Subject: "), FALSE);
6699 g_string_append_printf(header, "Subject: %s\n", buf);
6705 if (compose->msgid != NULL && strlen(compose->msgid) > 0) {
6706 g_string_append_printf(header, "Message-ID: <%s>\n",
6710 if (compose->remove_references == FALSE) {
6712 if (compose->inreplyto && compose->to_list)
6713 g_string_append_printf(header, "In-Reply-To: <%s>\n", compose->inreplyto);
6716 if (compose->references)
6717 g_string_append_printf(header, "References: %s\n", compose->references);
6721 compose_add_headerfield_from_headerlist(compose, header, "Followup-To", ",");
6724 compose_add_headerfield_from_headerlist(compose, header, "Reply-To", ", ");
6727 if (compose->account->organization &&
6728 strlen(compose->account->organization) &&
6729 !IS_IN_CUSTOM_HEADER("Organization")) {
6730 compose_convert_header(compose, buf, sizeof(buf),
6731 compose->account->organization,
6732 strlen("Organization: "), FALSE);
6733 g_string_append_printf(header, "Organization: %s\n", buf);
6736 /* Program version and system info */
6737 if (compose->account->gen_xmailer &&
6738 g_slist_length(compose->to_list) && !IS_IN_CUSTOM_HEADER("X-Mailer") &&
6739 !compose->newsgroup_list) {
6740 g_string_append_printf(header, "X-Mailer: %s (GTK+ %d.%d.%d; %s)\n",
6742 gtk_major_version, gtk_minor_version, gtk_micro_version,
6745 if (compose->account->gen_xmailer &&
6746 g_slist_length(compose->newsgroup_list) && !IS_IN_CUSTOM_HEADER("X-Newsreader")) {
6747 g_string_append_printf(header, "X-Newsreader: %s (GTK+ %d.%d.%d; %s)\n",
6749 gtk_major_version, gtk_minor_version, gtk_micro_version,
6753 /* custom headers */
6754 if (compose->account->add_customhdr) {
6757 for (cur = compose->account->customhdr_list; cur != NULL;
6759 CustomHeader *chdr = (CustomHeader *)cur->data;
6761 if (custom_header_is_allowed(chdr->name)
6762 && chdr->value != NULL
6763 && *(chdr->value) != '\0') {
6764 compose_convert_header
6765 (compose, buf, sizeof(buf),
6767 strlen(chdr->name) + 2, FALSE);
6768 g_string_append_printf(header, "%s: %s\n", chdr->name, buf);
6773 /* Automatic Faces and X-Faces */
6774 if (get_account_xface (buf, sizeof(buf), compose->account->account_name) == 0) {
6775 g_string_append_printf(header, "X-Face: %s\n", buf);
6777 else if (get_default_xface (buf, sizeof(buf)) == 0) {
6778 g_string_append_printf(header, "X-Face: %s\n", buf);
6780 if (get_account_face (buf, sizeof(buf), compose->account->account_name) == 0) {
6781 g_string_append_printf(header, "Face: %s\n", buf);
6783 else if (get_default_face (buf, sizeof(buf)) == 0) {
6784 g_string_append_printf(header, "Face: %s\n", buf);
6788 switch (compose->priority) {
6789 case PRIORITY_HIGHEST: g_string_append_printf(header, "Importance: high\n"
6790 "X-Priority: 1 (Highest)\n");
6792 case PRIORITY_HIGH: g_string_append_printf(header, "Importance: high\n"
6793 "X-Priority: 2 (High)\n");
6795 case PRIORITY_NORMAL: break;
6796 case PRIORITY_LOW: g_string_append_printf(header, "Importance: low\n"
6797 "X-Priority: 4 (Low)\n");
6799 case PRIORITY_LOWEST: g_string_append_printf(header, "Importance: low\n"
6800 "X-Priority: 5 (Lowest)\n");
6802 default: debug_print("compose: priority unknown : %d\n",
6806 /* get special headers */
6807 for (list = compose->header_list; list; list = list->next) {
6808 ComposeHeaderEntry *headerentry;
6811 gchar *headername_wcolon;
6812 const gchar *headername_trans;
6815 gboolean standard_header = FALSE;
6817 headerentry = ((ComposeHeaderEntry *)list->data);
6819 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6821 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6826 if (!strstr(tmp, ":")) {
6827 headername_wcolon = g_strconcat(tmp, ":", NULL);
6828 headername = g_strdup(tmp);
6830 headername_wcolon = g_strdup(tmp);
6831 headername = g_strdup(strtok(tmp, ":"));
6835 entry_str = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
6836 Xstrdup_a(headervalue, entry_str, return NULL);
6837 subst_char(headervalue, '\r', ' ');
6838 subst_char(headervalue, '\n', ' ');
6839 g_strstrip(headervalue);
6840 if (*headervalue != '\0') {
6841 string = std_headers;
6842 while (*string != NULL && !standard_header) {
6843 headername_trans = prefs_common_translated_header_name(*string);
6844 /* support mixed translated and untranslated headers */
6845 if (!strcmp(headername_trans, headername_wcolon) || !strcmp(*string, headername_wcolon))
6846 standard_header = TRUE;
6849 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername)) {
6850 /* store untranslated header name */
6851 g_string_append_printf(header, "%s %s\n",
6852 compose_untranslated_header_name(headername_wcolon), headervalue);
6856 g_free(headername_wcolon);
6860 g_string_free(header, FALSE);
6865 #undef IS_IN_CUSTOM_HEADER
6867 static void compose_convert_header(Compose *compose, gchar *dest, gint len, gchar *src,
6868 gint header_len, gboolean addr_field)
6870 gchar *tmpstr = NULL;
6871 const gchar *out_codeset = NULL;
6873 cm_return_if_fail(src != NULL);
6874 cm_return_if_fail(dest != NULL);
6876 if (len < 1) return;
6878 tmpstr = g_strdup(src);
6880 subst_char(tmpstr, '\n', ' ');
6881 subst_char(tmpstr, '\r', ' ');
6884 if (!g_utf8_validate(tmpstr, -1, NULL)) {
6885 gchar *mybuf = g_malloc(strlen(tmpstr)*2 +1);
6886 conv_localetodisp(mybuf, strlen(tmpstr)*2 +1, tmpstr);
6891 codeconv_set_strict(TRUE);
6892 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6893 conv_get_charset_str(compose->out_encoding));
6894 codeconv_set_strict(FALSE);
6896 if (!dest || *dest == '\0') {
6897 gchar *test_conv_global_out = NULL;
6898 gchar *test_conv_reply = NULL;
6900 /* automatic mode. be automatic. */
6901 codeconv_set_strict(TRUE);
6903 out_codeset = conv_get_outgoing_charset_str();
6905 debug_print("trying to convert to %s\n", out_codeset);
6906 test_conv_global_out = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6909 if (!test_conv_global_out && compose->orig_charset
6910 && strcmp(compose->orig_charset, CS_US_ASCII)) {
6911 out_codeset = compose->orig_charset;
6912 debug_print("failure; trying to convert to %s\n", out_codeset);
6913 test_conv_reply = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6916 if (!test_conv_global_out && !test_conv_reply) {
6918 out_codeset = CS_INTERNAL;
6919 debug_print("finally using %s\n", out_codeset);
6921 g_free(test_conv_global_out);
6922 g_free(test_conv_reply);
6923 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6925 codeconv_set_strict(FALSE);
6930 static void compose_add_to_addressbook_cb(GtkMenuItem *menuitem, gpointer user_data)
6934 cm_return_if_fail(user_data != NULL);
6936 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(user_data)));
6937 g_strstrip(address);
6938 if (*address != '\0') {
6939 gchar *name = procheader_get_fromname(address);
6940 extract_address(address);
6941 #ifndef USE_ALT_ADDRBOOK
6942 addressbook_add_contact(name, address, NULL, NULL);
6944 debug_print("%s: %s\n", name, address);
6945 if (addressadd_selection(name, address, NULL, NULL)) {
6946 debug_print( "addressbook_add_contact - added\n" );
6953 static void compose_entry_popup_extend(GtkEntry *entry, GtkMenu *menu, gpointer user_data)
6955 GtkWidget *menuitem;
6958 cm_return_if_fail(menu != NULL);
6959 cm_return_if_fail(GTK_IS_MENU_SHELL(menu));
6961 menuitem = gtk_separator_menu_item_new();
6962 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6963 gtk_widget_show(menuitem);
6965 menuitem = gtk_menu_item_new_with_mnemonic(_("Add to address _book"));
6966 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6968 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
6969 g_strstrip(address);
6970 if (*address == '\0') {
6971 gtk_widget_set_sensitive(GTK_WIDGET(menuitem), FALSE);
6974 g_signal_connect(G_OBJECT(menuitem), "activate",
6975 G_CALLBACK(compose_add_to_addressbook_cb), entry);
6976 gtk_widget_show(menuitem);
6979 void compose_add_extra_header(gchar *header, GtkListStore *model)
6982 if (strcmp(header, "")) {
6983 COMBOBOX_ADD(model, header, COMPOSE_TO);
6987 void compose_add_extra_header_entries(GtkListStore *model)
6991 gchar buf[BUFFSIZE];
6994 if (extra_headers == NULL) {
6995 exhrc = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, "extraheaderrc", NULL);
6996 if ((exh = g_fopen(exhrc, "rb")) == NULL) {
6997 debug_print("extra headers file not found\n");
6998 goto extra_headers_done;
7000 while (fgets(buf, BUFFSIZE, exh) != NULL) {
7001 lastc = strlen(buf) - 1; /* remove trailing control chars */
7002 while (lastc >= 0 && buf[lastc] != ':')
7003 buf[lastc--] = '\0';
7004 if (lastc > 0 && buf[0] != '#' && buf[lastc] == ':') {
7005 buf[lastc] = '\0'; /* remove trailing : for comparison */
7006 if (custom_header_is_allowed(buf)) {
7008 extra_headers = g_slist_prepend(extra_headers, g_strdup(buf));
7011 g_message("disallowed extra header line: %s\n", buf);
7015 g_message("invalid extra header line: %s\n", buf);
7021 extra_headers = g_slist_prepend(extra_headers, g_strdup("")); /* end of list */
7022 extra_headers = g_slist_reverse(extra_headers);
7024 g_slist_foreach(extra_headers, (GFunc)compose_add_extra_header, (gpointer)model);
7027 static void compose_create_header_entry(Compose *compose)
7029 gchar *headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
7036 const gchar *header = NULL;
7037 ComposeHeaderEntry *headerentry;
7038 gboolean standard_header = FALSE;
7039 GtkListStore *model;
7042 headerentry = g_new0(ComposeHeaderEntry, 1);
7044 /* Combo box model */
7045 model = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_BOOLEAN);
7046 #if !GTK_CHECK_VERSION(2, 24, 0)
7047 combo = gtk_combo_box_entry_new_with_model(GTK_TREE_MODEL(model), 0);
7049 COMBOBOX_ADD(model, prefs_common_translated_header_name("To:"),
7051 COMBOBOX_ADD(model, prefs_common_translated_header_name("Cc:"),
7053 COMBOBOX_ADD(model, prefs_common_translated_header_name("Bcc:"),
7055 COMBOBOX_ADD(model, prefs_common_translated_header_name("Newsgroups:"),
7056 COMPOSE_NEWSGROUPS);
7057 COMBOBOX_ADD(model, prefs_common_translated_header_name("Reply-To:"),
7059 COMBOBOX_ADD(model, prefs_common_translated_header_name("Followup-To:"),
7060 COMPOSE_FOLLOWUPTO);
7061 compose_add_extra_header_entries(model);
7064 #if GTK_CHECK_VERSION(2, 24, 0)
7065 combo = gtk_combo_box_new_with_model_and_entry(GTK_TREE_MODEL(model));
7066 GtkCellRenderer *cell = gtk_cell_renderer_text_new();
7067 gtk_cell_renderer_set_alignment(cell, 0.0, 0.5);
7068 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), cell, TRUE);
7069 gtk_combo_box_set_entry_text_column(GTK_COMBO_BOX(combo), 0);
7071 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
7072 g_signal_connect(G_OBJECT(gtk_bin_get_child(GTK_BIN(combo))), "grab_focus",
7073 G_CALLBACK(compose_grab_focus_cb), compose);
7074 gtk_widget_show(combo);
7076 /* Putting only the combobox child into focus chain of its parent causes
7077 * the parent to be skipped when changing focus via Tab or Shift+Tab.
7078 * This eliminates need to pres Tab twice in order to really get from the
7079 * combobox to next widget. */
7081 l = g_list_prepend(l, gtk_bin_get_child(GTK_BIN(combo)));
7082 gtk_container_set_focus_chain(GTK_CONTAINER(combo), l);
7085 gtk_table_attach(GTK_TABLE(compose->header_table), combo, 0, 1,
7086 compose->header_nextrow, compose->header_nextrow+1,
7087 GTK_SHRINK, GTK_FILL, 0, 0);
7088 if (compose->header_last && (compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN)) {
7089 const gchar *last_header_entry = gtk_entry_get_text(
7090 GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
7092 while (*string != NULL) {
7093 if (!strcmp(prefs_common_translated_header_name(*string), last_header_entry))
7094 standard_header = TRUE;
7097 if (standard_header)
7098 header = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
7100 if (!compose->header_last || !standard_header) {
7101 switch(compose->account->protocol) {
7103 header = prefs_common_translated_header_name("Newsgroups:");
7106 header = prefs_common_translated_header_name("To:");
7111 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((combo)))), header);
7113 gtk_editable_set_editable(
7114 GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((combo)))),
7115 prefs_common.type_any_header);
7117 g_signal_connect_after(G_OBJECT(gtk_bin_get_child(GTK_BIN((combo)))), "grab_focus",
7118 G_CALLBACK(compose_grab_focus_cb), compose);
7120 /* Entry field with cleanup button */
7121 button = gtk_button_new();
7122 gtk_button_set_image(GTK_BUTTON(button),
7123 gtk_image_new_from_stock(GTK_STOCK_CLEAR, GTK_ICON_SIZE_MENU));
7124 gtk_widget_show(button);
7125 CLAWS_SET_TIP(button,
7126 _("Delete entry contents"));
7127 entry = gtk_entry_new();
7128 gtk_widget_show(entry);
7129 CLAWS_SET_TIP(entry,
7130 _("Use <tab> to autocomplete from addressbook"));
7131 hbox = gtk_hbox_new (FALSE, 0);
7132 gtk_widget_show(hbox);
7133 gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, TRUE, 0);
7134 gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
7135 gtk_table_attach(GTK_TABLE(compose->header_table), hbox, 1, 2,
7136 compose->header_nextrow, compose->header_nextrow+1,
7137 GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
7139 g_signal_connect(G_OBJECT(entry), "key-press-event",
7140 G_CALLBACK(compose_headerentry_key_press_event_cb),
7142 g_signal_connect(G_OBJECT(entry), "changed",
7143 G_CALLBACK(compose_headerentry_changed_cb),
7145 g_signal_connect_after(G_OBJECT(entry), "grab_focus",
7146 G_CALLBACK(compose_grab_focus_cb), compose);
7148 g_signal_connect(G_OBJECT(button), "clicked",
7149 G_CALLBACK(compose_headerentry_button_clicked_cb),
7153 gtk_drag_dest_set(entry, GTK_DEST_DEFAULT_ALL, compose_mime_types,
7154 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7155 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7156 g_signal_connect(G_OBJECT(entry), "drag_data_received",
7157 G_CALLBACK(compose_header_drag_received_cb),
7159 g_signal_connect(G_OBJECT(entry), "drag-drop",
7160 G_CALLBACK(compose_drag_drop),
7162 g_signal_connect(G_OBJECT(entry), "populate-popup",
7163 G_CALLBACK(compose_entry_popup_extend),
7166 address_completion_register_entry(GTK_ENTRY(entry), TRUE);
7168 headerentry->compose = compose;
7169 headerentry->combo = combo;
7170 headerentry->entry = entry;
7171 headerentry->button = button;
7172 headerentry->hbox = hbox;
7173 headerentry->headernum = compose->header_nextrow;
7174 headerentry->type = PREF_NONE;
7176 compose->header_nextrow++;
7177 compose->header_last = headerentry;
7178 compose->header_list =
7179 g_slist_append(compose->header_list,
7183 static void compose_add_header_entry(Compose *compose, const gchar *header,
7184 gchar *text, ComposePrefType pref_type)
7186 ComposeHeaderEntry *last_header = compose->header_last;
7187 gchar *tmp = g_strdup(text), *email;
7188 gboolean replyto_hdr;
7190 replyto_hdr = (!strcasecmp(header,
7191 prefs_common_translated_header_name("Reply-To:")) ||
7193 prefs_common_translated_header_name("Followup-To:")) ||
7195 prefs_common_translated_header_name("In-Reply-To:")));
7197 extract_address(tmp);
7198 email = g_utf8_strdown(tmp, -1);
7200 if (replyto_hdr == FALSE &&
7201 g_hash_table_lookup(compose->email_hashtable, email) != NULL)
7203 debug_print("Ignoring duplicate address - %s %s, pref_type: %d\n",
7204 header, text, (gint) pref_type);
7210 if (!strcasecmp(header, prefs_common_translated_header_name("In-Reply-To:")))
7211 gtk_entry_set_text(GTK_ENTRY(
7212 gtk_bin_get_child(GTK_BIN(last_header->combo))), header);
7214 combobox_select_by_text(GTK_COMBO_BOX(last_header->combo), header);
7215 gtk_entry_set_text(GTK_ENTRY(last_header->entry), text);
7216 last_header->type = pref_type;
7218 if (replyto_hdr == FALSE)
7219 g_hash_table_insert(compose->email_hashtable, email,
7220 GUINT_TO_POINTER(1));
7227 static void compose_destroy_headerentry(Compose *compose,
7228 ComposeHeaderEntry *headerentry)
7230 gchar *text = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
7233 extract_address(text);
7234 email = g_utf8_strdown(text, -1);
7235 g_hash_table_remove(compose->email_hashtable, email);
7239 gtk_widget_destroy(headerentry->combo);
7240 gtk_widget_destroy(headerentry->entry);
7241 gtk_widget_destroy(headerentry->button);
7242 gtk_widget_destroy(headerentry->hbox);
7243 g_free(headerentry);
7246 static void compose_remove_header_entries(Compose *compose)
7249 for (list = compose->header_list; list; list = list->next)
7250 compose_destroy_headerentry(compose, (ComposeHeaderEntry *)list->data);
7252 compose->header_last = NULL;
7253 g_slist_free(compose->header_list);
7254 compose->header_list = NULL;
7255 compose->header_nextrow = 1;
7256 compose_create_header_entry(compose);
7259 static GtkWidget *compose_create_header(Compose *compose)
7261 GtkWidget *from_optmenu_hbox;
7262 GtkWidget *header_table_main;
7263 GtkWidget *header_scrolledwin;
7264 GtkWidget *header_table;
7266 /* parent with account selection and from header */
7267 header_table_main = gtk_table_new(2, 2, FALSE);
7268 gtk_widget_show(header_table_main);
7269 gtk_container_set_border_width(GTK_CONTAINER(header_table_main), BORDER_WIDTH);
7271 from_optmenu_hbox = compose_account_option_menu_create(compose);
7272 gtk_table_attach(GTK_TABLE(header_table_main), from_optmenu_hbox,
7273 0, 2, 0, 1, GTK_EXPAND | GTK_FILL, GTK_SHRINK, 0, 0);
7275 /* child with header labels and entries */
7276 header_scrolledwin = gtk_scrolled_window_new(NULL, NULL);
7277 gtk_widget_show(header_scrolledwin);
7278 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
7280 header_table = gtk_table_new(2, 2, FALSE);
7281 gtk_widget_show(header_table);
7282 gtk_container_set_border_width(GTK_CONTAINER(header_table), 0);
7283 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin), header_table);
7284 gtk_container_set_focus_vadjustment(GTK_CONTAINER(header_table),
7285 gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(header_scrolledwin)));
7286 gtk_viewport_set_shadow_type(GTK_VIEWPORT(gtk_bin_get_child(GTK_BIN(header_scrolledwin))), GTK_SHADOW_NONE);
7288 gtk_table_attach(GTK_TABLE(header_table_main), header_scrolledwin,
7289 0, 2, 1, 2, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 2);
7291 compose->header_table = header_table;
7292 compose->header_list = NULL;
7293 compose->header_nextrow = 0;
7295 compose_create_header_entry(compose);
7297 compose->table = NULL;
7299 return header_table_main;
7302 static gboolean popup_attach_button_pressed(GtkWidget *widget, gpointer data)
7304 Compose *compose = (Compose *)data;
7305 GdkEventButton event;
7308 event.time = gtk_get_current_event_time();
7310 return attach_button_pressed(compose->attach_clist, &event, compose);
7313 static GtkWidget *compose_create_attach(Compose *compose)
7315 GtkWidget *attach_scrwin;
7316 GtkWidget *attach_clist;
7318 GtkListStore *store;
7319 GtkCellRenderer *renderer;
7320 GtkTreeViewColumn *column;
7321 GtkTreeSelection *selection;
7323 /* attachment list */
7324 attach_scrwin = gtk_scrolled_window_new(NULL, NULL);
7325 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(attach_scrwin),
7326 GTK_POLICY_AUTOMATIC,
7327 GTK_POLICY_AUTOMATIC);
7328 gtk_widget_set_size_request(attach_scrwin, -1, 80);
7330 store = gtk_list_store_new(N_ATTACH_COLS,
7336 G_TYPE_AUTO_POINTER,
7338 attach_clist = GTK_WIDGET(gtk_tree_view_new_with_model
7339 (GTK_TREE_MODEL(store)));
7340 gtk_container_add(GTK_CONTAINER(attach_scrwin), attach_clist);
7341 g_object_unref(store);
7343 renderer = gtk_cell_renderer_text_new();
7344 column = gtk_tree_view_column_new_with_attributes
7345 (_("Mime type"), renderer, "text",
7346 COL_MIMETYPE, NULL);
7347 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7349 renderer = gtk_cell_renderer_text_new();
7350 column = gtk_tree_view_column_new_with_attributes
7351 (_("Size"), renderer, "text",
7353 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7355 renderer = gtk_cell_renderer_text_new();
7356 column = gtk_tree_view_column_new_with_attributes
7357 (_("Name"), renderer, "text",
7359 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7361 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(attach_clist),
7362 prefs_common.use_stripes_everywhere);
7363 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(attach_clist));
7364 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
7366 g_signal_connect(G_OBJECT(attach_clist), "row_activated",
7367 G_CALLBACK(attach_selected), compose);
7368 g_signal_connect(G_OBJECT(attach_clist), "button_press_event",
7369 G_CALLBACK(attach_button_pressed), compose);
7370 g_signal_connect(G_OBJECT(attach_clist), "popup-menu",
7371 G_CALLBACK(popup_attach_button_pressed), compose);
7372 g_signal_connect(G_OBJECT(attach_clist), "key_press_event",
7373 G_CALLBACK(attach_key_pressed), compose);
7376 gtk_drag_dest_set(attach_clist,
7377 GTK_DEST_DEFAULT_ALL, compose_mime_types,
7378 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7379 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7380 g_signal_connect(G_OBJECT(attach_clist), "drag_data_received",
7381 G_CALLBACK(compose_attach_drag_received_cb),
7383 g_signal_connect(G_OBJECT(attach_clist), "drag-drop",
7384 G_CALLBACK(compose_drag_drop),
7387 compose->attach_scrwin = attach_scrwin;
7388 compose->attach_clist = attach_clist;
7390 return attach_scrwin;
7393 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose);
7394 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose);
7396 static GtkWidget *compose_create_others(Compose *compose)
7399 GtkWidget *savemsg_checkbtn;
7400 GtkWidget *savemsg_combo;
7401 GtkWidget *savemsg_select;
7404 gchar *folderidentifier;
7406 /* Table for settings */
7407 table = gtk_table_new(3, 1, FALSE);
7408 gtk_container_set_border_width(GTK_CONTAINER(table), BORDER_WIDTH);
7409 gtk_widget_show(table);
7410 gtk_table_set_row_spacings(GTK_TABLE(table), VSPACING_NARROW);
7413 /* Save Message to folder */
7414 savemsg_checkbtn = gtk_check_button_new_with_label(_("Save Message to "));
7415 gtk_widget_show(savemsg_checkbtn);
7416 gtk_table_attach(GTK_TABLE(table), savemsg_checkbtn, 0, 1, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7417 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7418 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), prefs_common.savemsg);
7420 g_signal_connect(G_OBJECT(savemsg_checkbtn), "toggled",
7421 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
7423 #if !GTK_CHECK_VERSION(2, 24, 0)
7424 savemsg_combo = gtk_combo_box_entry_new_text();
7426 savemsg_combo = gtk_combo_box_text_new_with_entry();
7428 compose->savemsg_checkbtn = savemsg_checkbtn;
7429 compose->savemsg_combo = savemsg_combo;
7430 gtk_widget_show(savemsg_combo);
7432 if (prefs_common.compose_save_to_history)
7433 #if !GTK_CHECK_VERSION(2, 24, 0)
7434 combobox_set_popdown_strings(GTK_COMBO_BOX(savemsg_combo),
7435 prefs_common.compose_save_to_history);
7437 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(savemsg_combo),
7438 prefs_common.compose_save_to_history);
7440 gtk_table_attach(GTK_TABLE(table), savemsg_combo, 1, 2, rowcount, rowcount + 1, GTK_FILL|GTK_EXPAND, GTK_SHRINK, 0, 0);
7441 gtk_widget_set_sensitive(GTK_WIDGET(savemsg_combo), prefs_common.savemsg);
7442 g_signal_connect_after(G_OBJECT(savemsg_combo), "grab_focus",
7443 G_CALLBACK(compose_grab_focus_cb), compose);
7444 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7445 folderidentifier = folder_item_get_identifier(account_get_special_folder
7446 (compose->account, F_OUTBOX));
7447 compose_set_save_to(compose, folderidentifier);
7448 g_free(folderidentifier);
7451 savemsg_select = gtkut_get_browse_file_btn(_("_Browse"));
7452 gtk_widget_show(savemsg_select);
7453 gtk_table_attach(GTK_TABLE(table), savemsg_select, 2, 3, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7454 g_signal_connect(G_OBJECT(savemsg_select), "clicked",
7455 G_CALLBACK(compose_savemsg_select_cb),
7461 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose)
7463 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo),
7464 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn)));
7467 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose)
7472 dest = foldersel_folder_sel(NULL, FOLDER_SEL_COPY, NULL, FALSE,
7473 _("Select folder to save message to"));
7476 path = folder_item_get_identifier(dest);
7478 compose_set_save_to(compose, path);
7482 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry, gboolean wrap,
7483 GdkAtom clip, GtkTextIter *insert_place);
7486 static gboolean text_clicked(GtkWidget *text, GdkEventButton *event,
7490 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
7492 if (event->button == 3) {
7494 GtkTextIter sel_start, sel_end;
7495 gboolean stuff_selected;
7497 /* move the cursor to allow GtkAspell to check the word
7498 * under the mouse */
7499 if (event->x && event->y) {
7500 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7501 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7503 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7506 GtkTextMark *mark = gtk_text_buffer_get_insert(buffer);
7507 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7510 stuff_selected = gtk_text_buffer_get_selection_bounds(
7512 &sel_start, &sel_end);
7514 gtk_text_buffer_place_cursor (buffer, &iter);
7515 /* reselect stuff */
7517 && gtk_text_iter_in_range(&iter, &sel_start, &sel_end)) {
7518 gtk_text_buffer_select_range(buffer,
7519 &sel_start, &sel_end);
7521 return FALSE; /* pass the event so that the right-click goes through */
7524 if (event->button == 2) {
7529 /* get the middle-click position to paste at the correct place */
7530 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7531 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7533 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7536 entry_paste_clipboard(compose, text,
7537 prefs_common.linewrap_pastes,
7538 GDK_SELECTION_PRIMARY, &iter);
7546 static void compose_spell_menu_changed(void *data)
7548 Compose *compose = (Compose *)data;
7550 GtkWidget *menuitem;
7551 GtkWidget *parent_item;
7552 GtkMenu *menu = GTK_MENU(gtk_menu_new());
7555 if (compose->gtkaspell == NULL)
7558 parent_item = gtk_ui_manager_get_widget(compose->ui_manager,
7559 "/Menu/Spelling/Options");
7561 /* setting the submenu removes /Spelling/Options from the factory
7562 * so we need to save it */
7564 if (parent_item == NULL) {
7565 parent_item = compose->aspell_options_menu;
7566 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), NULL);
7568 compose->aspell_options_menu = parent_item;
7570 spell_menu = gtkaspell_make_config_menu(compose->gtkaspell);
7572 spell_menu = g_slist_reverse(spell_menu);
7573 for (items = spell_menu;
7574 items; items = items->next) {
7575 menuitem = GTK_WIDGET(GTK_MENU_ITEM(items->data));
7576 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), GTK_WIDGET(menuitem));
7577 gtk_widget_show(GTK_WIDGET(menuitem));
7579 g_slist_free(spell_menu);
7581 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), GTK_WIDGET(menu));
7582 gtk_widget_show(parent_item);
7585 static void compose_dict_changed(void *data)
7587 Compose *compose = (Compose *) data;
7589 if(!compose->gtkaspell)
7591 if(compose->gtkaspell->recheck_when_changing_dict == FALSE)
7594 gtkaspell_highlight_all(compose->gtkaspell);
7595 claws_spell_entry_recheck_all(CLAWS_SPELL_ENTRY(compose->subject_entry));
7599 static gboolean compose_popup_menu(GtkWidget *widget, gpointer data)
7601 Compose *compose = (Compose *)data;
7602 GdkEventButton event;
7605 event.time = gtk_get_current_event_time();
7609 return text_clicked(compose->text, &event, compose);
7612 static gboolean compose_force_window_origin = TRUE;
7613 static Compose *compose_create(PrefsAccount *account,
7622 GtkWidget *handlebox;
7624 GtkWidget *notebook;
7626 GtkWidget *attach_hbox;
7627 GtkWidget *attach_lab1;
7628 GtkWidget *attach_lab2;
7633 GtkWidget *subject_hbox;
7634 GtkWidget *subject_frame;
7635 GtkWidget *subject_entry;
7639 GtkWidget *edit_vbox;
7640 GtkWidget *ruler_hbox;
7642 GtkWidget *scrolledwin;
7644 GtkTextBuffer *buffer;
7645 GtkClipboard *clipboard;
7647 UndoMain *undostruct;
7649 GtkWidget *popupmenu;
7650 GtkWidget *tmpl_menu;
7651 GtkActionGroup *action_group = NULL;
7654 GtkAspell * gtkaspell = NULL;
7657 static GdkGeometry geometry;
7659 cm_return_val_if_fail(account != NULL, NULL);
7661 gtkut_convert_int_to_gdk_color(prefs_common.default_header_bgcolor,
7662 &default_header_bgcolor);
7663 gtkut_convert_int_to_gdk_color(prefs_common.default_header_color,
7664 &default_header_color);
7666 debug_print("Creating compose window...\n");
7667 compose = g_new0(Compose, 1);
7669 compose->batch = batch;
7670 compose->account = account;
7671 compose->folder = folder;
7673 compose->mutex = cm_mutex_new();
7674 compose->set_cursor_pos = -1;
7676 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose");
7678 gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
7679 gtk_widget_set_size_request(window, prefs_common.compose_width,
7680 prefs_common.compose_height);
7682 if (!geometry.max_width) {
7683 geometry.max_width = gdk_screen_width();
7684 geometry.max_height = gdk_screen_height();
7687 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7688 &geometry, GDK_HINT_MAX_SIZE);
7689 if (!geometry.min_width) {
7690 geometry.min_width = 600;
7691 geometry.min_height = 440;
7693 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7694 &geometry, GDK_HINT_MIN_SIZE);
7696 #ifndef GENERIC_UMPC
7697 if (compose_force_window_origin)
7698 gtk_window_move(GTK_WINDOW(window), prefs_common.compose_x,
7699 prefs_common.compose_y);
7701 g_signal_connect(G_OBJECT(window), "delete_event",
7702 G_CALLBACK(compose_delete_cb), compose);
7703 MANAGE_WINDOW_SIGNALS_CONNECT(window);
7704 gtk_widget_realize(window);
7706 gtkut_widget_set_composer_icon(window);
7708 vbox = gtk_vbox_new(FALSE, 0);
7709 gtk_container_add(GTK_CONTAINER(window), vbox);
7711 compose->ui_manager = gtk_ui_manager_new();
7712 action_group = cm_menu_create_action_group_full(compose->ui_manager,"Menu", compose_entries,
7713 G_N_ELEMENTS(compose_entries), (gpointer)compose);
7714 gtk_action_group_add_toggle_actions(action_group, compose_toggle_entries,
7715 G_N_ELEMENTS(compose_toggle_entries), (gpointer)compose);
7716 gtk_action_group_add_radio_actions(action_group, compose_radio_rm_entries,
7717 G_N_ELEMENTS(compose_radio_rm_entries), COMPOSE_REPLY, G_CALLBACK(compose_reply_change_mode_cb), (gpointer)compose);
7718 gtk_action_group_add_radio_actions(action_group, compose_radio_prio_entries,
7719 G_N_ELEMENTS(compose_radio_prio_entries), PRIORITY_NORMAL, G_CALLBACK(compose_set_priority_cb), (gpointer)compose);
7720 gtk_action_group_add_radio_actions(action_group, compose_radio_enc_entries,
7721 G_N_ELEMENTS(compose_radio_enc_entries), C_AUTO, G_CALLBACK(compose_set_encoding_cb), (gpointer)compose);
7723 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Menu", NULL, GTK_UI_MANAGER_MENUBAR)
7725 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Message", "Message", GTK_UI_MANAGER_MENU)
7726 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Edit", "Edit", GTK_UI_MANAGER_MENU)
7728 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Spelling", "Spelling", GTK_UI_MANAGER_MENU)
7730 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Options", "Options", GTK_UI_MANAGER_MENU)
7731 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Tools", "Tools", GTK_UI_MANAGER_MENU)
7732 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Help", "Help", GTK_UI_MANAGER_MENU)
7735 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Send", "Message/Send", GTK_UI_MANAGER_MENUITEM)
7736 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "SendLater", "Message/SendLater", GTK_UI_MANAGER_MENUITEM)
7737 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator1", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7738 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "AttachFile", "Message/AttachFile", GTK_UI_MANAGER_MENUITEM)
7739 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertFile", "Message/InsertFile", GTK_UI_MANAGER_MENUITEM)
7740 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertSig", "Message/InsertSig", GTK_UI_MANAGER_MENUITEM)
7741 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "ReplaceSig", "Message/ReplaceSig", GTK_UI_MANAGER_MENUITEM)
7742 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator2", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7743 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Save", "Message/Save", GTK_UI_MANAGER_MENUITEM)
7744 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator3", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7745 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Print", "Message/Print", GTK_UI_MANAGER_MENUITEM)
7746 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator4", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7747 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Close", "Message/Close", GTK_UI_MANAGER_MENUITEM)
7750 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Undo", "Edit/Undo", GTK_UI_MANAGER_MENUITEM)
7751 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Redo", "Edit/Redo", GTK_UI_MANAGER_MENUITEM)
7752 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator1", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7754 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Cut", "Edit/Cut", GTK_UI_MANAGER_MENUITEM)
7755 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Copy", "Edit/Copy", GTK_UI_MANAGER_MENUITEM)
7756 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Paste", "Edit/Paste", GTK_UI_MANAGER_MENUITEM)
7758 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SpecialPaste", "Edit/SpecialPaste", GTK_UI_MANAGER_MENU)
7759 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "AsQuotation", "Edit/SpecialPaste/AsQuotation", GTK_UI_MANAGER_MENUITEM)
7760 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Wrapped", "Edit/SpecialPaste/Wrapped", GTK_UI_MANAGER_MENUITEM)
7761 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Unwrapped", "Edit/SpecialPaste/Unwrapped", GTK_UI_MANAGER_MENUITEM)
7763 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SelectAll", "Edit/SelectAll", GTK_UI_MANAGER_MENUITEM)
7765 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Advanced", "Edit/Advanced", GTK_UI_MANAGER_MENU)
7766 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackChar", "Edit/Advanced/BackChar", GTK_UI_MANAGER_MENUITEM)
7767 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwChar", "Edit/Advanced/ForwChar", GTK_UI_MANAGER_MENUITEM)
7768 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackWord", "Edit/Advanced/BackWord", GTK_UI_MANAGER_MENUITEM)
7769 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwWord", "Edit/Advanced/ForwWord", GTK_UI_MANAGER_MENUITEM)
7770 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BegLine", "Edit/Advanced/BegLine", GTK_UI_MANAGER_MENUITEM)
7771 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "EndLine", "Edit/Advanced/EndLine", GTK_UI_MANAGER_MENUITEM)
7772 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "PrevLine", "Edit/Advanced/PrevLine", GTK_UI_MANAGER_MENUITEM)
7773 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "NextLine", "Edit/Advanced/NextLine", GTK_UI_MANAGER_MENUITEM)
7774 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackChar", "Edit/Advanced/DelBackChar", GTK_UI_MANAGER_MENUITEM)
7775 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwChar", "Edit/Advanced/DelForwChar", GTK_UI_MANAGER_MENUITEM)
7776 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackWord", "Edit/Advanced/DelBackWord", GTK_UI_MANAGER_MENUITEM)
7777 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwWord", "Edit/Advanced/DelForwWord", GTK_UI_MANAGER_MENUITEM)
7778 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelLine", "Edit/Advanced/DelLine", GTK_UI_MANAGER_MENUITEM)
7779 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelEndLine", "Edit/Advanced/DelEndLine", GTK_UI_MANAGER_MENUITEM)
7781 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator2", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7783 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Find", "Edit/Find", GTK_UI_MANAGER_MENUITEM)
7784 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapPara", "Edit/WrapPara", GTK_UI_MANAGER_MENUITEM)
7785 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapAllLines", "Edit/WrapAllLines", GTK_UI_MANAGER_MENUITEM)
7786 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoWrap", "Edit/AutoWrap", GTK_UI_MANAGER_MENUITEM)
7787 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoIndent", "Edit/AutoIndent", GTK_UI_MANAGER_MENUITEM)
7789 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator3", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7791 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "ExtEditor", "Edit/ExtEditor", GTK_UI_MANAGER_MENUITEM)
7795 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckAllSel", "Spelling/CheckAllSel", GTK_UI_MANAGER_MENUITEM)
7796 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "HighlightAll", "Spelling/HighlightAll", GTK_UI_MANAGER_MENUITEM)
7797 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckBackwards", "Spelling/CheckBackwards", GTK_UI_MANAGER_MENUITEM)
7798 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "ForwardNext", "Spelling/ForwardNext", GTK_UI_MANAGER_MENUITEM)
7799 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Separator1", "Spelling/---", GTK_UI_MANAGER_SEPARATOR)
7800 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Options", "Spelling/Options", GTK_UI_MANAGER_MENU)
7804 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "ReplyMode", "Options/ReplyMode", GTK_UI_MANAGER_MENU)
7805 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Normal", "Options/ReplyMode/Normal", GTK_UI_MANAGER_MENUITEM)
7806 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "All", "Options/ReplyMode/All", GTK_UI_MANAGER_MENUITEM)
7807 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Sender", "Options/ReplyMode/Sender", GTK_UI_MANAGER_MENUITEM)
7808 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "List", "Options/ReplyMode/List", GTK_UI_MANAGER_MENUITEM)
7810 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator1", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7811 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "PrivacySystem", "Options/PrivacySystem", GTK_UI_MANAGER_MENU)
7812 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/PrivacySystem", "PlaceHolder", "Options/PrivacySystem/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7813 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Sign", "Options/Sign", GTK_UI_MANAGER_MENUITEM)
7814 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encrypt", "Options/Encrypt", GTK_UI_MANAGER_MENUITEM)
7817 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator2", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7818 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Priority", "Options/Priority", GTK_UI_MANAGER_MENU)
7819 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Highest", "Options/Priority/Highest", GTK_UI_MANAGER_MENUITEM)
7820 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "High", "Options/Priority/High", GTK_UI_MANAGER_MENUITEM)
7821 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Normal", "Options/Priority/Normal", GTK_UI_MANAGER_MENUITEM)
7822 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Low", "Options/Priority/Low", GTK_UI_MANAGER_MENUITEM)
7823 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Lowest", "Options/Priority/Lowest", GTK_UI_MANAGER_MENUITEM)
7825 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator3", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7826 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RequestRetRcpt", "Options/RequestRetRcpt", GTK_UI_MANAGER_MENUITEM)
7827 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator4", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7828 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RemoveReferences", "Options/RemoveReferences", GTK_UI_MANAGER_MENUITEM)
7829 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator5", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7831 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encoding", "Options/Encoding", GTK_UI_MANAGER_MENU)
7833 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_AUTO, "Options/Encoding/"CS_AUTO, GTK_UI_MANAGER_MENUITEM)
7834 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator1", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7835 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_US_ASCII, "Options/Encoding/"CS_US_ASCII, GTK_UI_MANAGER_MENUITEM)
7836 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_UTF_8, "Options/Encoding/"CS_UTF_8, GTK_UI_MANAGER_MENUITEM)
7837 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator2", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7839 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Western", "Options/Encoding/Western", GTK_UI_MANAGER_MENU)
7840 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)
7841 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)
7842 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Western", CS_WINDOWS_1252, "Options/Encoding/Western/"CS_WINDOWS_1252, GTK_UI_MANAGER_MENUITEM)
7844 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_2, "Options/Encoding/"CS_ISO_8859_2, GTK_UI_MANAGER_MENUITEM)
7846 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Baltic", "Options/Encoding/Baltic", GTK_UI_MANAGER_MENU)
7847 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)
7848 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)
7850 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_7, "Options/Encoding/"CS_ISO_8859_7, GTK_UI_MANAGER_MENUITEM)
7852 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Hebrew", "Options/Encoding/Hebrew", GTK_UI_MANAGER_MENU)
7853 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)
7854 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Hebrew", CS_WINDOWS_1255, "Options/Encoding/Hebrew/"CS_WINDOWS_1255, GTK_UI_MANAGER_MENUITEM)
7856 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Arabic", "Options/Encoding/Arabic", GTK_UI_MANAGER_MENU)
7857 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)
7858 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Arabic", CS_WINDOWS_1256, "Options/Encoding/Arabic/"CS_WINDOWS_1256, GTK_UI_MANAGER_MENUITEM)
7860 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_9, "Options/Encoding/"CS_ISO_8859_9, GTK_UI_MANAGER_MENUITEM)
7862 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Cyrillic", "Options/Encoding/Cyrillic", GTK_UI_MANAGER_MENU)
7863 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)
7864 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_R, "Options/Encoding/Cyrillic/"CS_KOI8_R, GTK_UI_MANAGER_MENUITEM)
7865 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_MACCYR, "Options/Encoding/Cyrillic/"CS_MACCYR, GTK_UI_MANAGER_MENUITEM)
7866 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_U, "Options/Encoding/Cyrillic/"CS_KOI8_U, GTK_UI_MANAGER_MENUITEM)
7867 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_WINDOWS_1251, "Options/Encoding/Cyrillic/"CS_WINDOWS_1251, GTK_UI_MANAGER_MENUITEM)
7869 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Japanese", "Options/Encoding/Japanese", GTK_UI_MANAGER_MENU)
7870 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)
7871 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)
7872 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_EUC_JP, "Options/Encoding/Japanese/"CS_EUC_JP, GTK_UI_MANAGER_MENUITEM)
7873 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_SHIFT_JIS, "Options/Encoding/Japanese/"CS_SHIFT_JIS, GTK_UI_MANAGER_MENUITEM)
7875 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Chinese", "Options/Encoding/Chinese", GTK_UI_MANAGER_MENU)
7876 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB18030, "Options/Encoding/Chinese/"CS_GB18030, GTK_UI_MANAGER_MENUITEM)
7877 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB2312, "Options/Encoding/Chinese/"CS_GB2312, GTK_UI_MANAGER_MENUITEM)
7878 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GBK, "Options/Encoding/Chinese/"CS_GBK, GTK_UI_MANAGER_MENUITEM)
7879 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_BIG5, "Options/Encoding/Chinese/"CS_BIG5, GTK_UI_MANAGER_MENUITEM)
7880 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_EUC_TW, "Options/Encoding/Chinese/"CS_EUC_TW, GTK_UI_MANAGER_MENUITEM)
7882 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Korean", "Options/Encoding/Korean", GTK_UI_MANAGER_MENU)
7883 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Korean", CS_EUC_KR, "Options/Encoding/Korean/"CS_EUC_KR, GTK_UI_MANAGER_MENUITEM)
7884 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)
7886 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Thai", "Options/Encoding/Thai", GTK_UI_MANAGER_MENU)
7887 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_TIS_620, "Options/Encoding/Thai/"CS_TIS_620, GTK_UI_MANAGER_MENUITEM)
7888 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_WINDOWS_874, "Options/Encoding/Thai/"CS_WINDOWS_874, GTK_UI_MANAGER_MENUITEM)
7892 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "ShowRuler", "Tools/ShowRuler", GTK_UI_MANAGER_MENUITEM)
7893 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "AddressBook", "Tools/AddressBook", GTK_UI_MANAGER_MENUITEM)
7894 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Template", "Tools/Template", GTK_UI_MANAGER_MENU)
7895 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Template", "PlaceHolder", "Tools/Template/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7896 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Actions", "Tools/Actions", GTK_UI_MANAGER_MENU)
7897 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Actions", "PlaceHolder", "Tools/Actions/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7900 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Help", "About", "Help/About", GTK_UI_MANAGER_MENUITEM)
7902 menubar = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu");
7903 gtk_widget_show_all(menubar);
7905 gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(compose->ui_manager));
7906 gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, TRUE, 0);
7908 if (prefs_common.toolbar_detachable) {
7909 handlebox = gtk_handle_box_new();
7911 handlebox = gtk_hbox_new(FALSE, 0);
7913 gtk_box_pack_start(GTK_BOX(vbox), handlebox, FALSE, FALSE, 0);
7915 gtk_widget_realize(handlebox);
7916 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, handlebox,
7919 vbox2 = gtk_vbox_new(FALSE, 2);
7920 gtk_box_pack_start(GTK_BOX(vbox), vbox2, TRUE, TRUE, 0);
7921 gtk_container_set_border_width(GTK_CONTAINER(vbox2), 0);
7924 notebook = gtk_notebook_new();
7925 gtk_widget_show(notebook);
7927 /* header labels and entries */
7928 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7929 compose_create_header(compose),
7930 gtk_label_new_with_mnemonic(_("Hea_der")));
7931 /* attachment list */
7932 attach_hbox = gtk_hbox_new(FALSE, 0);
7933 gtk_widget_show(attach_hbox);
7935 attach_lab1 = gtk_label_new_with_mnemonic(_("_Attachments"));
7936 gtk_widget_show(attach_lab1);
7937 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab1, TRUE, TRUE, 0);
7939 attach_lab2 = gtk_label_new("");
7940 gtk_widget_show(attach_lab2);
7941 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab2, FALSE, FALSE, 0);
7943 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7944 compose_create_attach(compose),
7947 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7948 compose_create_others(compose),
7949 gtk_label_new_with_mnemonic(_("Othe_rs")));
7952 subject_hbox = gtk_hbox_new(FALSE, 0);
7953 gtk_widget_show(subject_hbox);
7955 subject_frame = gtk_frame_new(NULL);
7956 gtk_frame_set_shadow_type(GTK_FRAME(subject_frame), GTK_SHADOW_NONE);
7957 gtk_box_pack_start(GTK_BOX(subject_hbox), subject_frame, TRUE, TRUE, 0);
7958 gtk_widget_show(subject_frame);
7960 subject = gtk_hbox_new(FALSE, HSPACING_NARROW);
7961 gtk_container_set_border_width(GTK_CONTAINER(subject), 0);
7962 gtk_widget_show(subject);
7964 label = gtk_label_new_with_mnemonic(_("S_ubject:"));
7965 gtk_box_pack_start(GTK_BOX(subject), label, FALSE, FALSE, 0);
7966 gtk_widget_show(label);
7969 subject_entry = claws_spell_entry_new();
7971 subject_entry = gtk_entry_new();
7973 gtk_box_pack_start(GTK_BOX(subject), subject_entry, TRUE, TRUE, 0);
7974 g_signal_connect_after(G_OBJECT(subject_entry), "grab_focus",
7975 G_CALLBACK(compose_grab_focus_cb), compose);
7976 gtk_label_set_mnemonic_widget(GTK_LABEL(label), subject_entry);
7977 gtk_widget_show(subject_entry);
7978 compose->subject_entry = subject_entry;
7979 gtk_container_add(GTK_CONTAINER(subject_frame), subject);
7981 edit_vbox = gtk_vbox_new(FALSE, 0);
7983 gtk_box_pack_start(GTK_BOX(edit_vbox), subject_hbox, FALSE, FALSE, 0);
7986 ruler_hbox = gtk_hbox_new(FALSE, 0);
7987 gtk_box_pack_start(GTK_BOX(edit_vbox), ruler_hbox, FALSE, FALSE, 0);
7989 ruler = gtk_shruler_new(GTK_ORIENTATION_HORIZONTAL);
7990 gtk_shruler_set_range(GTK_SHRULER(ruler), 0.0, 100.0, 1.0);
7991 gtk_box_pack_start(GTK_BOX(ruler_hbox), ruler, TRUE, TRUE,
7995 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
7996 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
7997 GTK_POLICY_AUTOMATIC,
7998 GTK_POLICY_AUTOMATIC);
7999 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
8001 gtk_box_pack_start(GTK_BOX(edit_vbox), scrolledwin, TRUE, TRUE, 0);
8003 text = gtk_text_view_new();
8004 if (prefs_common.show_compose_margin) {
8005 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text), 6);
8006 gtk_text_view_set_right_margin(GTK_TEXT_VIEW(text), 6);
8008 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
8009 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
8010 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), TRUE);
8011 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
8012 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
8014 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
8015 g_signal_connect_after(G_OBJECT(text), "size_allocate",
8016 G_CALLBACK(compose_edit_size_alloc),
8018 g_signal_connect(G_OBJECT(buffer), "changed",
8019 G_CALLBACK(compose_changed_cb), compose);
8020 g_signal_connect(G_OBJECT(text), "grab_focus",
8021 G_CALLBACK(compose_grab_focus_cb), compose);
8022 g_signal_connect(G_OBJECT(buffer), "insert_text",
8023 G_CALLBACK(text_inserted), compose);
8024 g_signal_connect(G_OBJECT(text), "button_press_event",
8025 G_CALLBACK(text_clicked), compose);
8026 g_signal_connect(G_OBJECT(text), "popup-menu",
8027 G_CALLBACK(compose_popup_menu), compose);
8028 g_signal_connect(G_OBJECT(subject_entry), "changed",
8029 G_CALLBACK(compose_changed_cb), compose);
8030 g_signal_connect(G_OBJECT(subject_entry), "activate",
8031 G_CALLBACK(compose_subject_entry_activated), compose);
8034 gtk_drag_dest_set(text, GTK_DEST_DEFAULT_ALL, compose_mime_types,
8035 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
8036 GDK_ACTION_COPY | GDK_ACTION_MOVE);
8037 g_signal_connect(G_OBJECT(text), "drag_data_received",
8038 G_CALLBACK(compose_insert_drag_received_cb),
8040 g_signal_connect(G_OBJECT(text), "drag-drop",
8041 G_CALLBACK(compose_drag_drop),
8043 g_signal_connect(G_OBJECT(text), "key-press-event",
8044 G_CALLBACK(completion_set_focus_to_subject),
8046 gtk_widget_show_all(vbox);
8048 /* pane between attach clist and text */
8049 paned = gtk_vpaned_new();
8050 gtk_container_add(GTK_CONTAINER(vbox2), paned);
8051 gtk_paned_pack1(GTK_PANED(paned), notebook, FALSE, FALSE);
8052 gtk_paned_pack2(GTK_PANED(paned), edit_vbox, TRUE, FALSE);
8053 gtk_paned_set_position(GTK_PANED(paned), prefs_common.compose_notebook_height);
8054 g_signal_connect(G_OBJECT(notebook), "size_allocate",
8055 G_CALLBACK(compose_notebook_size_alloc), paned);
8057 gtk_widget_show_all(paned);
8060 if (prefs_common.textfont) {
8061 PangoFontDescription *font_desc;
8063 font_desc = pango_font_description_from_string
8064 (prefs_common.textfont);
8066 gtk_widget_modify_font(text, font_desc);
8067 pango_font_description_free(font_desc);
8071 gtk_action_group_add_actions(action_group, compose_popup_entries,
8072 G_N_ELEMENTS(compose_popup_entries), (gpointer)compose);
8073 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Popup", NULL, GTK_UI_MANAGER_MENUBAR)
8074 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup", "Compose", "Compose", GTK_UI_MANAGER_MENU)
8075 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Add", "Compose/Add", GTK_UI_MANAGER_MENUITEM)
8076 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Remove", "Compose/Remove", GTK_UI_MANAGER_MENUITEM)
8077 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Separator1", "Compose/---", GTK_UI_MANAGER_SEPARATOR)
8078 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Properties", "Compose/Properties", GTK_UI_MANAGER_MENUITEM)
8080 popupmenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(gtk_ui_manager_get_widget(compose->ui_manager, "/Popup/Compose")));
8082 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
8083 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
8084 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
8086 tmpl_menu = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu/Tools/Template");
8088 undostruct = undo_init(text);
8089 undo_set_change_state_func(undostruct, &compose_undo_state_changed,
8092 address_completion_start(window);
8094 compose->window = window;
8095 compose->vbox = vbox;
8096 compose->menubar = menubar;
8097 compose->handlebox = handlebox;
8099 compose->vbox2 = vbox2;
8101 compose->paned = paned;
8103 compose->attach_label = attach_lab2;
8105 compose->notebook = notebook;
8106 compose->edit_vbox = edit_vbox;
8107 compose->ruler_hbox = ruler_hbox;
8108 compose->ruler = ruler;
8109 compose->scrolledwin = scrolledwin;
8110 compose->text = text;
8112 compose->focused_editable = NULL;
8114 compose->popupmenu = popupmenu;
8116 compose->tmpl_menu = tmpl_menu;
8118 compose->mode = mode;
8119 compose->rmode = mode;
8121 compose->targetinfo = NULL;
8122 compose->replyinfo = NULL;
8123 compose->fwdinfo = NULL;
8125 compose->email_hashtable = g_hash_table_new_full(g_str_hash,
8126 g_str_equal, (GDestroyNotify) g_free, NULL);
8128 compose->replyto = NULL;
8130 compose->bcc = NULL;
8131 compose->followup_to = NULL;
8133 compose->ml_post = NULL;
8135 compose->inreplyto = NULL;
8136 compose->references = NULL;
8137 compose->msgid = NULL;
8138 compose->boundary = NULL;
8140 compose->autowrap = prefs_common.autowrap;
8141 compose->autoindent = prefs_common.auto_indent;
8142 compose->use_signing = FALSE;
8143 compose->use_encryption = FALSE;
8144 compose->privacy_system = NULL;
8145 compose->encdata = NULL;
8147 compose->modified = FALSE;
8149 compose->return_receipt = FALSE;
8151 compose->to_list = NULL;
8152 compose->newsgroup_list = NULL;
8154 compose->undostruct = undostruct;
8156 compose->sig_str = NULL;
8158 compose->exteditor_file = NULL;
8159 compose->exteditor_pid = -1;
8160 compose->exteditor_tag = -1;
8161 compose->exteditor_socket = NULL;
8162 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN; /* inhibit auto-drafting while loading */
8164 compose->folder_update_callback_id =
8165 hooks_register_hook(FOLDER_UPDATE_HOOKLIST,
8166 compose_update_folder_hook,
8167 (gpointer) compose);
8170 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
8171 if (mode != COMPOSE_REDIRECT) {
8172 if (prefs_common.enable_aspell && prefs_common.dictionary &&
8173 strcmp(prefs_common.dictionary, "")) {
8174 gtkaspell = gtkaspell_new(prefs_common.dictionary,
8175 prefs_common.alt_dictionary,
8176 conv_get_locale_charset_str(),
8177 prefs_common.misspelled_col,
8178 prefs_common.check_while_typing,
8179 prefs_common.recheck_when_changing_dict,
8180 prefs_common.use_alternate,
8181 prefs_common.use_both_dicts,
8182 GTK_TEXT_VIEW(text),
8183 GTK_WINDOW(compose->window),
8184 compose_dict_changed,
8185 compose_spell_menu_changed,
8188 alertpanel_error(_("Spell checker could not "
8190 gtkaspell_checkers_strerror());
8191 gtkaspell_checkers_reset_error();
8193 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", TRUE);
8197 compose->gtkaspell = gtkaspell;
8198 compose_spell_menu_changed(compose);
8199 claws_spell_entry_set_gtkaspell(CLAWS_SPELL_ENTRY(subject_entry), gtkaspell);
8202 compose_select_account(compose, account, TRUE);
8204 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", prefs_common.autowrap);
8205 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", prefs_common.auto_indent);
8207 if (account->set_autocc && account->auto_cc && mode != COMPOSE_REEDIT)
8208 compose_entry_append(compose, account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8210 if (account->set_autobcc && account->auto_bcc && mode != COMPOSE_REEDIT)
8211 compose_entry_append(compose, account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8213 if (account->set_autoreplyto && account->auto_replyto && mode != COMPOSE_REEDIT)
8214 compose_entry_append(compose, account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8216 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", compose->mode == COMPOSE_REPLY);
8217 if (account->protocol != A_NNTP)
8218 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
8219 prefs_common_translated_header_name("To:"));
8221 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
8222 prefs_common_translated_header_name("Newsgroups:"));
8224 #ifndef USE_ALT_ADDRBOOK
8225 addressbook_set_target_compose(compose);
8227 if (mode != COMPOSE_REDIRECT)
8228 compose_set_template_menu(compose);
8230 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Template", FALSE);
8233 compose_list = g_list_append(compose_list, compose);
8235 if (!prefs_common.show_ruler)
8236 gtk_widget_hide(ruler_hbox);
8238 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Tools/ShowRuler", prefs_common.show_ruler);
8241 compose->priority = PRIORITY_NORMAL;
8242 compose_update_priority_menu_item(compose);
8244 compose_set_out_encoding(compose);
8247 compose_update_actions_menu(compose);
8249 /* Privacy Systems menu */
8250 compose_update_privacy_systems_menu(compose);
8252 activate_privacy_system(compose, account, TRUE);
8253 toolbar_set_style(compose->toolbar->toolbar, compose->handlebox, prefs_common.toolbar_style);
8255 gtk_widget_realize(window);
8257 gtk_widget_show(window);
8263 static GtkWidget *compose_account_option_menu_create(Compose *compose)
8268 GtkWidget *optmenubox;
8269 GtkWidget *fromlabel;
8272 GtkWidget *from_name = NULL;
8274 gint num = 0, def_menu = 0;
8276 accounts = account_get_list();
8277 cm_return_val_if_fail(accounts != NULL, NULL);
8279 optmenubox = gtk_event_box_new();
8280 optmenu = gtkut_sc_combobox_create(optmenubox, FALSE);
8281 menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
8283 hbox = gtk_hbox_new(FALSE, 4);
8284 from_name = gtk_entry_new();
8286 g_signal_connect_after(G_OBJECT(from_name), "grab_focus",
8287 G_CALLBACK(compose_grab_focus_cb), compose);
8288 g_signal_connect_after(G_OBJECT(from_name), "activate",
8289 G_CALLBACK(from_name_activate_cb), optmenu);
8291 for (; accounts != NULL; accounts = accounts->next, num++) {
8292 PrefsAccount *ac = (PrefsAccount *)accounts->data;
8293 gchar *name, *from = NULL;
8295 if (ac == compose->account) def_menu = num;
8297 name = g_markup_printf_escaped("<i>%s</i>",
8300 if (ac == compose->account) {
8301 if (ac->name && *ac->name) {
8303 QUOTE_IF_REQUIRED_NORMAL(buf, ac->name, return NULL);
8304 from = g_strdup_printf("%s <%s>",
8306 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8308 from = g_strdup_printf("%s",
8310 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8312 if (cur_account != compose->account) {
8313 gtk_widget_modify_base(
8314 GTK_WIDGET(from_name),
8315 GTK_STATE_NORMAL, &default_header_bgcolor);
8316 gtk_widget_modify_text(
8317 GTK_WIDGET(from_name),
8318 GTK_STATE_NORMAL, &default_header_color);
8321 COMBOBOX_ADD(menu, name, ac->account_id);
8326 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), def_menu);
8328 g_signal_connect(G_OBJECT(optmenu), "changed",
8329 G_CALLBACK(account_activated),
8331 g_signal_connect(G_OBJECT(from_name), "populate-popup",
8332 G_CALLBACK(compose_entry_popup_extend),
8335 fromlabel = gtk_label_new_with_mnemonic(_("_From:"));
8336 gtk_label_set_mnemonic_widget(GTK_LABEL(fromlabel), from_name);
8338 gtk_box_pack_start(GTK_BOX(hbox), fromlabel, FALSE, FALSE, 4);
8339 gtk_box_pack_start(GTK_BOX(hbox), optmenubox, FALSE, FALSE, 0);
8340 gtk_box_pack_start(GTK_BOX(hbox), from_name, TRUE, TRUE, 0);
8342 /* Putting only the GtkEntry into focus chain of parent hbox causes
8343 * the account selector combobox next to it to be unreachable when
8344 * navigating widgets in GtkTable with up/down arrow keys.
8345 * Note: gtk_widget_set_can_focus() was not enough. */
8347 l = g_list_prepend(l, from_name);
8348 gtk_container_set_focus_chain(GTK_CONTAINER(hbox), l);
8351 CLAWS_SET_TIP(optmenubox,
8352 _("Account to use for this email"));
8353 CLAWS_SET_TIP(from_name,
8354 _("Sender address to be used"));
8356 compose->account_combo = optmenu;
8357 compose->from_name = from_name;
8362 static void compose_set_priority_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8364 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8365 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8366 Compose *compose = (Compose *) data;
8368 compose->priority = value;
8372 static void compose_reply_change_mode(Compose *compose,
8375 gboolean was_modified = compose->modified;
8377 gboolean all = FALSE, ml = FALSE, sender = FALSE, followup = FALSE;
8379 cm_return_if_fail(compose->replyinfo != NULL);
8381 if (action == COMPOSE_REPLY && prefs_common.default_reply_list)
8383 if (action == COMPOSE_REPLY && compose->rmode == COMPOSE_FOLLOWUP_AND_REPLY_TO)
8385 if (action == COMPOSE_REPLY_TO_ALL)
8387 if (action == COMPOSE_REPLY_TO_SENDER)
8389 if (action == COMPOSE_REPLY_TO_LIST)
8392 compose_remove_header_entries(compose);
8393 compose_reply_set_entry(compose, compose->replyinfo, all, ml, sender, followup);
8394 if (compose->account->set_autocc && compose->account->auto_cc)
8395 compose_entry_append(compose, compose->account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8397 if (compose->account->set_autobcc && compose->account->auto_bcc)
8398 compose_entry_append(compose, compose->account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8400 if (compose->account->set_autoreplyto && compose->account->auto_replyto)
8401 compose_entry_append(compose, compose->account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8402 compose_show_first_last_header(compose, TRUE);
8403 compose->modified = was_modified;
8404 compose_set_title(compose);
8407 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8409 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8410 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8411 Compose *compose = (Compose *) data;
8414 compose_reply_change_mode(compose, value);
8417 static void compose_update_priority_menu_item(Compose * compose)
8419 GtkWidget *menuitem = NULL;
8420 switch (compose->priority) {
8421 case PRIORITY_HIGHEST:
8422 menuitem = gtk_ui_manager_get_widget
8423 (compose->ui_manager, "/Menu/Options/Priority/Highest");
8426 menuitem = gtk_ui_manager_get_widget
8427 (compose->ui_manager, "/Menu/Options/Priority/High");
8429 case PRIORITY_NORMAL:
8430 menuitem = gtk_ui_manager_get_widget
8431 (compose->ui_manager, "/Menu/Options/Priority/Normal");
8434 menuitem = gtk_ui_manager_get_widget
8435 (compose->ui_manager, "/Menu/Options/Priority/Low");
8437 case PRIORITY_LOWEST:
8438 menuitem = gtk_ui_manager_get_widget
8439 (compose->ui_manager, "/Menu/Options/Priority/Lowest");
8442 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8445 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data)
8447 Compose *compose = (Compose *) data;
8449 gboolean can_sign = FALSE, can_encrypt = FALSE;
8451 cm_return_if_fail(GTK_IS_CHECK_MENU_ITEM(widget));
8453 if (!gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget)))
8456 systemid = g_object_get_data(G_OBJECT(widget), "privacy_system");
8457 g_free(compose->privacy_system);
8458 compose->privacy_system = NULL;
8459 g_free(compose->encdata);
8460 compose->encdata = NULL;
8461 if (systemid != NULL) {
8462 compose->privacy_system = g_strdup(systemid);
8464 can_sign = privacy_system_can_sign(systemid);
8465 can_encrypt = privacy_system_can_encrypt(systemid);
8468 debug_print("activated privacy system: %s\n", systemid != NULL ? systemid : "None");
8470 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8471 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8474 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn)
8476 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8477 GtkWidget *menuitem = NULL;
8478 GList *children, *amenu;
8479 gboolean can_sign = FALSE, can_encrypt = FALSE;
8480 gboolean found = FALSE;
8482 if (compose->privacy_system != NULL) {
8484 menuitem = gtk_menu_item_get_submenu(GTK_MENU_ITEM(
8485 gtk_ui_manager_get_widget(compose->ui_manager, branch_path)));
8486 cm_return_if_fail(menuitem != NULL);
8488 children = gtk_container_get_children(GTK_CONTAINER(GTK_MENU_SHELL(menuitem)));
8491 while (amenu != NULL) {
8492 systemid = g_object_get_data(G_OBJECT(amenu->data), "privacy_system");
8493 if (systemid != NULL) {
8494 if (strcmp(systemid, compose->privacy_system) == 0 &&
8495 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8496 menuitem = GTK_WIDGET(amenu->data);
8498 can_sign = privacy_system_can_sign(systemid);
8499 can_encrypt = privacy_system_can_encrypt(systemid);
8503 } else if (strlen(compose->privacy_system) == 0 &&
8504 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8505 menuitem = GTK_WIDGET(amenu->data);
8508 can_encrypt = FALSE;
8513 amenu = amenu->next;
8515 g_list_free(children);
8516 if (menuitem != NULL)
8517 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8519 if (warn && !found && strlen(compose->privacy_system)) {
8520 alertpanel_warning(_("The privacy system '%s' cannot be loaded. You "
8521 "will not be able to sign or encrypt this message."),
8522 compose->privacy_system);
8526 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8527 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8530 static void compose_set_out_encoding(Compose *compose)
8532 CharSet out_encoding;
8533 const gchar *branch = NULL;
8534 out_encoding = conv_get_charset_from_str(prefs_common.outgoing_charset);
8536 switch(out_encoding) {
8537 case C_AUTO: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8538 case C_US_ASCII: branch = "Menu/Options/Encoding/" CS_US_ASCII; break;
8539 case C_UTF_8: branch = "Menu/Options/Encoding/" CS_UTF_8; break;
8540 case C_ISO_8859_2: branch = "Menu/Options/Encoding/" CS_ISO_8859_2; break;
8541 case C_ISO_8859_7: branch = "Menu/Options/Encoding/" CS_ISO_8859_7; break;
8542 case C_ISO_8859_9: branch = "Menu/Options/Encoding/" CS_ISO_8859_9; break;
8543 case C_ISO_8859_1: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_1; break;
8544 case C_ISO_8859_15: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_15; break;
8545 case C_WINDOWS_1252: branch = "Menu/Options/Encoding/Western/" CS_WINDOWS_1252; break;
8546 case C_ISO_8859_13: branch = "Menu/Options/Encoding/Baltic/" CS_ISO_8859_13; break;
8547 case C_ISO_8859_4: branch = "Menu/Options/Encoding/Baltic" CS_ISO_8859_4; break;
8548 case C_ISO_8859_8: branch = "Menu/Options/Encoding/Hebrew/" CS_ISO_8859_8; break;
8549 case C_WINDOWS_1255: branch = "Menu/Options/Encoding/Hebrew/" CS_WINDOWS_1255; break;
8550 case C_ISO_8859_6: branch = "Menu/Options/Encoding/Arabic/" CS_ISO_8859_6; break;
8551 case C_WINDOWS_1256: branch = "Menu/Options/Encoding/Arabic/" CS_WINDOWS_1256; break;
8552 case C_ISO_8859_5: branch = "Menu/Options/Encoding/Cyrillic/" CS_ISO_8859_5; break;
8553 case C_KOI8_R: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_R; break;
8554 case C_MACCYR: branch = "Menu/Options/Encoding/Cyrillic/" CS_MACCYR; break;
8555 case C_KOI8_U: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_U; break;
8556 case C_WINDOWS_1251: branch = "Menu/Options/Encoding/Cyrillic/" CS_WINDOWS_1251; break;
8557 case C_ISO_2022_JP: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP; break;
8558 case C_ISO_2022_JP_2: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP_2; break;
8559 case C_EUC_JP: branch = "Menu/Options/Encoding/Japanese/" CS_EUC_JP; break;
8560 case C_SHIFT_JIS: branch = "Menu/Options/Encoding/Japanese/" CS_SHIFT_JIS; break;
8561 case C_GB18030: branch = "Menu/Options/Encoding/Chinese/" CS_GB18030; break;
8562 case C_GB2312: branch = "Menu/Options/Encoding/Chinese/" CS_GB2312; break;
8563 case C_GBK: branch = "Menu/Options/Encoding/Chinese/" CS_GBK; break;
8564 case C_BIG5: branch = "Menu/Options/Encoding/Chinese/" CS_BIG5; break;
8565 case C_EUC_TW: branch = "Menu/Options/Encoding/Chinese/" CS_EUC_TW; break;
8566 case C_EUC_KR: branch = "Menu/Options/Encoding/Korean/" CS_EUC_KR; break;
8567 case C_ISO_2022_KR: branch = "Menu/Options/Encoding/Korean/" CS_ISO_2022_KR; break;
8568 case C_TIS_620: branch = "Menu/Options/Encoding/Thai/" CS_TIS_620; break;
8569 case C_WINDOWS_874: branch = "Menu/Options/Encoding/Thai/" CS_WINDOWS_874; break;
8570 default: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8572 cm_toggle_menu_set_active_full(compose->ui_manager, (gchar *)branch, TRUE);
8575 static void compose_set_template_menu(Compose *compose)
8577 GSList *tmpl_list, *cur;
8581 tmpl_list = template_get_config();
8583 menu = gtk_menu_new();
8585 gtk_menu_set_accel_group (GTK_MENU (menu),
8586 gtk_ui_manager_get_accel_group(compose->ui_manager));
8587 for (cur = tmpl_list; cur != NULL; cur = cur->next) {
8588 Template *tmpl = (Template *)cur->data;
8589 gchar *accel_path = NULL;
8590 item = gtk_menu_item_new_with_label(tmpl->name);
8591 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
8592 g_signal_connect(G_OBJECT(item), "activate",
8593 G_CALLBACK(compose_template_activate_cb),
8595 g_object_set_data(G_OBJECT(item), "template", tmpl);
8596 gtk_widget_show(item);
8597 accel_path = g_strconcat("<ComposeTemplates>" , "/", tmpl->name, NULL);
8598 gtk_menu_item_set_accel_path(GTK_MENU_ITEM(item), accel_path);
8602 gtk_widget_show(menu);
8603 gtk_menu_item_set_submenu(GTK_MENU_ITEM(compose->tmpl_menu), menu);
8606 void compose_update_actions_menu(Compose *compose)
8608 action_update_compose_menu(compose->ui_manager, "/Menu/Tools/Actions", compose);
8611 static void compose_update_privacy_systems_menu(Compose *compose)
8613 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8614 GSList *systems, *cur;
8616 GtkWidget *system_none;
8618 GtkWidget *privacy_menuitem = gtk_ui_manager_get_widget(compose->ui_manager, branch_path);
8619 GtkWidget *privacy_menu = gtk_menu_new();
8621 system_none = gtk_radio_menu_item_new_with_mnemonic(NULL, _("_None"));
8622 g_object_set_data_full(G_OBJECT(system_none), "privacy_system", NULL, NULL);
8624 g_signal_connect(G_OBJECT(system_none), "activate",
8625 G_CALLBACK(compose_set_privacy_system_cb), compose);
8627 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), system_none);
8628 gtk_widget_show(system_none);
8630 systems = privacy_get_system_ids();
8631 for (cur = systems; cur != NULL; cur = g_slist_next(cur)) {
8632 gchar *systemid = cur->data;
8634 group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(system_none));
8635 widget = gtk_radio_menu_item_new_with_label(group,
8636 privacy_system_get_name(systemid));
8637 g_object_set_data_full(G_OBJECT(widget), "privacy_system",
8638 g_strdup(systemid), g_free);
8639 g_signal_connect(G_OBJECT(widget), "activate",
8640 G_CALLBACK(compose_set_privacy_system_cb), compose);
8642 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), widget);
8643 gtk_widget_show(widget);
8646 g_slist_free(systems);
8647 gtk_menu_item_set_submenu(GTK_MENU_ITEM(privacy_menuitem), privacy_menu);
8648 gtk_widget_show_all(privacy_menu);
8649 gtk_widget_show_all(privacy_menuitem);
8652 void compose_reflect_prefs_all(void)
8657 for (cur = compose_list; cur != NULL; cur = cur->next) {
8658 compose = (Compose *)cur->data;
8659 compose_set_template_menu(compose);
8663 void compose_reflect_prefs_pixmap_theme(void)
8668 for (cur = compose_list; cur != NULL; cur = cur->next) {
8669 compose = (Compose *)cur->data;
8670 toolbar_update(TOOLBAR_COMPOSE, compose);
8674 static const gchar *compose_quote_char_from_context(Compose *compose)
8676 const gchar *qmark = NULL;
8678 cm_return_val_if_fail(compose != NULL, NULL);
8680 switch (compose->mode) {
8681 /* use forward-specific quote char */
8682 case COMPOSE_FORWARD:
8683 case COMPOSE_FORWARD_AS_ATTACH:
8684 case COMPOSE_FORWARD_INLINE:
8685 if (compose->folder && compose->folder->prefs &&
8686 compose->folder->prefs->forward_with_format)
8687 qmark = compose->folder->prefs->forward_quotemark;
8688 else if (compose->account->forward_with_format)
8689 qmark = compose->account->forward_quotemark;
8691 qmark = prefs_common.fw_quotemark;
8694 /* use reply-specific quote char in all other modes */
8696 if (compose->folder && compose->folder->prefs &&
8697 compose->folder->prefs->reply_with_format)
8698 qmark = compose->folder->prefs->reply_quotemark;
8699 else if (compose->account->reply_with_format)
8700 qmark = compose->account->reply_quotemark;
8702 qmark = prefs_common.quotemark;
8706 if (qmark == NULL || *qmark == '\0')
8712 static void compose_template_apply(Compose *compose, Template *tmpl,
8716 GtkTextBuffer *buffer;
8720 gchar *parsed_str = NULL;
8721 gint cursor_pos = 0;
8722 const gchar *err_msg = _("The body of the template has an error at line %d.");
8725 /* process the body */
8727 text = GTK_TEXT_VIEW(compose->text);
8728 buffer = gtk_text_view_get_buffer(text);
8731 qmark = compose_quote_char_from_context(compose);
8733 if (compose->replyinfo != NULL) {
8736 gtk_text_buffer_set_text(buffer, "", -1);
8737 mark = gtk_text_buffer_get_insert(buffer);
8738 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8740 parsed_str = compose_quote_fmt(compose, compose->replyinfo,
8741 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8743 } else if (compose->fwdinfo != NULL) {
8746 gtk_text_buffer_set_text(buffer, "", -1);
8747 mark = gtk_text_buffer_get_insert(buffer);
8748 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8750 parsed_str = compose_quote_fmt(compose, compose->fwdinfo,
8751 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8754 MsgInfo* dummyinfo = compose_msginfo_new_from_compose(compose);
8756 GtkTextIter start, end;
8759 gtk_text_buffer_get_start_iter(buffer, &start);
8760 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
8761 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
8763 /* clear the buffer now */
8765 gtk_text_buffer_set_text(buffer, "", -1);
8767 parsed_str = compose_quote_fmt(compose, dummyinfo,
8768 tmpl->value, qmark, tmp, FALSE, FALSE, err_msg);
8769 procmsg_msginfo_free( &dummyinfo );
8775 gtk_text_buffer_set_text(buffer, "", -1);
8776 mark = gtk_text_buffer_get_insert(buffer);
8777 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8780 if (replace && parsed_str && compose->account->auto_sig)
8781 compose_insert_sig(compose, FALSE);
8783 if (replace && parsed_str) {
8784 gtk_text_buffer_get_start_iter(buffer, &iter);
8785 gtk_text_buffer_place_cursor(buffer, &iter);
8789 cursor_pos = quote_fmt_get_cursor_pos();
8790 compose->set_cursor_pos = cursor_pos;
8791 if (cursor_pos == -1)
8793 gtk_text_buffer_get_start_iter(buffer, &iter);
8794 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
8795 gtk_text_buffer_place_cursor(buffer, &iter);
8798 /* process the other fields */
8800 compose_template_apply_fields(compose, tmpl);
8801 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
8802 quote_fmt_reset_vartable();
8803 compose_changed_cb(NULL, compose);
8806 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
8807 gtkaspell_highlight_all(compose->gtkaspell);
8811 static void compose_template_apply_fields_error(const gchar *header)
8816 tr = g_strdup(C_("'%s' stands for a header name",
8817 "Template '%s' format error."));
8818 text = g_strdup_printf(tr, prefs_common_translated_header_name(header));
8819 alertpanel_error("%s", text);
8825 static void compose_template_apply_fields(Compose *compose, Template *tmpl)
8827 MsgInfo* dummyinfo = NULL;
8828 MsgInfo *msginfo = NULL;
8831 if (compose->replyinfo != NULL)
8832 msginfo = compose->replyinfo;
8833 else if (compose->fwdinfo != NULL)
8834 msginfo = compose->fwdinfo;
8836 dummyinfo = compose_msginfo_new_from_compose(compose);
8837 msginfo = dummyinfo;
8840 if (tmpl->from && *tmpl->from != '\0') {
8842 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8843 compose->gtkaspell);
8845 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8847 quote_fmt_scan_string(tmpl->from);
8850 buf = quote_fmt_get_buffer();
8852 compose_template_apply_fields_error("From");
8854 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
8858 if (tmpl->to && *tmpl->to != '\0') {
8860 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8861 compose->gtkaspell);
8863 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8865 quote_fmt_scan_string(tmpl->to);
8868 buf = quote_fmt_get_buffer();
8870 compose_template_apply_fields_error("To");
8872 compose_entry_append(compose, buf, COMPOSE_TO, PREF_TEMPLATE);
8876 if (tmpl->cc && *tmpl->cc != '\0') {
8878 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8879 compose->gtkaspell);
8881 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8883 quote_fmt_scan_string(tmpl->cc);
8886 buf = quote_fmt_get_buffer();
8888 compose_template_apply_fields_error("Cc");
8890 compose_entry_append(compose, buf, COMPOSE_CC, PREF_TEMPLATE);
8894 if (tmpl->bcc && *tmpl->bcc != '\0') {
8896 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8897 compose->gtkaspell);
8899 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8901 quote_fmt_scan_string(tmpl->bcc);
8904 buf = quote_fmt_get_buffer();
8906 compose_template_apply_fields_error("Bcc");
8908 compose_entry_append(compose, buf, COMPOSE_BCC, PREF_TEMPLATE);
8912 if (tmpl->replyto && *tmpl->replyto != '\0') {
8914 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8915 compose->gtkaspell);
8917 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8919 quote_fmt_scan_string(tmpl->replyto);
8922 buf = quote_fmt_get_buffer();
8924 compose_template_apply_fields_error("Reply-To");
8926 compose_entry_append(compose, buf, COMPOSE_REPLYTO, PREF_TEMPLATE);
8930 /* process the subject */
8931 if (tmpl->subject && *tmpl->subject != '\0') {
8933 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8934 compose->gtkaspell);
8936 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8938 quote_fmt_scan_string(tmpl->subject);
8941 buf = quote_fmt_get_buffer();
8943 compose_template_apply_fields_error("Subject");
8945 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
8949 procmsg_msginfo_free( &dummyinfo );
8952 static void compose_destroy(Compose *compose)
8954 GtkAllocation allocation;
8955 GtkTextBuffer *buffer;
8956 GtkClipboard *clipboard;
8958 compose_list = g_list_remove(compose_list, compose);
8960 if (compose->updating) {
8961 debug_print("danger, not destroying anything now\n");
8962 compose->deferred_destroy = TRUE;
8966 /* NOTE: address_completion_end() does nothing with the window
8967 * however this may change. */
8968 address_completion_end(compose->window);
8970 slist_free_strings_full(compose->to_list);
8971 slist_free_strings_full(compose->newsgroup_list);
8972 slist_free_strings_full(compose->header_list);
8974 slist_free_strings_full(extra_headers);
8975 extra_headers = NULL;
8977 compose->header_list = compose->newsgroup_list = compose->to_list = NULL;
8979 g_hash_table_destroy(compose->email_hashtable);
8981 hooks_unregister_hook(FOLDER_UPDATE_HOOKLIST,
8982 compose->folder_update_callback_id);
8984 procmsg_msginfo_free(&(compose->targetinfo));
8985 procmsg_msginfo_free(&(compose->replyinfo));
8986 procmsg_msginfo_free(&(compose->fwdinfo));
8988 g_free(compose->replyto);
8989 g_free(compose->cc);
8990 g_free(compose->bcc);
8991 g_free(compose->newsgroups);
8992 g_free(compose->followup_to);
8994 g_free(compose->ml_post);
8996 g_free(compose->inreplyto);
8997 g_free(compose->references);
8998 g_free(compose->msgid);
8999 g_free(compose->boundary);
9001 g_free(compose->redirect_filename);
9002 if (compose->undostruct)
9003 undo_destroy(compose->undostruct);
9005 g_free(compose->sig_str);
9007 g_free(compose->exteditor_file);
9009 g_free(compose->orig_charset);
9011 g_free(compose->privacy_system);
9012 g_free(compose->encdata);
9014 #ifndef USE_ALT_ADDRBOOK
9015 if (addressbook_get_target_compose() == compose)
9016 addressbook_set_target_compose(NULL);
9019 if (compose->gtkaspell) {
9020 gtkaspell_delete(compose->gtkaspell);
9021 compose->gtkaspell = NULL;
9025 if (!compose->batch) {
9026 gtk_widget_get_allocation(compose->window, &allocation);
9027 prefs_common.compose_width = allocation.width;
9028 prefs_common.compose_height = allocation.height;
9031 if (!gtk_widget_get_parent(compose->paned))
9032 gtk_widget_destroy(compose->paned);
9033 gtk_widget_destroy(compose->popupmenu);
9035 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
9036 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
9037 gtk_text_buffer_remove_selection_clipboard(buffer, clipboard);
9039 gtk_widget_destroy(compose->window);
9040 toolbar_destroy(compose->toolbar);
9041 g_free(compose->toolbar);
9042 cm_mutex_free(compose->mutex);
9046 static void compose_attach_info_free(AttachInfo *ainfo)
9048 g_free(ainfo->file);
9049 g_free(ainfo->content_type);
9050 g_free(ainfo->name);
9051 g_free(ainfo->charset);
9055 static void compose_attach_update_label(Compose *compose)
9060 GtkTreeModel *model;
9064 if (compose == NULL)
9067 model = gtk_tree_view_get_model(GTK_TREE_VIEW(compose->attach_clist));
9068 if (!gtk_tree_model_get_iter_first(model, &iter)) {
9069 gtk_label_set_text(GTK_LABEL(compose->attach_label), "");
9073 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
9074 total_size = ainfo->size;
9075 while(gtk_tree_model_iter_next(model, &iter)) {
9076 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
9077 total_size += ainfo->size;
9080 text = g_strdup_printf(" (%d/%s)", i, to_human_readable(total_size));
9081 gtk_label_set_text(GTK_LABEL(compose->attach_label), text);
9085 static void compose_attach_remove_selected(GtkAction *action, gpointer data)
9087 Compose *compose = (Compose *)data;
9088 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
9089 GtkTreeSelection *selection;
9091 GtkTreeModel *model;
9093 selection = gtk_tree_view_get_selection(tree_view);
9094 sel = gtk_tree_selection_get_selected_rows(selection, &model);
9099 for (cur = sel; cur != NULL; cur = cur->next) {
9100 GtkTreePath *path = cur->data;
9101 GtkTreeRowReference *ref = gtk_tree_row_reference_new
9104 gtk_tree_path_free(path);
9107 for (cur = sel; cur != NULL; cur = cur->next) {
9108 GtkTreeRowReference *ref = cur->data;
9109 GtkTreePath *path = gtk_tree_row_reference_get_path(ref);
9112 if (gtk_tree_model_get_iter(model, &iter, path))
9113 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
9115 gtk_tree_path_free(path);
9116 gtk_tree_row_reference_free(ref);
9120 compose_attach_update_label(compose);
9123 static struct _AttachProperty
9126 GtkWidget *mimetype_entry;
9127 GtkWidget *encoding_optmenu;
9128 GtkWidget *path_entry;
9129 GtkWidget *filename_entry;
9131 GtkWidget *cancel_btn;
9134 static void gtk_tree_path_free_(gpointer ptr, gpointer data)
9136 gtk_tree_path_free((GtkTreePath *)ptr);
9139 static void compose_attach_property(GtkAction *action, gpointer data)
9141 Compose *compose = (Compose *)data;
9142 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
9144 GtkComboBox *optmenu;
9145 GtkTreeSelection *selection;
9147 GtkTreeModel *model;
9150 static gboolean cancelled;
9152 /* only if one selected */
9153 selection = gtk_tree_view_get_selection(tree_view);
9154 if (gtk_tree_selection_count_selected_rows(selection) != 1)
9157 sel = gtk_tree_selection_get_selected_rows(selection, &model);
9161 path = (GtkTreePath *) sel->data;
9162 gtk_tree_model_get_iter(model, &iter, path);
9163 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
9166 g_list_foreach(sel, gtk_tree_path_free_, NULL);
9172 if (!attach_prop.window)
9173 compose_attach_property_create(&cancelled);
9174 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), TRUE);
9175 gtk_widget_grab_focus(attach_prop.ok_btn);
9176 gtk_widget_show(attach_prop.window);
9177 gtk_window_set_transient_for(GTK_WINDOW(attach_prop.window),
9178 GTK_WINDOW(compose->window));
9180 optmenu = GTK_COMBO_BOX(attach_prop.encoding_optmenu);
9181 if (ainfo->encoding == ENC_UNKNOWN)
9182 combobox_select_by_data(optmenu, ENC_BASE64);
9184 combobox_select_by_data(optmenu, ainfo->encoding);
9186 gtk_entry_set_text(GTK_ENTRY(attach_prop.mimetype_entry),
9187 ainfo->content_type ? ainfo->content_type : "");
9188 gtk_entry_set_text(GTK_ENTRY(attach_prop.path_entry),
9189 ainfo->file ? ainfo->file : "");
9190 gtk_entry_set_text(GTK_ENTRY(attach_prop.filename_entry),
9191 ainfo->name ? ainfo->name : "");
9194 const gchar *entry_text;
9196 gchar *cnttype = NULL;
9203 gtk_widget_hide(attach_prop.window);
9204 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), FALSE);
9209 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.mimetype_entry));
9210 if (*entry_text != '\0') {
9213 text = g_strstrip(g_strdup(entry_text));
9214 if ((p = strchr(text, '/')) && !strchr(p + 1, '/')) {
9215 cnttype = g_strdup(text);
9218 alertpanel_error(_("Invalid MIME type."));
9224 ainfo->encoding = combobox_get_active_data(optmenu);
9226 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.path_entry));
9227 if (*entry_text != '\0') {
9228 if (is_file_exist(entry_text) &&
9229 (size = get_file_size(entry_text)) > 0)
9230 file = g_strdup(entry_text);
9233 (_("File doesn't exist or is empty."));
9239 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.filename_entry));
9240 if (*entry_text != '\0') {
9241 g_free(ainfo->name);
9242 ainfo->name = g_strdup(entry_text);
9246 g_free(ainfo->content_type);
9247 ainfo->content_type = cnttype;
9250 g_free(ainfo->file);
9254 ainfo->size = (goffset)size;
9256 /* update tree store */
9257 text = to_human_readable(ainfo->size);
9258 gtk_tree_model_get_iter(model, &iter, path);
9259 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
9260 COL_MIMETYPE, ainfo->content_type,
9262 COL_NAME, ainfo->name,
9263 COL_CHARSET, ainfo->charset,
9269 gtk_tree_path_free(path);
9272 #define SET_LABEL_AND_ENTRY(str, entry, top) \
9274 label = gtk_label_new(str); \
9275 gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), \
9276 GTK_FILL, 0, 0, 0); \
9277 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); \
9279 entry = gtk_entry_new(); \
9280 gtk_table_attach(GTK_TABLE(table), entry, 1, 2, top, (top + 1), \
9281 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); \
9284 static void compose_attach_property_create(gboolean *cancelled)
9290 GtkWidget *mimetype_entry;
9293 GtkListStore *optmenu_menu;
9294 GtkWidget *path_entry;
9295 GtkWidget *filename_entry;
9298 GtkWidget *cancel_btn;
9299 GList *mime_type_list, *strlist;
9302 debug_print("Creating attach_property window...\n");
9304 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose_attach_property");
9305 gtk_widget_set_size_request(window, 480, -1);
9306 gtk_container_set_border_width(GTK_CONTAINER(window), 8);
9307 gtk_window_set_title(GTK_WINDOW(window), _("Properties"));
9308 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
9309 g_signal_connect(G_OBJECT(window), "delete_event",
9310 G_CALLBACK(attach_property_delete_event),
9312 g_signal_connect(G_OBJECT(window), "key_press_event",
9313 G_CALLBACK(attach_property_key_pressed),
9316 vbox = gtk_vbox_new(FALSE, 8);
9317 gtk_container_add(GTK_CONTAINER(window), vbox);
9319 table = gtk_table_new(4, 2, FALSE);
9320 gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
9321 gtk_table_set_row_spacings(GTK_TABLE(table), 8);
9322 gtk_table_set_col_spacings(GTK_TABLE(table), 8);
9324 label = gtk_label_new(_("MIME type"));
9325 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, (0 + 1),
9327 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
9328 #if !GTK_CHECK_VERSION(2, 24, 0)
9329 mimetype_entry = gtk_combo_box_entry_new_text();
9331 mimetype_entry = gtk_combo_box_text_new_with_entry();
9333 gtk_table_attach(GTK_TABLE(table), mimetype_entry, 1, 2, 0, (0 + 1),
9334 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
9336 /* stuff with list */
9337 mime_type_list = procmime_get_mime_type_list();
9339 for (; mime_type_list != NULL; mime_type_list = mime_type_list->next) {
9340 MimeType *type = (MimeType *) mime_type_list->data;
9343 tmp = g_strdup_printf("%s/%s", type->type, type->sub_type);
9345 if (g_list_find_custom(strlist, tmp, (GCompareFunc)strcmp2))
9348 strlist = g_list_insert_sorted(strlist, (gpointer)tmp,
9349 (GCompareFunc)strcmp2);
9352 for (mime_type_list = strlist; mime_type_list != NULL;
9353 mime_type_list = mime_type_list->next) {
9354 #if !GTK_CHECK_VERSION(2, 24, 0)
9355 gtk_combo_box_append_text(GTK_COMBO_BOX(mimetype_entry), mime_type_list->data);
9357 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(mimetype_entry), mime_type_list->data);
9359 g_free(mime_type_list->data);
9361 g_list_free(strlist);
9362 gtk_combo_box_set_active(GTK_COMBO_BOX(mimetype_entry), 0);
9363 mimetype_entry = gtk_bin_get_child(GTK_BIN((mimetype_entry)));
9365 label = gtk_label_new(_("Encoding"));
9366 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2,
9368 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
9370 hbox = gtk_hbox_new(FALSE, 0);
9371 gtk_table_attach(GTK_TABLE(table), hbox, 1, 2, 1, 2,
9372 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
9374 optmenu = gtkut_sc_combobox_create(NULL, TRUE);
9375 optmenu_menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
9377 COMBOBOX_ADD(optmenu_menu, "7bit", ENC_7BIT);
9378 COMBOBOX_ADD(optmenu_menu, "8bit", ENC_8BIT);
9379 COMBOBOX_ADD(optmenu_menu, "quoted-printable", ENC_QUOTED_PRINTABLE);
9380 COMBOBOX_ADD(optmenu_menu, "base64", ENC_BASE64);
9381 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), 0);
9383 gtk_box_pack_start(GTK_BOX(hbox), optmenu, TRUE, TRUE, 0);
9385 SET_LABEL_AND_ENTRY(_("Path"), path_entry, 2);
9386 SET_LABEL_AND_ENTRY(_("File name"), filename_entry, 3);
9388 gtkut_stock_button_set_create(&hbbox, &cancel_btn, GTK_STOCK_CANCEL,
9389 &ok_btn, GTK_STOCK_OK,
9391 gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
9392 gtk_widget_grab_default(ok_btn);
9394 g_signal_connect(G_OBJECT(ok_btn), "clicked",
9395 G_CALLBACK(attach_property_ok),
9397 g_signal_connect(G_OBJECT(cancel_btn), "clicked",
9398 G_CALLBACK(attach_property_cancel),
9401 gtk_widget_show_all(vbox);
9403 attach_prop.window = window;
9404 attach_prop.mimetype_entry = mimetype_entry;
9405 attach_prop.encoding_optmenu = optmenu;
9406 attach_prop.path_entry = path_entry;
9407 attach_prop.filename_entry = filename_entry;
9408 attach_prop.ok_btn = ok_btn;
9409 attach_prop.cancel_btn = cancel_btn;
9412 #undef SET_LABEL_AND_ENTRY
9414 static void attach_property_ok(GtkWidget *widget, gboolean *cancelled)
9420 static void attach_property_cancel(GtkWidget *widget, gboolean *cancelled)
9426 static gint attach_property_delete_event(GtkWidget *widget, GdkEventAny *event,
9427 gboolean *cancelled)
9435 static gboolean attach_property_key_pressed(GtkWidget *widget,
9437 gboolean *cancelled)
9439 if (event && event->keyval == GDK_KEY_Escape) {
9443 if (event && event->keyval == GDK_KEY_Return) {
9451 static void compose_exec_ext_editor(Compose *compose)
9456 GdkNativeWindow socket_wid = 0;
9460 tmp = g_strdup_printf("%s%ctmpmsg.%p", get_tmp_dir(),
9461 G_DIR_SEPARATOR, compose);
9463 if (compose_get_ext_editor_uses_socket()) {
9464 /* Only allow one socket */
9465 if (compose->exteditor_socket != NULL) {
9466 if (gtk_widget_is_focus(compose->exteditor_socket)) {
9467 /* Move the focus off of the socket */
9468 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9473 /* Create the receiving GtkSocket */
9474 socket = gtk_socket_new ();
9475 g_signal_connect (GTK_OBJECT(socket), "plug-removed",
9476 G_CALLBACK(compose_ext_editor_plug_removed_cb),
9478 gtk_box_pack_start(GTK_BOX(compose->edit_vbox), socket, TRUE, TRUE, 0);
9479 gtk_widget_set_size_request(socket, prefs_common.compose_width, -1);
9480 /* Realize the socket so that we can use its ID */
9481 gtk_widget_realize(socket);
9482 socket_wid = gtk_socket_get_id(GTK_SOCKET (socket));
9483 compose->exteditor_socket = socket;
9486 if (pipe(pipe_fds) < 0) {
9492 if ((pid = fork()) < 0) {
9499 /* close the write side of the pipe */
9502 compose->exteditor_file = g_strdup(tmp);
9503 compose->exteditor_pid = pid;
9505 compose_set_ext_editor_sensitive(compose, FALSE);
9508 compose->exteditor_ch = g_io_channel_unix_new(pipe_fds[0]);
9510 compose->exteditor_ch = g_io_channel_win32_new_fd(pipe_fds[0]);
9512 compose->exteditor_tag = g_io_add_watch(compose->exteditor_ch,
9516 } else { /* process-monitoring process */
9522 /* close the read side of the pipe */
9525 if (compose_write_body_to_file(compose, tmp) < 0) {
9526 fd_write_all(pipe_fds[1], "2\n", 2);
9530 pid_ed = compose_exec_ext_editor_real(tmp, socket_wid);
9532 fd_write_all(pipe_fds[1], "1\n", 2);
9536 /* wait until editor is terminated */
9537 waitpid(pid_ed, NULL, 0);
9539 fd_write_all(pipe_fds[1], "0\n", 2);
9546 #endif /* G_OS_UNIX */
9549 static gboolean compose_can_autosave(Compose *compose)
9551 if (compose->privacy_system && compose->use_encryption)
9552 return prefs_common.autosave && prefs_common.autosave_encrypted;
9554 return prefs_common.autosave;
9558 static gboolean compose_get_ext_editor_cmd_valid()
9560 gboolean has_s = FALSE;
9561 gboolean has_w = FALSE;
9562 const gchar *p = prefs_common_get_ext_editor_cmd();
9565 while ((p = strchr(p, '%'))) {
9571 } else if (*p == 'w') {
9582 static gint compose_exec_ext_editor_real(const gchar *file, GdkNativeWindow socket_wid)
9589 cm_return_val_if_fail(file != NULL, -1);
9591 if ((pid = fork()) < 0) {
9596 if (pid != 0) return pid;
9598 /* grandchild process */
9600 if (setpgid(0, getppid()))
9603 if (compose_get_ext_editor_cmd_valid()) {
9604 if (compose_get_ext_editor_uses_socket()) {
9605 p = g_strdup(prefs_common_get_ext_editor_cmd());
9606 s = strstr(p, "%w");
9608 if (strstr(p, "%s") < s)
9609 buf = g_strdup_printf(p, file, socket_wid);
9611 buf = g_strdup_printf(p, socket_wid, file);
9614 buf = g_strdup_printf(prefs_common_get_ext_editor_cmd(), file);
9617 if (prefs_common_get_ext_editor_cmd())
9618 g_warning("External editor command-line is invalid: '%s'",
9619 prefs_common_get_ext_editor_cmd());
9620 buf = g_strdup_printf(DEFAULT_EDITOR_CMD, file);
9623 cmdline = strsplit_with_quote(buf, " ", 0);
9625 execvp(cmdline[0], cmdline);
9628 g_strfreev(cmdline);
9633 static gboolean compose_ext_editor_kill(Compose *compose)
9635 pid_t pgid = compose->exteditor_pid * -1;
9638 ret = kill(pgid, 0);
9640 if (ret == 0 || (ret == -1 && EPERM == errno)) {
9644 msg = g_strdup_printf
9645 (_("The external editor is still working.\n"
9646 "Force terminating the process?\n"
9647 "process group id: %d"), -pgid);
9648 val = alertpanel_full(_("Notice"), msg, GTK_STOCK_NO, GTK_STOCK_YES,
9649 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
9653 if (val == G_ALERTALTERNATE) {
9654 g_source_remove(compose->exteditor_tag);
9655 g_io_channel_shutdown(compose->exteditor_ch,
9657 g_io_channel_unref(compose->exteditor_ch);
9659 if (kill(pgid, SIGTERM) < 0) perror("kill");
9660 waitpid(compose->exteditor_pid, NULL, 0);
9662 g_warning("Terminated process group id: %d. "
9663 "Temporary file: %s", -pgid, compose->exteditor_file);
9665 compose_set_ext_editor_sensitive(compose, TRUE);
9667 g_free(compose->exteditor_file);
9668 compose->exteditor_file = NULL;
9669 compose->exteditor_pid = -1;
9670 compose->exteditor_ch = NULL;
9671 compose->exteditor_tag = -1;
9679 static gboolean compose_input_cb(GIOChannel *source, GIOCondition condition,
9683 Compose *compose = (Compose *)data;
9686 debug_print("Compose: input from monitoring process\n");
9688 if (g_io_channel_read_chars(source, buf, sizeof(buf), &bytes_read, NULL) != G_IO_STATUS_NORMAL) {
9693 g_io_channel_shutdown(source, FALSE, NULL);
9694 g_io_channel_unref(source);
9696 waitpid(compose->exteditor_pid, NULL, 0);
9698 if (buf[0] == '0') { /* success */
9699 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9700 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
9701 GtkTextIter start, end;
9704 gtk_text_buffer_set_text(buffer, "", -1);
9705 compose_insert_file(compose, compose->exteditor_file);
9706 compose_changed_cb(NULL, compose);
9708 /* Check if we should save the draft or not */
9709 if (compose_can_autosave(compose))
9710 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
9712 if (claws_unlink(compose->exteditor_file) < 0)
9713 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9715 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
9716 gtk_text_buffer_get_start_iter(buffer, &start);
9717 gtk_text_buffer_get_end_iter(buffer, &end);
9718 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
9719 if (chars && strlen(chars) > 0)
9720 compose->modified = TRUE;
9722 } else if (buf[0] == '1') { /* failed */
9723 g_warning("Couldn't exec external editor");
9724 if (claws_unlink(compose->exteditor_file) < 0)
9725 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9726 } else if (buf[0] == '2') {
9727 g_warning("Couldn't write to file");
9728 } else if (buf[0] == '3') {
9729 g_warning("Pipe read failed");
9732 compose_set_ext_editor_sensitive(compose, TRUE);
9734 g_free(compose->exteditor_file);
9735 compose->exteditor_file = NULL;
9736 compose->exteditor_pid = -1;
9737 compose->exteditor_ch = NULL;
9738 compose->exteditor_tag = -1;
9739 if (compose->exteditor_socket) {
9740 gtk_widget_destroy(compose->exteditor_socket);
9741 compose->exteditor_socket = NULL;
9748 static char *ext_editor_menu_entries[] = {
9749 "Menu/Message/Send",
9750 "Menu/Message/SendLater",
9751 "Menu/Message/InsertFile",
9752 "Menu/Message/InsertSig",
9753 "Menu/Message/ReplaceSig",
9754 "Menu/Message/Save",
9755 "Menu/Message/Print",
9760 "Menu/Tools/ShowRuler",
9761 "Menu/Tools/Actions",
9766 static void compose_set_ext_editor_sensitive(Compose *compose,
9771 for (i = 0; ext_editor_menu_entries[i]; ++i) {
9772 cm_menu_set_sensitive_full(compose->ui_manager,
9773 ext_editor_menu_entries[i], sensitive);
9776 if (compose_get_ext_editor_uses_socket()) {
9778 if (compose->exteditor_socket)
9779 gtk_widget_hide(compose->exteditor_socket);
9780 gtk_widget_show(compose->scrolledwin);
9781 if (prefs_common.show_ruler)
9782 gtk_widget_show(compose->ruler_hbox);
9783 /* Fix the focus, as it doesn't go anywhere when the
9784 * socket is hidden or destroyed */
9785 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9787 g_assert (compose->exteditor_socket != NULL);
9788 /* Fix the focus, as it doesn't go anywhere when the
9789 * edit box is hidden */
9790 if (gtk_widget_is_focus(compose->text))
9791 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9792 gtk_widget_hide(compose->scrolledwin);
9793 gtk_widget_hide(compose->ruler_hbox);
9794 gtk_widget_show(compose->exteditor_socket);
9797 gtk_widget_set_sensitive(compose->text, sensitive);
9799 if (compose->toolbar->send_btn)
9800 gtk_widget_set_sensitive(compose->toolbar->send_btn, sensitive);
9801 if (compose->toolbar->sendl_btn)
9802 gtk_widget_set_sensitive(compose->toolbar->sendl_btn, sensitive);
9803 if (compose->toolbar->draft_btn)
9804 gtk_widget_set_sensitive(compose->toolbar->draft_btn, sensitive);
9805 if (compose->toolbar->insert_btn)
9806 gtk_widget_set_sensitive(compose->toolbar->insert_btn, sensitive);
9807 if (compose->toolbar->sig_btn)
9808 gtk_widget_set_sensitive(compose->toolbar->sig_btn, sensitive);
9809 if (compose->toolbar->exteditor_btn)
9810 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, sensitive);
9811 if (compose->toolbar->linewrap_current_btn)
9812 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, sensitive);
9813 if (compose->toolbar->linewrap_all_btn)
9814 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, sensitive);
9817 static gboolean compose_get_ext_editor_uses_socket()
9819 return (prefs_common_get_ext_editor_cmd() &&
9820 strstr(prefs_common_get_ext_editor_cmd(), "%w"));
9823 static gboolean compose_ext_editor_plug_removed_cb(GtkSocket *socket, Compose *compose)
9825 compose->exteditor_socket = NULL;
9826 /* returning FALSE allows destruction of the socket */
9829 #endif /* G_OS_UNIX */
9832 * compose_undo_state_changed:
9834 * Change the sensivity of the menuentries undo and redo
9836 static void compose_undo_state_changed(UndoMain *undostruct, gint undo_state,
9837 gint redo_state, gpointer data)
9839 Compose *compose = (Compose *)data;
9841 switch (undo_state) {
9842 case UNDO_STATE_TRUE:
9843 if (!undostruct->undo_state) {
9844 undostruct->undo_state = TRUE;
9845 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", TRUE);
9848 case UNDO_STATE_FALSE:
9849 if (undostruct->undo_state) {
9850 undostruct->undo_state = FALSE;
9851 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
9854 case UNDO_STATE_UNCHANGED:
9856 case UNDO_STATE_REFRESH:
9857 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", undostruct->undo_state);
9860 g_warning("Undo state not recognized");
9864 switch (redo_state) {
9865 case UNDO_STATE_TRUE:
9866 if (!undostruct->redo_state) {
9867 undostruct->redo_state = TRUE;
9868 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", TRUE);
9871 case UNDO_STATE_FALSE:
9872 if (undostruct->redo_state) {
9873 undostruct->redo_state = FALSE;
9874 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
9877 case UNDO_STATE_UNCHANGED:
9879 case UNDO_STATE_REFRESH:
9880 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", undostruct->redo_state);
9883 g_warning("Redo state not recognized");
9888 /* callback functions */
9890 static void compose_notebook_size_alloc(GtkNotebook *notebook,
9891 GtkAllocation *allocation,
9894 prefs_common.compose_notebook_height = gtk_paned_get_position(paned);
9897 /* compose_edit_size_alloc() - called when resized. don't know whether Gtk
9898 * includes "non-client" (windows-izm) in calculation, so this calculation
9899 * may not be accurate.
9901 static gboolean compose_edit_size_alloc(GtkEditable *widget,
9902 GtkAllocation *allocation,
9903 GtkSHRuler *shruler)
9905 if (prefs_common.show_ruler) {
9906 gint char_width = 0, char_height = 0;
9907 gint line_width_in_chars;
9909 gtkut_get_font_size(GTK_WIDGET(widget),
9910 &char_width, &char_height);
9911 line_width_in_chars =
9912 (allocation->width - allocation->x) / char_width;
9914 /* got the maximum */
9915 gtk_shruler_set_range(GTK_SHRULER(shruler),
9916 0.0, line_width_in_chars, 0);
9925 ComposePrefType type;
9926 gboolean entry_marked;
9929 static void account_activated(GtkComboBox *optmenu, gpointer data)
9931 Compose *compose = (Compose *)data;
9934 gchar *folderidentifier;
9935 gint account_id = 0;
9938 GSList *list, *saved_list = NULL;
9939 HeaderEntryState *state;
9941 /* Get ID of active account in the combo box */
9942 menu = gtk_combo_box_get_model(optmenu);
9943 cm_return_if_fail(gtk_combo_box_get_active_iter(optmenu, &iter));
9944 gtk_tree_model_get(menu, &iter, 1, &account_id, -1);
9946 ac = account_find_from_id(account_id);
9947 cm_return_if_fail(ac != NULL);
9949 if (ac != compose->account) {
9950 compose_select_account(compose, ac, FALSE);
9952 for (list = compose->header_list; list; list = list->next) {
9953 ComposeHeaderEntry *hentry=(ComposeHeaderEntry *)list->data;
9955 if (hentry->type == PREF_ACCOUNT || !list->next) {
9956 compose_destroy_headerentry(compose, hentry);
9959 state = g_malloc0(sizeof(HeaderEntryState));
9960 state->header = gtk_editable_get_chars(GTK_EDITABLE(
9961 gtk_bin_get_child(GTK_BIN(hentry->combo))), 0, -1);
9962 state->entry = gtk_editable_get_chars(
9963 GTK_EDITABLE(hentry->entry), 0, -1);
9964 state->type = hentry->type;
9966 saved_list = g_slist_append(saved_list, state);
9967 compose_destroy_headerentry(compose, hentry);
9970 compose->header_last = NULL;
9971 g_slist_free(compose->header_list);
9972 compose->header_list = NULL;
9973 compose->header_nextrow = 1;
9974 compose_create_header_entry(compose);
9976 if (ac->set_autocc && ac->auto_cc)
9977 compose_entry_append(compose, ac->auto_cc,
9978 COMPOSE_CC, PREF_ACCOUNT);
9979 if (ac->set_autobcc && ac->auto_bcc)
9980 compose_entry_append(compose, ac->auto_bcc,
9981 COMPOSE_BCC, PREF_ACCOUNT);
9982 if (ac->set_autoreplyto && ac->auto_replyto)
9983 compose_entry_append(compose, ac->auto_replyto,
9984 COMPOSE_REPLYTO, PREF_ACCOUNT);
9986 for (list = saved_list; list; list = list->next) {
9987 state = (HeaderEntryState *) list->data;
9989 compose_add_header_entry(compose, state->header,
9990 state->entry, state->type);
9992 g_free(state->header);
9993 g_free(state->entry);
9996 g_slist_free(saved_list);
9998 combobox_select_by_data(GTK_COMBO_BOX(compose->header_last->combo),
9999 (ac->protocol == A_NNTP) ?
10000 COMPOSE_NEWSGROUPS : COMPOSE_TO);
10003 /* Set message save folder */
10004 if (account_get_special_folder(compose->account, F_OUTBOX)) {
10005 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
10007 g_signal_connect(G_OBJECT(compose->savemsg_checkbtn), "toggled",
10008 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
10010 compose_set_save_to(compose, NULL);
10011 if (account_get_special_folder(compose->account, F_OUTBOX)) {
10012 folderidentifier = folder_item_get_identifier(account_get_special_folder
10013 (compose->account, F_OUTBOX));
10014 compose_set_save_to(compose, folderidentifier);
10015 g_free(folderidentifier);
10019 static void attach_selected(GtkTreeView *tree_view, GtkTreePath *tree_path,
10020 GtkTreeViewColumn *column, Compose *compose)
10022 compose_attach_property(NULL, compose);
10025 static gboolean attach_button_pressed(GtkWidget *widget, GdkEventButton *event,
10028 Compose *compose = (Compose *)data;
10029 GtkTreeSelection *attach_selection;
10030 gint attach_nr_selected;
10033 if (!event) return FALSE;
10035 if (event->button == 3) {
10036 attach_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
10037 attach_nr_selected = gtk_tree_selection_count_selected_rows(attach_selection);
10039 /* If no rows, or just one row is selected, right-click should
10040 * open menu relevant to the row being right-clicked on. We
10041 * achieve that by selecting the clicked row first. If more
10042 * than one row is selected, we shouldn't modify the selection,
10043 * as user may want to remove selected rows (attachments). */
10044 if (attach_nr_selected < 2) {
10045 gtk_tree_selection_unselect_all(attach_selection);
10046 attach_nr_selected = 0;
10047 gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget),
10048 event->x, event->y, &path, NULL, NULL, NULL);
10049 if (path != NULL) {
10050 gtk_tree_selection_select_path(attach_selection, path);
10051 gtk_tree_path_free(path);
10052 attach_nr_selected++;
10056 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", (attach_nr_selected > 0));
10057 /* Properties menu item makes no sense with more than one row
10058 * selected, the properties dialog can only edit one attachment. */
10059 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", (attach_nr_selected == 1));
10061 gtk_menu_popup(GTK_MENU(compose->popupmenu), NULL, NULL,
10062 NULL, NULL, event->button, event->time);
10069 static gboolean attach_key_pressed(GtkWidget *widget, GdkEventKey *event,
10072 Compose *compose = (Compose *)data;
10074 if (!event) return FALSE;
10076 switch (event->keyval) {
10077 case GDK_KEY_Delete:
10078 compose_attach_remove_selected(NULL, compose);
10084 static void compose_allow_user_actions (Compose *compose, gboolean allow)
10086 toolbar_comp_set_sensitive(compose, allow);
10087 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message", allow);
10088 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", allow);
10090 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", allow);
10092 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", allow);
10093 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools", allow);
10094 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Help", allow);
10096 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), allow);
10100 static void compose_send_cb(GtkAction *action, gpointer data)
10102 Compose *compose = (Compose *)data;
10105 if (compose->exteditor_tag != -1) {
10106 debug_print("ignoring send: external editor still open\n");
10110 if (prefs_common.work_offline &&
10111 !inc_offline_should_override(TRUE,
10112 _("Claws Mail needs network access in order "
10113 "to send this email.")))
10116 if (compose->draft_timeout_tag >= 0) { /* CLAWS: disable draft timeout */
10117 g_source_remove(compose->draft_timeout_tag);
10118 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
10121 compose_send(compose);
10124 static void compose_send_later_cb(GtkAction *action, gpointer data)
10126 Compose *compose = (Compose *)data;
10130 compose_allow_user_actions(compose, FALSE);
10131 val = compose_queue_sub(compose, NULL, NULL, NULL, TRUE, TRUE);
10132 compose_allow_user_actions(compose, TRUE);
10136 compose_close(compose);
10137 } else if (val == -1) {
10138 alertpanel_error(_("Could not queue message."));
10139 } else if (val == -2) {
10140 alertpanel_error(_("Could not queue message:\n\n%s."), g_strerror(errno));
10141 } else if (val == -3) {
10142 if (privacy_peek_error())
10143 alertpanel_error(_("Could not queue message for sending:\n\n"
10144 "Signature failed: %s"), privacy_get_error());
10145 } else if (val == -4) {
10146 alertpanel_error(_("Could not queue message for sending:\n\n"
10147 "Charset conversion failed."));
10148 } else if (val == -5) {
10149 alertpanel_error(_("Could not queue message for sending:\n\n"
10150 "Couldn't get recipient encryption key."));
10151 } else if (val == -6) {
10154 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
10157 #define DRAFTED_AT_EXIT "drafted_at_exit"
10158 static void compose_register_draft(MsgInfo *info)
10160 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10161 DRAFTED_AT_EXIT, NULL);
10162 FILE *fp = g_fopen(filepath, "ab");
10165 fprintf(fp, "%s\t%d\n", folder_item_get_identifier(info->folder),
10173 gboolean compose_draft (gpointer data, guint action)
10175 Compose *compose = (Compose *)data;
10180 MsgFlags flag = {0, 0};
10181 static gboolean lock = FALSE;
10182 MsgInfo *newmsginfo;
10184 gboolean target_locked = FALSE;
10185 gboolean err = FALSE;
10187 if (lock) return FALSE;
10189 if (compose->sending)
10192 draft = account_get_special_folder(compose->account, F_DRAFT);
10193 cm_return_val_if_fail(draft != NULL, FALSE);
10195 if (!g_mutex_trylock(compose->mutex)) {
10196 /* we don't want to lock the mutex once it's available,
10197 * because as the only other part of compose.c locking
10198 * it is compose_close - which means once unlocked,
10199 * the compose struct will be freed */
10200 debug_print("couldn't lock mutex, probably sending\n");
10206 tmp = g_strdup_printf("%s%cdraft.%p", get_tmp_dir(),
10207 G_DIR_SEPARATOR, compose);
10208 if ((fp = g_fopen(tmp, "wb")) == NULL) {
10209 FILE_OP_ERROR(tmp, "fopen");
10213 /* chmod for security */
10214 if (change_file_mode_rw(fp, tmp) < 0) {
10215 FILE_OP_ERROR(tmp, "chmod");
10216 g_warning("can't change file mode");
10219 /* Save draft infos */
10220 err |= (fprintf(fp, "X-Claws-Account-Id:%d\n", compose->account->account_id) < 0);
10221 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
10223 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
10224 gchar *savefolderid;
10226 savefolderid = compose_get_save_to(compose);
10227 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
10228 g_free(savefolderid);
10230 if (compose->return_receipt) {
10231 err |= (fprintf(fp, "RRCPT:1\n") < 0);
10233 if (compose->privacy_system) {
10234 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
10235 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
10236 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
10239 /* Message-ID of message replying to */
10240 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
10241 gchar *folderid = NULL;
10243 if (compose->replyinfo->folder)
10244 folderid = folder_item_get_identifier(compose->replyinfo->folder);
10245 if (folderid == NULL)
10246 folderid = g_strdup("NULL");
10248 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
10251 /* Message-ID of message forwarding to */
10252 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
10253 gchar *folderid = NULL;
10255 if (compose->fwdinfo->folder)
10256 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
10257 if (folderid == NULL)
10258 folderid = g_strdup("NULL");
10260 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
10264 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
10265 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
10267 sheaders = compose_get_manual_headers_info(compose);
10268 err |= (fprintf(fp, "X-Claws-Manual-Headers:%s\n", sheaders) < 0);
10271 /* end of headers */
10272 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
10279 if (compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_STORE, action != COMPOSE_AUTO_SAVE) < 0) {
10283 if (fclose(fp) == EOF) {
10287 flag.perm_flags = MSG_NEW|MSG_UNREAD;
10288 if (compose->targetinfo) {
10289 target_locked = MSG_IS_LOCKED(compose->targetinfo->flags);
10291 flag.perm_flags |= MSG_LOCKED;
10293 flag.tmp_flags = MSG_DRAFT;
10295 folder_item_scan(draft);
10296 if ((msgnum = folder_item_add_msg(draft, tmp, &flag, TRUE)) < 0) {
10297 MsgInfo *tmpinfo = NULL;
10298 debug_print("didn't get msgnum after adding draft [%s]\n", compose->msgid?compose->msgid:"no msgid");
10299 if (compose->msgid) {
10300 tmpinfo = folder_item_get_msginfo_by_msgid(draft, compose->msgid);
10303 msgnum = tmpinfo->msgnum;
10304 procmsg_msginfo_free(&tmpinfo);
10305 debug_print("got draft msgnum %d from scanning\n", msgnum);
10307 debug_print("didn't get draft msgnum after scanning\n");
10310 debug_print("got draft msgnum %d from adding\n", msgnum);
10316 if (action != COMPOSE_AUTO_SAVE) {
10317 if (action != COMPOSE_DRAFT_FOR_EXIT)
10318 alertpanel_error(_("Could not save draft."));
10321 gtkut_window_popup(compose->window);
10322 val = alertpanel_full(_("Could not save draft"),
10323 _("Could not save draft.\n"
10324 "Do you want to cancel exit or discard this email?"),
10325 _("_Cancel exit"), _("_Discard email"), NULL,
10326 FALSE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
10327 if (val == G_ALERTALTERNATE) {
10329 g_mutex_unlock(compose->mutex); /* must be done before closing */
10330 compose_close(compose);
10334 g_mutex_unlock(compose->mutex); /* must be done before closing */
10343 if (compose->mode == COMPOSE_REEDIT) {
10344 compose_remove_reedit_target(compose, TRUE);
10347 newmsginfo = folder_item_get_msginfo(draft, msgnum);
10350 procmsg_msginfo_unset_flags(newmsginfo, ~0, ~0);
10352 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD|MSG_LOCKED, MSG_DRAFT);
10354 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD, MSG_DRAFT);
10355 if (compose_use_attach(compose) && action != COMPOSE_AUTO_SAVE)
10356 procmsg_msginfo_set_flags(newmsginfo, 0,
10357 MSG_HAS_ATTACHMENT);
10359 if (action == COMPOSE_DRAFT_FOR_EXIT) {
10360 compose_register_draft(newmsginfo);
10362 procmsg_msginfo_free(&newmsginfo);
10365 folder_item_scan(draft);
10367 if (action == COMPOSE_QUIT_EDITING || action == COMPOSE_DRAFT_FOR_EXIT) {
10369 g_mutex_unlock(compose->mutex); /* must be done before closing */
10370 compose_close(compose);
10376 path = folder_item_fetch_msg(draft, msgnum);
10377 if (path == NULL) {
10378 debug_print("can't fetch %s:%d\n", draft->path, msgnum);
10381 if (g_stat(path, &s) < 0) {
10382 FILE_OP_ERROR(path, "stat");
10388 procmsg_msginfo_free(&(compose->targetinfo));
10389 compose->targetinfo = procmsg_msginfo_new();
10390 compose->targetinfo->msgnum = msgnum;
10391 compose->targetinfo->size = (goffset)s.st_size;
10392 compose->targetinfo->mtime = s.st_mtime;
10393 compose->targetinfo->folder = draft;
10395 procmsg_msginfo_set_flags(compose->targetinfo, MSG_LOCKED, 0);
10396 compose->mode = COMPOSE_REEDIT;
10398 if (action == COMPOSE_AUTO_SAVE) {
10399 compose->autosaved_draft = compose->targetinfo;
10401 compose->modified = FALSE;
10402 compose_set_title(compose);
10406 g_mutex_unlock(compose->mutex);
10410 void compose_clear_exit_drafts(void)
10412 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10413 DRAFTED_AT_EXIT, NULL);
10414 if (is_file_exist(filepath))
10415 claws_unlink(filepath);
10420 void compose_reopen_exit_drafts(void)
10422 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10423 DRAFTED_AT_EXIT, NULL);
10424 FILE *fp = g_fopen(filepath, "rb");
10428 while (fgets(buf, sizeof(buf), fp)) {
10429 gchar **parts = g_strsplit(buf, "\t", 2);
10430 const gchar *folder = parts[0];
10431 int msgnum = parts[1] ? atoi(parts[1]):-1;
10433 if (folder && *folder && msgnum > -1) {
10434 FolderItem *item = folder_find_item_from_identifier(folder);
10435 MsgInfo *info = folder_item_get_msginfo(item, msgnum);
10437 compose_reedit(info, FALSE);
10444 compose_clear_exit_drafts();
10447 static void compose_save_cb(GtkAction *action, gpointer data)
10449 Compose *compose = (Compose *)data;
10450 compose_draft(compose, COMPOSE_KEEP_EDITING);
10451 compose->rmode = COMPOSE_REEDIT;
10454 void compose_attach_from_list(Compose *compose, GList *file_list, gboolean free_data)
10456 if (compose && file_list) {
10459 for ( tmp = file_list; tmp; tmp = tmp->next) {
10460 gchar *file = (gchar *) tmp->data;
10461 gchar *utf8_filename = conv_filename_to_utf8(file);
10462 compose_attach_append(compose, file, utf8_filename, NULL, NULL);
10463 compose_changed_cb(NULL, compose);
10468 g_free(utf8_filename);
10473 static void compose_attach_cb(GtkAction *action, gpointer data)
10475 Compose *compose = (Compose *)data;
10478 if (compose->redirect_filename != NULL)
10481 /* Set focus_window properly, in case we were called via popup menu,
10482 * which unsets it (via focus_out_event callback on compose window). */
10483 manage_window_focus_in(compose->window, NULL, NULL);
10485 file_list = filesel_select_multiple_files_open(_("Select file"), NULL);
10488 compose_attach_from_list(compose, file_list, TRUE);
10489 g_list_free(file_list);
10493 static void compose_insert_file_cb(GtkAction *action, gpointer data)
10495 Compose *compose = (Compose *)data;
10497 gint files_inserted = 0;
10499 file_list = filesel_select_multiple_files_open(_("Select file"), NULL);
10504 for ( tmp = file_list; tmp; tmp = tmp->next) {
10505 gchar *file = (gchar *) tmp->data;
10506 gchar *filedup = g_strdup(file);
10507 gchar *shortfile = g_path_get_basename(filedup);
10508 ComposeInsertResult res;
10509 /* insert the file if the file is short or if the user confirmed that
10510 he/she wants to insert the large file */
10511 res = compose_insert_file(compose, file);
10512 if (res == COMPOSE_INSERT_READ_ERROR) {
10513 alertpanel_error(_("File '%s' could not be read."), shortfile);
10514 } else if (res == COMPOSE_INSERT_INVALID_CHARACTER) {
10515 alertpanel_error(_("File '%s' contained invalid characters\n"
10516 "for the current encoding, insertion may be incorrect."),
10518 } else if (res == COMPOSE_INSERT_SUCCESS)
10525 g_list_free(file_list);
10529 if (files_inserted > 0 && compose->gtkaspell &&
10530 compose->gtkaspell->check_while_typing)
10531 gtkaspell_highlight_all(compose->gtkaspell);
10535 static void compose_insert_sig_cb(GtkAction *action, gpointer data)
10537 Compose *compose = (Compose *)data;
10539 compose_insert_sig(compose, FALSE);
10542 static void compose_replace_sig_cb(GtkAction *action, gpointer data)
10544 Compose *compose = (Compose *)data;
10546 compose_insert_sig(compose, TRUE);
10549 static gint compose_delete_cb(GtkWidget *widget, GdkEventAny *event,
10553 Compose *compose = (Compose *)data;
10555 gtkut_widget_get_uposition(widget, &x, &y);
10556 if (!compose->batch) {
10557 prefs_common.compose_x = x;
10558 prefs_common.compose_y = y;
10560 if (compose->sending || compose->updating)
10562 compose_close_cb(NULL, compose);
10566 void compose_close_toolbar(Compose *compose)
10568 compose_close_cb(NULL, compose);
10571 static void compose_close_cb(GtkAction *action, gpointer data)
10573 Compose *compose = (Compose *)data;
10577 if (compose->exteditor_tag != -1) {
10578 if (!compose_ext_editor_kill(compose))
10583 if (compose->modified) {
10584 gboolean reedit = (compose->rmode == COMPOSE_REEDIT);
10585 if (!g_mutex_trylock(compose->mutex)) {
10586 /* we don't want to lock the mutex once it's available,
10587 * because as the only other part of compose.c locking
10588 * it is compose_close - which means once unlocked,
10589 * the compose struct will be freed */
10590 debug_print("couldn't lock mutex, probably sending\n");
10594 val = alertpanel(_("Discard message"),
10595 _("This message has been modified. Discard it?"),
10596 _("_Discard"), _("_Save to Drafts"), GTK_STOCK_CANCEL);
10598 val = alertpanel(_("Save changes"),
10599 _("This message has been modified. Save the latest changes?"),
10600 _("_Don't save"), g_strconcat("+", _("_Save to Drafts"), NULL),
10603 g_mutex_unlock(compose->mutex);
10605 case G_ALERTDEFAULT:
10606 if (compose_can_autosave(compose) && !reedit)
10607 compose_remove_draft(compose);
10609 case G_ALERTALTERNATE:
10610 compose_draft(data, COMPOSE_QUIT_EDITING);
10617 compose_close(compose);
10620 static void compose_print_cb(GtkAction *action, gpointer data)
10622 Compose *compose = (Compose *) data;
10624 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
10625 if (compose->targetinfo)
10626 messageview_print(compose->targetinfo, FALSE, -1, -1, 0);
10629 static void compose_set_encoding_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
10631 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
10632 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
10633 Compose *compose = (Compose *) data;
10636 compose->out_encoding = (CharSet)value;
10639 static void compose_address_cb(GtkAction *action, gpointer data)
10641 Compose *compose = (Compose *)data;
10643 #ifndef USE_ALT_ADDRBOOK
10644 addressbook_open(compose);
10646 GError* error = NULL;
10647 addressbook_connect_signals(compose);
10648 addressbook_dbus_open(TRUE, &error);
10650 g_warning("%s", error->message);
10651 g_error_free(error);
10656 static void about_show_cb(GtkAction *action, gpointer data)
10661 static void compose_template_activate_cb(GtkWidget *widget, gpointer data)
10663 Compose *compose = (Compose *)data;
10668 tmpl = g_object_get_data(G_OBJECT(widget), "template");
10669 cm_return_if_fail(tmpl != NULL);
10671 msg = g_strdup_printf(_("Do you want to apply the template '%s'?"),
10673 val = alertpanel(_("Apply template"), msg,
10674 _("_Replace"), _("_Insert"), GTK_STOCK_CANCEL);
10677 if (val == G_ALERTDEFAULT)
10678 compose_template_apply(compose, tmpl, TRUE);
10679 else if (val == G_ALERTALTERNATE)
10680 compose_template_apply(compose, tmpl, FALSE);
10683 static void compose_ext_editor_cb(GtkAction *action, gpointer data)
10685 Compose *compose = (Compose *)data;
10688 if (compose->exteditor_tag != -1) {
10689 debug_print("ignoring open external editor: external editor still open\n");
10693 compose_exec_ext_editor(compose);
10696 static void compose_undo_cb(GtkAction *action, gpointer data)
10698 Compose *compose = (Compose *)data;
10699 gboolean prev_autowrap = compose->autowrap;
10701 compose->autowrap = FALSE;
10702 undo_undo(compose->undostruct);
10703 compose->autowrap = prev_autowrap;
10706 static void compose_redo_cb(GtkAction *action, gpointer data)
10708 Compose *compose = (Compose *)data;
10709 gboolean prev_autowrap = compose->autowrap;
10711 compose->autowrap = FALSE;
10712 undo_redo(compose->undostruct);
10713 compose->autowrap = prev_autowrap;
10716 static void entry_cut_clipboard(GtkWidget *entry)
10718 if (GTK_IS_EDITABLE(entry))
10719 gtk_editable_cut_clipboard (GTK_EDITABLE(entry));
10720 else if (GTK_IS_TEXT_VIEW(entry))
10721 gtk_text_buffer_cut_clipboard(
10722 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10723 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
10727 static void entry_copy_clipboard(GtkWidget *entry)
10729 if (GTK_IS_EDITABLE(entry))
10730 gtk_editable_copy_clipboard (GTK_EDITABLE(entry));
10731 else if (GTK_IS_TEXT_VIEW(entry))
10732 gtk_text_buffer_copy_clipboard(
10733 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10734 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
10737 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry,
10738 gboolean wrap, GdkAtom clip, GtkTextIter *insert_place)
10740 if (GTK_IS_TEXT_VIEW(entry)) {
10741 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10742 GtkTextMark *mark_start = gtk_text_buffer_get_insert(buffer);
10743 GtkTextIter start_iter, end_iter;
10745 gchar *contents = gtk_clipboard_wait_for_text(gtk_clipboard_get(clip));
10747 if (contents == NULL)
10750 /* we shouldn't delete the selection when middle-click-pasting, or we
10751 * can't mid-click-paste our own selection */
10752 if (clip != GDK_SELECTION_PRIMARY) {
10753 undo_paste_clipboard(GTK_TEXT_VIEW(compose->text), compose->undostruct);
10754 gtk_text_buffer_delete_selection(buffer, FALSE, TRUE);
10757 if (insert_place == NULL) {
10758 /* if insert_place isn't specified, insert at the cursor.
10759 * used for Ctrl-V pasting */
10760 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10761 start = gtk_text_iter_get_offset(&start_iter);
10762 gtk_text_buffer_insert(buffer, &start_iter, contents, strlen(contents));
10764 /* if insert_place is specified, paste here.
10765 * used for mid-click-pasting */
10766 start = gtk_text_iter_get_offset(insert_place);
10767 gtk_text_buffer_insert(buffer, insert_place, contents, strlen(contents));
10768 if (prefs_common.primary_paste_unselects)
10769 gtk_text_buffer_select_range(buffer, insert_place, insert_place);
10773 /* paste unwrapped: mark the paste so it's not wrapped later */
10774 end = start + strlen(contents);
10775 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, start);
10776 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, end);
10777 gtk_text_buffer_apply_tag_by_name(buffer, "no_wrap", &start_iter, &end_iter);
10778 } else if (wrap && clip == GDK_SELECTION_PRIMARY) {
10779 /* rewrap paragraph now (after a mid-click-paste) */
10780 mark_start = gtk_text_buffer_get_insert(buffer);
10781 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10782 gtk_text_iter_backward_char(&start_iter);
10783 compose_beautify_paragraph(compose, &start_iter, TRUE);
10785 } else if (GTK_IS_EDITABLE(entry))
10786 gtk_editable_paste_clipboard (GTK_EDITABLE(entry));
10788 compose->modified = TRUE;
10791 static void entry_allsel(GtkWidget *entry)
10793 if (GTK_IS_EDITABLE(entry))
10794 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
10795 else if (GTK_IS_TEXT_VIEW(entry)) {
10796 GtkTextIter startiter, enditer;
10797 GtkTextBuffer *textbuf;
10799 textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10800 gtk_text_buffer_get_start_iter(textbuf, &startiter);
10801 gtk_text_buffer_get_end_iter(textbuf, &enditer);
10803 gtk_text_buffer_move_mark_by_name(textbuf,
10804 "selection_bound", &startiter);
10805 gtk_text_buffer_move_mark_by_name(textbuf,
10806 "insert", &enditer);
10810 static void compose_cut_cb(GtkAction *action, gpointer data)
10812 Compose *compose = (Compose *)data;
10813 if (compose->focused_editable
10814 #ifndef GENERIC_UMPC
10815 && gtk_widget_has_focus(compose->focused_editable)
10818 entry_cut_clipboard(compose->focused_editable);
10821 static void compose_copy_cb(GtkAction *action, gpointer data)
10823 Compose *compose = (Compose *)data;
10824 if (compose->focused_editable
10825 #ifndef GENERIC_UMPC
10826 && gtk_widget_has_focus(compose->focused_editable)
10829 entry_copy_clipboard(compose->focused_editable);
10832 static void compose_paste_cb(GtkAction *action, gpointer data)
10834 Compose *compose = (Compose *)data;
10835 gint prev_autowrap;
10836 GtkTextBuffer *buffer;
10838 if (compose->focused_editable &&
10839 #ifndef GENERIC_UMPC
10840 gtk_widget_has_focus(compose->focused_editable)
10843 entry_paste_clipboard(compose, compose->focused_editable,
10844 prefs_common.linewrap_pastes,
10845 GDK_SELECTION_CLIPBOARD, NULL);
10850 #ifndef GENERIC_UMPC
10851 gtk_widget_has_focus(compose->text) &&
10853 compose->gtkaspell &&
10854 compose->gtkaspell->check_while_typing)
10855 gtkaspell_highlight_all(compose->gtkaspell);
10859 static void compose_paste_as_quote_cb(GtkAction *action, gpointer data)
10861 Compose *compose = (Compose *)data;
10862 gint wrap_quote = prefs_common.linewrap_quote;
10863 if (compose->focused_editable
10864 #ifndef GENERIC_UMPC
10865 && gtk_widget_has_focus(compose->focused_editable)
10868 /* let text_insert() (called directly or at a later time
10869 * after the gtk_editable_paste_clipboard) know that
10870 * text is to be inserted as a quotation. implemented
10871 * by using a simple refcount... */
10872 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data(
10873 G_OBJECT(compose->focused_editable),
10874 "paste_as_quotation"));
10875 g_object_set_data(G_OBJECT(compose->focused_editable),
10876 "paste_as_quotation",
10877 GINT_TO_POINTER(paste_as_quotation + 1));
10878 prefs_common.linewrap_quote = prefs_common.linewrap_pastes;
10879 entry_paste_clipboard(compose, compose->focused_editable,
10880 prefs_common.linewrap_pastes,
10881 GDK_SELECTION_CLIPBOARD, NULL);
10882 prefs_common.linewrap_quote = wrap_quote;
10886 static void compose_paste_no_wrap_cb(GtkAction *action, gpointer data)
10888 Compose *compose = (Compose *)data;
10889 gint prev_autowrap;
10890 GtkTextBuffer *buffer;
10892 if (compose->focused_editable
10893 #ifndef GENERIC_UMPC
10894 && gtk_widget_has_focus(compose->focused_editable)
10897 entry_paste_clipboard(compose, compose->focused_editable, FALSE,
10898 GDK_SELECTION_CLIPBOARD, NULL);
10903 #ifndef GENERIC_UMPC
10904 gtk_widget_has_focus(compose->text) &&
10906 compose->gtkaspell &&
10907 compose->gtkaspell->check_while_typing)
10908 gtkaspell_highlight_all(compose->gtkaspell);
10912 static void compose_paste_wrap_cb(GtkAction *action, gpointer data)
10914 Compose *compose = (Compose *)data;
10915 gint prev_autowrap;
10916 GtkTextBuffer *buffer;
10918 if (compose->focused_editable
10919 #ifndef GENERIC_UMPC
10920 && gtk_widget_has_focus(compose->focused_editable)
10923 entry_paste_clipboard(compose, compose->focused_editable, TRUE,
10924 GDK_SELECTION_CLIPBOARD, NULL);
10929 #ifndef GENERIC_UMPC
10930 gtk_widget_has_focus(compose->text) &&
10932 compose->gtkaspell &&
10933 compose->gtkaspell->check_while_typing)
10934 gtkaspell_highlight_all(compose->gtkaspell);
10938 static void compose_allsel_cb(GtkAction *action, gpointer data)
10940 Compose *compose = (Compose *)data;
10941 if (compose->focused_editable
10942 #ifndef GENERIC_UMPC
10943 && gtk_widget_has_focus(compose->focused_editable)
10946 entry_allsel(compose->focused_editable);
10949 static void textview_move_beginning_of_line (GtkTextView *text)
10951 GtkTextBuffer *buffer;
10955 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10957 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10958 mark = gtk_text_buffer_get_insert(buffer);
10959 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10960 gtk_text_iter_set_line_offset(&ins, 0);
10961 gtk_text_buffer_place_cursor(buffer, &ins);
10964 static void textview_move_forward_character (GtkTextView *text)
10966 GtkTextBuffer *buffer;
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);
10975 if (gtk_text_iter_forward_cursor_position(&ins))
10976 gtk_text_buffer_place_cursor(buffer, &ins);
10979 static void textview_move_backward_character (GtkTextView *text)
10981 GtkTextBuffer *buffer;
10985 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10987 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10988 mark = gtk_text_buffer_get_insert(buffer);
10989 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10990 if (gtk_text_iter_backward_cursor_position(&ins))
10991 gtk_text_buffer_place_cursor(buffer, &ins);
10994 static void textview_move_forward_word (GtkTextView *text)
10996 GtkTextBuffer *buffer;
11001 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11003 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11004 mark = gtk_text_buffer_get_insert(buffer);
11005 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11006 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
11007 if (gtk_text_iter_forward_word_ends(&ins, count)) {
11008 gtk_text_iter_backward_word_start(&ins);
11009 gtk_text_buffer_place_cursor(buffer, &ins);
11013 static void textview_move_backward_word (GtkTextView *text)
11015 GtkTextBuffer *buffer;
11019 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11021 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11022 mark = gtk_text_buffer_get_insert(buffer);
11023 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11024 if (gtk_text_iter_backward_word_starts(&ins, 1))
11025 gtk_text_buffer_place_cursor(buffer, &ins);
11028 static void textview_move_end_of_line (GtkTextView *text)
11030 GtkTextBuffer *buffer;
11034 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11036 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11037 mark = gtk_text_buffer_get_insert(buffer);
11038 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11039 if (gtk_text_iter_forward_to_line_end(&ins))
11040 gtk_text_buffer_place_cursor(buffer, &ins);
11043 static void textview_move_next_line (GtkTextView *text)
11045 GtkTextBuffer *buffer;
11050 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11052 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11053 mark = gtk_text_buffer_get_insert(buffer);
11054 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11055 offset = gtk_text_iter_get_line_offset(&ins);
11056 if (gtk_text_iter_forward_line(&ins)) {
11057 gtk_text_iter_set_line_offset(&ins, offset);
11058 gtk_text_buffer_place_cursor(buffer, &ins);
11062 static void textview_move_previous_line (GtkTextView *text)
11064 GtkTextBuffer *buffer;
11069 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11071 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11072 mark = gtk_text_buffer_get_insert(buffer);
11073 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11074 offset = gtk_text_iter_get_line_offset(&ins);
11075 if (gtk_text_iter_backward_line(&ins)) {
11076 gtk_text_iter_set_line_offset(&ins, offset);
11077 gtk_text_buffer_place_cursor(buffer, &ins);
11081 static void textview_delete_forward_character (GtkTextView *text)
11083 GtkTextBuffer *buffer;
11085 GtkTextIter ins, end_iter;
11087 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11089 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11090 mark = gtk_text_buffer_get_insert(buffer);
11091 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11093 if (gtk_text_iter_forward_char(&end_iter)) {
11094 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11098 static void textview_delete_backward_character (GtkTextView *text)
11100 GtkTextBuffer *buffer;
11102 GtkTextIter ins, end_iter;
11104 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11106 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11107 mark = gtk_text_buffer_get_insert(buffer);
11108 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11110 if (gtk_text_iter_backward_char(&end_iter)) {
11111 gtk_text_buffer_delete(buffer, &end_iter, &ins);
11115 static void textview_delete_forward_word (GtkTextView *text)
11117 GtkTextBuffer *buffer;
11119 GtkTextIter ins, end_iter;
11121 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11123 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11124 mark = gtk_text_buffer_get_insert(buffer);
11125 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11127 if (gtk_text_iter_forward_word_end(&end_iter)) {
11128 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11132 static void textview_delete_backward_word (GtkTextView *text)
11134 GtkTextBuffer *buffer;
11136 GtkTextIter ins, end_iter;
11138 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11140 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11141 mark = gtk_text_buffer_get_insert(buffer);
11142 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11144 if (gtk_text_iter_backward_word_start(&end_iter)) {
11145 gtk_text_buffer_delete(buffer, &end_iter, &ins);
11149 static void textview_delete_line (GtkTextView *text)
11151 GtkTextBuffer *buffer;
11153 GtkTextIter ins, start_iter, end_iter;
11155 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11157 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11158 mark = gtk_text_buffer_get_insert(buffer);
11159 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11162 gtk_text_iter_set_line_offset(&start_iter, 0);
11165 if (gtk_text_iter_ends_line(&end_iter)){
11166 if (!gtk_text_iter_forward_char(&end_iter))
11167 gtk_text_iter_backward_char(&start_iter);
11170 gtk_text_iter_forward_to_line_end(&end_iter);
11171 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
11174 static void textview_delete_to_line_end (GtkTextView *text)
11176 GtkTextBuffer *buffer;
11178 GtkTextIter ins, end_iter;
11180 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11182 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11183 mark = gtk_text_buffer_get_insert(buffer);
11184 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11186 if (gtk_text_iter_ends_line(&end_iter))
11187 gtk_text_iter_forward_char(&end_iter);
11189 gtk_text_iter_forward_to_line_end(&end_iter);
11190 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11193 #define DO_ACTION(name, act) { \
11194 if(!strcmp(name, a_name)) { \
11198 static ComposeCallAdvancedAction compose_call_advanced_action_from_path(GtkAction *action)
11200 const gchar *a_name = gtk_action_get_name(action);
11201 DO_ACTION("Edit/Advanced/BackChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER);
11202 DO_ACTION("Edit/Advanced/ForwChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER);
11203 DO_ACTION("Edit/Advanced/BackWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD);
11204 DO_ACTION("Edit/Advanced/ForwWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD);
11205 DO_ACTION("Edit/Advanced/BegLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE);
11206 DO_ACTION("Edit/Advanced/EndLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE);
11207 DO_ACTION("Edit/Advanced/PrevLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE);
11208 DO_ACTION("Edit/Advanced/NextLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE);
11209 DO_ACTION("Edit/Advanced/DelBackChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER);
11210 DO_ACTION("Edit/Advanced/DelForwChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER);
11211 DO_ACTION("Edit/Advanced/DelBackWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD);
11212 DO_ACTION("Edit/Advanced/DelForwWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD);
11213 DO_ACTION("Edit/Advanced/DelLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE);
11214 DO_ACTION("Edit/Advanced/DelEndLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END);
11215 return COMPOSE_CALL_ADVANCED_ACTION_UNDEFINED;
11218 static void compose_advanced_action_cb(GtkAction *gaction, gpointer data)
11220 Compose *compose = (Compose *)data;
11221 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11222 ComposeCallAdvancedAction action = COMPOSE_CALL_ADVANCED_ACTION_UNDEFINED;
11224 action = compose_call_advanced_action_from_path(gaction);
11227 void (*do_action) (GtkTextView *text);
11228 } action_table[] = {
11229 {textview_move_beginning_of_line},
11230 {textview_move_forward_character},
11231 {textview_move_backward_character},
11232 {textview_move_forward_word},
11233 {textview_move_backward_word},
11234 {textview_move_end_of_line},
11235 {textview_move_next_line},
11236 {textview_move_previous_line},
11237 {textview_delete_forward_character},
11238 {textview_delete_backward_character},
11239 {textview_delete_forward_word},
11240 {textview_delete_backward_word},
11241 {textview_delete_line},
11242 {textview_delete_to_line_end}
11245 if (!gtk_widget_has_focus(GTK_WIDGET(text))) return;
11247 if (action >= COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE &&
11248 action <= COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END) {
11249 if (action_table[action].do_action)
11250 action_table[action].do_action(text);
11252 g_warning("Not implemented yet.");
11256 static void compose_grab_focus_cb(GtkWidget *widget, Compose *compose)
11258 GtkAllocation allocation;
11262 if (GTK_IS_EDITABLE(widget)) {
11263 str = gtk_editable_get_chars(GTK_EDITABLE(widget), 0, -1);
11264 gtk_editable_set_position(GTK_EDITABLE(widget),
11267 if ((parent = gtk_widget_get_parent(widget))
11268 && (parent = gtk_widget_get_parent(parent))
11269 && (parent = gtk_widget_get_parent(parent))) {
11270 if (GTK_IS_SCROLLED_WINDOW(parent)) {
11271 gtk_widget_get_allocation(widget, &allocation);
11272 gint y = allocation.y;
11273 gint height = allocation.height;
11274 GtkAdjustment *shown = gtk_scrolled_window_get_vadjustment
11275 (GTK_SCROLLED_WINDOW(parent));
11277 gfloat value = gtk_adjustment_get_value(shown);
11278 gfloat upper = gtk_adjustment_get_upper(shown);
11279 gfloat page_size = gtk_adjustment_get_page_size(shown);
11280 if (y < (int)value) {
11281 gtk_adjustment_set_value(shown, y - 1);
11283 if ((y + height) > ((int)value + (int)page_size)) {
11284 if ((y - height - 1) < ((int)upper - (int)page_size)) {
11285 gtk_adjustment_set_value(shown,
11286 y + height - (int)page_size - 1);
11288 gtk_adjustment_set_value(shown,
11289 (int)upper - (int)page_size - 1);
11296 if (GTK_IS_EDITABLE(widget) || GTK_IS_TEXT_VIEW(widget))
11297 compose->focused_editable = widget;
11299 #ifdef GENERIC_UMPC
11300 if (GTK_IS_TEXT_VIEW(widget)
11301 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->edit_vbox) {
11302 g_object_ref(compose->notebook);
11303 g_object_ref(compose->edit_vbox);
11304 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
11305 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
11306 gtk_paned_add1(GTK_PANED(compose->paned), compose->edit_vbox);
11307 gtk_paned_add2(GTK_PANED(compose->paned), compose->notebook);
11308 g_object_unref(compose->notebook);
11309 g_object_unref(compose->edit_vbox);
11310 g_signal_handlers_block_by_func(G_OBJECT(widget),
11311 G_CALLBACK(compose_grab_focus_cb),
11313 gtk_widget_grab_focus(widget);
11314 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
11315 G_CALLBACK(compose_grab_focus_cb),
11317 } else if (!GTK_IS_TEXT_VIEW(widget)
11318 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->notebook) {
11319 g_object_ref(compose->notebook);
11320 g_object_ref(compose->edit_vbox);
11321 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
11322 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
11323 gtk_paned_add1(GTK_PANED(compose->paned), compose->notebook);
11324 gtk_paned_add2(GTK_PANED(compose->paned), compose->edit_vbox);
11325 g_object_unref(compose->notebook);
11326 g_object_unref(compose->edit_vbox);
11327 g_signal_handlers_block_by_func(G_OBJECT(widget),
11328 G_CALLBACK(compose_grab_focus_cb),
11330 gtk_widget_grab_focus(widget);
11331 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
11332 G_CALLBACK(compose_grab_focus_cb),
11338 static void compose_changed_cb(GtkTextBuffer *textbuf, Compose *compose)
11340 compose->modified = TRUE;
11341 /* compose_beautify_paragraph(compose, NULL, TRUE); */
11342 #ifndef GENERIC_UMPC
11343 compose_set_title(compose);
11347 static void compose_wrap_cb(GtkAction *action, gpointer data)
11349 Compose *compose = (Compose *)data;
11350 compose_beautify_paragraph(compose, NULL, TRUE);
11353 static void compose_wrap_all_cb(GtkAction *action, gpointer data)
11355 Compose *compose = (Compose *)data;
11356 compose_wrap_all_full(compose, TRUE);
11359 static void compose_find_cb(GtkAction *action, gpointer data)
11361 Compose *compose = (Compose *)data;
11363 message_search_compose(compose);
11366 static void compose_toggle_autowrap_cb(GtkToggleAction *action,
11369 Compose *compose = (Compose *)data;
11370 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11371 if (compose->autowrap)
11372 compose_wrap_all_full(compose, TRUE);
11373 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11376 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
11379 Compose *compose = (Compose *)data;
11380 compose->autoindent = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11383 static void compose_toggle_sign_cb(GtkToggleAction *action, gpointer data)
11385 Compose *compose = (Compose *)data;
11387 compose->use_signing = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11390 static void compose_toggle_encrypt_cb(GtkToggleAction *action, gpointer data)
11392 Compose *compose = (Compose *)data;
11394 compose->use_encryption = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11397 static void activate_privacy_system(Compose *compose, PrefsAccount *account, gboolean warn)
11399 g_free(compose->privacy_system);
11400 g_free(compose->encdata);
11402 compose->privacy_system = g_strdup(account->default_privacy_system);
11403 compose_update_privacy_system_menu_item(compose, warn);
11406 static void compose_toggle_ruler_cb(GtkToggleAction *action, gpointer data)
11408 Compose *compose = (Compose *)data;
11410 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action))) {
11411 gtk_widget_show(compose->ruler_hbox);
11412 prefs_common.show_ruler = TRUE;
11414 gtk_widget_hide(compose->ruler_hbox);
11415 gtk_widget_queue_resize(compose->edit_vbox);
11416 prefs_common.show_ruler = FALSE;
11420 static void compose_attach_drag_received_cb (GtkWidget *widget,
11421 GdkDragContext *context,
11424 GtkSelectionData *data,
11427 gpointer user_data)
11429 Compose *compose = (Compose *)user_data;
11433 type = gtk_selection_data_get_data_type(data);
11434 if ((gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list"))
11435 && gtk_drag_get_source_widget(context) !=
11436 summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
11437 list = uri_list_extract_filenames(
11438 (const gchar *)gtk_selection_data_get_data(data));
11439 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11440 gchar *utf8_filename = conv_filename_to_utf8((const gchar *)tmp->data);
11441 compose_attach_append
11442 (compose, (const gchar *)tmp->data,
11443 utf8_filename, NULL, NULL);
11444 g_free(utf8_filename);
11446 if (list) compose_changed_cb(NULL, compose);
11447 list_free_strings(list);
11449 } else if (gtk_drag_get_source_widget(context)
11450 == summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
11451 /* comes from our summaryview */
11452 SummaryView * summaryview = NULL;
11453 GSList * list = NULL, *cur = NULL;
11455 if (mainwindow_get_mainwindow())
11456 summaryview = mainwindow_get_mainwindow()->summaryview;
11459 list = summary_get_selected_msg_list(summaryview);
11461 for (cur = list; cur; cur = cur->next) {
11462 MsgInfo *msginfo = (MsgInfo *)cur->data;
11463 gchar *file = NULL;
11465 file = procmsg_get_message_file_full(msginfo,
11468 compose_attach_append(compose, (const gchar *)file,
11469 (const gchar *)file, "message/rfc822", NULL);
11473 g_slist_free(list);
11477 static gboolean compose_drag_drop(GtkWidget *widget,
11478 GdkDragContext *drag_context,
11480 guint time, gpointer user_data)
11482 /* not handling this signal makes compose_insert_drag_received_cb
11487 static gboolean completion_set_focus_to_subject
11488 (GtkWidget *widget,
11489 GdkEventKey *event,
11492 cm_return_val_if_fail(compose != NULL, FALSE);
11494 /* make backtab move to subject field */
11495 if(event->keyval == GDK_KEY_ISO_Left_Tab) {
11496 gtk_widget_grab_focus(compose->subject_entry);
11502 static void compose_insert_drag_received_cb (GtkWidget *widget,
11503 GdkDragContext *drag_context,
11506 GtkSelectionData *data,
11509 gpointer user_data)
11511 Compose *compose = (Compose *)user_data;
11517 /* strangely, testing data->type == gdk_atom_intern("text/uri-list", TRUE)
11519 type = gtk_selection_data_get_data_type(data);
11520 if (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list")) {
11521 AlertValue val = G_ALERTDEFAULT;
11522 const gchar* ddata = (const gchar *)gtk_selection_data_get_data(data);
11524 list = uri_list_extract_filenames(ddata);
11525 num_files = g_list_length(list);
11526 if (list == NULL && strstr(ddata, "://")) {
11527 /* Assume a list of no files, and data has ://, is a remote link */
11528 gchar *tmpdata = g_strstrip(g_strdup(ddata));
11529 gchar *tmpfile = get_tmp_file();
11530 str_write_to_file(tmpdata, tmpfile);
11532 compose_insert_file(compose, tmpfile);
11533 claws_unlink(tmpfile);
11535 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11536 compose_beautify_paragraph(compose, NULL, TRUE);
11539 switch (prefs_common.compose_dnd_mode) {
11540 case COMPOSE_DND_ASK:
11541 msg = g_strdup_printf(
11543 "Do you want to insert the contents of the file "
11544 "into the message body, or attach it to the email?",
11545 "Do you want to insert the contents of the %d files "
11546 "into the message body, or attach them to the email?",
11549 val = alertpanel_full(_("Insert or attach?"), msg,
11550 GTK_STOCK_CANCEL, g_strconcat("+", _("_Insert"), NULL), _("_Attach"),
11551 TRUE, NULL, ALERT_QUESTION, G_ALERTALTERNATE);
11554 case COMPOSE_DND_INSERT:
11555 val = G_ALERTALTERNATE;
11557 case COMPOSE_DND_ATTACH:
11558 val = G_ALERTOTHER;
11561 /* unexpected case */
11562 g_warning("error: unexpected compose_dnd_mode option value in compose_insert_drag_received_cb()");
11565 if (val & G_ALERTDISABLE) {
11566 val &= ~G_ALERTDISABLE;
11567 /* remember what action to perform by default, only if we don't click Cancel */
11568 if (val == G_ALERTALTERNATE)
11569 prefs_common.compose_dnd_mode = COMPOSE_DND_INSERT;
11570 else if (val == G_ALERTOTHER)
11571 prefs_common.compose_dnd_mode = COMPOSE_DND_ATTACH;
11574 if (val == G_ALERTDEFAULT || val == G_ALERTCANCEL) {
11575 gtk_drag_finish(drag_context, FALSE, FALSE, time);
11576 list_free_strings(list);
11579 } else if (val == G_ALERTOTHER) {
11580 compose_attach_drag_received_cb(widget, drag_context, x, y, data, info, time, user_data);
11581 list_free_strings(list);
11586 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11587 compose_insert_file(compose, (const gchar *)tmp->data);
11589 list_free_strings(list);
11591 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11596 static void compose_header_drag_received_cb (GtkWidget *widget,
11597 GdkDragContext *drag_context,
11600 GtkSelectionData *data,
11603 gpointer user_data)
11605 GtkEditable *entry = (GtkEditable *)user_data;
11606 const gchar *email = (const gchar *)gtk_selection_data_get_data(data);
11608 /* strangely, testing data->type == gdk_atom_intern("text/plain", TRUE)
11611 if (!strncmp(email, "mailto:", strlen("mailto:"))) {
11612 gchar *decoded=g_new(gchar, strlen(email));
11615 decode_uri(decoded, email + strlen("mailto:")); /* will fit */
11616 gtk_editable_delete_text(entry, 0, -1);
11617 gtk_editable_insert_text(entry, decoded, strlen(decoded), &start);
11618 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11622 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11625 static void compose_toggle_return_receipt_cb(GtkToggleAction *action, gpointer data)
11627 Compose *compose = (Compose *)data;
11629 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11630 compose->return_receipt = TRUE;
11632 compose->return_receipt = FALSE;
11635 static void compose_toggle_remove_refs_cb(GtkToggleAction *action, gpointer data)
11637 Compose *compose = (Compose *)data;
11639 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11640 compose->remove_references = TRUE;
11642 compose->remove_references = FALSE;
11645 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
11646 ComposeHeaderEntry *headerentry)
11648 gtk_entry_set_text(GTK_ENTRY(headerentry->entry), "");
11652 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
11653 GdkEventKey *event,
11654 ComposeHeaderEntry *headerentry)
11656 if ((g_slist_length(headerentry->compose->header_list) > 0) &&
11657 ((headerentry->headernum + 1) != headerentry->compose->header_nextrow) &&
11658 !(event->state & GDK_MODIFIER_MASK) &&
11659 (event->keyval == GDK_KEY_BackSpace) &&
11660 (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) == 0)) {
11661 gtk_container_remove
11662 (GTK_CONTAINER(headerentry->compose->header_table),
11663 headerentry->combo);
11664 gtk_container_remove
11665 (GTK_CONTAINER(headerentry->compose->header_table),
11666 headerentry->entry);
11667 headerentry->compose->header_list =
11668 g_slist_remove(headerentry->compose->header_list,
11670 g_free(headerentry);
11671 } else if (event->keyval == GDK_KEY_Tab) {
11672 if (headerentry->compose->header_last == headerentry) {
11673 /* Override default next focus, and give it to subject_entry
11674 * instead of notebook tabs
11676 g_signal_stop_emission_by_name(G_OBJECT(entry), "key-press-event");
11677 gtk_widget_grab_focus(headerentry->compose->subject_entry);
11684 static gboolean scroll_postpone(gpointer data)
11686 Compose *compose = (Compose *)data;
11688 if (compose->batch)
11691 GTK_EVENTS_FLUSH();
11692 compose_show_first_last_header(compose, FALSE);
11696 static void compose_headerentry_changed_cb(GtkWidget *entry,
11697 ComposeHeaderEntry *headerentry)
11699 if (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) != 0) {
11700 compose_create_header_entry(headerentry->compose);
11701 g_signal_handlers_disconnect_matched
11702 (G_OBJECT(entry), G_SIGNAL_MATCH_DATA,
11703 0, 0, NULL, NULL, headerentry);
11705 if (!headerentry->compose->batch)
11706 g_timeout_add(0, scroll_postpone, headerentry->compose);
11710 static gboolean compose_defer_auto_save_draft(Compose *compose)
11712 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
11713 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
11717 static void compose_show_first_last_header(Compose *compose, gboolean show_first)
11719 GtkAdjustment *vadj;
11721 cm_return_if_fail(compose);
11726 cm_return_if_fail(GTK_IS_WIDGET(compose->header_table));
11727 cm_return_if_fail(GTK_IS_VIEWPORT(gtk_widget_get_parent(compose->header_table)));
11728 vadj = gtk_viewport_get_vadjustment(GTK_VIEWPORT(
11729 gtk_widget_get_parent(compose->header_table)));
11730 gtk_adjustment_set_value(vadj, (show_first ?
11731 gtk_adjustment_get_lower(vadj) :
11732 (gtk_adjustment_get_upper(vadj) -
11733 gtk_adjustment_get_page_size(vadj))));
11734 gtk_adjustment_changed(vadj);
11737 static void text_inserted(GtkTextBuffer *buffer, GtkTextIter *iter,
11738 const gchar *text, gint len, Compose *compose)
11740 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data
11741 (G_OBJECT(compose->text), "paste_as_quotation"));
11744 cm_return_if_fail(text != NULL);
11746 g_signal_handlers_block_by_func(G_OBJECT(buffer),
11747 G_CALLBACK(text_inserted),
11749 if (paste_as_quotation) {
11751 const gchar *qmark;
11753 GtkTextIter start_iter;
11756 len = strlen(text);
11758 new_text = g_strndup(text, len);
11760 qmark = compose_quote_char_from_context(compose);
11762 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11763 gtk_text_buffer_place_cursor(buffer, iter);
11765 pos = gtk_text_iter_get_offset(iter);
11767 compose_quote_fmt(compose, NULL, "%Q", qmark, new_text, TRUE, FALSE,
11768 _("Quote format error at line %d."));
11769 quote_fmt_reset_vartable();
11771 g_object_set_data(G_OBJECT(compose->text), "paste_as_quotation",
11772 GINT_TO_POINTER(paste_as_quotation - 1));
11774 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11775 gtk_text_buffer_place_cursor(buffer, iter);
11776 gtk_text_buffer_delete_mark(buffer, mark);
11778 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, pos);
11779 mark = gtk_text_buffer_create_mark(buffer, NULL, &start_iter, FALSE);
11780 compose_beautify_paragraph(compose, &start_iter, FALSE);
11781 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark);
11782 gtk_text_buffer_delete_mark(buffer, mark);
11784 if (strcmp(text, "\n") || compose->automatic_break
11785 || gtk_text_iter_starts_line(iter)) {
11786 GtkTextIter before_ins;
11787 gtk_text_buffer_insert(buffer, iter, text, len);
11788 if (!strstr(text, "\n") && gtk_text_iter_has_tag(iter, compose->no_join_tag)) {
11789 before_ins = *iter;
11790 gtk_text_iter_backward_chars(&before_ins, len);
11791 gtk_text_buffer_remove_tag_by_name(buffer, "no_join", &before_ins, iter);
11794 /* check if the preceding is just whitespace or quote */
11795 GtkTextIter start_line;
11796 gchar *tmp = NULL, *quote = NULL;
11797 gint quote_len = 0, is_normal = 0;
11798 start_line = *iter;
11799 gtk_text_iter_set_line_offset(&start_line, 0);
11800 tmp = gtk_text_buffer_get_text(buffer, &start_line, iter, FALSE);
11803 if (*tmp == '\0') {
11806 quote = compose_get_quote_str(buffer, &start_line, "e_len);
11814 gtk_text_buffer_insert(buffer, iter, text, len);
11816 gtk_text_buffer_insert_with_tags_by_name(buffer,
11817 iter, text, len, "no_join", NULL);
11822 if (!paste_as_quotation) {
11823 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11824 compose_beautify_paragraph(compose, iter, FALSE);
11825 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11826 gtk_text_buffer_delete_mark(buffer, mark);
11829 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
11830 G_CALLBACK(text_inserted),
11832 g_signal_stop_emission_by_name(G_OBJECT(buffer), "insert-text");
11834 if (compose_can_autosave(compose) &&
11835 gtk_text_buffer_get_char_count(buffer) % prefs_common.autosave_length == 0 &&
11836 compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN /* disabled while loading */)
11837 compose->draft_timeout_tag = g_timeout_add
11838 (500, (GSourceFunc) compose_defer_auto_save_draft, compose);
11842 static void compose_check_all(GtkAction *action, gpointer data)
11844 Compose *compose = (Compose *)data;
11845 if (!compose->gtkaspell)
11848 if (gtk_widget_has_focus(compose->subject_entry))
11849 claws_spell_entry_check_all(
11850 CLAWS_SPELL_ENTRY(compose->subject_entry));
11852 gtkaspell_check_all(compose->gtkaspell);
11855 static void compose_highlight_all(GtkAction *action, gpointer data)
11857 Compose *compose = (Compose *)data;
11858 if (compose->gtkaspell) {
11859 claws_spell_entry_recheck_all(
11860 CLAWS_SPELL_ENTRY(compose->subject_entry));
11861 gtkaspell_highlight_all(compose->gtkaspell);
11865 static void compose_check_backwards(GtkAction *action, gpointer data)
11867 Compose *compose = (Compose *)data;
11868 if (!compose->gtkaspell) {
11869 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
11873 if (gtk_widget_has_focus(compose->subject_entry))
11874 claws_spell_entry_check_backwards(
11875 CLAWS_SPELL_ENTRY(compose->subject_entry));
11877 gtkaspell_check_backwards(compose->gtkaspell);
11880 static void compose_check_forwards_go(GtkAction *action, gpointer data)
11882 Compose *compose = (Compose *)data;
11883 if (!compose->gtkaspell) {
11884 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
11888 if (gtk_widget_has_focus(compose->subject_entry))
11889 claws_spell_entry_check_forwards_go(
11890 CLAWS_SPELL_ENTRY(compose->subject_entry));
11892 gtkaspell_check_forwards_go(compose->gtkaspell);
11897 *\brief Guess originating forward account from MsgInfo and several
11898 * "common preference" settings. Return NULL if no guess.
11900 static PrefsAccount *compose_find_account(MsgInfo *msginfo)
11902 PrefsAccount *account = NULL;
11904 cm_return_val_if_fail(msginfo, NULL);
11905 cm_return_val_if_fail(msginfo->folder, NULL);
11906 cm_return_val_if_fail(msginfo->folder->prefs, NULL);
11908 if (msginfo->folder->prefs->enable_default_account)
11909 account = account_find_from_id(msginfo->folder->prefs->default_account);
11911 if (!account && msginfo->to && prefs_common.forward_account_autosel) {
11913 Xstrdup_a(to, msginfo->to, return NULL);
11914 extract_address(to);
11915 account = account_find_from_address(to, FALSE);
11918 if (!account && prefs_common.forward_account_autosel) {
11920 if (!procheader_get_header_from_msginfo
11921 (msginfo, &cc, "Cc:")) {
11922 gchar *buf = cc + strlen("Cc:");
11923 extract_address(buf);
11924 account = account_find_from_address(buf, FALSE);
11929 if (!account && prefs_common.forward_account_autosel) {
11930 gchar *deliveredto = NULL;
11931 if (!procheader_get_header_from_msginfo
11932 (msginfo, &deliveredto, "Delivered-To:")) {
11933 gchar *buf = deliveredto + strlen("Delivered-To:");
11934 extract_address(buf);
11935 account = account_find_from_address(buf, FALSE);
11936 g_free(deliveredto);
11941 account = msginfo->folder->folder->account;
11946 gboolean compose_close(Compose *compose)
11950 cm_return_val_if_fail(compose, FALSE);
11952 if (!g_mutex_trylock(compose->mutex)) {
11953 /* we have to wait for the (possibly deferred by auto-save)
11954 * drafting to be done, before destroying the compose under
11956 debug_print("waiting for drafting to finish...\n");
11957 compose_allow_user_actions(compose, FALSE);
11958 if (compose->close_timeout_tag == 0) {
11959 compose->close_timeout_tag =
11960 g_timeout_add (500, (GSourceFunc) compose_close,
11966 if (compose->draft_timeout_tag >= 0) {
11967 g_source_remove(compose->draft_timeout_tag);
11968 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN;
11971 gtkut_widget_get_uposition(compose->window, &x, &y);
11972 if (!compose->batch) {
11973 prefs_common.compose_x = x;
11974 prefs_common.compose_y = y;
11976 g_mutex_unlock(compose->mutex);
11977 compose_destroy(compose);
11982 * Add entry field for each address in list.
11983 * \param compose E-Mail composition object.
11984 * \param listAddress List of (formatted) E-Mail addresses.
11986 static void compose_add_field_list( Compose *compose, GList *listAddress ) {
11989 node = listAddress;
11991 addr = ( gchar * ) node->data;
11992 compose_entry_append( compose, addr, COMPOSE_TO, PREF_NONE );
11993 node = g_list_next( node );
11997 static void compose_reply_from_messageview_real(MessageView *msgview, GSList *msginfo_list,
11998 guint action, gboolean opening_multiple)
12000 gchar *body = NULL;
12001 GSList *new_msglist = NULL;
12002 MsgInfo *tmp_msginfo = NULL;
12003 gboolean originally_enc = FALSE;
12004 gboolean originally_sig = FALSE;
12005 Compose *compose = NULL;
12006 gchar *s_system = NULL;
12008 cm_return_if_fail(msgview != NULL);
12010 cm_return_if_fail(msginfo_list != NULL);
12012 if (g_slist_length(msginfo_list) == 1 && !opening_multiple) {
12013 MimeInfo *mimeinfo = messageview_get_selected_mime_part(msgview);
12014 MsgInfo *orig_msginfo = (MsgInfo *)msginfo_list->data;
12016 if (mimeinfo != NULL && mimeinfo->type == MIMETYPE_MESSAGE &&
12017 !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
12018 tmp_msginfo = procmsg_msginfo_new_from_mimeinfo(
12019 orig_msginfo, mimeinfo);
12020 if (tmp_msginfo != NULL) {
12021 new_msglist = g_slist_append(NULL, tmp_msginfo);
12023 originally_enc = MSG_IS_ENCRYPTED(orig_msginfo->flags);
12024 privacy_msginfo_get_signed_state(orig_msginfo, &s_system);
12025 originally_sig = MSG_IS_SIGNED(orig_msginfo->flags);
12027 tmp_msginfo->folder = orig_msginfo->folder;
12028 tmp_msginfo->msgnum = orig_msginfo->msgnum;
12029 if (orig_msginfo->tags) {
12030 tmp_msginfo->tags = g_slist_copy(orig_msginfo->tags);
12031 tmp_msginfo->folder->tags_dirty = TRUE;
12037 if (!opening_multiple)
12038 body = messageview_get_selection(msgview);
12041 compose = compose_reply_mode((ComposeMode)action, new_msglist, body);
12042 procmsg_msginfo_free(&tmp_msginfo);
12043 g_slist_free(new_msglist);
12045 compose = compose_reply_mode((ComposeMode)action, msginfo_list, body);
12047 if (compose && originally_enc) {
12048 compose_force_encryption(compose, compose->account, FALSE, s_system);
12051 if (compose && originally_sig && compose->account->default_sign_reply) {
12052 compose_force_signing(compose, compose->account, s_system);
12056 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
12059 void compose_reply_from_messageview(MessageView *msgview, GSList *msginfo_list,
12062 if ((!prefs_common.forward_as_attachment || action != COMPOSE_FORWARD)
12063 && action != COMPOSE_FORWARD_AS_ATTACH && g_slist_length(msginfo_list) > 1) {
12064 GSList *cur = msginfo_list;
12065 gchar *msg = g_strdup_printf(_("You are about to reply to %d "
12066 "messages. Opening the windows "
12067 "could take some time. Do you "
12068 "want to continue?"),
12069 g_slist_length(msginfo_list));
12070 if (g_slist_length(msginfo_list) > 9
12071 && alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, "+" GTK_STOCK_YES, NULL)
12072 != G_ALERTALTERNATE) {
12077 /* We'll open multiple compose windows */
12078 /* let the WM place the next windows */
12079 compose_force_window_origin = FALSE;
12080 for (; cur; cur = cur->next) {
12082 tmplist.data = cur->data;
12083 tmplist.next = NULL;
12084 compose_reply_from_messageview_real(msgview, &tmplist, action, TRUE);
12086 compose_force_window_origin = TRUE;
12088 /* forwarding multiple mails as attachments is done via a
12089 * single compose window */
12090 compose_reply_from_messageview_real(msgview, msginfo_list, action, FALSE);
12094 void compose_check_for_email_account(Compose *compose)
12096 PrefsAccount *ac = NULL, *curr = NULL;
12102 if (compose->account && compose->account->protocol == A_NNTP) {
12103 ac = account_get_cur_account();
12104 if (ac->protocol == A_NNTP) {
12105 list = account_get_list();
12107 for( ; list != NULL ; list = g_list_next(list)) {
12108 curr = (PrefsAccount *) list->data;
12109 if (curr->protocol != A_NNTP) {
12115 combobox_select_by_data(GTK_COMBO_BOX(compose->account_combo),
12120 void compose_reply_to_address(MessageView *msgview, MsgInfo *msginfo,
12121 const gchar *address)
12123 GSList *msginfo_list = NULL;
12124 gchar *body = messageview_get_selection(msgview);
12127 msginfo_list = g_slist_prepend(msginfo_list, msginfo);
12129 compose = compose_reply_mode(COMPOSE_REPLY_TO_ADDRESS, msginfo_list, body);
12130 compose_check_for_email_account(compose);
12131 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
12132 compose_entry_append(compose, address, COMPOSE_TO, PREF_NONE);
12133 compose_reply_set_subject(compose, msginfo);
12136 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
12139 void compose_set_position(Compose *compose, gint pos)
12141 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
12143 gtkut_text_view_set_position(text, pos);
12146 gboolean compose_search_string(Compose *compose,
12147 const gchar *str, gboolean case_sens)
12149 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
12151 return gtkut_text_view_search_string(text, str, case_sens);
12154 gboolean compose_search_string_backward(Compose *compose,
12155 const gchar *str, gboolean case_sens)
12157 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
12159 return gtkut_text_view_search_string_backward(text, str, case_sens);
12162 /* allocate a msginfo structure and populate its data from a compose data structure */
12163 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose)
12165 MsgInfo *newmsginfo;
12167 gchar date[RFC822_DATE_BUFFSIZE];
12169 cm_return_val_if_fail( compose != NULL, NULL );
12171 newmsginfo = procmsg_msginfo_new();
12174 get_rfc822_date(date, sizeof(date));
12175 newmsginfo->date = g_strdup(date);
12178 if (compose->from_name) {
12179 newmsginfo->from = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
12180 newmsginfo->fromname = procheader_get_fromname(newmsginfo->from);
12184 if (compose->subject_entry)
12185 newmsginfo->subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
12187 /* to, cc, reply-to, newsgroups */
12188 for (list = compose->header_list; list; list = list->next) {
12189 gchar *header = gtk_editable_get_chars(
12191 gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
12192 gchar *entry = gtk_editable_get_chars(
12193 GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
12195 if ( strcasecmp(header, prefs_common_translated_header_name("To:")) == 0 ) {
12196 if ( newmsginfo->to == NULL ) {
12197 newmsginfo->to = g_strdup(entry);
12198 } else if (entry && *entry) {
12199 gchar *tmp = g_strconcat(newmsginfo->to, ", ", entry, NULL);
12200 g_free(newmsginfo->to);
12201 newmsginfo->to = tmp;
12204 if ( strcasecmp(header, prefs_common_translated_header_name("Cc:")) == 0 ) {
12205 if ( newmsginfo->cc == NULL ) {
12206 newmsginfo->cc = g_strdup(entry);
12207 } else if (entry && *entry) {
12208 gchar *tmp = g_strconcat(newmsginfo->cc, ", ", entry, NULL);
12209 g_free(newmsginfo->cc);
12210 newmsginfo->cc = tmp;
12213 if ( strcasecmp(header,
12214 prefs_common_translated_header_name("Newsgroups:")) == 0 ) {
12215 if ( newmsginfo->newsgroups == NULL ) {
12216 newmsginfo->newsgroups = g_strdup(entry);
12217 } else if (entry && *entry) {
12218 gchar *tmp = g_strconcat(newmsginfo->newsgroups, ", ", entry, NULL);
12219 g_free(newmsginfo->newsgroups);
12220 newmsginfo->newsgroups = tmp;
12228 /* other data is unset */
12234 /* update compose's dictionaries from folder dict settings */
12235 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
12236 FolderItem *folder_item)
12238 cm_return_if_fail(compose != NULL);
12240 if (compose->gtkaspell && folder_item && folder_item->prefs) {
12241 FolderItemPrefs *prefs = folder_item->prefs;
12243 if (prefs->enable_default_dictionary)
12244 gtkaspell_change_dict(compose->gtkaspell,
12245 prefs->default_dictionary, FALSE);
12246 if (folder_item->prefs->enable_default_alt_dictionary)
12247 gtkaspell_change_alt_dict(compose->gtkaspell,
12248 prefs->default_alt_dictionary);
12249 if (prefs->enable_default_dictionary
12250 || prefs->enable_default_alt_dictionary)
12251 compose_spell_menu_changed(compose);
12256 static void compose_subject_entry_activated(GtkWidget *widget, gpointer data)
12258 Compose *compose = (Compose *)data;
12260 cm_return_if_fail(compose != NULL);
12262 gtk_widget_grab_focus(compose->text);
12265 static void from_name_activate_cb(GtkWidget *widget, gpointer data)
12267 gtk_combo_box_popup(GTK_COMBO_BOX(data));