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->msgid = g_strdup(msginfo->msgid);
2987 if (msginfo->msgid && *msginfo->msgid)
2988 compose->inreplyto = g_strdup(msginfo->msgid);
2990 if (!compose->references) {
2991 if (msginfo->msgid && *msginfo->msgid) {
2992 if (msginfo->inreplyto && *msginfo->inreplyto)
2993 compose->references =
2994 g_strdup_printf("<%s>\n\t<%s>",
2998 compose->references =
2999 g_strconcat("<", msginfo->msgid, ">",
3001 } else if (msginfo->inreplyto && *msginfo->inreplyto) {
3002 compose->references =
3003 g_strconcat("<", msginfo->inreplyto, ">",
3012 static gint compose_parse_manual_headers(Compose *compose, MsgInfo *msginfo, HeaderEntry *entries)
3017 cm_return_val_if_fail(msginfo != NULL, -1);
3019 if ((fp = procmsg_open_message(msginfo)) == NULL) return -1;
3020 procheader_get_header_fields(fp, entries);
3024 while (he != NULL && he->name != NULL) {
3026 GtkListStore *model = NULL;
3028 debug_print("Adding manual header: %s with value %s\n", he->name, he->body);
3029 model = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(compose->header_last->combo)));
3030 COMBOBOX_ADD(model, he->name, COMPOSE_TO);
3031 gtk_combo_box_set_active_iter(GTK_COMBO_BOX(compose->header_last->combo), &iter);
3032 gtk_entry_set_text(GTK_ENTRY(compose->header_last->entry), he->body);
3039 static gchar *compose_parse_references(const gchar *ref, const gchar *msgid)
3041 GSList *ref_id_list, *cur;
3045 ref_id_list = references_list_append(NULL, ref);
3046 if (!ref_id_list) return NULL;
3047 if (msgid && *msgid)
3048 ref_id_list = g_slist_append(ref_id_list, g_strdup(msgid));
3053 for (cur = ref_id_list; cur != NULL; cur = cur->next)
3054 /* "<" + Message-ID + ">" + CR+LF+TAB */
3055 len += strlen((gchar *)cur->data) + 5;
3057 if (len > MAX_REFERENCES_LEN) {
3058 /* remove second message-ID */
3059 if (ref_id_list && ref_id_list->next &&
3060 ref_id_list->next->next) {
3061 g_free(ref_id_list->next->data);
3062 ref_id_list = g_slist_remove
3063 (ref_id_list, ref_id_list->next->data);
3065 slist_free_strings_full(ref_id_list);
3072 new_ref = g_string_new("");
3073 for (cur = ref_id_list; cur != NULL; cur = cur->next) {
3074 if (new_ref->len > 0)
3075 g_string_append(new_ref, "\n\t");
3076 g_string_append_printf(new_ref, "<%s>", (gchar *)cur->data);
3079 slist_free_strings_full(ref_id_list);
3081 new_ref_str = new_ref->str;
3082 g_string_free(new_ref, FALSE);
3087 static gchar *compose_quote_fmt(Compose *compose, MsgInfo *msginfo,
3088 const gchar *fmt, const gchar *qmark,
3089 const gchar *body, gboolean rewrap,
3090 gboolean need_unescape,
3091 const gchar *err_msg)
3093 MsgInfo* dummyinfo = NULL;
3094 gchar *quote_str = NULL;
3096 gboolean prev_autowrap;
3097 const gchar *trimmed_body = body;
3098 gint cursor_pos = -1;
3099 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3100 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3105 SIGNAL_BLOCK(buffer);
3108 dummyinfo = compose_msginfo_new_from_compose(compose);
3109 msginfo = dummyinfo;
3112 if (qmark != NULL) {
3114 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
3115 compose->gtkaspell);
3117 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
3119 quote_fmt_scan_string(qmark);
3122 buf = quote_fmt_get_buffer();
3124 alertpanel_error(_("The \"Quotation mark\" of the template is invalid."));
3126 Xstrdup_a(quote_str, buf, goto error)
3129 if (fmt && *fmt != '\0') {
3132 while (*trimmed_body == '\n')
3136 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE,
3137 compose->gtkaspell);
3139 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE);
3141 if (need_unescape) {
3144 /* decode \-escape sequences in the internal representation of the quote format */
3145 tmp = g_malloc(strlen(fmt)+1);
3146 pref_get_unescaped_pref(tmp, fmt);
3147 quote_fmt_scan_string(tmp);
3151 quote_fmt_scan_string(fmt);
3155 buf = quote_fmt_get_buffer();
3157 gint line = quote_fmt_get_line();
3158 alertpanel_error(err_msg, line);
3164 prev_autowrap = compose->autowrap;
3165 compose->autowrap = FALSE;
3167 mark = gtk_text_buffer_get_insert(buffer);
3168 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3169 if (g_utf8_validate(buf, -1, NULL)) {
3170 gtk_text_buffer_insert(buffer, &iter, buf, -1);
3172 gchar *tmpout = NULL;
3173 tmpout = conv_codeset_strdup
3174 (buf, conv_get_locale_charset_str_no_utf8(),
3176 if (!tmpout || !g_utf8_validate(tmpout, -1, NULL)) {
3178 tmpout = g_malloc(strlen(buf)*2+1);
3179 conv_localetodisp(tmpout, strlen(buf)*2+1, buf);
3181 gtk_text_buffer_insert(buffer, &iter, tmpout, -1);
3185 cursor_pos = quote_fmt_get_cursor_pos();
3186 if (cursor_pos == -1)
3187 cursor_pos = gtk_text_iter_get_offset(&iter);
3188 compose->set_cursor_pos = cursor_pos;
3190 gtk_text_buffer_get_start_iter(buffer, &iter);
3191 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
3192 gtk_text_buffer_place_cursor(buffer, &iter);
3194 compose->autowrap = prev_autowrap;
3195 if (compose->autowrap && rewrap)
3196 compose_wrap_all(compose);
3203 SIGNAL_UNBLOCK(buffer);
3205 procmsg_msginfo_free( &dummyinfo );
3210 /* if ml_post is of type addr@host and from is of type
3211 * addr-anything@host, return TRUE
3213 static gboolean is_subscription(const gchar *ml_post, const gchar *from)
3215 gchar *left_ml = NULL;
3216 gchar *right_ml = NULL;
3217 gchar *left_from = NULL;
3218 gchar *right_from = NULL;
3219 gboolean result = FALSE;
3221 if (!ml_post || !from)
3224 left_ml = g_strdup(ml_post);
3225 if (strstr(left_ml, "@")) {
3226 right_ml = strstr(left_ml, "@")+1;
3227 *(strstr(left_ml, "@")) = '\0';
3230 left_from = g_strdup(from);
3231 if (strstr(left_from, "@")) {
3232 right_from = strstr(left_from, "@")+1;
3233 *(strstr(left_from, "@")) = '\0';
3236 if (right_ml && right_from
3237 && !strncmp(left_from, left_ml, strlen(left_ml))
3238 && !strcmp(right_from, right_ml)) {
3247 static void compose_set_folder_prefs(Compose *compose, FolderItem *folder,
3248 gboolean respect_default_to)
3252 if (!folder || !folder->prefs)
3255 if (respect_default_to && folder->prefs->enable_default_to) {
3256 compose_entry_append(compose, folder->prefs->default_to,
3257 COMPOSE_TO, PREF_FOLDER);
3258 compose_entry_indicate(compose, folder->prefs->default_to);
3260 if (folder->prefs->enable_default_cc) {
3261 compose_entry_append(compose, folder->prefs->default_cc,
3262 COMPOSE_CC, PREF_FOLDER);
3263 compose_entry_indicate(compose, folder->prefs->default_cc);
3265 if (folder->prefs->enable_default_bcc) {
3266 compose_entry_append(compose, folder->prefs->default_bcc,
3267 COMPOSE_BCC, PREF_FOLDER);
3268 compose_entry_indicate(compose, folder->prefs->default_bcc);
3270 if (folder->prefs->enable_default_replyto) {
3271 compose_entry_append(compose, folder->prefs->default_replyto,
3272 COMPOSE_REPLYTO, PREF_FOLDER);
3273 compose_entry_indicate(compose, folder->prefs->default_replyto);
3277 static void compose_reply_set_subject(Compose *compose, MsgInfo *msginfo)
3282 if (!compose || !msginfo)
3285 if (msginfo->subject && *msginfo->subject) {
3286 buf = p = g_strdup(msginfo->subject);
3287 p += subject_get_prefix_length(p);
3288 memmove(buf, p, strlen(p) + 1);
3290 buf2 = g_strdup_printf("Re: %s", buf);
3291 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
3296 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), "Re: ");
3299 static void compose_reply_set_entry(Compose *compose, MsgInfo *msginfo,
3300 gboolean to_all, gboolean to_ml,
3302 gboolean followup_and_reply_to)
3304 GSList *cc_list = NULL;
3307 gchar *replyto = NULL;
3308 gchar *ac_email = NULL;
3310 gboolean reply_to_ml = FALSE;
3311 gboolean default_reply_to = FALSE;
3313 cm_return_if_fail(compose->account != NULL);
3314 cm_return_if_fail(msginfo != NULL);
3316 reply_to_ml = to_ml && compose->ml_post;
3318 default_reply_to = msginfo->folder &&
3319 msginfo->folder->prefs->enable_default_reply_to;
3321 if (compose->account->protocol != A_NNTP) {
3322 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
3324 if (reply_to_ml && !default_reply_to) {
3326 gboolean is_subscr = is_subscription(compose->ml_post,
3329 /* normal answer to ml post with a reply-to */
3330 compose_entry_append(compose,
3332 COMPOSE_TO, PREF_ML);
3333 if (compose->replyto)
3334 compose_entry_append(compose,
3336 COMPOSE_CC, PREF_ML);
3338 /* answer to subscription confirmation */
3339 if (compose->replyto)
3340 compose_entry_append(compose,
3342 COMPOSE_TO, PREF_ML);
3343 else if (msginfo->from)
3344 compose_entry_append(compose,
3346 COMPOSE_TO, PREF_ML);
3349 else if (!(to_all || to_sender) && default_reply_to) {
3350 compose_entry_append(compose,
3351 msginfo->folder->prefs->default_reply_to,
3352 COMPOSE_TO, PREF_FOLDER);
3353 compose_entry_indicate(compose,
3354 msginfo->folder->prefs->default_reply_to);
3360 compose_entry_append(compose, msginfo->from,
3361 COMPOSE_TO, PREF_NONE);
3363 Xstrdup_a(tmp1, msginfo->from, return);
3364 extract_address(tmp1);
3365 compose_entry_append(compose,
3366 (!account_find_from_address(tmp1, FALSE))
3369 COMPOSE_TO, PREF_NONE);
3370 if (compose->replyto)
3371 compose_entry_append(compose,
3373 COMPOSE_CC, PREF_NONE);
3375 if (!folder_has_parent_of_type(msginfo->folder, F_QUEUE) &&
3376 !folder_has_parent_of_type(msginfo->folder, F_OUTBOX) &&
3377 !folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
3378 if (compose->replyto) {
3379 compose_entry_append(compose,
3381 COMPOSE_TO, PREF_NONE);
3383 compose_entry_append(compose,
3384 msginfo->from ? msginfo->from : "",
3385 COMPOSE_TO, PREF_NONE);
3388 /* replying to own mail, use original recp */
3389 compose_entry_append(compose,
3390 msginfo->to ? msginfo->to : "",
3391 COMPOSE_TO, PREF_NONE);
3392 compose_entry_append(compose,
3393 msginfo->cc ? msginfo->cc : "",
3394 COMPOSE_CC, PREF_NONE);
3399 if (to_sender || (compose->followup_to &&
3400 !strncmp(compose->followup_to, "poster", 6)))
3401 compose_entry_append
3403 (compose->replyto ? compose->replyto :
3404 msginfo->from ? msginfo->from : ""),
3405 COMPOSE_TO, PREF_NONE);
3407 else if (followup_and_reply_to || to_all) {
3408 compose_entry_append
3410 (compose->replyto ? compose->replyto :
3411 msginfo->from ? msginfo->from : ""),
3412 COMPOSE_TO, PREF_NONE);
3414 compose_entry_append
3416 compose->followup_to ? compose->followup_to :
3417 compose->newsgroups ? compose->newsgroups : "",
3418 COMPOSE_NEWSGROUPS, PREF_NONE);
3420 compose_entry_append
3422 msginfo->cc ? msginfo->cc : "",
3423 COMPOSE_CC, PREF_NONE);
3426 compose_entry_append
3428 compose->followup_to ? compose->followup_to :
3429 compose->newsgroups ? compose->newsgroups : "",
3430 COMPOSE_NEWSGROUPS, PREF_NONE);
3432 compose_reply_set_subject(compose, msginfo);
3434 if (to_ml && compose->ml_post) return;
3435 if (!to_all || compose->account->protocol == A_NNTP) return;
3437 if (compose->replyto) {
3438 Xstrdup_a(replyto, compose->replyto, return);
3439 extract_address(replyto);
3441 if (msginfo->from) {
3442 Xstrdup_a(from, msginfo->from, return);
3443 extract_address(from);
3446 if (replyto && from)
3447 cc_list = address_list_append_with_comments(cc_list, from);
3448 if (to_all && msginfo->folder &&
3449 msginfo->folder->prefs->enable_default_reply_to)
3450 cc_list = address_list_append_with_comments(cc_list,
3451 msginfo->folder->prefs->default_reply_to);
3452 cc_list = address_list_append_with_comments(cc_list, msginfo->to);
3453 cc_list = address_list_append_with_comments(cc_list, compose->cc);
3455 ac_email = g_utf8_strdown(compose->account->address, -1);
3458 for (cur = cc_list; cur != NULL; cur = cur->next) {
3459 gchar *addr = g_utf8_strdown(cur->data, -1);
3460 extract_address(addr);
3462 if (strcmp(ac_email, addr))
3463 compose_entry_append(compose, (gchar *)cur->data,
3464 COMPOSE_CC, PREF_NONE);
3466 debug_print("Cc address same as compose account's, ignoring\n");
3471 slist_free_strings_full(cc_list);
3477 #define SET_ENTRY(entry, str) \
3480 gtk_entry_set_text(GTK_ENTRY(compose->entry), str); \
3483 #define SET_ADDRESS(type, str) \
3486 compose_entry_append(compose, str, type, PREF_NONE); \
3489 static void compose_reedit_set_entry(Compose *compose, MsgInfo *msginfo)
3491 cm_return_if_fail(msginfo != NULL);
3493 SET_ENTRY(subject_entry, msginfo->subject);
3494 SET_ENTRY(from_name, msginfo->from);
3495 SET_ADDRESS(COMPOSE_TO, msginfo->to);
3496 SET_ADDRESS(COMPOSE_CC, compose->cc);
3497 SET_ADDRESS(COMPOSE_BCC, compose->bcc);
3498 SET_ADDRESS(COMPOSE_REPLYTO, compose->replyto);
3499 SET_ADDRESS(COMPOSE_NEWSGROUPS, compose->newsgroups);
3500 SET_ADDRESS(COMPOSE_FOLLOWUPTO, compose->followup_to);
3502 compose_update_priority_menu_item(compose);
3503 compose_update_privacy_system_menu_item(compose, FALSE);
3504 compose_show_first_last_header(compose, TRUE);
3510 static void compose_insert_sig(Compose *compose, gboolean replace)
3512 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3513 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3515 GtkTextIter iter, iter_end;
3516 gint cur_pos, ins_pos;
3517 gboolean prev_autowrap;
3518 gboolean found = FALSE;
3519 gboolean exists = FALSE;
3521 cm_return_if_fail(compose->account != NULL);
3525 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3526 G_CALLBACK(compose_changed_cb),
3529 mark = gtk_text_buffer_get_insert(buffer);
3530 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3531 cur_pos = gtk_text_iter_get_offset (&iter);
3534 gtk_text_buffer_get_end_iter(buffer, &iter);
3536 exists = (compose->sig_str != NULL);
3539 GtkTextIter first_iter, start_iter, end_iter;
3541 gtk_text_buffer_get_start_iter(buffer, &first_iter);
3543 if (!exists || compose->sig_str[0] == '\0')
3546 found = gtk_text_iter_forward_to_tag_toggle(&first_iter,
3547 compose->signature_tag);
3550 /* include previous \n\n */
3551 gtk_text_iter_backward_chars(&first_iter, 1);
3552 start_iter = first_iter;
3553 end_iter = first_iter;
3555 found = gtk_text_iter_forward_to_tag_toggle(&end_iter,
3556 compose->signature_tag);
3557 found &= gtk_text_iter_forward_to_tag_toggle(&end_iter,
3558 compose->signature_tag);
3560 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
3566 g_free(compose->sig_str);
3567 compose->sig_str = account_get_signature_str(compose->account);
3569 cur_pos = gtk_text_iter_get_offset(&iter);
3571 if (!compose->sig_str || (replace && !compose->account->auto_sig)) {
3572 g_free(compose->sig_str);
3573 compose->sig_str = NULL;
3575 if (compose->sig_inserted == FALSE)
3576 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
3577 compose->sig_inserted = TRUE;
3579 cur_pos = gtk_text_iter_get_offset(&iter);
3580 gtk_text_buffer_insert(buffer, &iter, compose->sig_str, -1);
3582 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cur_pos);
3583 gtk_text_iter_forward_chars(&iter, 1);
3584 gtk_text_buffer_get_end_iter(buffer, &iter_end);
3585 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &iter_end);
3587 if (cur_pos > gtk_text_buffer_get_char_count (buffer))
3588 cur_pos = gtk_text_buffer_get_char_count (buffer);
3591 /* put the cursor where it should be
3592 * either where the quote_fmt says, either where it was */
3593 if (compose->set_cursor_pos < 0)
3594 gtk_text_buffer_get_iter_at_offset(buffer, &iter, ins_pos);
3596 gtk_text_buffer_get_iter_at_offset(buffer, &iter,
3597 compose->set_cursor_pos);
3599 compose->set_cursor_pos = -1;
3600 gtk_text_buffer_place_cursor(buffer, &iter);
3601 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3602 G_CALLBACK(compose_changed_cb),
3608 static ComposeInsertResult compose_insert_file(Compose *compose, const gchar *file)
3611 GtkTextBuffer *buffer;
3614 const gchar *cur_encoding;
3615 gchar buf[BUFFSIZE];
3618 gboolean prev_autowrap;
3621 GString *file_contents = NULL;
3622 ComposeInsertResult result = COMPOSE_INSERT_SUCCESS;
3624 cm_return_val_if_fail(file != NULL, COMPOSE_INSERT_NO_FILE);
3626 /* get the size of the file we are about to insert */
3627 ret = g_stat(file, &file_stat);
3629 gchar *shortfile = g_path_get_basename(file);
3630 alertpanel_error(_("Could not get size of file '%s'."), shortfile);
3632 return COMPOSE_INSERT_NO_FILE;
3633 } else if (prefs_common.warn_large_insert == TRUE) {
3635 /* ask user for confirmation if the file is large */
3636 if (prefs_common.warn_large_insert_size < 0 ||
3637 file_stat.st_size > (prefs_common.warn_large_insert_size * 1024)) {
3641 msg = g_strdup_printf(_("You are about to insert a file of %s "
3642 "in the message body. Are you sure you want to do that?"),
3643 to_human_readable(file_stat.st_size));
3644 aval = alertpanel_full(_("Are you sure?"), msg, GTK_STOCK_CANCEL,
3645 g_strconcat("+", _("_Insert"), NULL), NULL, TRUE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
3648 /* do we ask for confirmation next time? */
3649 if (aval & G_ALERTDISABLE) {
3650 /* no confirmation next time, disable feature in preferences */
3651 aval &= ~G_ALERTDISABLE;
3652 prefs_common.warn_large_insert = FALSE;
3655 /* abort file insertion if user canceled action */
3656 if (aval != G_ALERTALTERNATE) {
3657 return COMPOSE_INSERT_NO_FILE;
3663 if ((fp = g_fopen(file, "rb")) == NULL) {
3664 FILE_OP_ERROR(file, "fopen");
3665 return COMPOSE_INSERT_READ_ERROR;
3668 prev_autowrap = compose->autowrap;
3669 compose->autowrap = FALSE;
3671 text = GTK_TEXT_VIEW(compose->text);
3672 buffer = gtk_text_view_get_buffer(text);
3673 mark = gtk_text_buffer_get_insert(buffer);
3674 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3676 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3677 G_CALLBACK(text_inserted),
3680 cur_encoding = conv_get_locale_charset_str_no_utf8();
3682 file_contents = g_string_new("");
3683 while (fgets(buf, sizeof(buf), fp) != NULL) {
3686 if (g_utf8_validate(buf, -1, NULL) == TRUE)
3687 str = g_strdup(buf);
3689 codeconv_set_strict(TRUE);
3690 str = conv_codeset_strdup
3691 (buf, cur_encoding, CS_INTERNAL);
3692 codeconv_set_strict(FALSE);
3695 result = COMPOSE_INSERT_INVALID_CHARACTER;
3701 /* strip <CR> if DOS/Windows file,
3702 replace <CR> with <LF> if Macintosh file. */
3705 if (len > 0 && str[len - 1] != '\n') {
3707 if (str[len] == '\r') str[len] = '\n';
3710 file_contents = g_string_append(file_contents, str);
3714 if (result == COMPOSE_INSERT_SUCCESS) {
3715 gtk_text_buffer_insert(buffer, &iter, file_contents->str, -1);
3717 compose_changed_cb(NULL, compose);
3718 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3719 G_CALLBACK(text_inserted),
3721 compose->autowrap = prev_autowrap;
3722 if (compose->autowrap)
3723 compose_wrap_all(compose);
3726 g_string_free(file_contents, TRUE);
3732 static gboolean compose_attach_append(Compose *compose, const gchar *file,
3733 const gchar *filename,
3734 const gchar *content_type,
3735 const gchar *charset)
3743 GtkListStore *store;
3745 gboolean has_binary = FALSE;
3747 if (!is_file_exist(file)) {
3748 gchar *file_from_uri = g_filename_from_uri(file, NULL, NULL);
3749 gboolean result = FALSE;
3750 if (file_from_uri && is_file_exist(file_from_uri)) {
3751 result = compose_attach_append(
3752 compose, file_from_uri,
3753 filename, content_type,
3756 g_free(file_from_uri);
3759 alertpanel_error("File %s doesn't exist\n", filename);
3762 if ((size = get_file_size(file)) < 0) {
3763 alertpanel_error("Can't get file size of %s\n", filename);
3767 /* In batch mode, we allow 0-length files to be attached no questions asked */
3768 if (size == 0 && !compose->batch) {
3769 gchar * msg = g_strdup_printf(_("File %s is empty."), filename);
3770 AlertValue aval = alertpanel_full(_("Empty file"), msg,
3771 GTK_STOCK_CANCEL, g_strconcat("+", _("_Attach anyway"), NULL), NULL, FALSE,
3772 NULL, ALERT_WARNING, G_ALERTDEFAULT);
3775 if (aval != G_ALERTALTERNATE) {
3779 if ((fp = g_fopen(file, "rb")) == NULL) {
3780 alertpanel_error(_("Can't read %s."), filename);
3785 ainfo = g_new0(AttachInfo, 1);
3786 auto_ainfo = g_auto_pointer_new_with_free
3787 (ainfo, (GFreeFunc) compose_attach_info_free);
3788 ainfo->file = g_strdup(file);
3791 ainfo->content_type = g_strdup(content_type);
3792 if (!g_ascii_strcasecmp(content_type, "message/rfc822")) {
3794 MsgFlags flags = {0, 0};
3796 if (procmime_get_encoding_for_text_file(file, &has_binary) == ENC_7BIT)
3797 ainfo->encoding = ENC_7BIT;
3799 ainfo->encoding = ENC_8BIT;
3801 msginfo = procheader_parse_file(file, flags, FALSE, FALSE);
3802 if (msginfo && msginfo->subject)
3803 name = g_strdup(msginfo->subject);
3805 name = g_path_get_basename(filename ? filename : file);
3807 ainfo->name = g_strdup_printf(_("Message: %s"), name);
3809 procmsg_msginfo_free(&msginfo);
3811 if (!g_ascii_strncasecmp(content_type, "text/", 5)) {
3812 ainfo->charset = g_strdup(charset);
3813 ainfo->encoding = procmime_get_encoding_for_text_file(file, &has_binary);
3815 ainfo->encoding = ENC_BASE64;
3817 name = g_path_get_basename(filename ? filename : file);
3818 ainfo->name = g_strdup(name);
3822 ainfo->content_type = procmime_get_mime_type(file);
3823 if (!ainfo->content_type) {
3824 ainfo->content_type =
3825 g_strdup("application/octet-stream");
3826 ainfo->encoding = ENC_BASE64;
3827 } else if (!g_ascii_strncasecmp(ainfo->content_type, "text/", 5))
3829 procmime_get_encoding_for_text_file(file, &has_binary);
3831 ainfo->encoding = ENC_BASE64;
3832 name = g_path_get_basename(filename ? filename : file);
3833 ainfo->name = g_strdup(name);
3837 if (ainfo->name != NULL
3838 && !strcmp(ainfo->name, ".")) {
3839 g_free(ainfo->name);
3843 if (!strcmp(ainfo->content_type, "unknown") || has_binary) {
3844 g_free(ainfo->content_type);
3845 ainfo->content_type = g_strdup("application/octet-stream");
3846 g_free(ainfo->charset);
3847 ainfo->charset = NULL;
3850 ainfo->size = (goffset)size;
3851 size_text = to_human_readable((goffset)size);
3853 store = GTK_LIST_STORE(gtk_tree_view_get_model
3854 (GTK_TREE_VIEW(compose->attach_clist)));
3856 gtk_list_store_append(store, &iter);
3857 gtk_list_store_set(store, &iter,
3858 COL_MIMETYPE, ainfo->content_type,
3859 COL_SIZE, size_text,
3860 COL_NAME, ainfo->name,
3861 COL_CHARSET, ainfo->charset,
3863 COL_AUTODATA, auto_ainfo,
3866 g_auto_pointer_free(auto_ainfo);
3867 compose_attach_update_label(compose);
3871 static void compose_use_signing(Compose *compose, gboolean use_signing)
3873 compose->use_signing = use_signing;
3874 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", use_signing);
3877 static void compose_use_encryption(Compose *compose, gboolean use_encryption)
3879 compose->use_encryption = use_encryption;
3880 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", use_encryption);
3883 #define NEXT_PART_NOT_CHILD(info) \
3885 node = info->node; \
3886 while (node->children) \
3887 node = g_node_last_child(node); \
3888 info = procmime_mimeinfo_next((MimeInfo *)node->data); \
3891 static void compose_attach_parts(Compose *compose, MsgInfo *msginfo)
3895 MimeInfo *firsttext = NULL;
3896 MimeInfo *encrypted = NULL;
3899 const gchar *partname = NULL;
3901 mimeinfo = procmime_scan_message(msginfo);
3902 if (!mimeinfo) return;
3904 if (mimeinfo->node->children == NULL) {
3905 procmime_mimeinfo_free_all(&mimeinfo);
3909 /* find first content part */
3910 child = (MimeInfo *) mimeinfo->node->children->data;
3911 while (child && child->node->children && (child->type == MIMETYPE_MULTIPART))
3912 child = (MimeInfo *)child->node->children->data;
3915 if (child->type == MIMETYPE_TEXT) {
3917 debug_print("First text part found\n");
3918 } else if (compose->mode == COMPOSE_REEDIT &&
3919 child->type == MIMETYPE_APPLICATION &&
3920 !g_ascii_strcasecmp(child->subtype, "pgp-encrypted")) {
3921 encrypted = (MimeInfo *)child->node->parent->data;
3924 child = (MimeInfo *) mimeinfo->node->children->data;
3925 while (child != NULL) {
3928 if (child == encrypted) {
3929 /* skip this part of tree */
3930 NEXT_PART_NOT_CHILD(child);
3934 if (child->type == MIMETYPE_MULTIPART) {
3935 /* get the actual content */
3936 child = procmime_mimeinfo_next(child);
3940 if (child == firsttext) {
3941 child = procmime_mimeinfo_next(child);
3945 outfile = procmime_get_tmp_file_name(child);
3946 if ((err = procmime_get_part(outfile, child)) < 0)
3947 g_warning("Can't get the part of multipart message. (%s)", g_strerror(-err));
3949 gchar *content_type;
3951 content_type = procmime_get_content_type_str(child->type, child->subtype);
3953 /* if we meet a pgp signature, we don't attach it, but
3954 * we force signing. */
3955 if ((strcmp(content_type, "application/pgp-signature") &&
3956 strcmp(content_type, "application/pkcs7-signature") &&
3957 strcmp(content_type, "application/x-pkcs7-signature"))
3958 || compose->mode == COMPOSE_REDIRECT) {
3959 partname = procmime_mimeinfo_get_parameter(child, "filename");
3960 if (partname == NULL)
3961 partname = procmime_mimeinfo_get_parameter(child, "name");
3962 if (partname == NULL)
3964 compose_attach_append(compose, outfile,
3965 partname, content_type,
3966 procmime_mimeinfo_get_parameter(child, "charset"));
3968 compose_force_signing(compose, compose->account, NULL);
3970 g_free(content_type);
3973 NEXT_PART_NOT_CHILD(child);
3975 procmime_mimeinfo_free_all(&mimeinfo);
3978 #undef NEXT_PART_NOT_CHILD
3983 WAIT_FOR_INDENT_CHAR,
3984 WAIT_FOR_INDENT_CHAR_OR_SPACE,
3987 /* return indent length, we allow:
3988 indent characters followed by indent characters or spaces/tabs,
3989 alphabets and numbers immediately followed by indent characters,
3990 and the repeating sequences of the above
3991 If quote ends with multiple spaces, only the first one is included. */
3992 static gchar *compose_get_quote_str(GtkTextBuffer *buffer,
3993 const GtkTextIter *start, gint *len)
3995 GtkTextIter iter = *start;
3999 IndentState state = WAIT_FOR_INDENT_CHAR;
4002 gint alnum_count = 0;
4003 gint space_count = 0;
4006 if (prefs_common.quote_chars == NULL) {
4010 while (!gtk_text_iter_ends_line(&iter)) {
4011 wc = gtk_text_iter_get_char(&iter);
4012 if (g_unichar_iswide(wc))
4014 clen = g_unichar_to_utf8(wc, ch);
4018 is_indent = strchr(prefs_common.quote_chars, ch[0]) ? TRUE : FALSE;
4019 is_space = g_unichar_isspace(wc);
4021 if (state == WAIT_FOR_INDENT_CHAR) {
4022 if (!is_indent && !g_unichar_isalnum(wc))
4025 quote_len += alnum_count + space_count + 1;
4026 alnum_count = space_count = 0;
4027 state = WAIT_FOR_INDENT_CHAR_OR_SPACE;
4030 } else if (state == WAIT_FOR_INDENT_CHAR_OR_SPACE) {
4031 if (!is_indent && !is_space && !g_unichar_isalnum(wc))
4035 else if (is_indent) {
4036 quote_len += alnum_count + space_count + 1;
4037 alnum_count = space_count = 0;
4040 state = WAIT_FOR_INDENT_CHAR;
4044 gtk_text_iter_forward_char(&iter);
4047 if (quote_len > 0 && space_count > 0)
4053 if (quote_len > 0) {
4055 gtk_text_iter_forward_chars(&iter, quote_len);
4056 return gtk_text_buffer_get_text(buffer, start, &iter, FALSE);
4062 /* return >0 if the line is itemized */
4063 static int compose_itemized_length(GtkTextBuffer *buffer,
4064 const GtkTextIter *start)
4066 GtkTextIter iter = *start;
4071 if (gtk_text_iter_ends_line(&iter))
4076 wc = gtk_text_iter_get_char(&iter);
4077 if (!g_unichar_isspace(wc))
4079 gtk_text_iter_forward_char(&iter);
4080 if (gtk_text_iter_ends_line(&iter))
4084 clen = g_unichar_to_utf8(wc, ch);
4088 if (!strchr("*-+", ch[0]))
4091 gtk_text_iter_forward_char(&iter);
4092 if (gtk_text_iter_ends_line(&iter))
4094 wc = gtk_text_iter_get_char(&iter);
4095 if (g_unichar_isspace(wc)) {
4101 /* return the string at the start of the itemization */
4102 static gchar * compose_get_itemized_chars(GtkTextBuffer *buffer,
4103 const GtkTextIter *start)
4105 GtkTextIter iter = *start;
4108 GString *item_chars = g_string_new("");
4111 if (gtk_text_iter_ends_line(&iter))
4116 wc = gtk_text_iter_get_char(&iter);
4117 if (!g_unichar_isspace(wc))
4119 gtk_text_iter_forward_char(&iter);
4120 if (gtk_text_iter_ends_line(&iter))
4122 g_string_append_unichar(item_chars, wc);
4125 str = item_chars->str;
4126 g_string_free(item_chars, FALSE);
4130 /* return the number of spaces at a line's start */
4131 static int compose_left_offset_length(GtkTextBuffer *buffer,
4132 const GtkTextIter *start)
4134 GtkTextIter iter = *start;
4137 if (gtk_text_iter_ends_line(&iter))
4141 wc = gtk_text_iter_get_char(&iter);
4142 if (!g_unichar_isspace(wc))
4145 gtk_text_iter_forward_char(&iter);
4146 if (gtk_text_iter_ends_line(&iter))
4150 gtk_text_iter_forward_char(&iter);
4151 if (gtk_text_iter_ends_line(&iter))
4156 static gboolean compose_get_line_break_pos(GtkTextBuffer *buffer,
4157 const GtkTextIter *start,
4158 GtkTextIter *break_pos,
4162 GtkTextIter iter = *start, line_end = *start;
4163 PangoLogAttr *attrs;
4170 gboolean can_break = FALSE;
4171 gboolean do_break = FALSE;
4172 gboolean was_white = FALSE;
4173 gboolean prev_dont_break = FALSE;
4175 gtk_text_iter_forward_to_line_end(&line_end);
4176 str = gtk_text_buffer_get_text(buffer, &iter, &line_end, FALSE);
4177 len = g_utf8_strlen(str, -1);
4181 g_warning("compose_get_line_break_pos: len = 0!");
4185 /* g_print("breaking line: %d: %s (len = %d)\n",
4186 gtk_text_iter_get_line(&iter), str, len); */
4188 attrs = g_new(PangoLogAttr, len + 1);
4190 pango_default_break(str, -1, NULL, attrs, len + 1);
4194 /* skip quote and leading spaces */
4195 for (i = 0; *p != '\0' && i < len; i++) {
4198 wc = g_utf8_get_char(p);
4199 if (i >= quote_len && !g_unichar_isspace(wc))
4201 if (g_unichar_iswide(wc))
4203 else if (*p == '\t')
4207 p = g_utf8_next_char(p);
4210 for (; *p != '\0' && i < len; i++) {
4211 PangoLogAttr *attr = attrs + i;
4215 if (attr->is_line_break && can_break && was_white && !prev_dont_break)
4218 was_white = attr->is_white;
4220 /* don't wrap URI */
4221 if ((uri_len = get_uri_len(p)) > 0) {
4223 if (pos > 0 && col > max_col) {
4233 wc = g_utf8_get_char(p);
4234 if (g_unichar_iswide(wc)) {
4236 if (prev_dont_break && can_break && attr->is_line_break)
4238 } else if (*p == '\t')
4242 if (pos > 0 && col > max_col) {
4247 if (*p == '-' || *p == '/')
4248 prev_dont_break = TRUE;
4250 prev_dont_break = FALSE;
4252 p = g_utf8_next_char(p);
4256 /* debug_print("compose_get_line_break_pos(): do_break = %d, pos = %d, col = %d\n", do_break, pos, col); */
4261 *break_pos = *start;
4262 gtk_text_iter_set_line_offset(break_pos, pos);
4267 static gboolean compose_join_next_line(Compose *compose,
4268 GtkTextBuffer *buffer,
4270 const gchar *quote_str)
4272 GtkTextIter iter_ = *iter, cur, prev, next, end;
4273 PangoLogAttr attrs[3];
4275 gchar *next_quote_str;
4278 gboolean keep_cursor = FALSE;
4280 if (!gtk_text_iter_forward_line(&iter_) ||
4281 gtk_text_iter_ends_line(&iter_)) {
4284 next_quote_str = compose_get_quote_str(buffer, &iter_, "e_len);
4286 if ((quote_str || next_quote_str) &&
4287 strcmp2(quote_str, next_quote_str) != 0) {
4288 g_free(next_quote_str);
4291 g_free(next_quote_str);
4294 if (quote_len > 0) {
4295 gtk_text_iter_forward_chars(&end, quote_len);
4296 if (gtk_text_iter_ends_line(&end)) {
4301 /* don't join itemized lines */
4302 if (compose_itemized_length(buffer, &end) > 0) {
4306 /* don't join signature separator */
4307 if (compose_is_sig_separator(compose, buffer, &iter_)) {
4310 /* delete quote str */
4312 gtk_text_buffer_delete(buffer, &iter_, &end);
4314 /* don't join line breaks put by the user */
4316 gtk_text_iter_backward_char(&cur);
4317 if (gtk_text_iter_has_tag(&cur, compose->no_join_tag)) {
4318 gtk_text_iter_forward_char(&cur);
4322 gtk_text_iter_forward_char(&cur);
4323 /* delete linebreak and extra spaces */
4324 while (gtk_text_iter_backward_char(&cur)) {
4325 wc1 = gtk_text_iter_get_char(&cur);
4326 if (!g_unichar_isspace(wc1))
4331 while (!gtk_text_iter_ends_line(&cur)) {
4332 wc1 = gtk_text_iter_get_char(&cur);
4333 if (!g_unichar_isspace(wc1))
4335 gtk_text_iter_forward_char(&cur);
4338 if (!gtk_text_iter_equal(&prev, &next)) {
4341 mark = gtk_text_buffer_get_insert(buffer);
4342 gtk_text_buffer_get_iter_at_mark(buffer, &cur, mark);
4343 if (gtk_text_iter_equal(&prev, &cur))
4345 gtk_text_buffer_delete(buffer, &prev, &next);
4349 /* insert space if required */
4350 gtk_text_iter_backward_char(&prev);
4351 wc1 = gtk_text_iter_get_char(&prev);
4352 wc2 = gtk_text_iter_get_char(&next);
4353 gtk_text_iter_forward_char(&next);
4354 str = gtk_text_buffer_get_text(buffer, &prev, &next, FALSE);
4355 pango_default_break(str, -1, NULL, attrs, 3);
4356 if (!attrs[1].is_line_break ||
4357 (!g_unichar_iswide(wc1) || !g_unichar_iswide(wc2))) {
4358 gtk_text_buffer_insert(buffer, &iter_, " ", 1);
4360 gtk_text_iter_backward_char(&iter_);
4361 gtk_text_buffer_place_cursor(buffer, &iter_);
4370 #define ADD_TXT_POS(bp_, ep_, pti_) \
4371 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
4372 last = last->next; \
4373 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
4374 last->next = NULL; \
4376 g_warning("alloc error scanning URIs"); \
4379 static gboolean compose_beautify_paragraph(Compose *compose, GtkTextIter *par_iter, gboolean force)
4381 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4382 GtkTextBuffer *buffer;
4383 GtkTextIter iter, break_pos, end_of_line;
4384 gchar *quote_str = NULL;
4386 gboolean wrap_quote = force || prefs_common.linewrap_quote;
4387 gboolean prev_autowrap = compose->autowrap;
4388 gint startq_offset = -1, noq_offset = -1;
4389 gint uri_start = -1, uri_stop = -1;
4390 gint nouri_start = -1, nouri_stop = -1;
4391 gint num_blocks = 0;
4392 gint quotelevel = -1;
4393 gboolean modified = force;
4394 gboolean removed = FALSE;
4395 gboolean modified_before_remove = FALSE;
4397 gboolean start = TRUE;
4398 gint itemized_len = 0, rem_item_len = 0;
4399 gchar *itemized_chars = NULL;
4400 gboolean item_continuation = FALSE;
4405 if (compose->draft_timeout_tag == COMPOSE_DRAFT_TIMEOUT_FORBIDDEN) {
4409 compose->autowrap = FALSE;
4411 buffer = gtk_text_view_get_buffer(text);
4412 undo_wrapping(compose->undostruct, TRUE);
4417 mark = gtk_text_buffer_get_insert(buffer);
4418 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
4422 if (compose->draft_timeout_tag == COMPOSE_DRAFT_TIMEOUT_FORBIDDEN) {
4423 if (gtk_text_iter_ends_line(&iter)) {
4424 while (gtk_text_iter_ends_line(&iter) &&
4425 gtk_text_iter_forward_line(&iter))
4428 while (gtk_text_iter_backward_line(&iter)) {
4429 if (gtk_text_iter_ends_line(&iter)) {
4430 gtk_text_iter_forward_line(&iter);
4436 /* move to line start */
4437 gtk_text_iter_set_line_offset(&iter, 0);
4440 itemized_len = compose_itemized_length(buffer, &iter);
4442 if (!itemized_len) {
4443 itemized_len = compose_left_offset_length(buffer, &iter);
4444 item_continuation = TRUE;
4448 itemized_chars = compose_get_itemized_chars(buffer, &iter);
4450 /* go until paragraph end (empty line) */
4451 while (start || !gtk_text_iter_ends_line(&iter)) {
4452 gchar *scanpos = NULL;
4453 /* parse table - in order of priority */
4455 const gchar *needle; /* token */
4457 /* token search function */
4458 gchar *(*search) (const gchar *haystack,
4459 const gchar *needle);
4460 /* part parsing function */
4461 gboolean (*parse) (const gchar *start,
4462 const gchar *scanpos,
4466 /* part to URI function */
4467 gchar *(*build_uri) (const gchar *bp,
4471 static struct table parser[] = {
4472 {"http://", strcasestr, get_uri_part, make_uri_string},
4473 {"https://", strcasestr, get_uri_part, make_uri_string},
4474 {"ftp://", strcasestr, get_uri_part, make_uri_string},
4475 {"sftp://", strcasestr, get_uri_part, make_uri_string},
4476 {"gopher://",strcasestr, get_uri_part, make_uri_string},
4477 {"www.", strcasestr, get_uri_part, make_http_string},
4478 {"mailto:", strcasestr, get_uri_part, make_uri_string},
4479 {"@", strcasestr, get_email_part, make_email_string}
4481 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
4482 gint last_index = PARSE_ELEMS;
4484 gchar *o_walk = NULL, *walk = NULL, *bp = NULL, *ep = NULL;
4488 if (!prev_autowrap && num_blocks == 0) {
4490 g_signal_handlers_block_by_func(G_OBJECT(buffer),
4491 G_CALLBACK(text_inserted),
4494 if (gtk_text_iter_has_tag(&iter, compose->no_wrap_tag) && !force)
4497 uri_start = uri_stop = -1;
4499 quote_str = compose_get_quote_str(buffer, &iter, "e_len);
4502 /* debug_print("compose_beautify_paragraph(): quote_str = '%s'\n", quote_str); */
4503 if (startq_offset == -1)
4504 startq_offset = gtk_text_iter_get_offset(&iter);
4505 quotelevel = get_quote_level(quote_str, prefs_common.quote_chars);
4506 if (quotelevel > 2) {
4507 /* recycle colors */
4508 if (prefs_common.recycle_quote_colors)
4517 if (startq_offset == -1)
4518 noq_offset = gtk_text_iter_get_offset(&iter);
4522 if (prev_autowrap == FALSE && !force && !wrap_quote) {
4525 if (gtk_text_iter_ends_line(&iter)) {
4527 } else if (compose_get_line_break_pos(buffer, &iter, &break_pos,
4528 prefs_common.linewrap_len,
4530 GtkTextIter prev, next, cur;
4531 if (prev_autowrap != FALSE || force) {
4532 compose->automatic_break = TRUE;
4534 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4535 compose->automatic_break = FALSE;
4536 if (itemized_len && compose->autoindent) {
4537 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4538 if (!item_continuation)
4539 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4541 } else if (quote_str && wrap_quote) {
4542 compose->automatic_break = TRUE;
4544 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4545 compose->automatic_break = FALSE;
4546 if (itemized_len && compose->autoindent) {
4547 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4548 if (!item_continuation)
4549 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4553 /* remove trailing spaces */
4555 rem_item_len = itemized_len;
4556 while (compose->autoindent && rem_item_len-- > 0)
4557 gtk_text_iter_backward_char(&cur);
4558 gtk_text_iter_backward_char(&cur);
4561 while (!gtk_text_iter_starts_line(&cur)) {
4564 gtk_text_iter_backward_char(&cur);
4565 wc = gtk_text_iter_get_char(&cur);
4566 if (!g_unichar_isspace(wc))
4570 if (!gtk_text_iter_equal(&prev, &next)) {
4571 gtk_text_buffer_delete(buffer, &prev, &next);
4573 gtk_text_iter_forward_char(&break_pos);
4577 gtk_text_buffer_insert(buffer, &break_pos,
4581 modified |= compose_join_next_line(compose, buffer, &iter, quote_str);
4583 /* move iter to current line start */
4584 gtk_text_iter_set_line_offset(&iter, 0);
4591 /* move iter to next line start */
4597 if (!prev_autowrap && num_blocks > 0) {
4599 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
4600 G_CALLBACK(text_inserted),
4604 while (!gtk_text_iter_ends_line(&end_of_line)) {
4605 gtk_text_iter_forward_char(&end_of_line);
4607 o_walk = walk = gtk_text_buffer_get_text(buffer, &iter, &end_of_line, FALSE);
4609 nouri_start = gtk_text_iter_get_offset(&iter);
4610 nouri_stop = gtk_text_iter_get_offset(&end_of_line);
4612 walk_pos = gtk_text_iter_get_offset(&iter);
4613 /* FIXME: this looks phony. scanning for anything in the parse table */
4614 for (n = 0; n < PARSE_ELEMS; n++) {
4617 tmp = parser[n].search(walk, parser[n].needle);
4619 if (scanpos == NULL || tmp < scanpos) {
4628 /* check if URI can be parsed */
4629 if (parser[last_index].parse(walk, scanpos, (const gchar **)&bp,
4630 (const gchar **)&ep, FALSE)
4631 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
4635 strlen(parser[last_index].needle);
4638 uri_start = walk_pos + (bp - o_walk);
4639 uri_stop = walk_pos + (ep - o_walk);
4643 gtk_text_iter_forward_line(&iter);
4646 if (startq_offset != -1) {
4647 GtkTextIter startquote, endquote;
4648 gtk_text_buffer_get_iter_at_offset(
4649 buffer, &startquote, startq_offset);
4652 switch (quotelevel) {
4654 if (!gtk_text_iter_has_tag(&startquote, compose->quote0_tag) ||
4655 !gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) {
4656 gtk_text_buffer_apply_tag_by_name(
4657 buffer, "quote0", &startquote, &endquote);
4658 gtk_text_buffer_remove_tag_by_name(
4659 buffer, "quote1", &startquote, &endquote);
4660 gtk_text_buffer_remove_tag_by_name(
4661 buffer, "quote2", &startquote, &endquote);
4666 if (!gtk_text_iter_has_tag(&startquote, compose->quote1_tag) ||
4667 !gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) {
4668 gtk_text_buffer_apply_tag_by_name(
4669 buffer, "quote1", &startquote, &endquote);
4670 gtk_text_buffer_remove_tag_by_name(
4671 buffer, "quote0", &startquote, &endquote);
4672 gtk_text_buffer_remove_tag_by_name(
4673 buffer, "quote2", &startquote, &endquote);
4678 if (!gtk_text_iter_has_tag(&startquote, compose->quote2_tag) ||
4679 !gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag)) {
4680 gtk_text_buffer_apply_tag_by_name(
4681 buffer, "quote2", &startquote, &endquote);
4682 gtk_text_buffer_remove_tag_by_name(
4683 buffer, "quote0", &startquote, &endquote);
4684 gtk_text_buffer_remove_tag_by_name(
4685 buffer, "quote1", &startquote, &endquote);
4691 } else if (noq_offset != -1) {
4692 GtkTextIter startnoquote, endnoquote;
4693 gtk_text_buffer_get_iter_at_offset(
4694 buffer, &startnoquote, noq_offset);
4697 if ((gtk_text_iter_has_tag(&startnoquote, compose->quote0_tag)
4698 && gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) ||
4699 (gtk_text_iter_has_tag(&startnoquote, compose->quote1_tag)
4700 && gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) ||
4701 (gtk_text_iter_has_tag(&startnoquote, compose->quote2_tag)
4702 && gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag))) {
4703 gtk_text_buffer_remove_tag_by_name(
4704 buffer, "quote0", &startnoquote, &endnoquote);
4705 gtk_text_buffer_remove_tag_by_name(
4706 buffer, "quote1", &startnoquote, &endnoquote);
4707 gtk_text_buffer_remove_tag_by_name(
4708 buffer, "quote2", &startnoquote, &endnoquote);
4714 if (uri_start != nouri_start && uri_stop != nouri_stop) {
4715 GtkTextIter nouri_start_iter, nouri_end_iter;
4716 gtk_text_buffer_get_iter_at_offset(
4717 buffer, &nouri_start_iter, nouri_start);
4718 gtk_text_buffer_get_iter_at_offset(
4719 buffer, &nouri_end_iter, nouri_stop);
4720 if (gtk_text_iter_has_tag(&nouri_start_iter, compose->uri_tag) &&
4721 gtk_text_iter_has_tag(&nouri_end_iter, compose->uri_tag)) {
4722 gtk_text_buffer_remove_tag_by_name(
4723 buffer, "link", &nouri_start_iter, &nouri_end_iter);
4724 modified_before_remove = modified;
4729 if (uri_start >= 0 && uri_stop > 0) {
4730 GtkTextIter uri_start_iter, uri_end_iter, back;
4731 gtk_text_buffer_get_iter_at_offset(
4732 buffer, &uri_start_iter, uri_start);
4733 gtk_text_buffer_get_iter_at_offset(
4734 buffer, &uri_end_iter, uri_stop);
4735 back = uri_end_iter;
4736 gtk_text_iter_backward_char(&back);
4737 if (!gtk_text_iter_has_tag(&uri_start_iter, compose->uri_tag) ||
4738 !gtk_text_iter_has_tag(&back, compose->uri_tag)) {
4739 gtk_text_buffer_apply_tag_by_name(
4740 buffer, "link", &uri_start_iter, &uri_end_iter);
4742 if (removed && !modified_before_remove) {
4748 /* debug_print("not modified, out after %d lines\n", lines); */
4752 /* debug_print("modified, out after %d lines\n", lines); */
4754 g_free(itemized_chars);
4757 undo_wrapping(compose->undostruct, FALSE);
4758 compose->autowrap = prev_autowrap;
4763 void compose_action_cb(void *data)
4765 Compose *compose = (Compose *)data;
4766 compose_wrap_all(compose);
4769 static void compose_wrap_all(Compose *compose)
4771 compose_wrap_all_full(compose, FALSE);
4774 static void compose_wrap_all_full(Compose *compose, gboolean force)
4776 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4777 GtkTextBuffer *buffer;
4779 gboolean modified = TRUE;
4781 buffer = gtk_text_view_get_buffer(text);
4783 gtk_text_buffer_get_start_iter(buffer, &iter);
4785 undo_wrapping(compose->undostruct, TRUE);
4787 while (!gtk_text_iter_is_end(&iter) && modified)
4788 modified = compose_beautify_paragraph(compose, &iter, force);
4790 undo_wrapping(compose->undostruct, FALSE);
4794 static void compose_set_title(Compose *compose)
4800 edited = compose->modified ? _(" [Edited]") : "";
4802 subject = gtk_editable_get_chars(
4803 GTK_EDITABLE(compose->subject_entry), 0, -1);
4805 #ifndef GENERIC_UMPC
4806 if (subject && strlen(subject))
4807 str = g_strdup_printf(_("%s - Compose message%s"),
4810 str = g_strdup_printf(_("[no subject] - Compose message%s"), edited);
4812 str = g_strdup(_("Compose message"));
4815 gtk_window_set_title(GTK_WINDOW(compose->window), str);
4821 * compose_current_mail_account:
4823 * Find a current mail account (the currently selected account, or the
4824 * default account, if a news account is currently selected). If a
4825 * mail account cannot be found, display an error message.
4827 * Return value: Mail account, or NULL if not found.
4829 static PrefsAccount *
4830 compose_current_mail_account(void)
4834 if (cur_account && cur_account->protocol != A_NNTP)
4837 ac = account_get_default();
4838 if (!ac || ac->protocol == A_NNTP) {
4839 alertpanel_error(_("Account for sending mail is not specified.\n"
4840 "Please select a mail account before sending."));
4847 #define QUOTE_IF_REQUIRED(out, str) \
4849 if (*str != '"' && strpbrk(str, ",.:;[]<>()@\\\"")) { \
4853 len = strlen(str) + 3; \
4854 if ((__tmp = alloca(len)) == NULL) { \
4855 g_warning("can't allocate memory"); \
4856 g_string_free(header, TRUE); \
4859 g_snprintf(__tmp, len, "\"%s\"", str); \
4864 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4865 g_warning("can't allocate memory"); \
4866 g_string_free(header, TRUE); \
4869 strcpy(__tmp, str); \
4875 #define QUOTE_IF_REQUIRED_NORMAL(out, str, errret) \
4877 if (*str != '"' && strpbrk(str, ",.:;[]<>()@\\\"")) { \
4881 len = strlen(str) + 3; \
4882 if ((__tmp = alloca(len)) == NULL) { \
4883 g_warning("can't allocate memory"); \
4886 g_snprintf(__tmp, len, "\"%s\"", str); \
4891 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4892 g_warning("can't allocate memory"); \
4895 strcpy(__tmp, str); \
4901 static void compose_select_account(Compose *compose, PrefsAccount *account,
4904 gchar *from = NULL, *header = NULL;
4905 ComposeHeaderEntry *header_entry;
4906 #if GTK_CHECK_VERSION(2, 24, 0)
4910 cm_return_if_fail(account != NULL);
4912 compose->account = account;
4913 if (account->name && *account->name) {
4915 QUOTE_IF_REQUIRED_NORMAL(buf, account->name, return);
4916 qbuf = escape_internal_quotes(buf, '"');
4917 from = g_strdup_printf("%s <%s>",
4918 qbuf, account->address);
4921 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4923 from = g_strdup_printf("<%s>",
4925 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4930 compose_set_title(compose);
4932 if (account->default_sign && compose->mode != COMPOSE_REDIRECT)
4933 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", TRUE);
4935 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", FALSE);
4936 if (account->default_encrypt && compose->mode != COMPOSE_REDIRECT)
4937 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", TRUE);
4939 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", FALSE);
4941 activate_privacy_system(compose, account, FALSE);
4943 if (!init && compose->mode != COMPOSE_REDIRECT) {
4944 undo_block(compose->undostruct);
4945 compose_insert_sig(compose, TRUE);
4946 undo_unblock(compose->undostruct);
4949 header_entry = (ComposeHeaderEntry *) compose->header_list->data;
4950 #if !GTK_CHECK_VERSION(2, 24, 0)
4951 header = gtk_combo_box_get_active_text(GTK_COMBO_BOX(header_entry->combo));
4953 if (gtk_combo_box_get_active_iter(GTK_COMBO_BOX(header_entry->combo), &iter))
4954 gtk_tree_model_get(gtk_combo_box_get_model(GTK_COMBO_BOX(
4955 header_entry->combo)), &iter, COMBOBOX_TEXT, &header, -1);
4958 if (header && !strlen(gtk_entry_get_text(GTK_ENTRY(header_entry->entry)))) {
4959 if (account->protocol == A_NNTP) {
4960 if (!strcmp(header, _("To:")))
4961 combobox_select_by_text(
4962 GTK_COMBO_BOX(header_entry->combo),
4965 if (!strcmp(header, _("Newsgroups:")))
4966 combobox_select_by_text(
4967 GTK_COMBO_BOX(header_entry->combo),
4975 /* use account's dict info if set */
4976 if (compose->gtkaspell) {
4977 if (account->enable_default_dictionary)
4978 gtkaspell_change_dict(compose->gtkaspell,
4979 account->default_dictionary, FALSE);
4980 if (account->enable_default_alt_dictionary)
4981 gtkaspell_change_alt_dict(compose->gtkaspell,
4982 account->default_alt_dictionary);
4983 if (account->enable_default_dictionary
4984 || account->enable_default_alt_dictionary)
4985 compose_spell_menu_changed(compose);
4990 gboolean compose_check_for_valid_recipient(Compose *compose) {
4991 gchar *recipient_headers_mail[] = {"To:", "Cc:", "Bcc:", NULL};
4992 gchar *recipient_headers_news[] = {"Newsgroups:", NULL};
4993 gboolean recipient_found = FALSE;
4997 /* free to and newsgroup list */
4998 slist_free_strings_full(compose->to_list);
4999 compose->to_list = NULL;
5001 slist_free_strings_full(compose->newsgroup_list);
5002 compose->newsgroup_list = NULL;
5004 /* search header entries for to and newsgroup entries */
5005 for (list = compose->header_list; list; list = list->next) {
5008 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5009 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5012 if (entry[0] != '\0') {
5013 for (strptr = recipient_headers_mail; *strptr != NULL; strptr++) {
5014 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
5015 compose->to_list = address_list_append(compose->to_list, entry);
5016 recipient_found = TRUE;
5019 for (strptr = recipient_headers_news; *strptr != NULL; strptr++) {
5020 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
5021 compose->newsgroup_list = newsgroup_list_append(compose->newsgroup_list, entry);
5022 recipient_found = TRUE;
5029 return recipient_found;
5032 static gboolean compose_check_for_set_recipients(Compose *compose)
5034 if (compose->account->set_autocc && compose->account->auto_cc) {
5035 gboolean found_other = FALSE;
5037 /* search header entries for to and newsgroup entries */
5038 for (list = compose->header_list; list; list = list->next) {
5041 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5042 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5045 if (strcmp(entry, compose->account->auto_cc)
5046 || strcmp(header, prefs_common_translated_header_name("Cc:"))) {
5057 if (compose->batch) {
5058 gtk_widget_show_all(compose->window);
5060 text = g_strdup_printf(_("The only recipient is the default '%s' address. Send anyway?"),
5061 prefs_common_translated_header_name("Cc"));
5062 aval = alertpanel(_("Send"),
5064 GTK_STOCK_CANCEL, g_strconcat("+", _("_Send"), NULL), NULL);
5066 if (aval != G_ALERTALTERNATE)
5070 if (compose->account->set_autobcc && compose->account->auto_bcc) {
5071 gboolean found_other = FALSE;
5073 /* search header entries for to and newsgroup entries */
5074 for (list = compose->header_list; list; list = list->next) {
5077 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5078 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5081 if (strcmp(entry, compose->account->auto_bcc)
5082 || strcmp(header, prefs_common_translated_header_name("Bcc:"))) {
5094 if (compose->batch) {
5095 gtk_widget_show_all(compose->window);
5097 text = g_strdup_printf(_("The only recipient is the default '%s' address. Send anyway?"),
5098 prefs_common_translated_header_name("Bcc"));
5099 aval = alertpanel(_("Send"),
5101 GTK_STOCK_CANCEL, g_strconcat("+", _("_Send"), NULL), NULL);
5103 if (aval != G_ALERTALTERNATE)
5110 static gboolean compose_check_entries(Compose *compose, gboolean check_everything)
5114 if (compose_check_for_valid_recipient(compose) == FALSE) {
5115 if (compose->batch) {
5116 gtk_widget_show_all(compose->window);
5118 alertpanel_error(_("Recipient is not specified."));
5122 if (compose_check_for_set_recipients(compose) == FALSE) {
5126 if (!compose->batch && prefs_common.warn_empty_subj == TRUE) {
5127 str = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5128 if (*str == '\0' && check_everything == TRUE &&
5129 compose->mode != COMPOSE_REDIRECT) {
5131 gchar *button_label;
5134 if (compose->sending)
5135 button_label = g_strconcat("+", _("_Send"), NULL);
5137 button_label = g_strconcat("+", _("_Queue"), NULL);
5138 message = g_strdup_printf(_("Subject is empty. %s"),
5139 compose->sending?_("Send it anyway?"):
5140 _("Queue it anyway?"));
5142 aval = alertpanel_full(compose->sending?_("Send"):_("Send later"), message,
5143 GTK_STOCK_CANCEL, button_label, NULL, TRUE, NULL,
5144 ALERT_QUESTION, G_ALERTDEFAULT);
5146 if (aval & G_ALERTDISABLE) {
5147 aval &= ~G_ALERTDISABLE;
5148 prefs_common.warn_empty_subj = FALSE;
5150 if (aval != G_ALERTALTERNATE)
5155 if (!compose->batch && prefs_common.warn_sending_many_recipients == TRUE
5156 && check_everything == TRUE) {
5160 /* count To and Cc recipients */
5161 for (list = compose->header_list; list; list = list->next) {
5165 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5166 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5169 if ((entry[0] != '\0')
5170 && (strcmp(header, prefs_common_translated_header_name("To:"))
5171 || strcmp(header, prefs_common_translated_header_name("Cc:")))) {
5177 if (cnt > prefs_common.warn_sending_many_recipients_num) {
5179 gchar *button_label;
5182 if (compose->sending)
5183 button_label = g_strconcat("+", _("_Send"), NULL);
5185 button_label = g_strconcat("+", _("_Queue"), NULL);
5186 message = g_strdup_printf(_("Sending to %d recipients. %s"), cnt,
5187 compose->sending?_("Send it anyway?"):
5188 _("Queue it anyway?"));
5190 aval = alertpanel_full(compose->sending?_("Send"):_("Send later"), message,
5191 GTK_STOCK_CANCEL, button_label, NULL, TRUE, NULL,
5192 ALERT_QUESTION, G_ALERTDEFAULT);
5194 if (aval & G_ALERTDISABLE) {
5195 aval &= ~G_ALERTDISABLE;
5196 prefs_common.warn_empty_subj = FALSE;
5198 if (aval != G_ALERTALTERNATE)
5203 if (check_everything && hooks_invoke(COMPOSE_CHECK_BEFORE_SEND_HOOKLIST, compose))
5209 gint compose_send(Compose *compose)
5212 FolderItem *folder = NULL;
5214 gchar *msgpath = NULL;
5215 gboolean discard_window = FALSE;
5216 gchar *errstr = NULL;
5217 gchar *tmsgid = NULL;
5218 MainWindow *mainwin = mainwindow_get_mainwindow();
5219 gboolean queued_removed = FALSE;
5221 if (prefs_common.send_dialog_invisible
5222 || compose->batch == TRUE)
5223 discard_window = TRUE;
5225 compose_allow_user_actions (compose, FALSE);
5226 compose->sending = TRUE;
5228 if (compose_check_entries(compose, TRUE) == FALSE) {
5229 if (compose->batch) {
5230 gtk_widget_show_all(compose->window);
5236 val = compose_queue(compose, &msgnum, &folder, &msgpath, TRUE);
5239 if (compose->batch) {
5240 gtk_widget_show_all(compose->window);
5243 alertpanel_error(_("Could not queue message for sending:\n\n"
5244 "Charset conversion failed."));
5245 } else if (val == -5) {
5246 alertpanel_error(_("Could not queue message for sending:\n\n"
5247 "Couldn't get recipient encryption key."));
5248 } else if (val == -6) {
5250 } else if (val == -3) {
5251 if (privacy_peek_error())
5252 alertpanel_error(_("Could not queue message for sending:\n\n"
5253 "Signature failed: %s"), privacy_get_error());
5254 } else if (val == -2 && errno != 0) {
5255 alertpanel_error(_("Could not queue message for sending:\n\n%s."), g_strerror(errno));
5257 alertpanel_error(_("Could not queue message for sending."));
5262 tmsgid = compose->msgid ? g_strdup(compose->msgid) : NULL;
5263 if (discard_window) {
5264 compose->sending = FALSE;
5265 compose_close(compose);
5266 /* No more compose access in the normal codepath
5267 * after this point! */
5272 alertpanel_error(_("The message was queued but could not be "
5273 "sent.\nUse \"Send queued messages\" from "
5274 "the main window to retry."));
5275 if (!discard_window) {
5282 if (msgpath == NULL) {
5283 msgpath = folder_item_fetch_msg(folder, msgnum);
5284 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5287 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5288 claws_unlink(msgpath);
5291 if (!discard_window) {
5293 if (!queued_removed)
5294 folder_item_remove_msg(folder, msgnum);
5295 folder_item_scan(folder);
5297 /* make sure we delete that */
5298 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5300 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5301 folder_item_remove_msg(folder, tmp->msgnum);
5302 procmsg_msginfo_free(&tmp);
5309 if (!queued_removed)
5310 folder_item_remove_msg(folder, msgnum);
5311 folder_item_scan(folder);
5313 /* make sure we delete that */
5314 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5316 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5317 folder_item_remove_msg(folder, tmp->msgnum);
5318 procmsg_msginfo_free(&tmp);
5321 if (!discard_window) {
5322 compose->sending = FALSE;
5323 compose_allow_user_actions (compose, TRUE);
5324 compose_close(compose);
5328 alertpanel_error_log(_("%s\nUse \"Send queued messages\" from "
5329 "the main window to retry."), errstr);
5332 alertpanel_error_log(_("The message was queued but could not be "
5333 "sent.\nUse \"Send queued messages\" from "
5334 "the main window to retry."));
5336 if (!discard_window) {
5345 toolbar_main_set_sensitive(mainwin);
5346 main_window_set_menu_sensitive(mainwin);
5352 compose_allow_user_actions (compose, TRUE);
5353 compose->sending = FALSE;
5354 compose->modified = TRUE;
5355 toolbar_main_set_sensitive(mainwin);
5356 main_window_set_menu_sensitive(mainwin);
5361 static gboolean compose_use_attach(Compose *compose)
5363 GtkTreeModel *model = gtk_tree_view_get_model
5364 (GTK_TREE_VIEW(compose->attach_clist));
5365 return gtk_tree_model_iter_n_children(model, NULL) > 0;
5368 static gint compose_redirect_write_headers_from_headerlist(Compose *compose,
5371 gchar buf[BUFFSIZE];
5373 gboolean first_to_address;
5374 gboolean first_cc_address;
5376 ComposeHeaderEntry *headerentry;
5377 const gchar *headerentryname;
5378 const gchar *cc_hdr;
5379 const gchar *to_hdr;
5380 gboolean err = FALSE;
5382 debug_print("Writing redirect header\n");
5384 cc_hdr = prefs_common_translated_header_name("Cc:");
5385 to_hdr = prefs_common_translated_header_name("To:");
5387 first_to_address = TRUE;
5388 for (list = compose->header_list; list; list = list->next) {
5389 headerentry = ((ComposeHeaderEntry *)list->data);
5390 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5392 if (g_utf8_collate(headerentryname, to_hdr) == 0) {
5393 const gchar *entstr = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5394 Xstrdup_a(str, entstr, return -1);
5396 if (str[0] != '\0') {
5397 compose_convert_header
5398 (compose, buf, sizeof(buf), str,
5399 strlen("Resent-To") + 2, TRUE);
5401 if (first_to_address) {
5402 err |= (fprintf(fp, "Resent-To: ") < 0);
5403 first_to_address = FALSE;
5405 err |= (fprintf(fp, ",") < 0);
5407 err |= (fprintf(fp, "%s", buf) < 0);
5411 if (!first_to_address) {
5412 err |= (fprintf(fp, "\n") < 0);
5415 first_cc_address = TRUE;
5416 for (list = compose->header_list; list; list = list->next) {
5417 headerentry = ((ComposeHeaderEntry *)list->data);
5418 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5420 if (g_utf8_collate(headerentryname, cc_hdr) == 0) {
5421 const gchar *strg = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5422 Xstrdup_a(str, strg, return -1);
5424 if (str[0] != '\0') {
5425 compose_convert_header
5426 (compose, buf, sizeof(buf), str,
5427 strlen("Resent-Cc") + 2, TRUE);
5429 if (first_cc_address) {
5430 err |= (fprintf(fp, "Resent-Cc: ") < 0);
5431 first_cc_address = FALSE;
5433 err |= (fprintf(fp, ",") < 0);
5435 err |= (fprintf(fp, "%s", buf) < 0);
5439 if (!first_cc_address) {
5440 err |= (fprintf(fp, "\n") < 0);
5443 return (err ? -1:0);
5446 static gint compose_redirect_write_headers(Compose *compose, FILE *fp)
5448 gchar date[RFC822_DATE_BUFFSIZE];
5449 gchar buf[BUFFSIZE];
5451 const gchar *entstr;
5452 /* struct utsname utsbuf; */
5453 gboolean err = FALSE;
5455 cm_return_val_if_fail(fp != NULL, -1);
5456 cm_return_val_if_fail(compose->account != NULL, -1);
5457 cm_return_val_if_fail(compose->account->address != NULL, -1);
5460 if (prefs_common.hide_timezone)
5461 get_rfc822_date_hide_tz(date, sizeof(date));
5463 get_rfc822_date(date, sizeof(date));
5464 err |= (fprintf(fp, "Resent-Date: %s\n", buf) < 0);
5467 if (compose->account->name && *compose->account->name) {
5468 compose_convert_header
5469 (compose, buf, sizeof(buf), compose->account->name,
5470 strlen("From: "), TRUE);
5471 err |= (fprintf(fp, "Resent-From: %s <%s>\n",
5472 buf, compose->account->address) < 0);
5474 err |= (fprintf(fp, "Resent-From: %s\n", compose->account->address) < 0);
5477 entstr = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5478 if (*entstr != '\0') {
5479 Xstrdup_a(str, entstr, return -1);
5482 compose_convert_header(compose, buf, sizeof(buf), str,
5483 strlen("Subject: "), FALSE);
5484 err |= (fprintf(fp, "Subject: %s\n", buf) < 0);
5488 /* Resent-Message-ID */
5489 if (compose->account->gen_msgid) {
5490 gchar *addr = prefs_account_generate_msgid(compose->account);
5491 err |= (fprintf(fp, "Resent-Message-ID: <%s>\n", addr) < 0);
5493 g_free(compose->msgid);
5494 compose->msgid = addr;
5496 compose->msgid = NULL;
5499 if (compose_redirect_write_headers_from_headerlist(compose, fp))
5502 /* separator between header and body */
5503 err |= (fputs("\n", fp) == EOF);
5505 return (err ? -1:0);
5508 static gint compose_redirect_write_to_file(Compose *compose, FILE *fdest)
5513 gchar rewrite_buf[BUFFSIZE];
5515 gboolean skip = FALSE;
5516 gboolean err = FALSE;
5517 gchar *not_included[]={
5518 "Return-Path:", "Delivered-To:", "Received:",
5519 "Subject:", "X-UIDL:", "AF:",
5520 "NF:", "PS:", "SRH:",
5521 "SFN:", "DSR:", "MID:",
5522 "CFG:", "PT:", "S:",
5523 "RQ:", "SSV:", "NSV:",
5524 "SSH:", "R:", "MAID:",
5525 "NAID:", "RMID:", "FMID:",
5526 "SCF:", "RRCPT:", "NG:",
5527 "X-Claws-Privacy", "X-Claws-Sign:", "X-Claws-Encrypt",
5528 "X-Claws-End-Special-Headers:", "X-Claws-Account-Id:",
5529 "X-Sylpheed-Privacy", "X-Sylpheed-Sign:", "X-Sylpheed-Encrypt",
5530 "X-Sylpheed-End-Special-Headers:", "X-Sylpheed-Account-Id:",
5531 "X-Claws-Auto-Wrapping:", "X-Claws-Auto-Indent:",
5536 if ((fp = g_fopen(compose->redirect_filename, "rb")) == NULL) {
5537 FILE_OP_ERROR(compose->redirect_filename, "fopen");
5541 while ((ret = procheader_get_one_field_asis(&buf, fp)) != -1) {
5543 for (i = 0; not_included[i] != NULL; i++) {
5544 if (g_ascii_strncasecmp(buf, not_included[i],
5545 strlen(not_included[i])) == 0) {
5555 if (fputs(buf, fdest) == -1) {
5561 if (!prefs_common.redirect_keep_from) {
5562 if (g_ascii_strncasecmp(buf, "From:",
5563 strlen("From:")) == 0) {
5564 err |= (fputs(" (by way of ", fdest) == EOF);
5565 if (compose->account->name
5566 && *compose->account->name) {
5567 gchar buffer[BUFFSIZE];
5569 compose_convert_header
5570 (compose, buffer, sizeof(buffer),
5571 compose->account->name,
5574 err |= (fprintf(fdest, "%s <%s>",
5576 compose->account->address) < 0);
5578 err |= (fprintf(fdest, "%s",
5579 compose->account->address) < 0);
5580 err |= (fputs(")", fdest) == EOF);
5586 if (fputs("\n", fdest) == -1)
5593 if (compose_redirect_write_headers(compose, fdest))
5596 while ((len = fread(rewrite_buf, sizeof(gchar), sizeof(rewrite_buf), fp)) > 0) {
5597 if (fwrite(rewrite_buf, sizeof(gchar), len, fdest) != len)
5611 static gint compose_write_to_file(Compose *compose, FILE *fp, gint action, gboolean attach_parts)
5613 GtkTextBuffer *buffer;
5614 GtkTextIter start, end;
5615 gchar *chars, *tmp_enc_file, *content;
5617 const gchar *out_codeset;
5618 EncodingType encoding = ENC_UNKNOWN;
5619 MimeInfo *mimemsg, *mimetext;
5621 const gchar *src_codeset = CS_INTERNAL;
5622 gchar *from_addr = NULL;
5623 gchar *from_name = NULL;
5626 if (action == COMPOSE_WRITE_FOR_SEND) {
5627 attach_parts = TRUE;
5629 /* We're sending the message, generate a Message-ID
5631 if (compose->msgid == NULL &&
5632 compose->account->gen_msgid) {
5633 compose->msgid = prefs_account_generate_msgid(compose->account);
5637 /* create message MimeInfo */
5638 mimemsg = procmime_mimeinfo_new();
5639 mimemsg->type = MIMETYPE_MESSAGE;
5640 mimemsg->subtype = g_strdup("rfc822");
5641 mimemsg->content = MIMECONTENT_MEM;
5642 mimemsg->tmp = TRUE; /* must free content later */
5643 mimemsg->data.mem = compose_get_header(compose);
5645 /* Create text part MimeInfo */
5646 /* get all composed text */
5647 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5648 gtk_text_buffer_get_start_iter(buffer, &start);
5649 gtk_text_buffer_get_end_iter(buffer, &end);
5650 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5652 out_codeset = conv_get_charset_str(compose->out_encoding);
5654 if (!out_codeset && is_ascii_str(chars)) {
5655 out_codeset = CS_US_ASCII;
5656 } else if (prefs_common.outgoing_fallback_to_ascii &&
5657 is_ascii_str(chars)) {
5658 out_codeset = CS_US_ASCII;
5659 encoding = ENC_7BIT;
5663 gchar *test_conv_global_out = NULL;
5664 gchar *test_conv_reply = NULL;
5666 /* automatic mode. be automatic. */
5667 codeconv_set_strict(TRUE);
5669 out_codeset = conv_get_outgoing_charset_str();
5671 debug_print("trying to convert to %s\n", out_codeset);
5672 test_conv_global_out = conv_codeset_strdup(chars, src_codeset, out_codeset);
5675 if (!test_conv_global_out && compose->orig_charset
5676 && strcmp(compose->orig_charset, CS_US_ASCII)) {
5677 out_codeset = compose->orig_charset;
5678 debug_print("failure; trying to convert to %s\n", out_codeset);
5679 test_conv_reply = conv_codeset_strdup(chars, src_codeset, out_codeset);
5682 if (!test_conv_global_out && !test_conv_reply) {
5684 out_codeset = CS_INTERNAL;
5685 debug_print("failure; finally using %s\n", out_codeset);
5687 g_free(test_conv_global_out);
5688 g_free(test_conv_reply);
5689 codeconv_set_strict(FALSE);
5692 if (encoding == ENC_UNKNOWN) {
5693 if (prefs_common.encoding_method == CTE_BASE64)
5694 encoding = ENC_BASE64;
5695 else if (prefs_common.encoding_method == CTE_QUOTED_PRINTABLE)
5696 encoding = ENC_QUOTED_PRINTABLE;
5697 else if (prefs_common.encoding_method == CTE_8BIT)
5698 encoding = ENC_8BIT;
5700 encoding = procmime_get_encoding_for_charset(out_codeset);
5703 debug_print("src encoding = %s, out encoding = %s, transfer encoding = %s\n",
5704 src_codeset, out_codeset, procmime_get_encoding_str(encoding));
5706 if (action == COMPOSE_WRITE_FOR_SEND) {
5707 codeconv_set_strict(TRUE);
5708 buf = conv_codeset_strdup(chars, src_codeset, out_codeset);
5709 codeconv_set_strict(FALSE);
5714 msg = g_strdup_printf(_("Can't convert the character encoding of the message \n"
5715 "to the specified %s charset.\n"
5716 "Send it as %s?"), out_codeset, src_codeset);
5717 aval = alertpanel_full(_("Error"), msg, GTK_STOCK_CANCEL,
5718 g_strconcat("+", _("_Send"), NULL), NULL, FALSE,
5719 NULL, ALERT_ERROR, G_ALERTDEFAULT);
5722 if (aval != G_ALERTALTERNATE) {
5727 out_codeset = src_codeset;
5733 out_codeset = src_codeset;
5738 if (prefs_common.rewrite_first_from && (encoding == ENC_8BIT || encoding == ENC_7BIT)) {
5739 if (!strncmp(buf, "From ", sizeof("From ")-1) ||
5740 strstr(buf, "\nFrom ") != NULL) {
5741 encoding = ENC_QUOTED_PRINTABLE;
5745 mimetext = procmime_mimeinfo_new();
5746 mimetext->content = MIMECONTENT_MEM;
5747 mimetext->tmp = TRUE; /* must free content later */
5748 /* dup'ed because procmime_encode_content can turn it into a tmpfile
5749 * and free the data, which we need later. */
5750 mimetext->data.mem = g_strdup(buf);
5751 mimetext->type = MIMETYPE_TEXT;
5752 mimetext->subtype = g_strdup("plain");
5753 g_hash_table_insert(mimetext->typeparameters, g_strdup("charset"),
5754 g_strdup(out_codeset));
5756 /* protect trailing spaces when signing message */
5757 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5758 privacy_system_can_sign(compose->privacy_system)) {
5759 encoding = ENC_QUOTED_PRINTABLE;
5763 debug_print("main text: %Id bytes encoded as %s in %d\n",
5765 debug_print("main text: %zd bytes encoded as %s in %d\n",
5767 strlen(buf), out_codeset, encoding);
5769 /* check for line length limit */
5770 if (action == COMPOSE_WRITE_FOR_SEND &&
5771 encoding != ENC_QUOTED_PRINTABLE && encoding != ENC_BASE64 &&
5772 check_line_length(buf, 1000, &line) < 0) {
5775 msg = g_strdup_printf
5776 (_("Line %d exceeds the line length limit (998 bytes).\n"
5777 "The contents of the message might be broken on the way to the delivery.\n"
5779 "Send it anyway?"), line + 1);
5780 aval = alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, GTK_STOCK_OK, NULL);
5782 if (aval != G_ALERTALTERNATE) {
5788 if (encoding != ENC_UNKNOWN)
5789 procmime_encode_content(mimetext, encoding);
5791 /* append attachment parts */
5792 if (compose_use_attach(compose) && attach_parts) {
5793 MimeInfo *mimempart;
5794 gchar *boundary = NULL;
5795 mimempart = procmime_mimeinfo_new();
5796 mimempart->content = MIMECONTENT_EMPTY;
5797 mimempart->type = MIMETYPE_MULTIPART;
5798 mimempart->subtype = g_strdup("mixed");
5802 boundary = generate_mime_boundary(NULL);
5803 } while (strstr(buf, boundary) != NULL);
5805 g_hash_table_insert(mimempart->typeparameters, g_strdup("boundary"),
5808 mimetext->disposition = DISPOSITIONTYPE_INLINE;
5810 g_node_append(mimempart->node, mimetext->node);
5811 g_node_append(mimemsg->node, mimempart->node);
5813 if (compose_add_attachments(compose, mimempart) < 0)
5816 g_node_append(mimemsg->node, mimetext->node);
5820 if (strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) != 0) {
5821 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
5822 /* extract name and address */
5823 if (strstr(spec, " <") && strstr(spec, ">")) {
5824 from_addr = g_strdup(strrchr(spec, '<')+1);
5825 *(strrchr(from_addr, '>')) = '\0';
5826 from_name = g_strdup(spec);
5827 *(strrchr(from_name, '<')) = '\0';
5834 /* sign message if sending */
5835 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5836 privacy_system_can_sign(compose->privacy_system))
5837 if (!privacy_sign(compose->privacy_system, mimemsg,
5838 compose->account, from_addr)) {
5846 if (compose->use_encryption) {
5847 if (compose->encdata != NULL &&
5848 strcmp(compose->encdata, "_DONT_ENCRYPT_")) {
5850 /* First, write an unencrypted copy and save it to outbox, if
5851 * user wants that. */
5852 if (compose->account->save_encrypted_as_clear_text) {
5853 debug_print("saving sent message unencrypted...\n");
5854 FILE *tmpfp = get_tmpfile_in_dir(get_mime_tmp_dir(), &tmp_enc_file);
5858 /* fp now points to a file with headers written,
5859 * let's make a copy. */
5861 content = file_read_stream_to_str(fp);
5863 str_write_to_file(content, tmp_enc_file);
5866 /* Now write the unencrypted body. */
5867 if ((tmpfp = g_fopen(tmp_enc_file, "a")) != NULL) {
5868 procmime_write_mimeinfo(mimemsg, tmpfp);
5871 outbox = folder_find_item_from_identifier(compose_get_save_to(compose));
5873 outbox = folder_get_default_outbox();
5875 procmsg_save_to_outbox(outbox, tmp_enc_file, TRUE);
5876 claws_unlink(tmp_enc_file);
5878 g_warning("Can't open file '%s'", tmp_enc_file);
5881 g_warning("couldn't get tempfile");
5884 if (!privacy_encrypt(compose->privacy_system, mimemsg, compose->encdata)) {
5885 debug_print("Couldn't encrypt mime structure: %s.\n",
5886 privacy_get_error());
5887 alertpanel_error(_("Couldn't encrypt the email: %s"),
5888 privacy_get_error());
5893 procmime_write_mimeinfo(mimemsg, fp);
5895 procmime_mimeinfo_free_all(&mimemsg);
5900 static gint compose_write_body_to_file(Compose *compose, const gchar *file)
5902 GtkTextBuffer *buffer;
5903 GtkTextIter start, end;
5908 if ((fp = g_fopen(file, "wb")) == NULL) {
5909 FILE_OP_ERROR(file, "fopen");
5913 /* chmod for security */
5914 if (change_file_mode_rw(fp, file) < 0) {
5915 FILE_OP_ERROR(file, "chmod");
5916 g_warning("can't change file mode");
5919 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5920 gtk_text_buffer_get_start_iter(buffer, &start);
5921 gtk_text_buffer_get_end_iter(buffer, &end);
5922 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5924 chars = conv_codeset_strdup
5925 (tmp, CS_INTERNAL, conv_get_locale_charset_str());
5934 len = strlen(chars);
5935 if (fwrite(chars, sizeof(gchar), len, fp) != len) {
5936 FILE_OP_ERROR(file, "fwrite");
5945 if (fclose(fp) == EOF) {
5946 FILE_OP_ERROR(file, "fclose");
5953 static gint compose_remove_reedit_target(Compose *compose, gboolean force)
5956 MsgInfo *msginfo = compose->targetinfo;
5958 cm_return_val_if_fail(compose->mode == COMPOSE_REEDIT, -1);
5959 if (!msginfo) return -1;
5961 if (!force && MSG_IS_LOCKED(msginfo->flags))
5964 item = msginfo->folder;
5965 cm_return_val_if_fail(item != NULL, -1);
5967 if (procmsg_msg_exist(msginfo) &&
5968 (folder_has_parent_of_type(item, F_QUEUE) ||
5969 folder_has_parent_of_type(item, F_DRAFT)
5970 || msginfo == compose->autosaved_draft)) {
5971 if (folder_item_remove_msg(item, msginfo->msgnum) < 0) {
5972 g_warning("can't remove the old message");
5975 debug_print("removed reedit target %d\n", msginfo->msgnum);
5982 static void compose_remove_draft(Compose *compose)
5985 MsgInfo *msginfo = compose->targetinfo;
5986 drafts = account_get_special_folder(compose->account, F_DRAFT);
5988 if (procmsg_msg_exist(msginfo)) {
5989 folder_item_remove_msg(drafts, msginfo->msgnum);
5994 gint compose_queue(Compose *compose, gint *msgnum, FolderItem **item, gchar **msgpath,
5995 gboolean remove_reedit_target)
5997 return compose_queue_sub (compose, msgnum, item, msgpath, FALSE, remove_reedit_target);
6000 static gboolean compose_warn_encryption(Compose *compose)
6002 const gchar *warning = privacy_get_encrypt_warning(compose->privacy_system);
6003 AlertValue val = G_ALERTALTERNATE;
6005 if (warning == NULL)
6008 val = alertpanel_full(_("Encryption warning"), warning,
6009 GTK_STOCK_CANCEL, g_strconcat("+", _("C_ontinue"), NULL), NULL,
6010 TRUE, NULL, ALERT_WARNING, G_ALERTALTERNATE);
6011 if (val & G_ALERTDISABLE) {
6012 val &= ~G_ALERTDISABLE;
6013 if (val == G_ALERTALTERNATE)
6014 privacy_inhibit_encrypt_warning(compose->privacy_system,
6018 if (val == G_ALERTALTERNATE) {
6025 static gint compose_queue_sub(Compose *compose, gint *msgnum, FolderItem **item,
6026 gchar **msgpath, gboolean perform_checks,
6027 gboolean remove_reedit_target)
6034 PrefsAccount *mailac = NULL, *newsac = NULL;
6035 gboolean err = FALSE;
6037 debug_print("queueing message...\n");
6038 cm_return_val_if_fail(compose->account != NULL, -1);
6040 if (compose_check_entries(compose, perform_checks) == FALSE) {
6041 if (compose->batch) {
6042 gtk_widget_show_all(compose->window);
6047 if (!compose->to_list && !compose->newsgroup_list) {
6048 g_warning("can't get recipient list.");
6052 if (compose->to_list) {
6053 if (compose->account->protocol != A_NNTP)
6054 mailac = compose->account;
6055 else if (cur_account && cur_account->protocol != A_NNTP)
6056 mailac = cur_account;
6057 else if (!(mailac = compose_current_mail_account())) {
6058 alertpanel_error(_("No account for sending mails available!"));
6063 if (compose->newsgroup_list) {
6064 if (compose->account->protocol == A_NNTP)
6065 newsac = compose->account;
6067 alertpanel_error(_("Selected account isn't NNTP: Posting is impossible."));
6072 /* write queue header */
6073 tmp = g_strdup_printf("%s%cqueue.%p%08x", get_tmp_dir(),
6074 G_DIR_SEPARATOR, compose, (guint) rand());
6075 debug_print("queuing to %s\n", tmp);
6076 if ((fp = g_fopen(tmp, "w+b")) == NULL) {
6077 FILE_OP_ERROR(tmp, "fopen");
6082 if (change_file_mode_rw(fp, tmp) < 0) {
6083 FILE_OP_ERROR(tmp, "chmod");
6084 g_warning("can't change file mode");
6087 /* queueing variables */
6088 err |= (fprintf(fp, "AF:\n") < 0);
6089 err |= (fprintf(fp, "NF:0\n") < 0);
6090 err |= (fprintf(fp, "PS:10\n") < 0);
6091 err |= (fprintf(fp, "SRH:1\n") < 0);
6092 err |= (fprintf(fp, "SFN:\n") < 0);
6093 err |= (fprintf(fp, "DSR:\n") < 0);
6095 err |= (fprintf(fp, "MID:<%s>\n", compose->msgid) < 0);
6097 err |= (fprintf(fp, "MID:\n") < 0);
6098 err |= (fprintf(fp, "CFG:\n") < 0);
6099 err |= (fprintf(fp, "PT:0\n") < 0);
6100 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
6101 err |= (fprintf(fp, "RQ:\n") < 0);
6103 err |= (fprintf(fp, "SSV:%s\n", mailac->smtp_server) < 0);
6105 err |= (fprintf(fp, "SSV:\n") < 0);
6107 err |= (fprintf(fp, "NSV:%s\n", newsac->nntp_server) < 0);
6109 err |= (fprintf(fp, "NSV:\n") < 0);
6110 err |= (fprintf(fp, "SSH:\n") < 0);
6111 /* write recepient list */
6112 if (compose->to_list) {
6113 err |= (fprintf(fp, "R:<%s>", (gchar *)compose->to_list->data) < 0);
6114 for (cur = compose->to_list->next; cur != NULL;
6116 err |= (fprintf(fp, ",<%s>", (gchar *)cur->data) < 0);
6117 err |= (fprintf(fp, "\n") < 0);
6119 /* write newsgroup list */
6120 if (compose->newsgroup_list) {
6121 err |= (fprintf(fp, "NG:") < 0);
6122 err |= (fprintf(fp, "%s", (gchar *)compose->newsgroup_list->data) < 0);
6123 for (cur = compose->newsgroup_list->next; cur != NULL; cur = cur->next)
6124 err |= (fprintf(fp, ",%s", (gchar *)cur->data) < 0);
6125 err |= (fprintf(fp, "\n") < 0);
6127 /* Sylpheed account IDs */
6129 err |= (fprintf(fp, "MAID:%d\n", mailac->account_id) < 0);
6131 err |= (fprintf(fp, "NAID:%d\n", newsac->account_id) < 0);
6134 if (compose->privacy_system != NULL) {
6135 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
6136 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
6137 if (compose->use_encryption) {
6138 if (!compose_warn_encryption(compose)) {
6144 if (mailac && mailac->encrypt_to_self) {
6145 GSList *tmp_list = g_slist_copy(compose->to_list);
6146 tmp_list = g_slist_append(tmp_list, compose->account->address);
6147 compose->encdata = privacy_get_encrypt_data(compose->privacy_system, tmp_list);
6148 g_slist_free(tmp_list);
6150 compose->encdata = privacy_get_encrypt_data(compose->privacy_system, compose->to_list);
6152 if (compose->encdata != NULL) {
6153 if (strcmp(compose->encdata, "_DONT_ENCRYPT_")) {
6154 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
6155 err |= (fprintf(fp, "X-Claws-Encrypt-Data:%s\n",
6156 compose->encdata) < 0);
6157 } /* else we finally dont want to encrypt */
6159 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
6160 /* and if encdata was null, it means there's been a problem in
6163 g_warning("failed to write queue message");
6172 /* Save copy folder */
6173 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
6174 gchar *savefolderid;
6176 savefolderid = compose_get_save_to(compose);
6177 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
6178 g_free(savefolderid);
6180 /* Save copy folder */
6181 if (compose->return_receipt) {
6182 err |= (fprintf(fp, "RRCPT:1\n") < 0);
6184 /* Message-ID of message replying to */
6185 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
6186 gchar *folderid = NULL;
6188 if (compose->replyinfo->folder)
6189 folderid = folder_item_get_identifier(compose->replyinfo->folder);
6190 if (folderid == NULL)
6191 folderid = g_strdup("NULL");
6193 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
6196 /* Message-ID of message forwarding to */
6197 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
6198 gchar *folderid = NULL;
6200 if (compose->fwdinfo->folder)
6201 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
6202 if (folderid == NULL)
6203 folderid = g_strdup("NULL");
6205 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
6209 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
6210 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
6212 /* end of headers */
6213 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
6215 if (compose->redirect_filename != NULL) {
6216 if (compose_redirect_write_to_file(compose, fp) < 0) {
6224 if ((result = compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_SEND, TRUE)) < 0) {
6228 return result - 1; /* -2 for a generic error, -3 for signing error, -4 for encoding */
6232 g_warning("failed to write queue message");
6238 if (fclose(fp) == EOF) {
6239 FILE_OP_ERROR(tmp, "fclose");
6245 if (item && *item) {
6248 queue = account_get_special_folder(compose->account, F_QUEUE);
6251 g_warning("can't find queue folder");
6256 folder_item_scan(queue);
6257 if ((num = folder_item_add_msg(queue, tmp, NULL, FALSE)) < 0) {
6258 g_warning("can't queue the message");
6264 if (msgpath == NULL) {
6270 if (compose->mode == COMPOSE_REEDIT && remove_reedit_target) {
6271 compose_remove_reedit_target(compose, FALSE);
6274 if ((msgnum != NULL) && (item != NULL)) {
6282 static int compose_add_attachments(Compose *compose, MimeInfo *parent)
6285 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
6288 gchar *type, *subtype;
6289 GtkTreeModel *model;
6292 model = gtk_tree_view_get_model(tree_view);
6294 if (!gtk_tree_model_get_iter_first(model, &iter))
6297 gtk_tree_model_get(model, &iter,
6301 if (!is_file_exist(ainfo->file)) {
6302 gchar *msg = g_strdup_printf(_("Attachment %s doesn't exist anymore. Ignore?"), ainfo->file);
6303 AlertValue val = alertpanel_full(_("Warning"), msg, _("Cancel sending"), _("Ignore attachment"),
6304 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
6306 if (val == G_ALERTDEFAULT) {
6311 if (g_stat(ainfo->file, &statbuf) < 0)
6314 mimepart = procmime_mimeinfo_new();
6315 mimepart->content = MIMECONTENT_FILE;
6316 mimepart->data.filename = g_strdup(ainfo->file);
6317 mimepart->tmp = FALSE; /* or we destroy our attachment */
6318 mimepart->offset = 0;
6319 mimepart->length = statbuf.st_size;
6321 type = g_strdup(ainfo->content_type);
6323 if (!strchr(type, '/')) {
6325 type = g_strdup("application/octet-stream");
6328 subtype = strchr(type, '/') + 1;
6329 *(subtype - 1) = '\0';
6330 mimepart->type = procmime_get_media_type(type);
6331 mimepart->subtype = g_strdup(subtype);
6334 if (mimepart->type == MIMETYPE_MESSAGE &&
6335 !g_ascii_strcasecmp(mimepart->subtype, "rfc822")) {
6336 mimepart->disposition = DISPOSITIONTYPE_INLINE;
6337 } else if (mimepart->type == MIMETYPE_TEXT) {
6338 if (!ainfo->name && g_ascii_strcasecmp(mimepart->subtype, "plain")) {
6339 /* Text parts with no name come from multipart/alternative
6340 * forwards. Make sure the recipient won't look at the
6341 * original HTML part by mistake. */
6342 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6343 ainfo->name = g_strdup_printf(_("Original %s part"),
6347 g_hash_table_insert(mimepart->typeparameters,
6348 g_strdup("charset"), g_strdup(ainfo->charset));
6350 if (ainfo->name && mimepart->type != MIMETYPE_MESSAGE) {
6351 if (mimepart->type == MIMETYPE_APPLICATION &&
6352 !strcmp2(mimepart->subtype, "octet-stream"))
6353 g_hash_table_insert(mimepart->typeparameters,
6354 g_strdup("name"), g_strdup(ainfo->name));
6355 g_hash_table_insert(mimepart->dispositionparameters,
6356 g_strdup("filename"), g_strdup(ainfo->name));
6357 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6360 if (mimepart->type == MIMETYPE_MESSAGE
6361 || mimepart->type == MIMETYPE_MULTIPART)
6362 ainfo->encoding = ENC_BINARY;
6363 else if (compose->use_signing) {
6364 if (ainfo->encoding == ENC_7BIT)
6365 ainfo->encoding = ENC_QUOTED_PRINTABLE;
6366 else if (ainfo->encoding == ENC_8BIT)
6367 ainfo->encoding = ENC_BASE64;
6372 procmime_encode_content(mimepart, ainfo->encoding);
6374 g_node_append(parent->node, mimepart->node);
6375 } while (gtk_tree_model_iter_next(model, &iter));
6380 static gchar *compose_quote_list_of_addresses(gchar *str)
6382 GSList *list = NULL, *item = NULL;
6383 gchar *qname = NULL, *faddr = NULL, *result = NULL;
6385 list = address_list_append_with_comments(list, str);
6386 for (item = list; item != NULL; item = item->next) {
6387 gchar *spec = item->data;
6388 gchar *endofname = strstr(spec, " <");
6389 if (endofname != NULL) {
6392 QUOTE_IF_REQUIRED_NORMAL(qname, spec, return NULL);
6393 qqname = escape_internal_quotes(qname, '"');
6395 if (*qname != *spec || qqname != qname) { /* has been quoted, compute new */
6396 gchar *addr = g_strdup(endofname);
6397 gchar *name = (qqname != qname)? qqname: g_strdup(qname);
6398 faddr = g_strconcat(name, addr, NULL);
6401 debug_print("new auto-quoted address: '%s'\n", faddr);
6405 result = g_strdup((faddr != NULL)? faddr: spec);
6407 result = g_strconcat(result,
6409 (faddr != NULL)? faddr: spec,
6412 if (faddr != NULL) {
6417 slist_free_strings_full(list);
6422 #define IS_IN_CUSTOM_HEADER(header) \
6423 (compose->account->add_customhdr && \
6424 custom_header_find(compose->account->customhdr_list, header) != NULL)
6426 static const gchar *compose_untranslated_header_name(gchar *header_name)
6428 /* return the untranslated header name, if header_name is a known
6429 header name, in either its translated or untranslated form, with
6430 or without trailing colon. return NULL if no matching header name
6431 is found or if header_name is NULL. */
6432 gchar *translated_header_name;
6433 gchar *translated_header_name_wcolon;
6434 const gchar *untranslated_header_name;
6435 const gchar *untranslated_header_name_wcolon;
6438 cm_return_val_if_fail(header_name != NULL, NULL);
6440 for (i = 0; HEADERS[i].header_name != NULL; i++) {
6441 untranslated_header_name = HEADERS[i].header_name;
6442 untranslated_header_name_wcolon = HEADERS[i].header_name_w_colon;
6444 translated_header_name = gettext(untranslated_header_name);
6445 translated_header_name_wcolon = gettext(untranslated_header_name_wcolon);
6447 if (!strcmp(header_name, untranslated_header_name) ||
6448 !strcmp(header_name, translated_header_name)) {
6449 return untranslated_header_name;
6451 if (!strcmp(header_name, untranslated_header_name_wcolon) ||
6452 !strcmp(header_name, translated_header_name_wcolon)) {
6453 return untranslated_header_name_wcolon;
6457 debug_print("compose_untranslated_header_name: unknown header '%s'\n", header_name);
6461 static void compose_add_headerfield_from_headerlist(Compose *compose,
6463 const gchar *fieldname,
6464 const gchar *seperator)
6466 gchar *str, *fieldname_w_colon;
6467 gboolean add_field = FALSE;
6469 ComposeHeaderEntry *headerentry;
6470 const gchar *headerentryname;
6471 const gchar *trans_fieldname;
6474 if (IS_IN_CUSTOM_HEADER(fieldname))
6477 debug_print("Adding %s-fields\n", fieldname);
6479 fieldstr = g_string_sized_new(64);
6481 fieldname_w_colon = g_strconcat(fieldname, ":", NULL);
6482 trans_fieldname = prefs_common_translated_header_name(fieldname_w_colon);
6484 for (list = compose->header_list; list; list = list->next) {
6485 headerentry = ((ComposeHeaderEntry *)list->data);
6486 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
6488 if (!g_utf8_collate(trans_fieldname, headerentryname)) {
6489 gchar * ustr = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
6491 str = compose_quote_list_of_addresses(ustr);
6493 if (str != NULL && str[0] != '\0') {
6495 g_string_append(fieldstr, seperator);
6496 g_string_append(fieldstr, str);
6505 buf = g_new0(gchar, fieldstr->len * 4 + 256);
6506 compose_convert_header
6507 (compose, buf, fieldstr->len * 4 + 256, fieldstr->str,
6508 strlen(fieldname) + 2, TRUE);
6509 g_string_append_printf(header, "%s: %s\n", fieldname, buf);
6513 g_free(fieldname_w_colon);
6514 g_string_free(fieldstr, TRUE);
6519 static gchar *compose_get_manual_headers_info(Compose *compose)
6521 GString *sh_header = g_string_new(" ");
6523 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6525 for (list = compose->header_list; list; list = list->next) {
6526 ComposeHeaderEntry *headerentry;
6529 gchar *headername_wcolon;
6530 const gchar *headername_trans;
6532 gboolean standard_header = FALSE;
6534 headerentry = ((ComposeHeaderEntry *)list->data);
6536 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6538 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6543 if (!strstr(tmp, ":")) {
6544 headername_wcolon = g_strconcat(tmp, ":", NULL);
6545 headername = g_strdup(tmp);
6547 headername_wcolon = g_strdup(tmp);
6548 headername = g_strdup(strtok(tmp, ":"));
6552 string = std_headers;
6553 while (*string != NULL) {
6554 headername_trans = prefs_common_translated_header_name(*string);
6555 if (!strcmp(headername_trans, headername_wcolon))
6556 standard_header = TRUE;
6559 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
6560 g_string_append_printf(sh_header, "%s ", headername);
6562 g_free(headername_wcolon);
6564 g_string_truncate(sh_header, strlen(sh_header->str) - 1); /* remove last space */
6565 return g_string_free(sh_header, FALSE);
6568 static gchar *compose_get_header(Compose *compose)
6570 gchar date[RFC822_DATE_BUFFSIZE];
6571 gchar buf[BUFFSIZE];
6572 const gchar *entry_str;
6576 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6578 gchar *from_name = NULL, *from_address = NULL;
6581 cm_return_val_if_fail(compose->account != NULL, NULL);
6582 cm_return_val_if_fail(compose->account->address != NULL, NULL);
6584 header = g_string_sized_new(64);
6587 if (prefs_common.hide_timezone)
6588 get_rfc822_date_hide_tz(date, sizeof(date));
6590 get_rfc822_date(date, sizeof(date));
6591 g_string_append_printf(header, "Date: %s\n", date);
6595 if (compose->account->name && *compose->account->name) {
6597 QUOTE_IF_REQUIRED(buf, compose->account->name);
6598 tmp = g_strdup_printf("%s <%s>",
6599 buf, compose->account->address);
6601 tmp = g_strdup_printf("%s",
6602 compose->account->address);
6604 if (!strcmp(gtk_entry_get_text(GTK_ENTRY(compose->from_name)), tmp)
6605 || strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) == 0) {
6607 from_name = compose->account->name ? g_strdup(compose->account->name):NULL;
6608 from_address = g_strdup(compose->account->address);
6610 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
6611 /* extract name and address */
6612 if (strstr(spec, " <") && strstr(spec, ">")) {
6613 from_address = g_strdup(strrchr(spec, '<')+1);
6614 *(strrchr(from_address, '>')) = '\0';
6615 from_name = g_strdup(spec);
6616 *(strrchr(from_name, '<')) = '\0';
6619 from_address = g_strdup(spec);
6626 if (from_name && *from_name) {
6628 compose_convert_header
6629 (compose, buf, sizeof(buf), from_name,
6630 strlen("From: "), TRUE);
6631 QUOTE_IF_REQUIRED(name, buf);
6632 qname = escape_internal_quotes(name, '"');
6634 g_string_append_printf(header, "From: %s <%s>\n",
6635 qname, from_address);
6636 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To") &&
6637 compose->return_receipt) {
6638 compose_convert_header(compose, buf, sizeof(buf), from_name,
6639 strlen("Disposition-Notification-To: "),
6641 g_string_append_printf(header, "Disposition-Notification-To: %s <%s>\n", buf, from_address);
6646 g_string_append_printf(header, "From: %s\n", from_address);
6647 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To") &&
6648 compose->return_receipt)
6649 g_string_append_printf(header, "Disposition-Notification-To: %s\n", from_address);
6653 g_free(from_address);
6656 compose_add_headerfield_from_headerlist(compose, header, "To", ", ");
6659 compose_add_headerfield_from_headerlist(compose, header, "Newsgroups", ",");
6662 compose_add_headerfield_from_headerlist(compose, header, "Cc", ", ");
6666 * If this account is a NNTP account remove Bcc header from
6667 * message body since it otherwise will be publicly shown
6669 if (compose->account->protocol != A_NNTP)
6670 compose_add_headerfield_from_headerlist(compose, header, "Bcc", ", ");
6673 str = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
6675 if (*str != '\0' && !IS_IN_CUSTOM_HEADER("Subject")) {
6678 compose_convert_header(compose, buf, sizeof(buf), str,
6679 strlen("Subject: "), FALSE);
6680 g_string_append_printf(header, "Subject: %s\n", buf);
6686 if (compose->msgid != NULL && strlen(compose->msgid) > 0) {
6687 g_string_append_printf(header, "Message-ID: <%s>\n",
6691 if (compose->remove_references == FALSE) {
6693 if (compose->inreplyto && compose->to_list)
6694 g_string_append_printf(header, "In-Reply-To: <%s>\n", compose->inreplyto);
6697 if (compose->references)
6698 g_string_append_printf(header, "References: %s\n", compose->references);
6702 compose_add_headerfield_from_headerlist(compose, header, "Followup-To", ",");
6705 compose_add_headerfield_from_headerlist(compose, header, "Reply-To", ", ");
6708 if (compose->account->organization &&
6709 strlen(compose->account->organization) &&
6710 !IS_IN_CUSTOM_HEADER("Organization")) {
6711 compose_convert_header(compose, buf, sizeof(buf),
6712 compose->account->organization,
6713 strlen("Organization: "), FALSE);
6714 g_string_append_printf(header, "Organization: %s\n", buf);
6717 /* Program version and system info */
6718 if (compose->account->gen_xmailer &&
6719 g_slist_length(compose->to_list) && !IS_IN_CUSTOM_HEADER("X-Mailer") &&
6720 !compose->newsgroup_list) {
6721 g_string_append_printf(header, "X-Mailer: %s (GTK+ %d.%d.%d; %s)\n",
6723 gtk_major_version, gtk_minor_version, gtk_micro_version,
6726 if (compose->account->gen_xmailer &&
6727 g_slist_length(compose->newsgroup_list) && !IS_IN_CUSTOM_HEADER("X-Newsreader")) {
6728 g_string_append_printf(header, "X-Newsreader: %s (GTK+ %d.%d.%d; %s)\n",
6730 gtk_major_version, gtk_minor_version, gtk_micro_version,
6734 /* custom headers */
6735 if (compose->account->add_customhdr) {
6738 for (cur = compose->account->customhdr_list; cur != NULL;
6740 CustomHeader *chdr = (CustomHeader *)cur->data;
6742 if (custom_header_is_allowed(chdr->name)
6743 && chdr->value != NULL
6744 && *(chdr->value) != '\0') {
6745 compose_convert_header
6746 (compose, buf, sizeof(buf),
6748 strlen(chdr->name) + 2, FALSE);
6749 g_string_append_printf(header, "%s: %s\n", chdr->name, buf);
6754 /* Automatic Faces and X-Faces */
6755 if (get_account_xface (buf, sizeof(buf), compose->account->account_name) == 0) {
6756 g_string_append_printf(header, "X-Face: %s\n", buf);
6758 else if (get_default_xface (buf, sizeof(buf)) == 0) {
6759 g_string_append_printf(header, "X-Face: %s\n", buf);
6761 if (get_account_face (buf, sizeof(buf), compose->account->account_name) == 0) {
6762 g_string_append_printf(header, "Face: %s\n", buf);
6764 else if (get_default_face (buf, sizeof(buf)) == 0) {
6765 g_string_append_printf(header, "Face: %s\n", buf);
6769 switch (compose->priority) {
6770 case PRIORITY_HIGHEST: g_string_append_printf(header, "Importance: high\n"
6771 "X-Priority: 1 (Highest)\n");
6773 case PRIORITY_HIGH: g_string_append_printf(header, "Importance: high\n"
6774 "X-Priority: 2 (High)\n");
6776 case PRIORITY_NORMAL: break;
6777 case PRIORITY_LOW: g_string_append_printf(header, "Importance: low\n"
6778 "X-Priority: 4 (Low)\n");
6780 case PRIORITY_LOWEST: g_string_append_printf(header, "Importance: low\n"
6781 "X-Priority: 5 (Lowest)\n");
6783 default: debug_print("compose: priority unknown : %d\n",
6787 /* get special headers */
6788 for (list = compose->header_list; list; list = list->next) {
6789 ComposeHeaderEntry *headerentry;
6792 gchar *headername_wcolon;
6793 const gchar *headername_trans;
6796 gboolean standard_header = FALSE;
6798 headerentry = ((ComposeHeaderEntry *)list->data);
6800 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6802 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6807 if (!strstr(tmp, ":")) {
6808 headername_wcolon = g_strconcat(tmp, ":", NULL);
6809 headername = g_strdup(tmp);
6811 headername_wcolon = g_strdup(tmp);
6812 headername = g_strdup(strtok(tmp, ":"));
6816 entry_str = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
6817 Xstrdup_a(headervalue, entry_str, return NULL);
6818 subst_char(headervalue, '\r', ' ');
6819 subst_char(headervalue, '\n', ' ');
6820 g_strstrip(headervalue);
6821 if (*headervalue != '\0') {
6822 string = std_headers;
6823 while (*string != NULL && !standard_header) {
6824 headername_trans = prefs_common_translated_header_name(*string);
6825 /* support mixed translated and untranslated headers */
6826 if (!strcmp(headername_trans, headername_wcolon) || !strcmp(*string, headername_wcolon))
6827 standard_header = TRUE;
6830 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername)) {
6831 /* store untranslated header name */
6832 g_string_append_printf(header, "%s %s\n",
6833 compose_untranslated_header_name(headername_wcolon), headervalue);
6837 g_free(headername_wcolon);
6841 g_string_free(header, FALSE);
6846 #undef IS_IN_CUSTOM_HEADER
6848 static void compose_convert_header(Compose *compose, gchar *dest, gint len, gchar *src,
6849 gint header_len, gboolean addr_field)
6851 gchar *tmpstr = NULL;
6852 const gchar *out_codeset = NULL;
6854 cm_return_if_fail(src != NULL);
6855 cm_return_if_fail(dest != NULL);
6857 if (len < 1) return;
6859 tmpstr = g_strdup(src);
6861 subst_char(tmpstr, '\n', ' ');
6862 subst_char(tmpstr, '\r', ' ');
6865 if (!g_utf8_validate(tmpstr, -1, NULL)) {
6866 gchar *mybuf = g_malloc(strlen(tmpstr)*2 +1);
6867 conv_localetodisp(mybuf, strlen(tmpstr)*2 +1, tmpstr);
6872 codeconv_set_strict(TRUE);
6873 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6874 conv_get_charset_str(compose->out_encoding));
6875 codeconv_set_strict(FALSE);
6877 if (!dest || *dest == '\0') {
6878 gchar *test_conv_global_out = NULL;
6879 gchar *test_conv_reply = NULL;
6881 /* automatic mode. be automatic. */
6882 codeconv_set_strict(TRUE);
6884 out_codeset = conv_get_outgoing_charset_str();
6886 debug_print("trying to convert to %s\n", out_codeset);
6887 test_conv_global_out = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6890 if (!test_conv_global_out && compose->orig_charset
6891 && strcmp(compose->orig_charset, CS_US_ASCII)) {
6892 out_codeset = compose->orig_charset;
6893 debug_print("failure; trying to convert to %s\n", out_codeset);
6894 test_conv_reply = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6897 if (!test_conv_global_out && !test_conv_reply) {
6899 out_codeset = CS_INTERNAL;
6900 debug_print("finally using %s\n", out_codeset);
6902 g_free(test_conv_global_out);
6903 g_free(test_conv_reply);
6904 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6906 codeconv_set_strict(FALSE);
6911 static void compose_add_to_addressbook_cb(GtkMenuItem *menuitem, gpointer user_data)
6915 cm_return_if_fail(user_data != NULL);
6917 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(user_data)));
6918 g_strstrip(address);
6919 if (*address != '\0') {
6920 gchar *name = procheader_get_fromname(address);
6921 extract_address(address);
6922 #ifndef USE_ALT_ADDRBOOK
6923 addressbook_add_contact(name, address, NULL, NULL);
6925 debug_print("%s: %s\n", name, address);
6926 if (addressadd_selection(name, address, NULL, NULL)) {
6927 debug_print( "addressbook_add_contact - added\n" );
6934 static void compose_entry_popup_extend(GtkEntry *entry, GtkMenu *menu, gpointer user_data)
6936 GtkWidget *menuitem;
6939 cm_return_if_fail(menu != NULL);
6940 cm_return_if_fail(GTK_IS_MENU_SHELL(menu));
6942 menuitem = gtk_separator_menu_item_new();
6943 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6944 gtk_widget_show(menuitem);
6946 menuitem = gtk_menu_item_new_with_mnemonic(_("Add to address _book"));
6947 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6949 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
6950 g_strstrip(address);
6951 if (*address == '\0') {
6952 gtk_widget_set_sensitive(GTK_WIDGET(menuitem), FALSE);
6955 g_signal_connect(G_OBJECT(menuitem), "activate",
6956 G_CALLBACK(compose_add_to_addressbook_cb), entry);
6957 gtk_widget_show(menuitem);
6960 void compose_add_extra_header(gchar *header, GtkListStore *model)
6963 if (strcmp(header, "")) {
6964 COMBOBOX_ADD(model, header, COMPOSE_TO);
6968 void compose_add_extra_header_entries(GtkListStore *model)
6972 gchar buf[BUFFSIZE];
6975 if (extra_headers == NULL) {
6976 exhrc = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, "extraheaderrc", NULL);
6977 if ((exh = g_fopen(exhrc, "rb")) == NULL) {
6978 debug_print("extra headers file not found\n");
6979 goto extra_headers_done;
6981 while (fgets(buf, BUFFSIZE, exh) != NULL) {
6982 lastc = strlen(buf) - 1; /* remove trailing control chars */
6983 while (lastc >= 0 && buf[lastc] != ':')
6984 buf[lastc--] = '\0';
6985 if (lastc > 0 && buf[0] != '#' && buf[lastc] == ':') {
6986 buf[lastc] = '\0'; /* remove trailing : for comparison */
6987 if (custom_header_is_allowed(buf)) {
6989 extra_headers = g_slist_prepend(extra_headers, g_strdup(buf));
6992 g_message("disallowed extra header line: %s\n", buf);
6996 g_message("invalid extra header line: %s\n", buf);
7002 extra_headers = g_slist_prepend(extra_headers, g_strdup("")); /* end of list */
7003 extra_headers = g_slist_reverse(extra_headers);
7005 g_slist_foreach(extra_headers, (GFunc)compose_add_extra_header, (gpointer)model);
7008 static void compose_create_header_entry(Compose *compose)
7010 gchar *headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
7017 const gchar *header = NULL;
7018 ComposeHeaderEntry *headerentry;
7019 gboolean standard_header = FALSE;
7020 GtkListStore *model;
7023 headerentry = g_new0(ComposeHeaderEntry, 1);
7025 /* Combo box model */
7026 model = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_BOOLEAN);
7027 #if !GTK_CHECK_VERSION(2, 24, 0)
7028 combo = gtk_combo_box_entry_new_with_model(GTK_TREE_MODEL(model), 0);
7030 COMBOBOX_ADD(model, prefs_common_translated_header_name("To:"),
7032 COMBOBOX_ADD(model, prefs_common_translated_header_name("Cc:"),
7034 COMBOBOX_ADD(model, prefs_common_translated_header_name("Bcc:"),
7036 COMBOBOX_ADD(model, prefs_common_translated_header_name("Newsgroups:"),
7037 COMPOSE_NEWSGROUPS);
7038 COMBOBOX_ADD(model, prefs_common_translated_header_name("Reply-To:"),
7040 COMBOBOX_ADD(model, prefs_common_translated_header_name("Followup-To:"),
7041 COMPOSE_FOLLOWUPTO);
7042 compose_add_extra_header_entries(model);
7045 #if GTK_CHECK_VERSION(2, 24, 0)
7046 combo = gtk_combo_box_new_with_model_and_entry(GTK_TREE_MODEL(model));
7047 GtkCellRenderer *cell = gtk_cell_renderer_text_new();
7048 gtk_cell_renderer_set_alignment(cell, 0.0, 0.5);
7049 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), cell, TRUE);
7050 gtk_combo_box_set_entry_text_column(GTK_COMBO_BOX(combo), 0);
7052 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
7053 g_signal_connect(G_OBJECT(gtk_bin_get_child(GTK_BIN(combo))), "grab_focus",
7054 G_CALLBACK(compose_grab_focus_cb), compose);
7055 gtk_widget_show(combo);
7057 /* Putting only the combobox child into focus chain of its parent causes
7058 * the parent to be skipped when changing focus via Tab or Shift+Tab.
7059 * This eliminates need to pres Tab twice in order to really get from the
7060 * combobox to next widget. */
7062 l = g_list_prepend(l, gtk_bin_get_child(GTK_BIN(combo)));
7063 gtk_container_set_focus_chain(GTK_CONTAINER(combo), l);
7066 gtk_table_attach(GTK_TABLE(compose->header_table), combo, 0, 1,
7067 compose->header_nextrow, compose->header_nextrow+1,
7068 GTK_SHRINK, GTK_FILL, 0, 0);
7069 if (compose->header_last && (compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN)) {
7070 const gchar *last_header_entry = gtk_entry_get_text(
7071 GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
7073 while (*string != NULL) {
7074 if (!strcmp(prefs_common_translated_header_name(*string), last_header_entry))
7075 standard_header = TRUE;
7078 if (standard_header)
7079 header = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
7081 if (!compose->header_last || !standard_header) {
7082 switch(compose->account->protocol) {
7084 header = prefs_common_translated_header_name("Newsgroups:");
7087 header = prefs_common_translated_header_name("To:");
7092 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((combo)))), header);
7094 gtk_editable_set_editable(
7095 GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((combo)))),
7096 prefs_common.type_any_header);
7098 g_signal_connect_after(G_OBJECT(gtk_bin_get_child(GTK_BIN((combo)))), "grab_focus",
7099 G_CALLBACK(compose_grab_focus_cb), compose);
7101 /* Entry field with cleanup button */
7102 button = gtk_button_new();
7103 gtk_button_set_image(GTK_BUTTON(button),
7104 gtk_image_new_from_stock(GTK_STOCK_CLEAR, GTK_ICON_SIZE_MENU));
7105 gtk_widget_show(button);
7106 CLAWS_SET_TIP(button,
7107 _("Delete entry contents"));
7108 entry = gtk_entry_new();
7109 gtk_widget_show(entry);
7110 CLAWS_SET_TIP(entry,
7111 _("Use <tab> to autocomplete from addressbook"));
7112 hbox = gtk_hbox_new (FALSE, 0);
7113 gtk_widget_show(hbox);
7114 gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, TRUE, 0);
7115 gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
7116 gtk_table_attach(GTK_TABLE(compose->header_table), hbox, 1, 2,
7117 compose->header_nextrow, compose->header_nextrow+1,
7118 GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
7120 g_signal_connect(G_OBJECT(entry), "key-press-event",
7121 G_CALLBACK(compose_headerentry_key_press_event_cb),
7123 g_signal_connect(G_OBJECT(entry), "changed",
7124 G_CALLBACK(compose_headerentry_changed_cb),
7126 g_signal_connect_after(G_OBJECT(entry), "grab_focus",
7127 G_CALLBACK(compose_grab_focus_cb), compose);
7129 g_signal_connect(G_OBJECT(button), "clicked",
7130 G_CALLBACK(compose_headerentry_button_clicked_cb),
7134 gtk_drag_dest_set(entry, GTK_DEST_DEFAULT_ALL, compose_mime_types,
7135 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7136 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7137 g_signal_connect(G_OBJECT(entry), "drag_data_received",
7138 G_CALLBACK(compose_header_drag_received_cb),
7140 g_signal_connect(G_OBJECT(entry), "drag-drop",
7141 G_CALLBACK(compose_drag_drop),
7143 g_signal_connect(G_OBJECT(entry), "populate-popup",
7144 G_CALLBACK(compose_entry_popup_extend),
7147 address_completion_register_entry(GTK_ENTRY(entry), TRUE);
7149 headerentry->compose = compose;
7150 headerentry->combo = combo;
7151 headerentry->entry = entry;
7152 headerentry->button = button;
7153 headerentry->hbox = hbox;
7154 headerentry->headernum = compose->header_nextrow;
7155 headerentry->type = PREF_NONE;
7157 compose->header_nextrow++;
7158 compose->header_last = headerentry;
7159 compose->header_list =
7160 g_slist_append(compose->header_list,
7164 static void compose_add_header_entry(Compose *compose, const gchar *header,
7165 gchar *text, ComposePrefType pref_type)
7167 ComposeHeaderEntry *last_header = compose->header_last;
7168 gchar *tmp = g_strdup(text), *email;
7169 gboolean replyto_hdr;
7171 replyto_hdr = (!strcasecmp(header,
7172 prefs_common_translated_header_name("Reply-To:")) ||
7174 prefs_common_translated_header_name("Followup-To:")) ||
7176 prefs_common_translated_header_name("In-Reply-To:")));
7178 extract_address(tmp);
7179 email = g_utf8_strdown(tmp, -1);
7181 if (replyto_hdr == FALSE &&
7182 g_hash_table_lookup(compose->email_hashtable, email) != NULL)
7184 debug_print("Ignoring duplicate address - %s %s, pref_type: %d\n",
7185 header, text, (gint) pref_type);
7191 if (!strcasecmp(header, prefs_common_translated_header_name("In-Reply-To:")))
7192 gtk_entry_set_text(GTK_ENTRY(
7193 gtk_bin_get_child(GTK_BIN(last_header->combo))), header);
7195 combobox_select_by_text(GTK_COMBO_BOX(last_header->combo), header);
7196 gtk_entry_set_text(GTK_ENTRY(last_header->entry), text);
7197 last_header->type = pref_type;
7199 if (replyto_hdr == FALSE)
7200 g_hash_table_insert(compose->email_hashtable, email,
7201 GUINT_TO_POINTER(1));
7208 static void compose_destroy_headerentry(Compose *compose,
7209 ComposeHeaderEntry *headerentry)
7211 gchar *text = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
7214 extract_address(text);
7215 email = g_utf8_strdown(text, -1);
7216 g_hash_table_remove(compose->email_hashtable, email);
7220 gtk_widget_destroy(headerentry->combo);
7221 gtk_widget_destroy(headerentry->entry);
7222 gtk_widget_destroy(headerentry->button);
7223 gtk_widget_destroy(headerentry->hbox);
7224 g_free(headerentry);
7227 static void compose_remove_header_entries(Compose *compose)
7230 for (list = compose->header_list; list; list = list->next)
7231 compose_destroy_headerentry(compose, (ComposeHeaderEntry *)list->data);
7233 compose->header_last = NULL;
7234 g_slist_free(compose->header_list);
7235 compose->header_list = NULL;
7236 compose->header_nextrow = 1;
7237 compose_create_header_entry(compose);
7240 static GtkWidget *compose_create_header(Compose *compose)
7242 GtkWidget *from_optmenu_hbox;
7243 GtkWidget *header_table_main;
7244 GtkWidget *header_scrolledwin;
7245 GtkWidget *header_table;
7247 /* parent with account selection and from header */
7248 header_table_main = gtk_table_new(2, 2, FALSE);
7249 gtk_widget_show(header_table_main);
7250 gtk_container_set_border_width(GTK_CONTAINER(header_table_main), BORDER_WIDTH);
7252 from_optmenu_hbox = compose_account_option_menu_create(compose);
7253 gtk_table_attach(GTK_TABLE(header_table_main), from_optmenu_hbox,
7254 0, 2, 0, 1, GTK_EXPAND | GTK_FILL, GTK_SHRINK, 0, 0);
7256 /* child with header labels and entries */
7257 header_scrolledwin = gtk_scrolled_window_new(NULL, NULL);
7258 gtk_widget_show(header_scrolledwin);
7259 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
7261 header_table = gtk_table_new(2, 2, FALSE);
7262 gtk_widget_show(header_table);
7263 gtk_container_set_border_width(GTK_CONTAINER(header_table), 0);
7264 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin), header_table);
7265 gtk_container_set_focus_vadjustment(GTK_CONTAINER(header_table),
7266 gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(header_scrolledwin)));
7267 gtk_viewport_set_shadow_type(GTK_VIEWPORT(gtk_bin_get_child(GTK_BIN(header_scrolledwin))), GTK_SHADOW_NONE);
7269 gtk_table_attach(GTK_TABLE(header_table_main), header_scrolledwin,
7270 0, 2, 1, 2, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 2);
7272 compose->header_table = header_table;
7273 compose->header_list = NULL;
7274 compose->header_nextrow = 0;
7276 compose_create_header_entry(compose);
7278 compose->table = NULL;
7280 return header_table_main;
7283 static gboolean popup_attach_button_pressed(GtkWidget *widget, gpointer data)
7285 Compose *compose = (Compose *)data;
7286 GdkEventButton event;
7289 event.time = gtk_get_current_event_time();
7291 return attach_button_pressed(compose->attach_clist, &event, compose);
7294 static GtkWidget *compose_create_attach(Compose *compose)
7296 GtkWidget *attach_scrwin;
7297 GtkWidget *attach_clist;
7299 GtkListStore *store;
7300 GtkCellRenderer *renderer;
7301 GtkTreeViewColumn *column;
7302 GtkTreeSelection *selection;
7304 /* attachment list */
7305 attach_scrwin = gtk_scrolled_window_new(NULL, NULL);
7306 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(attach_scrwin),
7307 GTK_POLICY_AUTOMATIC,
7308 GTK_POLICY_AUTOMATIC);
7309 gtk_widget_set_size_request(attach_scrwin, -1, 80);
7311 store = gtk_list_store_new(N_ATTACH_COLS,
7317 G_TYPE_AUTO_POINTER,
7319 attach_clist = GTK_WIDGET(gtk_tree_view_new_with_model
7320 (GTK_TREE_MODEL(store)));
7321 gtk_container_add(GTK_CONTAINER(attach_scrwin), attach_clist);
7322 g_object_unref(store);
7324 renderer = gtk_cell_renderer_text_new();
7325 column = gtk_tree_view_column_new_with_attributes
7326 (_("Mime type"), renderer, "text",
7327 COL_MIMETYPE, NULL);
7328 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7330 renderer = gtk_cell_renderer_text_new();
7331 column = gtk_tree_view_column_new_with_attributes
7332 (_("Size"), renderer, "text",
7334 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7336 renderer = gtk_cell_renderer_text_new();
7337 column = gtk_tree_view_column_new_with_attributes
7338 (_("Name"), renderer, "text",
7340 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7342 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(attach_clist),
7343 prefs_common.use_stripes_everywhere);
7344 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(attach_clist));
7345 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
7347 g_signal_connect(G_OBJECT(attach_clist), "row_activated",
7348 G_CALLBACK(attach_selected), compose);
7349 g_signal_connect(G_OBJECT(attach_clist), "button_press_event",
7350 G_CALLBACK(attach_button_pressed), compose);
7351 g_signal_connect(G_OBJECT(attach_clist), "popup-menu",
7352 G_CALLBACK(popup_attach_button_pressed), compose);
7353 g_signal_connect(G_OBJECT(attach_clist), "key_press_event",
7354 G_CALLBACK(attach_key_pressed), compose);
7357 gtk_drag_dest_set(attach_clist,
7358 GTK_DEST_DEFAULT_ALL, compose_mime_types,
7359 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7360 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7361 g_signal_connect(G_OBJECT(attach_clist), "drag_data_received",
7362 G_CALLBACK(compose_attach_drag_received_cb),
7364 g_signal_connect(G_OBJECT(attach_clist), "drag-drop",
7365 G_CALLBACK(compose_drag_drop),
7368 compose->attach_scrwin = attach_scrwin;
7369 compose->attach_clist = attach_clist;
7371 return attach_scrwin;
7374 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose);
7375 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose);
7377 static GtkWidget *compose_create_others(Compose *compose)
7380 GtkWidget *savemsg_checkbtn;
7381 GtkWidget *savemsg_combo;
7382 GtkWidget *savemsg_select;
7385 gchar *folderidentifier;
7387 /* Table for settings */
7388 table = gtk_table_new(3, 1, FALSE);
7389 gtk_container_set_border_width(GTK_CONTAINER(table), BORDER_WIDTH);
7390 gtk_widget_show(table);
7391 gtk_table_set_row_spacings(GTK_TABLE(table), VSPACING_NARROW);
7394 /* Save Message to folder */
7395 savemsg_checkbtn = gtk_check_button_new_with_label(_("Save Message to "));
7396 gtk_widget_show(savemsg_checkbtn);
7397 gtk_table_attach(GTK_TABLE(table), savemsg_checkbtn, 0, 1, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7398 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7399 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), prefs_common.savemsg);
7401 g_signal_connect(G_OBJECT(savemsg_checkbtn), "toggled",
7402 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
7404 #if !GTK_CHECK_VERSION(2, 24, 0)
7405 savemsg_combo = gtk_combo_box_entry_new_text();
7407 savemsg_combo = gtk_combo_box_text_new_with_entry();
7409 compose->savemsg_checkbtn = savemsg_checkbtn;
7410 compose->savemsg_combo = savemsg_combo;
7411 gtk_widget_show(savemsg_combo);
7413 if (prefs_common.compose_save_to_history)
7414 #if !GTK_CHECK_VERSION(2, 24, 0)
7415 combobox_set_popdown_strings(GTK_COMBO_BOX(savemsg_combo),
7416 prefs_common.compose_save_to_history);
7418 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(savemsg_combo),
7419 prefs_common.compose_save_to_history);
7421 gtk_table_attach(GTK_TABLE(table), savemsg_combo, 1, 2, rowcount, rowcount + 1, GTK_FILL|GTK_EXPAND, GTK_SHRINK, 0, 0);
7422 gtk_widget_set_sensitive(GTK_WIDGET(savemsg_combo), prefs_common.savemsg);
7423 g_signal_connect_after(G_OBJECT(savemsg_combo), "grab_focus",
7424 G_CALLBACK(compose_grab_focus_cb), compose);
7425 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7426 folderidentifier = folder_item_get_identifier(account_get_special_folder
7427 (compose->account, F_OUTBOX));
7428 compose_set_save_to(compose, folderidentifier);
7429 g_free(folderidentifier);
7432 savemsg_select = gtkut_get_browse_file_btn(_("_Browse"));
7433 gtk_widget_show(savemsg_select);
7434 gtk_table_attach(GTK_TABLE(table), savemsg_select, 2, 3, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7435 g_signal_connect(G_OBJECT(savemsg_select), "clicked",
7436 G_CALLBACK(compose_savemsg_select_cb),
7442 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose)
7444 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo),
7445 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn)));
7448 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose)
7453 dest = foldersel_folder_sel(NULL, FOLDER_SEL_COPY, NULL, FALSE);
7456 path = folder_item_get_identifier(dest);
7458 compose_set_save_to(compose, path);
7462 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry, gboolean wrap,
7463 GdkAtom clip, GtkTextIter *insert_place);
7466 static gboolean text_clicked(GtkWidget *text, GdkEventButton *event,
7470 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
7472 if (event->button == 3) {
7474 GtkTextIter sel_start, sel_end;
7475 gboolean stuff_selected;
7477 /* move the cursor to allow GtkAspell to check the word
7478 * under the mouse */
7479 if (event->x && event->y) {
7480 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7481 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7483 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7486 GtkTextMark *mark = gtk_text_buffer_get_insert(buffer);
7487 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7490 stuff_selected = gtk_text_buffer_get_selection_bounds(
7492 &sel_start, &sel_end);
7494 gtk_text_buffer_place_cursor (buffer, &iter);
7495 /* reselect stuff */
7497 && gtk_text_iter_in_range(&iter, &sel_start, &sel_end)) {
7498 gtk_text_buffer_select_range(buffer,
7499 &sel_start, &sel_end);
7501 return FALSE; /* pass the event so that the right-click goes through */
7504 if (event->button == 2) {
7509 /* get the middle-click position to paste at the correct place */
7510 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7511 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7513 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7516 entry_paste_clipboard(compose, text,
7517 prefs_common.linewrap_pastes,
7518 GDK_SELECTION_PRIMARY, &iter);
7526 static void compose_spell_menu_changed(void *data)
7528 Compose *compose = (Compose *)data;
7530 GtkWidget *menuitem;
7531 GtkWidget *parent_item;
7532 GtkMenu *menu = GTK_MENU(gtk_menu_new());
7535 if (compose->gtkaspell == NULL)
7538 parent_item = gtk_ui_manager_get_widget(compose->ui_manager,
7539 "/Menu/Spelling/Options");
7541 /* setting the submenu removes /Spelling/Options from the factory
7542 * so we need to save it */
7544 if (parent_item == NULL) {
7545 parent_item = compose->aspell_options_menu;
7546 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), NULL);
7548 compose->aspell_options_menu = parent_item;
7550 spell_menu = gtkaspell_make_config_menu(compose->gtkaspell);
7552 spell_menu = g_slist_reverse(spell_menu);
7553 for (items = spell_menu;
7554 items; items = items->next) {
7555 menuitem = GTK_WIDGET(GTK_MENU_ITEM(items->data));
7556 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), GTK_WIDGET(menuitem));
7557 gtk_widget_show(GTK_WIDGET(menuitem));
7559 g_slist_free(spell_menu);
7561 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), GTK_WIDGET(menu));
7562 gtk_widget_show(parent_item);
7565 static void compose_dict_changed(void *data)
7567 Compose *compose = (Compose *) data;
7569 if(!compose->gtkaspell)
7571 if(compose->gtkaspell->recheck_when_changing_dict == FALSE)
7574 gtkaspell_highlight_all(compose->gtkaspell);
7575 claws_spell_entry_recheck_all(CLAWS_SPELL_ENTRY(compose->subject_entry));
7579 static gboolean compose_popup_menu(GtkWidget *widget, gpointer data)
7581 Compose *compose = (Compose *)data;
7582 GdkEventButton event;
7585 event.time = gtk_get_current_event_time();
7589 return text_clicked(compose->text, &event, compose);
7592 static gboolean compose_force_window_origin = TRUE;
7593 static Compose *compose_create(PrefsAccount *account,
7602 GtkWidget *handlebox;
7604 GtkWidget *notebook;
7606 GtkWidget *attach_hbox;
7607 GtkWidget *attach_lab1;
7608 GtkWidget *attach_lab2;
7613 GtkWidget *subject_hbox;
7614 GtkWidget *subject_frame;
7615 GtkWidget *subject_entry;
7619 GtkWidget *edit_vbox;
7620 GtkWidget *ruler_hbox;
7622 GtkWidget *scrolledwin;
7624 GtkTextBuffer *buffer;
7625 GtkClipboard *clipboard;
7627 UndoMain *undostruct;
7629 GtkWidget *popupmenu;
7630 GtkWidget *tmpl_menu;
7631 GtkActionGroup *action_group = NULL;
7634 GtkAspell * gtkaspell = NULL;
7637 static GdkGeometry geometry;
7639 cm_return_val_if_fail(account != NULL, NULL);
7641 gtkut_convert_int_to_gdk_color(prefs_common.default_header_bgcolor,
7642 &default_header_bgcolor);
7643 gtkut_convert_int_to_gdk_color(prefs_common.default_header_color,
7644 &default_header_color);
7646 debug_print("Creating compose window...\n");
7647 compose = g_new0(Compose, 1);
7649 compose->batch = batch;
7650 compose->account = account;
7651 compose->folder = folder;
7653 compose->mutex = cm_mutex_new();
7654 compose->set_cursor_pos = -1;
7656 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose");
7658 gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
7659 gtk_widget_set_size_request(window, prefs_common.compose_width,
7660 prefs_common.compose_height);
7662 if (!geometry.max_width) {
7663 geometry.max_width = gdk_screen_width();
7664 geometry.max_height = gdk_screen_height();
7667 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7668 &geometry, GDK_HINT_MAX_SIZE);
7669 if (!geometry.min_width) {
7670 geometry.min_width = 600;
7671 geometry.min_height = 440;
7673 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7674 &geometry, GDK_HINT_MIN_SIZE);
7676 #ifndef GENERIC_UMPC
7677 if (compose_force_window_origin)
7678 gtk_window_move(GTK_WINDOW(window), prefs_common.compose_x,
7679 prefs_common.compose_y);
7681 g_signal_connect(G_OBJECT(window), "delete_event",
7682 G_CALLBACK(compose_delete_cb), compose);
7683 MANAGE_WINDOW_SIGNALS_CONNECT(window);
7684 gtk_widget_realize(window);
7686 gtkut_widget_set_composer_icon(window);
7688 vbox = gtk_vbox_new(FALSE, 0);
7689 gtk_container_add(GTK_CONTAINER(window), vbox);
7691 compose->ui_manager = gtk_ui_manager_new();
7692 action_group = cm_menu_create_action_group_full(compose->ui_manager,"Menu", compose_entries,
7693 G_N_ELEMENTS(compose_entries), (gpointer)compose);
7694 gtk_action_group_add_toggle_actions(action_group, compose_toggle_entries,
7695 G_N_ELEMENTS(compose_toggle_entries), (gpointer)compose);
7696 gtk_action_group_add_radio_actions(action_group, compose_radio_rm_entries,
7697 G_N_ELEMENTS(compose_radio_rm_entries), COMPOSE_REPLY, G_CALLBACK(compose_reply_change_mode_cb), (gpointer)compose);
7698 gtk_action_group_add_radio_actions(action_group, compose_radio_prio_entries,
7699 G_N_ELEMENTS(compose_radio_prio_entries), PRIORITY_NORMAL, G_CALLBACK(compose_set_priority_cb), (gpointer)compose);
7700 gtk_action_group_add_radio_actions(action_group, compose_radio_enc_entries,
7701 G_N_ELEMENTS(compose_radio_enc_entries), C_AUTO, G_CALLBACK(compose_set_encoding_cb), (gpointer)compose);
7703 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Menu", NULL, GTK_UI_MANAGER_MENUBAR)
7705 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Message", "Message", GTK_UI_MANAGER_MENU)
7706 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Edit", "Edit", GTK_UI_MANAGER_MENU)
7708 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Spelling", "Spelling", GTK_UI_MANAGER_MENU)
7710 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Options", "Options", GTK_UI_MANAGER_MENU)
7711 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Tools", "Tools", GTK_UI_MANAGER_MENU)
7712 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Help", "Help", GTK_UI_MANAGER_MENU)
7715 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Send", "Message/Send", GTK_UI_MANAGER_MENUITEM)
7716 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "SendLater", "Message/SendLater", GTK_UI_MANAGER_MENUITEM)
7717 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator1", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7718 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "AttachFile", "Message/AttachFile", GTK_UI_MANAGER_MENUITEM)
7719 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertFile", "Message/InsertFile", GTK_UI_MANAGER_MENUITEM)
7720 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertSig", "Message/InsertSig", GTK_UI_MANAGER_MENUITEM)
7721 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "ReplaceSig", "Message/ReplaceSig", GTK_UI_MANAGER_MENUITEM)
7722 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator2", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7723 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Save", "Message/Save", GTK_UI_MANAGER_MENUITEM)
7724 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator3", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7725 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Print", "Message/Print", GTK_UI_MANAGER_MENUITEM)
7726 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator4", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7727 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Close", "Message/Close", GTK_UI_MANAGER_MENUITEM)
7730 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Undo", "Edit/Undo", GTK_UI_MANAGER_MENUITEM)
7731 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Redo", "Edit/Redo", GTK_UI_MANAGER_MENUITEM)
7732 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator1", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7734 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Cut", "Edit/Cut", GTK_UI_MANAGER_MENUITEM)
7735 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Copy", "Edit/Copy", GTK_UI_MANAGER_MENUITEM)
7736 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Paste", "Edit/Paste", GTK_UI_MANAGER_MENUITEM)
7738 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SpecialPaste", "Edit/SpecialPaste", GTK_UI_MANAGER_MENU)
7739 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "AsQuotation", "Edit/SpecialPaste/AsQuotation", GTK_UI_MANAGER_MENUITEM)
7740 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Wrapped", "Edit/SpecialPaste/Wrapped", GTK_UI_MANAGER_MENUITEM)
7741 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Unwrapped", "Edit/SpecialPaste/Unwrapped", GTK_UI_MANAGER_MENUITEM)
7743 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SelectAll", "Edit/SelectAll", GTK_UI_MANAGER_MENUITEM)
7745 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Advanced", "Edit/Advanced", GTK_UI_MANAGER_MENU)
7746 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackChar", "Edit/Advanced/BackChar", GTK_UI_MANAGER_MENUITEM)
7747 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwChar", "Edit/Advanced/ForwChar", GTK_UI_MANAGER_MENUITEM)
7748 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackWord", "Edit/Advanced/BackWord", GTK_UI_MANAGER_MENUITEM)
7749 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwWord", "Edit/Advanced/ForwWord", GTK_UI_MANAGER_MENUITEM)
7750 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BegLine", "Edit/Advanced/BegLine", GTK_UI_MANAGER_MENUITEM)
7751 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "EndLine", "Edit/Advanced/EndLine", GTK_UI_MANAGER_MENUITEM)
7752 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "PrevLine", "Edit/Advanced/PrevLine", GTK_UI_MANAGER_MENUITEM)
7753 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "NextLine", "Edit/Advanced/NextLine", GTK_UI_MANAGER_MENUITEM)
7754 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackChar", "Edit/Advanced/DelBackChar", GTK_UI_MANAGER_MENUITEM)
7755 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwChar", "Edit/Advanced/DelForwChar", GTK_UI_MANAGER_MENUITEM)
7756 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackWord", "Edit/Advanced/DelBackWord", GTK_UI_MANAGER_MENUITEM)
7757 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwWord", "Edit/Advanced/DelForwWord", GTK_UI_MANAGER_MENUITEM)
7758 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelLine", "Edit/Advanced/DelLine", GTK_UI_MANAGER_MENUITEM)
7759 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelEndLine", "Edit/Advanced/DelEndLine", GTK_UI_MANAGER_MENUITEM)
7761 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator2", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7763 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Find", "Edit/Find", GTK_UI_MANAGER_MENUITEM)
7764 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapPara", "Edit/WrapPara", GTK_UI_MANAGER_MENUITEM)
7765 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapAllLines", "Edit/WrapAllLines", GTK_UI_MANAGER_MENUITEM)
7766 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoWrap", "Edit/AutoWrap", GTK_UI_MANAGER_MENUITEM)
7767 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoIndent", "Edit/AutoIndent", GTK_UI_MANAGER_MENUITEM)
7769 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator3", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7771 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "ExtEditor", "Edit/ExtEditor", GTK_UI_MANAGER_MENUITEM)
7775 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckAllSel", "Spelling/CheckAllSel", GTK_UI_MANAGER_MENUITEM)
7776 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "HighlightAll", "Spelling/HighlightAll", GTK_UI_MANAGER_MENUITEM)
7777 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckBackwards", "Spelling/CheckBackwards", GTK_UI_MANAGER_MENUITEM)
7778 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "ForwardNext", "Spelling/ForwardNext", GTK_UI_MANAGER_MENUITEM)
7779 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Separator1", "Spelling/---", GTK_UI_MANAGER_SEPARATOR)
7780 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Options", "Spelling/Options", GTK_UI_MANAGER_MENU)
7784 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "ReplyMode", "Options/ReplyMode", GTK_UI_MANAGER_MENU)
7785 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Normal", "Options/ReplyMode/Normal", GTK_UI_MANAGER_MENUITEM)
7786 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "All", "Options/ReplyMode/All", GTK_UI_MANAGER_MENUITEM)
7787 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Sender", "Options/ReplyMode/Sender", GTK_UI_MANAGER_MENUITEM)
7788 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "List", "Options/ReplyMode/List", GTK_UI_MANAGER_MENUITEM)
7790 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator1", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7791 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "PrivacySystem", "Options/PrivacySystem", GTK_UI_MANAGER_MENU)
7792 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/PrivacySystem", "PlaceHolder", "Options/PrivacySystem/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7793 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Sign", "Options/Sign", GTK_UI_MANAGER_MENUITEM)
7794 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encrypt", "Options/Encrypt", GTK_UI_MANAGER_MENUITEM)
7797 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator2", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7798 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Priority", "Options/Priority", GTK_UI_MANAGER_MENU)
7799 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Highest", "Options/Priority/Highest", GTK_UI_MANAGER_MENUITEM)
7800 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "High", "Options/Priority/High", GTK_UI_MANAGER_MENUITEM)
7801 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Normal", "Options/Priority/Normal", GTK_UI_MANAGER_MENUITEM)
7802 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Low", "Options/Priority/Low", GTK_UI_MANAGER_MENUITEM)
7803 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Lowest", "Options/Priority/Lowest", GTK_UI_MANAGER_MENUITEM)
7805 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator3", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7806 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RequestRetRcpt", "Options/RequestRetRcpt", GTK_UI_MANAGER_MENUITEM)
7807 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator4", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7808 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RemoveReferences", "Options/RemoveReferences", GTK_UI_MANAGER_MENUITEM)
7809 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator5", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7811 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encoding", "Options/Encoding", GTK_UI_MANAGER_MENU)
7813 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_AUTO, "Options/Encoding/"CS_AUTO, GTK_UI_MANAGER_MENUITEM)
7814 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator1", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7815 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_US_ASCII, "Options/Encoding/"CS_US_ASCII, GTK_UI_MANAGER_MENUITEM)
7816 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_UTF_8, "Options/Encoding/"CS_UTF_8, GTK_UI_MANAGER_MENUITEM)
7817 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator2", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7819 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Western", "Options/Encoding/Western", GTK_UI_MANAGER_MENU)
7820 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)
7821 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)
7822 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Western", CS_WINDOWS_1252, "Options/Encoding/Western/"CS_WINDOWS_1252, GTK_UI_MANAGER_MENUITEM)
7824 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_2, "Options/Encoding/"CS_ISO_8859_2, GTK_UI_MANAGER_MENUITEM)
7826 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Baltic", "Options/Encoding/Baltic", GTK_UI_MANAGER_MENU)
7827 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)
7828 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)
7830 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_7, "Options/Encoding/"CS_ISO_8859_7, GTK_UI_MANAGER_MENUITEM)
7832 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Hebrew", "Options/Encoding/Hebrew", GTK_UI_MANAGER_MENU)
7833 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)
7834 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Hebrew", CS_WINDOWS_1255, "Options/Encoding/Hebrew/"CS_WINDOWS_1255, GTK_UI_MANAGER_MENUITEM)
7836 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Arabic", "Options/Encoding/Arabic", GTK_UI_MANAGER_MENU)
7837 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)
7838 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Arabic", CS_WINDOWS_1256, "Options/Encoding/Arabic/"CS_WINDOWS_1256, GTK_UI_MANAGER_MENUITEM)
7840 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_9, "Options/Encoding/"CS_ISO_8859_9, GTK_UI_MANAGER_MENUITEM)
7842 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Cyrillic", "Options/Encoding/Cyrillic", GTK_UI_MANAGER_MENU)
7843 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)
7844 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_R, "Options/Encoding/Cyrillic/"CS_KOI8_R, GTK_UI_MANAGER_MENUITEM)
7845 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_MACCYR, "Options/Encoding/Cyrillic/"CS_MACCYR, GTK_UI_MANAGER_MENUITEM)
7846 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_U, "Options/Encoding/Cyrillic/"CS_KOI8_U, GTK_UI_MANAGER_MENUITEM)
7847 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_WINDOWS_1251, "Options/Encoding/Cyrillic/"CS_WINDOWS_1251, GTK_UI_MANAGER_MENUITEM)
7849 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Japanese", "Options/Encoding/Japanese", GTK_UI_MANAGER_MENU)
7850 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)
7851 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)
7852 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_EUC_JP, "Options/Encoding/Japanese/"CS_EUC_JP, GTK_UI_MANAGER_MENUITEM)
7853 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_SHIFT_JIS, "Options/Encoding/Japanese/"CS_SHIFT_JIS, GTK_UI_MANAGER_MENUITEM)
7855 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Chinese", "Options/Encoding/Chinese", GTK_UI_MANAGER_MENU)
7856 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB18030, "Options/Encoding/Chinese/"CS_GB18030, GTK_UI_MANAGER_MENUITEM)
7857 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB2312, "Options/Encoding/Chinese/"CS_GB2312, GTK_UI_MANAGER_MENUITEM)
7858 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GBK, "Options/Encoding/Chinese/"CS_GBK, GTK_UI_MANAGER_MENUITEM)
7859 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_BIG5, "Options/Encoding/Chinese/"CS_BIG5, GTK_UI_MANAGER_MENUITEM)
7860 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_EUC_TW, "Options/Encoding/Chinese/"CS_EUC_TW, GTK_UI_MANAGER_MENUITEM)
7862 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Korean", "Options/Encoding/Korean", GTK_UI_MANAGER_MENU)
7863 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Korean", CS_EUC_KR, "Options/Encoding/Korean/"CS_EUC_KR, GTK_UI_MANAGER_MENUITEM)
7864 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)
7866 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Thai", "Options/Encoding/Thai", GTK_UI_MANAGER_MENU)
7867 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_TIS_620, "Options/Encoding/Thai/"CS_TIS_620, GTK_UI_MANAGER_MENUITEM)
7868 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_WINDOWS_874, "Options/Encoding/Thai/"CS_WINDOWS_874, GTK_UI_MANAGER_MENUITEM)
7872 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "ShowRuler", "Tools/ShowRuler", GTK_UI_MANAGER_MENUITEM)
7873 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "AddressBook", "Tools/AddressBook", GTK_UI_MANAGER_MENUITEM)
7874 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Template", "Tools/Template", GTK_UI_MANAGER_MENU)
7875 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Template", "PlaceHolder", "Tools/Template/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7876 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Actions", "Tools/Actions", GTK_UI_MANAGER_MENU)
7877 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Actions", "PlaceHolder", "Tools/Actions/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7880 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Help", "About", "Help/About", GTK_UI_MANAGER_MENUITEM)
7882 menubar = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu");
7883 gtk_widget_show_all(menubar);
7885 gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(compose->ui_manager));
7886 gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, TRUE, 0);
7888 if (prefs_common.toolbar_detachable) {
7889 handlebox = gtk_handle_box_new();
7891 handlebox = gtk_hbox_new(FALSE, 0);
7893 gtk_box_pack_start(GTK_BOX(vbox), handlebox, FALSE, FALSE, 0);
7895 gtk_widget_realize(handlebox);
7896 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, handlebox,
7899 vbox2 = gtk_vbox_new(FALSE, 2);
7900 gtk_box_pack_start(GTK_BOX(vbox), vbox2, TRUE, TRUE, 0);
7901 gtk_container_set_border_width(GTK_CONTAINER(vbox2), 0);
7904 notebook = gtk_notebook_new();
7905 gtk_widget_show(notebook);
7907 /* header labels and entries */
7908 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7909 compose_create_header(compose),
7910 gtk_label_new_with_mnemonic(_("Hea_der")));
7911 /* attachment list */
7912 attach_hbox = gtk_hbox_new(FALSE, 0);
7913 gtk_widget_show(attach_hbox);
7915 attach_lab1 = gtk_label_new_with_mnemonic(_("_Attachments"));
7916 gtk_widget_show(attach_lab1);
7917 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab1, TRUE, TRUE, 0);
7919 attach_lab2 = gtk_label_new("");
7920 gtk_widget_show(attach_lab2);
7921 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab2, FALSE, FALSE, 0);
7923 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7924 compose_create_attach(compose),
7927 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7928 compose_create_others(compose),
7929 gtk_label_new_with_mnemonic(_("Othe_rs")));
7932 subject_hbox = gtk_hbox_new(FALSE, 0);
7933 gtk_widget_show(subject_hbox);
7935 subject_frame = gtk_frame_new(NULL);
7936 gtk_frame_set_shadow_type(GTK_FRAME(subject_frame), GTK_SHADOW_NONE);
7937 gtk_box_pack_start(GTK_BOX(subject_hbox), subject_frame, TRUE, TRUE, 0);
7938 gtk_widget_show(subject_frame);
7940 subject = gtk_hbox_new(FALSE, HSPACING_NARROW);
7941 gtk_container_set_border_width(GTK_CONTAINER(subject), 0);
7942 gtk_widget_show(subject);
7944 label = gtk_label_new_with_mnemonic(_("S_ubject:"));
7945 gtk_box_pack_start(GTK_BOX(subject), label, FALSE, FALSE, 0);
7946 gtk_widget_show(label);
7949 subject_entry = claws_spell_entry_new();
7951 subject_entry = gtk_entry_new();
7953 gtk_box_pack_start(GTK_BOX(subject), subject_entry, TRUE, TRUE, 0);
7954 g_signal_connect_after(G_OBJECT(subject_entry), "grab_focus",
7955 G_CALLBACK(compose_grab_focus_cb), compose);
7956 gtk_label_set_mnemonic_widget(GTK_LABEL(label), subject_entry);
7957 gtk_widget_show(subject_entry);
7958 compose->subject_entry = subject_entry;
7959 gtk_container_add(GTK_CONTAINER(subject_frame), subject);
7961 edit_vbox = gtk_vbox_new(FALSE, 0);
7963 gtk_box_pack_start(GTK_BOX(edit_vbox), subject_hbox, FALSE, FALSE, 0);
7966 ruler_hbox = gtk_hbox_new(FALSE, 0);
7967 gtk_box_pack_start(GTK_BOX(edit_vbox), ruler_hbox, FALSE, FALSE, 0);
7969 ruler = gtk_shruler_new(GTK_ORIENTATION_HORIZONTAL);
7970 gtk_shruler_set_range(GTK_SHRULER(ruler), 0.0, 100.0, 1.0);
7971 gtk_box_pack_start(GTK_BOX(ruler_hbox), ruler, TRUE, TRUE,
7975 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
7976 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
7977 GTK_POLICY_AUTOMATIC,
7978 GTK_POLICY_AUTOMATIC);
7979 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
7981 gtk_box_pack_start(GTK_BOX(edit_vbox), scrolledwin, TRUE, TRUE, 0);
7983 text = gtk_text_view_new();
7984 if (prefs_common.show_compose_margin) {
7985 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text), 6);
7986 gtk_text_view_set_right_margin(GTK_TEXT_VIEW(text), 6);
7988 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
7989 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
7990 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), TRUE);
7991 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
7992 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
7994 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
7995 g_signal_connect_after(G_OBJECT(text), "size_allocate",
7996 G_CALLBACK(compose_edit_size_alloc),
7998 g_signal_connect(G_OBJECT(buffer), "changed",
7999 G_CALLBACK(compose_changed_cb), compose);
8000 g_signal_connect(G_OBJECT(text), "grab_focus",
8001 G_CALLBACK(compose_grab_focus_cb), compose);
8002 g_signal_connect(G_OBJECT(buffer), "insert_text",
8003 G_CALLBACK(text_inserted), compose);
8004 g_signal_connect(G_OBJECT(text), "button_press_event",
8005 G_CALLBACK(text_clicked), compose);
8006 g_signal_connect(G_OBJECT(text), "popup-menu",
8007 G_CALLBACK(compose_popup_menu), compose);
8008 g_signal_connect(G_OBJECT(subject_entry), "changed",
8009 G_CALLBACK(compose_changed_cb), compose);
8010 g_signal_connect(G_OBJECT(subject_entry), "activate",
8011 G_CALLBACK(compose_subject_entry_activated), compose);
8014 gtk_drag_dest_set(text, GTK_DEST_DEFAULT_ALL, compose_mime_types,
8015 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
8016 GDK_ACTION_COPY | GDK_ACTION_MOVE);
8017 g_signal_connect(G_OBJECT(text), "drag_data_received",
8018 G_CALLBACK(compose_insert_drag_received_cb),
8020 g_signal_connect(G_OBJECT(text), "drag-drop",
8021 G_CALLBACK(compose_drag_drop),
8023 g_signal_connect(G_OBJECT(text), "key-press-event",
8024 G_CALLBACK(completion_set_focus_to_subject),
8026 gtk_widget_show_all(vbox);
8028 /* pane between attach clist and text */
8029 paned = gtk_vpaned_new();
8030 gtk_container_add(GTK_CONTAINER(vbox2), paned);
8031 gtk_paned_pack1(GTK_PANED(paned), notebook, FALSE, FALSE);
8032 gtk_paned_pack2(GTK_PANED(paned), edit_vbox, TRUE, FALSE);
8033 gtk_paned_set_position(GTK_PANED(paned), prefs_common.compose_notebook_height);
8034 g_signal_connect(G_OBJECT(notebook), "size_allocate",
8035 G_CALLBACK(compose_notebook_size_alloc), paned);
8037 gtk_widget_show_all(paned);
8040 if (prefs_common.textfont) {
8041 PangoFontDescription *font_desc;
8043 font_desc = pango_font_description_from_string
8044 (prefs_common.textfont);
8046 gtk_widget_modify_font(text, font_desc);
8047 pango_font_description_free(font_desc);
8051 gtk_action_group_add_actions(action_group, compose_popup_entries,
8052 G_N_ELEMENTS(compose_popup_entries), (gpointer)compose);
8053 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Popup", NULL, GTK_UI_MANAGER_MENUBAR)
8054 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup", "Compose", "Compose", GTK_UI_MANAGER_MENU)
8055 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Add", "Compose/Add", GTK_UI_MANAGER_MENUITEM)
8056 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Remove", "Compose/Remove", GTK_UI_MANAGER_MENUITEM)
8057 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Separator1", "Compose/---", GTK_UI_MANAGER_SEPARATOR)
8058 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Properties", "Compose/Properties", GTK_UI_MANAGER_MENUITEM)
8060 popupmenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(gtk_ui_manager_get_widget(compose->ui_manager, "/Popup/Compose")));
8062 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
8063 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
8064 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
8066 tmpl_menu = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu/Tools/Template");
8068 undostruct = undo_init(text);
8069 undo_set_change_state_func(undostruct, &compose_undo_state_changed,
8072 address_completion_start(window);
8074 compose->window = window;
8075 compose->vbox = vbox;
8076 compose->menubar = menubar;
8077 compose->handlebox = handlebox;
8079 compose->vbox2 = vbox2;
8081 compose->paned = paned;
8083 compose->attach_label = attach_lab2;
8085 compose->notebook = notebook;
8086 compose->edit_vbox = edit_vbox;
8087 compose->ruler_hbox = ruler_hbox;
8088 compose->ruler = ruler;
8089 compose->scrolledwin = scrolledwin;
8090 compose->text = text;
8092 compose->focused_editable = NULL;
8094 compose->popupmenu = popupmenu;
8096 compose->tmpl_menu = tmpl_menu;
8098 compose->mode = mode;
8099 compose->rmode = mode;
8101 compose->targetinfo = NULL;
8102 compose->replyinfo = NULL;
8103 compose->fwdinfo = NULL;
8105 compose->email_hashtable = g_hash_table_new_full(g_str_hash,
8106 g_str_equal, (GDestroyNotify) g_free, NULL);
8108 compose->replyto = NULL;
8110 compose->bcc = NULL;
8111 compose->followup_to = NULL;
8113 compose->ml_post = NULL;
8115 compose->inreplyto = NULL;
8116 compose->references = NULL;
8117 compose->msgid = NULL;
8118 compose->boundary = NULL;
8120 compose->autowrap = prefs_common.autowrap;
8121 compose->autoindent = prefs_common.auto_indent;
8122 compose->use_signing = FALSE;
8123 compose->use_encryption = FALSE;
8124 compose->privacy_system = NULL;
8125 compose->encdata = NULL;
8127 compose->modified = FALSE;
8129 compose->return_receipt = FALSE;
8131 compose->to_list = NULL;
8132 compose->newsgroup_list = NULL;
8134 compose->undostruct = undostruct;
8136 compose->sig_str = NULL;
8138 compose->exteditor_file = NULL;
8139 compose->exteditor_pid = -1;
8140 compose->exteditor_tag = -1;
8141 compose->exteditor_socket = NULL;
8142 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN; /* inhibit auto-drafting while loading */
8144 compose->folder_update_callback_id =
8145 hooks_register_hook(FOLDER_UPDATE_HOOKLIST,
8146 compose_update_folder_hook,
8147 (gpointer) compose);
8150 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
8151 if (mode != COMPOSE_REDIRECT) {
8152 if (prefs_common.enable_aspell && prefs_common.dictionary &&
8153 strcmp(prefs_common.dictionary, "")) {
8154 gtkaspell = gtkaspell_new(prefs_common.dictionary,
8155 prefs_common.alt_dictionary,
8156 conv_get_locale_charset_str(),
8157 prefs_common.misspelled_col,
8158 prefs_common.check_while_typing,
8159 prefs_common.recheck_when_changing_dict,
8160 prefs_common.use_alternate,
8161 prefs_common.use_both_dicts,
8162 GTK_TEXT_VIEW(text),
8163 GTK_WINDOW(compose->window),
8164 compose_dict_changed,
8165 compose_spell_menu_changed,
8168 alertpanel_error(_("Spell checker could not "
8170 gtkaspell_checkers_strerror());
8171 gtkaspell_checkers_reset_error();
8173 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", TRUE);
8177 compose->gtkaspell = gtkaspell;
8178 compose_spell_menu_changed(compose);
8179 claws_spell_entry_set_gtkaspell(CLAWS_SPELL_ENTRY(subject_entry), gtkaspell);
8182 compose_select_account(compose, account, TRUE);
8184 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", prefs_common.autowrap);
8185 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", prefs_common.auto_indent);
8187 if (account->set_autocc && account->auto_cc && mode != COMPOSE_REEDIT)
8188 compose_entry_append(compose, account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8190 if (account->set_autobcc && account->auto_bcc && mode != COMPOSE_REEDIT)
8191 compose_entry_append(compose, account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8193 if (account->set_autoreplyto && account->auto_replyto && mode != COMPOSE_REEDIT)
8194 compose_entry_append(compose, account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8196 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", compose->mode == COMPOSE_REPLY);
8197 if (account->protocol != A_NNTP)
8198 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
8199 prefs_common_translated_header_name("To:"));
8201 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
8202 prefs_common_translated_header_name("Newsgroups:"));
8204 #ifndef USE_ALT_ADDRBOOK
8205 addressbook_set_target_compose(compose);
8207 if (mode != COMPOSE_REDIRECT)
8208 compose_set_template_menu(compose);
8210 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Template", FALSE);
8213 compose_list = g_list_append(compose_list, compose);
8215 if (!prefs_common.show_ruler)
8216 gtk_widget_hide(ruler_hbox);
8218 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Tools/ShowRuler", prefs_common.show_ruler);
8221 compose->priority = PRIORITY_NORMAL;
8222 compose_update_priority_menu_item(compose);
8224 compose_set_out_encoding(compose);
8227 compose_update_actions_menu(compose);
8229 /* Privacy Systems menu */
8230 compose_update_privacy_systems_menu(compose);
8232 activate_privacy_system(compose, account, TRUE);
8233 toolbar_set_style(compose->toolbar->toolbar, compose->handlebox, prefs_common.toolbar_style);
8235 gtk_widget_realize(window);
8237 gtk_widget_show(window);
8243 static GtkWidget *compose_account_option_menu_create(Compose *compose)
8248 GtkWidget *optmenubox;
8249 GtkWidget *fromlabel;
8252 GtkWidget *from_name = NULL;
8254 gint num = 0, def_menu = 0;
8256 accounts = account_get_list();
8257 cm_return_val_if_fail(accounts != NULL, NULL);
8259 optmenubox = gtk_event_box_new();
8260 optmenu = gtkut_sc_combobox_create(optmenubox, FALSE);
8261 menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
8263 hbox = gtk_hbox_new(FALSE, 4);
8264 from_name = gtk_entry_new();
8266 g_signal_connect_after(G_OBJECT(from_name), "grab_focus",
8267 G_CALLBACK(compose_grab_focus_cb), compose);
8268 g_signal_connect_after(G_OBJECT(from_name), "activate",
8269 G_CALLBACK(from_name_activate_cb), optmenu);
8271 for (; accounts != NULL; accounts = accounts->next, num++) {
8272 PrefsAccount *ac = (PrefsAccount *)accounts->data;
8273 gchar *name, *from = NULL;
8275 if (ac == compose->account) def_menu = num;
8277 name = g_markup_printf_escaped("<i>%s</i>",
8280 if (ac == compose->account) {
8281 if (ac->name && *ac->name) {
8283 QUOTE_IF_REQUIRED_NORMAL(buf, ac->name, return NULL);
8284 from = g_strdup_printf("%s <%s>",
8286 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8288 from = g_strdup_printf("%s",
8290 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8292 if (cur_account != compose->account) {
8293 gtk_widget_modify_base(
8294 GTK_WIDGET(from_name),
8295 GTK_STATE_NORMAL, &default_header_bgcolor);
8296 gtk_widget_modify_text(
8297 GTK_WIDGET(from_name),
8298 GTK_STATE_NORMAL, &default_header_color);
8301 COMBOBOX_ADD(menu, name, ac->account_id);
8306 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), def_menu);
8308 g_signal_connect(G_OBJECT(optmenu), "changed",
8309 G_CALLBACK(account_activated),
8311 g_signal_connect(G_OBJECT(from_name), "populate-popup",
8312 G_CALLBACK(compose_entry_popup_extend),
8315 fromlabel = gtk_label_new_with_mnemonic(_("_From:"));
8316 gtk_label_set_mnemonic_widget(GTK_LABEL(fromlabel), from_name);
8318 gtk_box_pack_start(GTK_BOX(hbox), fromlabel, FALSE, FALSE, 4);
8319 gtk_box_pack_start(GTK_BOX(hbox), optmenubox, FALSE, FALSE, 0);
8320 gtk_box_pack_start(GTK_BOX(hbox), from_name, TRUE, TRUE, 0);
8322 /* Putting only the GtkEntry into focus chain of parent hbox causes
8323 * the account selector combobox next to it to be unreachable when
8324 * navigating widgets in GtkTable with up/down arrow keys.
8325 * Note: gtk_widget_set_can_focus() was not enough. */
8327 l = g_list_prepend(l, from_name);
8328 gtk_container_set_focus_chain(GTK_CONTAINER(hbox), l);
8331 CLAWS_SET_TIP(optmenubox,
8332 _("Account to use for this email"));
8333 CLAWS_SET_TIP(from_name,
8334 _("Sender address to be used"));
8336 compose->account_combo = optmenu;
8337 compose->from_name = from_name;
8342 static void compose_set_priority_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8344 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8345 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8346 Compose *compose = (Compose *) data;
8348 compose->priority = value;
8352 static void compose_reply_change_mode(Compose *compose,
8355 gboolean was_modified = compose->modified;
8357 gboolean all = FALSE, ml = FALSE, sender = FALSE, followup = FALSE;
8359 cm_return_if_fail(compose->replyinfo != NULL);
8361 if (action == COMPOSE_REPLY && prefs_common.default_reply_list)
8363 if (action == COMPOSE_REPLY && compose->rmode == COMPOSE_FOLLOWUP_AND_REPLY_TO)
8365 if (action == COMPOSE_REPLY_TO_ALL)
8367 if (action == COMPOSE_REPLY_TO_SENDER)
8369 if (action == COMPOSE_REPLY_TO_LIST)
8372 compose_remove_header_entries(compose);
8373 compose_reply_set_entry(compose, compose->replyinfo, all, ml, sender, followup);
8374 if (compose->account->set_autocc && compose->account->auto_cc)
8375 compose_entry_append(compose, compose->account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8377 if (compose->account->set_autobcc && compose->account->auto_bcc)
8378 compose_entry_append(compose, compose->account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8380 if (compose->account->set_autoreplyto && compose->account->auto_replyto)
8381 compose_entry_append(compose, compose->account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8382 compose_show_first_last_header(compose, TRUE);
8383 compose->modified = was_modified;
8384 compose_set_title(compose);
8387 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8389 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8390 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8391 Compose *compose = (Compose *) data;
8394 compose_reply_change_mode(compose, value);
8397 static void compose_update_priority_menu_item(Compose * compose)
8399 GtkWidget *menuitem = NULL;
8400 switch (compose->priority) {
8401 case PRIORITY_HIGHEST:
8402 menuitem = gtk_ui_manager_get_widget
8403 (compose->ui_manager, "/Menu/Options/Priority/Highest");
8406 menuitem = gtk_ui_manager_get_widget
8407 (compose->ui_manager, "/Menu/Options/Priority/High");
8409 case PRIORITY_NORMAL:
8410 menuitem = gtk_ui_manager_get_widget
8411 (compose->ui_manager, "/Menu/Options/Priority/Normal");
8414 menuitem = gtk_ui_manager_get_widget
8415 (compose->ui_manager, "/Menu/Options/Priority/Low");
8417 case PRIORITY_LOWEST:
8418 menuitem = gtk_ui_manager_get_widget
8419 (compose->ui_manager, "/Menu/Options/Priority/Lowest");
8422 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8425 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data)
8427 Compose *compose = (Compose *) data;
8429 gboolean can_sign = FALSE, can_encrypt = FALSE;
8431 cm_return_if_fail(GTK_IS_CHECK_MENU_ITEM(widget));
8433 if (!gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget)))
8436 systemid = g_object_get_data(G_OBJECT(widget), "privacy_system");
8437 g_free(compose->privacy_system);
8438 compose->privacy_system = NULL;
8439 g_free(compose->encdata);
8440 compose->encdata = NULL;
8441 if (systemid != NULL) {
8442 compose->privacy_system = g_strdup(systemid);
8444 can_sign = privacy_system_can_sign(systemid);
8445 can_encrypt = privacy_system_can_encrypt(systemid);
8448 debug_print("activated privacy system: %s\n", systemid != NULL ? systemid : "None");
8450 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8451 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8454 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn)
8456 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8457 GtkWidget *menuitem = NULL;
8458 GList *children, *amenu;
8459 gboolean can_sign = FALSE, can_encrypt = FALSE;
8460 gboolean found = FALSE;
8462 if (compose->privacy_system != NULL) {
8464 menuitem = gtk_menu_item_get_submenu(GTK_MENU_ITEM(
8465 gtk_ui_manager_get_widget(compose->ui_manager, branch_path)));
8466 cm_return_if_fail(menuitem != NULL);
8468 children = gtk_container_get_children(GTK_CONTAINER(GTK_MENU_SHELL(menuitem)));
8471 while (amenu != NULL) {
8472 systemid = g_object_get_data(G_OBJECT(amenu->data), "privacy_system");
8473 if (systemid != NULL) {
8474 if (strcmp(systemid, compose->privacy_system) == 0 &&
8475 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8476 menuitem = GTK_WIDGET(amenu->data);
8478 can_sign = privacy_system_can_sign(systemid);
8479 can_encrypt = privacy_system_can_encrypt(systemid);
8483 } else if (strlen(compose->privacy_system) == 0 &&
8484 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8485 menuitem = GTK_WIDGET(amenu->data);
8488 can_encrypt = FALSE;
8493 amenu = amenu->next;
8495 g_list_free(children);
8496 if (menuitem != NULL)
8497 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8499 if (warn && !found && strlen(compose->privacy_system)) {
8500 alertpanel_warning(_("The privacy system '%s' cannot be loaded. You "
8501 "will not be able to sign or encrypt this message."),
8502 compose->privacy_system);
8506 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8507 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8510 static void compose_set_out_encoding(Compose *compose)
8512 CharSet out_encoding;
8513 const gchar *branch = NULL;
8514 out_encoding = conv_get_charset_from_str(prefs_common.outgoing_charset);
8516 switch(out_encoding) {
8517 case C_AUTO: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8518 case C_US_ASCII: branch = "Menu/Options/Encoding/" CS_US_ASCII; break;
8519 case C_UTF_8: branch = "Menu/Options/Encoding/" CS_UTF_8; break;
8520 case C_ISO_8859_2: branch = "Menu/Options/Encoding/" CS_ISO_8859_2; break;
8521 case C_ISO_8859_7: branch = "Menu/Options/Encoding/" CS_ISO_8859_7; break;
8522 case C_ISO_8859_9: branch = "Menu/Options/Encoding/" CS_ISO_8859_9; break;
8523 case C_ISO_8859_1: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_1; break;
8524 case C_ISO_8859_15: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_15; break;
8525 case C_WINDOWS_1252: branch = "Menu/Options/Encoding/Western/" CS_WINDOWS_1252; break;
8526 case C_ISO_8859_13: branch = "Menu/Options/Encoding/Baltic/" CS_ISO_8859_13; break;
8527 case C_ISO_8859_4: branch = "Menu/Options/Encoding/Baltic" CS_ISO_8859_4; break;
8528 case C_ISO_8859_8: branch = "Menu/Options/Encoding/Hebrew/" CS_ISO_8859_8; break;
8529 case C_WINDOWS_1255: branch = "Menu/Options/Encoding/Hebrew/" CS_WINDOWS_1255; break;
8530 case C_ISO_8859_6: branch = "Menu/Options/Encoding/Arabic/" CS_ISO_8859_6; break;
8531 case C_WINDOWS_1256: branch = "Menu/Options/Encoding/Arabic/" CS_WINDOWS_1256; break;
8532 case C_ISO_8859_5: branch = "Menu/Options/Encoding/Cyrillic/" CS_ISO_8859_5; break;
8533 case C_KOI8_R: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_R; break;
8534 case C_MACCYR: branch = "Menu/Options/Encoding/Cyrillic/" CS_MACCYR; break;
8535 case C_KOI8_U: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_U; break;
8536 case C_WINDOWS_1251: branch = "Menu/Options/Encoding/Cyrillic/" CS_WINDOWS_1251; break;
8537 case C_ISO_2022_JP: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP; break;
8538 case C_ISO_2022_JP_2: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP_2; break;
8539 case C_EUC_JP: branch = "Menu/Options/Encoding/Japanese/" CS_EUC_JP; break;
8540 case C_SHIFT_JIS: branch = "Menu/Options/Encoding/Japanese/" CS_SHIFT_JIS; break;
8541 case C_GB18030: branch = "Menu/Options/Encoding/Chinese/" CS_GB18030; break;
8542 case C_GB2312: branch = "Menu/Options/Encoding/Chinese/" CS_GB2312; break;
8543 case C_GBK: branch = "Menu/Options/Encoding/Chinese/" CS_GBK; break;
8544 case C_BIG5: branch = "Menu/Options/Encoding/Chinese/" CS_BIG5; break;
8545 case C_EUC_TW: branch = "Menu/Options/Encoding/Chinese/" CS_EUC_TW; break;
8546 case C_EUC_KR: branch = "Menu/Options/Encoding/Korean/" CS_EUC_KR; break;
8547 case C_ISO_2022_KR: branch = "Menu/Options/Encoding/Korean/" CS_ISO_2022_KR; break;
8548 case C_TIS_620: branch = "Menu/Options/Encoding/Thai/" CS_TIS_620; break;
8549 case C_WINDOWS_874: branch = "Menu/Options/Encoding/Thai/" CS_WINDOWS_874; break;
8550 default: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8552 cm_toggle_menu_set_active_full(compose->ui_manager, (gchar *)branch, TRUE);
8555 static void compose_set_template_menu(Compose *compose)
8557 GSList *tmpl_list, *cur;
8561 tmpl_list = template_get_config();
8563 menu = gtk_menu_new();
8565 gtk_menu_set_accel_group (GTK_MENU (menu),
8566 gtk_ui_manager_get_accel_group(compose->ui_manager));
8567 for (cur = tmpl_list; cur != NULL; cur = cur->next) {
8568 Template *tmpl = (Template *)cur->data;
8569 gchar *accel_path = NULL;
8570 item = gtk_menu_item_new_with_label(tmpl->name);
8571 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
8572 g_signal_connect(G_OBJECT(item), "activate",
8573 G_CALLBACK(compose_template_activate_cb),
8575 g_object_set_data(G_OBJECT(item), "template", tmpl);
8576 gtk_widget_show(item);
8577 accel_path = g_strconcat("<ComposeTemplates>" , "/", tmpl->name, NULL);
8578 gtk_menu_item_set_accel_path(GTK_MENU_ITEM(item), accel_path);
8582 gtk_widget_show(menu);
8583 gtk_menu_item_set_submenu(GTK_MENU_ITEM(compose->tmpl_menu), menu);
8586 void compose_update_actions_menu(Compose *compose)
8588 action_update_compose_menu(compose->ui_manager, "/Menu/Tools/Actions", compose);
8591 static void compose_update_privacy_systems_menu(Compose *compose)
8593 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8594 GSList *systems, *cur;
8596 GtkWidget *system_none;
8598 GtkWidget *privacy_menuitem = gtk_ui_manager_get_widget(compose->ui_manager, branch_path);
8599 GtkWidget *privacy_menu = gtk_menu_new();
8601 system_none = gtk_radio_menu_item_new_with_mnemonic(NULL, _("_None"));
8602 g_object_set_data_full(G_OBJECT(system_none), "privacy_system", NULL, NULL);
8604 g_signal_connect(G_OBJECT(system_none), "activate",
8605 G_CALLBACK(compose_set_privacy_system_cb), compose);
8607 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), system_none);
8608 gtk_widget_show(system_none);
8610 systems = privacy_get_system_ids();
8611 for (cur = systems; cur != NULL; cur = g_slist_next(cur)) {
8612 gchar *systemid = cur->data;
8614 group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(system_none));
8615 widget = gtk_radio_menu_item_new_with_label(group,
8616 privacy_system_get_name(systemid));
8617 g_object_set_data_full(G_OBJECT(widget), "privacy_system",
8618 g_strdup(systemid), g_free);
8619 g_signal_connect(G_OBJECT(widget), "activate",
8620 G_CALLBACK(compose_set_privacy_system_cb), compose);
8622 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), widget);
8623 gtk_widget_show(widget);
8626 g_slist_free(systems);
8627 gtk_menu_item_set_submenu(GTK_MENU_ITEM(privacy_menuitem), privacy_menu);
8628 gtk_widget_show_all(privacy_menu);
8629 gtk_widget_show_all(privacy_menuitem);
8632 void compose_reflect_prefs_all(void)
8637 for (cur = compose_list; cur != NULL; cur = cur->next) {
8638 compose = (Compose *)cur->data;
8639 compose_set_template_menu(compose);
8643 void compose_reflect_prefs_pixmap_theme(void)
8648 for (cur = compose_list; cur != NULL; cur = cur->next) {
8649 compose = (Compose *)cur->data;
8650 toolbar_update(TOOLBAR_COMPOSE, compose);
8654 static const gchar *compose_quote_char_from_context(Compose *compose)
8656 const gchar *qmark = NULL;
8658 cm_return_val_if_fail(compose != NULL, NULL);
8660 switch (compose->mode) {
8661 /* use forward-specific quote char */
8662 case COMPOSE_FORWARD:
8663 case COMPOSE_FORWARD_AS_ATTACH:
8664 case COMPOSE_FORWARD_INLINE:
8665 if (compose->folder && compose->folder->prefs &&
8666 compose->folder->prefs->forward_with_format)
8667 qmark = compose->folder->prefs->forward_quotemark;
8668 else if (compose->account->forward_with_format)
8669 qmark = compose->account->forward_quotemark;
8671 qmark = prefs_common.fw_quotemark;
8674 /* use reply-specific quote char in all other modes */
8676 if (compose->folder && compose->folder->prefs &&
8677 compose->folder->prefs->reply_with_format)
8678 qmark = compose->folder->prefs->reply_quotemark;
8679 else if (compose->account->reply_with_format)
8680 qmark = compose->account->reply_quotemark;
8682 qmark = prefs_common.quotemark;
8686 if (qmark == NULL || *qmark == '\0')
8692 static void compose_template_apply(Compose *compose, Template *tmpl,
8696 GtkTextBuffer *buffer;
8700 gchar *parsed_str = NULL;
8701 gint cursor_pos = 0;
8702 const gchar *err_msg = _("The body of the template has an error at line %d.");
8705 /* process the body */
8707 text = GTK_TEXT_VIEW(compose->text);
8708 buffer = gtk_text_view_get_buffer(text);
8711 qmark = compose_quote_char_from_context(compose);
8713 if (compose->replyinfo != NULL) {
8716 gtk_text_buffer_set_text(buffer, "", -1);
8717 mark = gtk_text_buffer_get_insert(buffer);
8718 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8720 parsed_str = compose_quote_fmt(compose, compose->replyinfo,
8721 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8723 } else if (compose->fwdinfo != NULL) {
8726 gtk_text_buffer_set_text(buffer, "", -1);
8727 mark = gtk_text_buffer_get_insert(buffer);
8728 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8730 parsed_str = compose_quote_fmt(compose, compose->fwdinfo,
8731 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8734 MsgInfo* dummyinfo = compose_msginfo_new_from_compose(compose);
8736 GtkTextIter start, end;
8739 gtk_text_buffer_get_start_iter(buffer, &start);
8740 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
8741 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
8743 /* clear the buffer now */
8745 gtk_text_buffer_set_text(buffer, "", -1);
8747 parsed_str = compose_quote_fmt(compose, dummyinfo,
8748 tmpl->value, qmark, tmp, FALSE, FALSE, err_msg);
8749 procmsg_msginfo_free( &dummyinfo );
8755 gtk_text_buffer_set_text(buffer, "", -1);
8756 mark = gtk_text_buffer_get_insert(buffer);
8757 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8760 if (replace && parsed_str && compose->account->auto_sig)
8761 compose_insert_sig(compose, FALSE);
8763 if (replace && parsed_str) {
8764 gtk_text_buffer_get_start_iter(buffer, &iter);
8765 gtk_text_buffer_place_cursor(buffer, &iter);
8769 cursor_pos = quote_fmt_get_cursor_pos();
8770 compose->set_cursor_pos = cursor_pos;
8771 if (cursor_pos == -1)
8773 gtk_text_buffer_get_start_iter(buffer, &iter);
8774 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
8775 gtk_text_buffer_place_cursor(buffer, &iter);
8778 /* process the other fields */
8780 compose_template_apply_fields(compose, tmpl);
8781 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
8782 quote_fmt_reset_vartable();
8783 compose_changed_cb(NULL, compose);
8786 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
8787 gtkaspell_highlight_all(compose->gtkaspell);
8791 static void compose_template_apply_fields_error(const gchar *header)
8796 tr = g_strdup(C_("'%s' stands for a header name",
8797 "Template '%s' format error."));
8798 text = g_strdup_printf(tr, prefs_common_translated_header_name(header));
8799 alertpanel_error(text);
8805 static void compose_template_apply_fields(Compose *compose, Template *tmpl)
8807 MsgInfo* dummyinfo = NULL;
8808 MsgInfo *msginfo = NULL;
8811 if (compose->replyinfo != NULL)
8812 msginfo = compose->replyinfo;
8813 else if (compose->fwdinfo != NULL)
8814 msginfo = compose->fwdinfo;
8816 dummyinfo = compose_msginfo_new_from_compose(compose);
8817 msginfo = dummyinfo;
8820 if (tmpl->from && *tmpl->from != '\0') {
8822 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8823 compose->gtkaspell);
8825 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8827 quote_fmt_scan_string(tmpl->from);
8830 buf = quote_fmt_get_buffer();
8832 compose_template_apply_fields_error("From");
8834 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
8838 if (tmpl->to && *tmpl->to != '\0') {
8840 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8841 compose->gtkaspell);
8843 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8845 quote_fmt_scan_string(tmpl->to);
8848 buf = quote_fmt_get_buffer();
8850 compose_template_apply_fields_error("To");
8852 compose_entry_append(compose, buf, COMPOSE_TO, PREF_TEMPLATE);
8856 if (tmpl->cc && *tmpl->cc != '\0') {
8858 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8859 compose->gtkaspell);
8861 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8863 quote_fmt_scan_string(tmpl->cc);
8866 buf = quote_fmt_get_buffer();
8868 compose_template_apply_fields_error("Cc");
8870 compose_entry_append(compose, buf, COMPOSE_CC, PREF_TEMPLATE);
8874 if (tmpl->bcc && *tmpl->bcc != '\0') {
8876 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8877 compose->gtkaspell);
8879 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8881 quote_fmt_scan_string(tmpl->bcc);
8884 buf = quote_fmt_get_buffer();
8886 compose_template_apply_fields_error("Bcc");
8888 compose_entry_append(compose, buf, COMPOSE_BCC, PREF_TEMPLATE);
8892 if (tmpl->replyto && *tmpl->replyto != '\0') {
8894 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8895 compose->gtkaspell);
8897 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8899 quote_fmt_scan_string(tmpl->replyto);
8902 buf = quote_fmt_get_buffer();
8904 compose_template_apply_fields_error("Reply-To");
8906 compose_entry_append(compose, buf, COMPOSE_REPLYTO, PREF_TEMPLATE);
8910 /* process the subject */
8911 if (tmpl->subject && *tmpl->subject != '\0') {
8913 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8914 compose->gtkaspell);
8916 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8918 quote_fmt_scan_string(tmpl->subject);
8921 buf = quote_fmt_get_buffer();
8923 compose_template_apply_fields_error("Subject");
8925 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
8929 procmsg_msginfo_free( &dummyinfo );
8932 static void compose_destroy(Compose *compose)
8934 GtkAllocation allocation;
8935 GtkTextBuffer *buffer;
8936 GtkClipboard *clipboard;
8938 compose_list = g_list_remove(compose_list, compose);
8940 if (compose->updating) {
8941 debug_print("danger, not destroying anything now\n");
8942 compose->deferred_destroy = TRUE;
8946 /* NOTE: address_completion_end() does nothing with the window
8947 * however this may change. */
8948 address_completion_end(compose->window);
8950 slist_free_strings_full(compose->to_list);
8951 slist_free_strings_full(compose->newsgroup_list);
8952 slist_free_strings_full(compose->header_list);
8954 slist_free_strings_full(extra_headers);
8955 extra_headers = NULL;
8957 compose->header_list = compose->newsgroup_list = compose->to_list = NULL;
8959 g_hash_table_destroy(compose->email_hashtable);
8961 hooks_unregister_hook(FOLDER_UPDATE_HOOKLIST,
8962 compose->folder_update_callback_id);
8964 procmsg_msginfo_free(&(compose->targetinfo));
8965 procmsg_msginfo_free(&(compose->replyinfo));
8966 procmsg_msginfo_free(&(compose->fwdinfo));
8968 g_free(compose->replyto);
8969 g_free(compose->cc);
8970 g_free(compose->bcc);
8971 g_free(compose->newsgroups);
8972 g_free(compose->followup_to);
8974 g_free(compose->ml_post);
8976 g_free(compose->inreplyto);
8977 g_free(compose->references);
8978 g_free(compose->msgid);
8979 g_free(compose->boundary);
8981 g_free(compose->redirect_filename);
8982 if (compose->undostruct)
8983 undo_destroy(compose->undostruct);
8985 g_free(compose->sig_str);
8987 g_free(compose->exteditor_file);
8989 g_free(compose->orig_charset);
8991 g_free(compose->privacy_system);
8992 g_free(compose->encdata);
8994 #ifndef USE_ALT_ADDRBOOK
8995 if (addressbook_get_target_compose() == compose)
8996 addressbook_set_target_compose(NULL);
8999 if (compose->gtkaspell) {
9000 gtkaspell_delete(compose->gtkaspell);
9001 compose->gtkaspell = NULL;
9005 if (!compose->batch) {
9006 gtk_widget_get_allocation(compose->window, &allocation);
9007 prefs_common.compose_width = allocation.width;
9008 prefs_common.compose_height = allocation.height;
9011 if (!gtk_widget_get_parent(compose->paned))
9012 gtk_widget_destroy(compose->paned);
9013 gtk_widget_destroy(compose->popupmenu);
9015 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
9016 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
9017 gtk_text_buffer_remove_selection_clipboard(buffer, clipboard);
9019 gtk_widget_destroy(compose->window);
9020 toolbar_destroy(compose->toolbar);
9021 g_free(compose->toolbar);
9022 cm_mutex_free(compose->mutex);
9026 static void compose_attach_info_free(AttachInfo *ainfo)
9028 g_free(ainfo->file);
9029 g_free(ainfo->content_type);
9030 g_free(ainfo->name);
9031 g_free(ainfo->charset);
9035 static void compose_attach_update_label(Compose *compose)
9040 GtkTreeModel *model;
9045 model = gtk_tree_view_get_model(GTK_TREE_VIEW(compose->attach_clist));
9046 if(!gtk_tree_model_get_iter_first(model, &iter)) {
9047 gtk_label_set_text(GTK_LABEL(compose->attach_label), "");
9051 while(gtk_tree_model_iter_next(model, &iter))
9054 text = g_strdup_printf("(%d)", i);
9055 gtk_label_set_text(GTK_LABEL(compose->attach_label), text);
9059 static void compose_attach_remove_selected(GtkAction *action, gpointer data)
9061 Compose *compose = (Compose *)data;
9062 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
9063 GtkTreeSelection *selection;
9065 GtkTreeModel *model;
9067 selection = gtk_tree_view_get_selection(tree_view);
9068 sel = gtk_tree_selection_get_selected_rows(selection, &model);
9073 for (cur = sel; cur != NULL; cur = cur->next) {
9074 GtkTreePath *path = cur->data;
9075 GtkTreeRowReference *ref = gtk_tree_row_reference_new
9078 gtk_tree_path_free(path);
9081 for (cur = sel; cur != NULL; cur = cur->next) {
9082 GtkTreeRowReference *ref = cur->data;
9083 GtkTreePath *path = gtk_tree_row_reference_get_path(ref);
9086 if (gtk_tree_model_get_iter(model, &iter, path))
9087 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
9089 gtk_tree_path_free(path);
9090 gtk_tree_row_reference_free(ref);
9094 compose_attach_update_label(compose);
9097 static struct _AttachProperty
9100 GtkWidget *mimetype_entry;
9101 GtkWidget *encoding_optmenu;
9102 GtkWidget *path_entry;
9103 GtkWidget *filename_entry;
9105 GtkWidget *cancel_btn;
9108 static void gtk_tree_path_free_(gpointer ptr, gpointer data)
9110 gtk_tree_path_free((GtkTreePath *)ptr);
9113 static void compose_attach_property(GtkAction *action, gpointer data)
9115 Compose *compose = (Compose *)data;
9116 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
9118 GtkComboBox *optmenu;
9119 GtkTreeSelection *selection;
9121 GtkTreeModel *model;
9124 static gboolean cancelled;
9126 /* only if one selected */
9127 selection = gtk_tree_view_get_selection(tree_view);
9128 if (gtk_tree_selection_count_selected_rows(selection) != 1)
9131 sel = gtk_tree_selection_get_selected_rows(selection, &model);
9135 path = (GtkTreePath *) sel->data;
9136 gtk_tree_model_get_iter(model, &iter, path);
9137 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
9140 g_list_foreach(sel, gtk_tree_path_free_, NULL);
9146 if (!attach_prop.window)
9147 compose_attach_property_create(&cancelled);
9148 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), TRUE);
9149 gtk_widget_grab_focus(attach_prop.ok_btn);
9150 gtk_widget_show(attach_prop.window);
9151 gtk_window_set_transient_for(GTK_WINDOW(attach_prop.window),
9152 GTK_WINDOW(compose->window));
9154 optmenu = GTK_COMBO_BOX(attach_prop.encoding_optmenu);
9155 if (ainfo->encoding == ENC_UNKNOWN)
9156 combobox_select_by_data(optmenu, ENC_BASE64);
9158 combobox_select_by_data(optmenu, ainfo->encoding);
9160 gtk_entry_set_text(GTK_ENTRY(attach_prop.mimetype_entry),
9161 ainfo->content_type ? ainfo->content_type : "");
9162 gtk_entry_set_text(GTK_ENTRY(attach_prop.path_entry),
9163 ainfo->file ? ainfo->file : "");
9164 gtk_entry_set_text(GTK_ENTRY(attach_prop.filename_entry),
9165 ainfo->name ? ainfo->name : "");
9168 const gchar *entry_text;
9170 gchar *cnttype = NULL;
9177 gtk_widget_hide(attach_prop.window);
9178 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), FALSE);
9183 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.mimetype_entry));
9184 if (*entry_text != '\0') {
9187 text = g_strstrip(g_strdup(entry_text));
9188 if ((p = strchr(text, '/')) && !strchr(p + 1, '/')) {
9189 cnttype = g_strdup(text);
9192 alertpanel_error(_("Invalid MIME type."));
9198 ainfo->encoding = combobox_get_active_data(optmenu);
9200 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.path_entry));
9201 if (*entry_text != '\0') {
9202 if (is_file_exist(entry_text) &&
9203 (size = get_file_size(entry_text)) > 0)
9204 file = g_strdup(entry_text);
9207 (_("File doesn't exist or is empty."));
9213 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.filename_entry));
9214 if (*entry_text != '\0') {
9215 g_free(ainfo->name);
9216 ainfo->name = g_strdup(entry_text);
9220 g_free(ainfo->content_type);
9221 ainfo->content_type = cnttype;
9224 g_free(ainfo->file);
9228 ainfo->size = (goffset)size;
9230 /* update tree store */
9231 text = to_human_readable(ainfo->size);
9232 gtk_tree_model_get_iter(model, &iter, path);
9233 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
9234 COL_MIMETYPE, ainfo->content_type,
9236 COL_NAME, ainfo->name,
9237 COL_CHARSET, ainfo->charset,
9243 gtk_tree_path_free(path);
9246 #define SET_LABEL_AND_ENTRY(str, entry, top) \
9248 label = gtk_label_new(str); \
9249 gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), \
9250 GTK_FILL, 0, 0, 0); \
9251 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); \
9253 entry = gtk_entry_new(); \
9254 gtk_table_attach(GTK_TABLE(table), entry, 1, 2, top, (top + 1), \
9255 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); \
9258 static void compose_attach_property_create(gboolean *cancelled)
9264 GtkWidget *mimetype_entry;
9267 GtkListStore *optmenu_menu;
9268 GtkWidget *path_entry;
9269 GtkWidget *filename_entry;
9272 GtkWidget *cancel_btn;
9273 GList *mime_type_list, *strlist;
9276 debug_print("Creating attach_property window...\n");
9278 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose_attach_property");
9279 gtk_widget_set_size_request(window, 480, -1);
9280 gtk_container_set_border_width(GTK_CONTAINER(window), 8);
9281 gtk_window_set_title(GTK_WINDOW(window), _("Properties"));
9282 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
9283 g_signal_connect(G_OBJECT(window), "delete_event",
9284 G_CALLBACK(attach_property_delete_event),
9286 g_signal_connect(G_OBJECT(window), "key_press_event",
9287 G_CALLBACK(attach_property_key_pressed),
9290 vbox = gtk_vbox_new(FALSE, 8);
9291 gtk_container_add(GTK_CONTAINER(window), vbox);
9293 table = gtk_table_new(4, 2, FALSE);
9294 gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
9295 gtk_table_set_row_spacings(GTK_TABLE(table), 8);
9296 gtk_table_set_col_spacings(GTK_TABLE(table), 8);
9298 label = gtk_label_new(_("MIME type"));
9299 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, (0 + 1),
9301 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
9302 #if !GTK_CHECK_VERSION(2, 24, 0)
9303 mimetype_entry = gtk_combo_box_entry_new_text();
9305 mimetype_entry = gtk_combo_box_text_new_with_entry();
9307 gtk_table_attach(GTK_TABLE(table), mimetype_entry, 1, 2, 0, (0 + 1),
9308 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
9310 /* stuff with list */
9311 mime_type_list = procmime_get_mime_type_list();
9313 for (; mime_type_list != NULL; mime_type_list = mime_type_list->next) {
9314 MimeType *type = (MimeType *) mime_type_list->data;
9317 tmp = g_strdup_printf("%s/%s", type->type, type->sub_type);
9319 if (g_list_find_custom(strlist, tmp, (GCompareFunc)strcmp2))
9322 strlist = g_list_insert_sorted(strlist, (gpointer)tmp,
9323 (GCompareFunc)strcmp2);
9326 for (mime_type_list = strlist; mime_type_list != NULL;
9327 mime_type_list = mime_type_list->next) {
9328 #if !GTK_CHECK_VERSION(2, 24, 0)
9329 gtk_combo_box_append_text(GTK_COMBO_BOX(mimetype_entry), mime_type_list->data);
9331 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(mimetype_entry), mime_type_list->data);
9333 g_free(mime_type_list->data);
9335 g_list_free(strlist);
9336 gtk_combo_box_set_active(GTK_COMBO_BOX(mimetype_entry), 0);
9337 mimetype_entry = gtk_bin_get_child(GTK_BIN((mimetype_entry)));
9339 label = gtk_label_new(_("Encoding"));
9340 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2,
9342 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
9344 hbox = gtk_hbox_new(FALSE, 0);
9345 gtk_table_attach(GTK_TABLE(table), hbox, 1, 2, 1, 2,
9346 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
9348 optmenu = gtkut_sc_combobox_create(NULL, TRUE);
9349 optmenu_menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
9351 COMBOBOX_ADD(optmenu_menu, "7bit", ENC_7BIT);
9352 COMBOBOX_ADD(optmenu_menu, "8bit", ENC_8BIT);
9353 COMBOBOX_ADD(optmenu_menu, "quoted-printable", ENC_QUOTED_PRINTABLE);
9354 COMBOBOX_ADD(optmenu_menu, "base64", ENC_BASE64);
9355 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), 0);
9357 gtk_box_pack_start(GTK_BOX(hbox), optmenu, TRUE, TRUE, 0);
9359 SET_LABEL_AND_ENTRY(_("Path"), path_entry, 2);
9360 SET_LABEL_AND_ENTRY(_("File name"), filename_entry, 3);
9362 gtkut_stock_button_set_create(&hbbox, &cancel_btn, GTK_STOCK_CANCEL,
9363 &ok_btn, GTK_STOCK_OK,
9365 gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
9366 gtk_widget_grab_default(ok_btn);
9368 g_signal_connect(G_OBJECT(ok_btn), "clicked",
9369 G_CALLBACK(attach_property_ok),
9371 g_signal_connect(G_OBJECT(cancel_btn), "clicked",
9372 G_CALLBACK(attach_property_cancel),
9375 gtk_widget_show_all(vbox);
9377 attach_prop.window = window;
9378 attach_prop.mimetype_entry = mimetype_entry;
9379 attach_prop.encoding_optmenu = optmenu;
9380 attach_prop.path_entry = path_entry;
9381 attach_prop.filename_entry = filename_entry;
9382 attach_prop.ok_btn = ok_btn;
9383 attach_prop.cancel_btn = cancel_btn;
9386 #undef SET_LABEL_AND_ENTRY
9388 static void attach_property_ok(GtkWidget *widget, gboolean *cancelled)
9394 static void attach_property_cancel(GtkWidget *widget, gboolean *cancelled)
9400 static gint attach_property_delete_event(GtkWidget *widget, GdkEventAny *event,
9401 gboolean *cancelled)
9409 static gboolean attach_property_key_pressed(GtkWidget *widget,
9411 gboolean *cancelled)
9413 if (event && event->keyval == GDK_KEY_Escape) {
9417 if (event && event->keyval == GDK_KEY_Return) {
9425 static void compose_exec_ext_editor(Compose *compose)
9430 GdkNativeWindow socket_wid = 0;
9434 tmp = g_strdup_printf("%s%ctmpmsg.%p", get_tmp_dir(),
9435 G_DIR_SEPARATOR, compose);
9437 if (compose_get_ext_editor_uses_socket()) {
9438 /* Only allow one socket */
9439 if (compose->exteditor_socket != NULL) {
9440 if (gtk_widget_is_focus(compose->exteditor_socket)) {
9441 /* Move the focus off of the socket */
9442 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9447 /* Create the receiving GtkSocket */
9448 socket = gtk_socket_new ();
9449 g_signal_connect (GTK_OBJECT(socket), "plug-removed",
9450 G_CALLBACK(compose_ext_editor_plug_removed_cb),
9452 gtk_box_pack_start(GTK_BOX(compose->edit_vbox), socket, TRUE, TRUE, 0);
9453 gtk_widget_set_size_request(socket, prefs_common.compose_width, -1);
9454 /* Realize the socket so that we can use its ID */
9455 gtk_widget_realize(socket);
9456 socket_wid = gtk_socket_get_id(GTK_SOCKET (socket));
9457 compose->exteditor_socket = socket;
9460 if (pipe(pipe_fds) < 0) {
9466 if ((pid = fork()) < 0) {
9473 /* close the write side of the pipe */
9476 compose->exteditor_file = g_strdup(tmp);
9477 compose->exteditor_pid = pid;
9479 compose_set_ext_editor_sensitive(compose, FALSE);
9482 compose->exteditor_ch = g_io_channel_unix_new(pipe_fds[0]);
9484 compose->exteditor_ch = g_io_channel_win32_new_fd(pipe_fds[0]);
9486 compose->exteditor_tag = g_io_add_watch(compose->exteditor_ch,
9490 } else { /* process-monitoring process */
9496 /* close the read side of the pipe */
9499 if (compose_write_body_to_file(compose, tmp) < 0) {
9500 fd_write_all(pipe_fds[1], "2\n", 2);
9504 pid_ed = compose_exec_ext_editor_real(tmp, socket_wid);
9506 fd_write_all(pipe_fds[1], "1\n", 2);
9510 /* wait until editor is terminated */
9511 waitpid(pid_ed, NULL, 0);
9513 fd_write_all(pipe_fds[1], "0\n", 2);
9520 #endif /* G_OS_UNIX */
9523 static gboolean compose_can_autosave(Compose *compose)
9525 if (compose->privacy_system && compose->use_encryption)
9526 return prefs_common.autosave && prefs_common.autosave_encrypted;
9528 return prefs_common.autosave;
9532 static gboolean compose_get_ext_editor_cmd_valid()
9534 gboolean has_s = FALSE;
9535 gboolean has_w = FALSE;
9536 const gchar *p = prefs_common_get_ext_editor_cmd();
9539 while ((p = strchr(p, '%'))) {
9545 } else if (*p == 'w') {
9556 static gint compose_exec_ext_editor_real(const gchar *file, GdkNativeWindow socket_wid)
9563 cm_return_val_if_fail(file != NULL, -1);
9565 if ((pid = fork()) < 0) {
9570 if (pid != 0) return pid;
9572 /* grandchild process */
9574 if (setpgid(0, getppid()))
9577 if (compose_get_ext_editor_cmd_valid()) {
9578 if (compose_get_ext_editor_uses_socket()) {
9579 p = g_strdup(prefs_common_get_ext_editor_cmd());
9580 s = strstr(p, "%w");
9582 if (strstr(p, "%s") < s)
9583 buf = g_strdup_printf(p, file, socket_wid);
9585 buf = g_strdup_printf(p, socket_wid, file);
9588 buf = g_strdup_printf(prefs_common_get_ext_editor_cmd(), file);
9591 if (prefs_common_get_ext_editor_cmd())
9592 g_warning("External editor command-line is invalid: '%s'",
9593 prefs_common_get_ext_editor_cmd());
9594 buf = g_strdup_printf(DEFAULT_EDITOR_CMD, file);
9597 cmdline = strsplit_with_quote(buf, " ", 0);
9599 execvp(cmdline[0], cmdline);
9602 g_strfreev(cmdline);
9607 static gboolean compose_ext_editor_kill(Compose *compose)
9609 pid_t pgid = compose->exteditor_pid * -1;
9612 ret = kill(pgid, 0);
9614 if (ret == 0 || (ret == -1 && EPERM == errno)) {
9618 msg = g_strdup_printf
9619 (_("The external editor is still working.\n"
9620 "Force terminating the process?\n"
9621 "process group id: %d"), -pgid);
9622 val = alertpanel_full(_("Notice"), msg, GTK_STOCK_NO, GTK_STOCK_YES,
9623 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
9627 if (val == G_ALERTALTERNATE) {
9628 g_source_remove(compose->exteditor_tag);
9629 g_io_channel_shutdown(compose->exteditor_ch,
9631 g_io_channel_unref(compose->exteditor_ch);
9633 if (kill(pgid, SIGTERM) < 0) perror("kill");
9634 waitpid(compose->exteditor_pid, NULL, 0);
9636 g_warning("Terminated process group id: %d. "
9637 "Temporary file: %s", -pgid, compose->exteditor_file);
9639 compose_set_ext_editor_sensitive(compose, TRUE);
9641 g_free(compose->exteditor_file);
9642 compose->exteditor_file = NULL;
9643 compose->exteditor_pid = -1;
9644 compose->exteditor_ch = NULL;
9645 compose->exteditor_tag = -1;
9653 static gboolean compose_input_cb(GIOChannel *source, GIOCondition condition,
9657 Compose *compose = (Compose *)data;
9660 debug_print("Compose: input from monitoring process\n");
9662 if (g_io_channel_read_chars(source, buf, sizeof(buf), &bytes_read, NULL) != G_IO_STATUS_NORMAL) {
9667 g_io_channel_shutdown(source, FALSE, NULL);
9668 g_io_channel_unref(source);
9670 waitpid(compose->exteditor_pid, NULL, 0);
9672 if (buf[0] == '0') { /* success */
9673 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9674 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
9675 GtkTextIter start, end;
9678 gtk_text_buffer_set_text(buffer, "", -1);
9679 compose_insert_file(compose, compose->exteditor_file);
9680 compose_changed_cb(NULL, compose);
9682 /* Check if we should save the draft or not */
9683 if (compose_can_autosave(compose))
9684 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
9686 if (claws_unlink(compose->exteditor_file) < 0)
9687 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9689 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
9690 gtk_text_buffer_get_start_iter(buffer, &start);
9691 gtk_text_buffer_get_end_iter(buffer, &end);
9692 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
9693 if (chars && strlen(chars) > 0)
9694 compose->modified = TRUE;
9696 } else if (buf[0] == '1') { /* failed */
9697 g_warning("Couldn't exec external editor");
9698 if (claws_unlink(compose->exteditor_file) < 0)
9699 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9700 } else if (buf[0] == '2') {
9701 g_warning("Couldn't write to file");
9702 } else if (buf[0] == '3') {
9703 g_warning("Pipe read failed");
9706 compose_set_ext_editor_sensitive(compose, TRUE);
9708 g_free(compose->exteditor_file);
9709 compose->exteditor_file = NULL;
9710 compose->exteditor_pid = -1;
9711 compose->exteditor_ch = NULL;
9712 compose->exteditor_tag = -1;
9713 if (compose->exteditor_socket) {
9714 gtk_widget_destroy(compose->exteditor_socket);
9715 compose->exteditor_socket = NULL;
9722 static char *ext_editor_menu_entries[] = {
9723 "Menu/Message/Send",
9724 "Menu/Message/SendLater",
9725 "Menu/Message/InsertFile",
9726 "Menu/Message/InsertSig",
9727 "Menu/Message/ReplaceSig",
9728 "Menu/Message/Save",
9729 "Menu/Message/Print",
9734 "Menu/Tools/ShowRuler",
9735 "Menu/Tools/Actions",
9740 static void compose_set_ext_editor_sensitive(Compose *compose,
9745 for (i = 0; ext_editor_menu_entries[i]; ++i) {
9746 cm_menu_set_sensitive_full(compose->ui_manager,
9747 ext_editor_menu_entries[i], sensitive);
9750 if (compose_get_ext_editor_uses_socket()) {
9752 if (compose->exteditor_socket)
9753 gtk_widget_hide(compose->exteditor_socket);
9754 gtk_widget_show(compose->scrolledwin);
9755 if (prefs_common.show_ruler)
9756 gtk_widget_show(compose->ruler_hbox);
9757 /* Fix the focus, as it doesn't go anywhere when the
9758 * socket is hidden or destroyed */
9759 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9761 g_assert (compose->exteditor_socket != NULL);
9762 /* Fix the focus, as it doesn't go anywhere when the
9763 * edit box is hidden */
9764 if (gtk_widget_is_focus(compose->text))
9765 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9766 gtk_widget_hide(compose->scrolledwin);
9767 gtk_widget_hide(compose->ruler_hbox);
9768 gtk_widget_show(compose->exteditor_socket);
9771 gtk_widget_set_sensitive(compose->text, sensitive);
9773 if (compose->toolbar->send_btn)
9774 gtk_widget_set_sensitive(compose->toolbar->send_btn, sensitive);
9775 if (compose->toolbar->sendl_btn)
9776 gtk_widget_set_sensitive(compose->toolbar->sendl_btn, sensitive);
9777 if (compose->toolbar->draft_btn)
9778 gtk_widget_set_sensitive(compose->toolbar->draft_btn, sensitive);
9779 if (compose->toolbar->insert_btn)
9780 gtk_widget_set_sensitive(compose->toolbar->insert_btn, sensitive);
9781 if (compose->toolbar->sig_btn)
9782 gtk_widget_set_sensitive(compose->toolbar->sig_btn, sensitive);
9783 if (compose->toolbar->exteditor_btn)
9784 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, sensitive);
9785 if (compose->toolbar->linewrap_current_btn)
9786 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, sensitive);
9787 if (compose->toolbar->linewrap_all_btn)
9788 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, sensitive);
9791 static gboolean compose_get_ext_editor_uses_socket()
9793 return (prefs_common_get_ext_editor_cmd() &&
9794 strstr(prefs_common_get_ext_editor_cmd(), "%w"));
9797 static gboolean compose_ext_editor_plug_removed_cb(GtkSocket *socket, Compose *compose)
9799 compose->exteditor_socket = NULL;
9800 /* returning FALSE allows destruction of the socket */
9803 #endif /* G_OS_UNIX */
9806 * compose_undo_state_changed:
9808 * Change the sensivity of the menuentries undo and redo
9810 static void compose_undo_state_changed(UndoMain *undostruct, gint undo_state,
9811 gint redo_state, gpointer data)
9813 Compose *compose = (Compose *)data;
9815 switch (undo_state) {
9816 case UNDO_STATE_TRUE:
9817 if (!undostruct->undo_state) {
9818 undostruct->undo_state = TRUE;
9819 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", TRUE);
9822 case UNDO_STATE_FALSE:
9823 if (undostruct->undo_state) {
9824 undostruct->undo_state = FALSE;
9825 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
9828 case UNDO_STATE_UNCHANGED:
9830 case UNDO_STATE_REFRESH:
9831 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", undostruct->undo_state);
9834 g_warning("Undo state not recognized");
9838 switch (redo_state) {
9839 case UNDO_STATE_TRUE:
9840 if (!undostruct->redo_state) {
9841 undostruct->redo_state = TRUE;
9842 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", TRUE);
9845 case UNDO_STATE_FALSE:
9846 if (undostruct->redo_state) {
9847 undostruct->redo_state = FALSE;
9848 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
9851 case UNDO_STATE_UNCHANGED:
9853 case UNDO_STATE_REFRESH:
9854 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", undostruct->redo_state);
9857 g_warning("Redo state not recognized");
9862 /* callback functions */
9864 static void compose_notebook_size_alloc(GtkNotebook *notebook,
9865 GtkAllocation *allocation,
9868 prefs_common.compose_notebook_height = gtk_paned_get_position(paned);
9871 /* compose_edit_size_alloc() - called when resized. don't know whether Gtk
9872 * includes "non-client" (windows-izm) in calculation, so this calculation
9873 * may not be accurate.
9875 static gboolean compose_edit_size_alloc(GtkEditable *widget,
9876 GtkAllocation *allocation,
9877 GtkSHRuler *shruler)
9879 if (prefs_common.show_ruler) {
9880 gint char_width = 0, char_height = 0;
9881 gint line_width_in_chars;
9883 gtkut_get_font_size(GTK_WIDGET(widget),
9884 &char_width, &char_height);
9885 line_width_in_chars =
9886 (allocation->width - allocation->x) / char_width;
9888 /* got the maximum */
9889 gtk_shruler_set_range(GTK_SHRULER(shruler),
9890 0.0, line_width_in_chars, 0);
9899 ComposePrefType type;
9900 gboolean entry_marked;
9903 static void account_activated(GtkComboBox *optmenu, gpointer data)
9905 Compose *compose = (Compose *)data;
9908 gchar *folderidentifier;
9909 gint account_id = 0;
9912 GSList *list, *saved_list = NULL;
9913 HeaderEntryState *state;
9915 /* Get ID of active account in the combo box */
9916 menu = gtk_combo_box_get_model(optmenu);
9917 cm_return_if_fail(gtk_combo_box_get_active_iter(optmenu, &iter));
9918 gtk_tree_model_get(menu, &iter, 1, &account_id, -1);
9920 ac = account_find_from_id(account_id);
9921 cm_return_if_fail(ac != NULL);
9923 if (ac != compose->account) {
9924 compose_select_account(compose, ac, FALSE);
9926 for (list = compose->header_list; list; list = list->next) {
9927 ComposeHeaderEntry *hentry=(ComposeHeaderEntry *)list->data;
9929 if (hentry->type == PREF_ACCOUNT || !list->next) {
9930 compose_destroy_headerentry(compose, hentry);
9933 state = g_malloc0(sizeof(HeaderEntryState));
9934 state->header = gtk_editable_get_chars(GTK_EDITABLE(
9935 gtk_bin_get_child(GTK_BIN(hentry->combo))), 0, -1);
9936 state->entry = gtk_editable_get_chars(
9937 GTK_EDITABLE(hentry->entry), 0, -1);
9938 state->type = hentry->type;
9940 saved_list = g_slist_append(saved_list, state);
9941 compose_destroy_headerentry(compose, hentry);
9944 compose->header_last = NULL;
9945 g_slist_free(compose->header_list);
9946 compose->header_list = NULL;
9947 compose->header_nextrow = 1;
9948 compose_create_header_entry(compose);
9950 if (ac->set_autocc && ac->auto_cc)
9951 compose_entry_append(compose, ac->auto_cc,
9952 COMPOSE_CC, PREF_ACCOUNT);
9953 if (ac->set_autobcc && ac->auto_bcc)
9954 compose_entry_append(compose, ac->auto_bcc,
9955 COMPOSE_BCC, PREF_ACCOUNT);
9956 if (ac->set_autoreplyto && ac->auto_replyto)
9957 compose_entry_append(compose, ac->auto_replyto,
9958 COMPOSE_REPLYTO, PREF_ACCOUNT);
9960 for (list = saved_list; list; list = list->next) {
9961 state = (HeaderEntryState *) list->data;
9963 compose_add_header_entry(compose, state->header,
9964 state->entry, state->type);
9966 g_free(state->header);
9967 g_free(state->entry);
9970 g_slist_free(saved_list);
9972 combobox_select_by_data(GTK_COMBO_BOX(compose->header_last->combo),
9973 (ac->protocol == A_NNTP) ?
9974 COMPOSE_NEWSGROUPS : COMPOSE_TO);
9977 /* Set message save folder */
9978 if (account_get_special_folder(compose->account, F_OUTBOX)) {
9979 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
9981 g_signal_connect(G_OBJECT(compose->savemsg_checkbtn), "toggled",
9982 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
9984 compose_set_save_to(compose, NULL);
9985 if (account_get_special_folder(compose->account, F_OUTBOX)) {
9986 folderidentifier = folder_item_get_identifier(account_get_special_folder
9987 (compose->account, F_OUTBOX));
9988 compose_set_save_to(compose, folderidentifier);
9989 g_free(folderidentifier);
9993 static void attach_selected(GtkTreeView *tree_view, GtkTreePath *tree_path,
9994 GtkTreeViewColumn *column, Compose *compose)
9996 compose_attach_property(NULL, compose);
9999 static gboolean attach_button_pressed(GtkWidget *widget, GdkEventButton *event,
10002 Compose *compose = (Compose *)data;
10003 GtkTreeSelection *attach_selection;
10004 gint attach_nr_selected;
10007 if (!event) return FALSE;
10009 if (event->button == 3) {
10010 attach_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
10011 attach_nr_selected = gtk_tree_selection_count_selected_rows(attach_selection);
10013 /* If no rows, or just one row is selected, right-click should
10014 * open menu relevant to the row being right-clicked on. We
10015 * achieve that by selecting the clicked row first. If more
10016 * than one row is selected, we shouldn't modify the selection,
10017 * as user may want to remove selected rows (attachments). */
10018 if (attach_nr_selected < 2) {
10019 gtk_tree_selection_unselect_all(attach_selection);
10020 attach_nr_selected = 0;
10021 gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget),
10022 event->x, event->y, &path, NULL, NULL, NULL);
10023 if (path != NULL) {
10024 gtk_tree_selection_select_path(attach_selection, path);
10025 gtk_tree_path_free(path);
10026 attach_nr_selected++;
10030 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", (attach_nr_selected > 0));
10031 /* Properties menu item makes no sense with more than one row
10032 * selected, the properties dialog can only edit one attachment. */
10033 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", (attach_nr_selected == 1));
10035 gtk_menu_popup(GTK_MENU(compose->popupmenu), NULL, NULL,
10036 NULL, NULL, event->button, event->time);
10043 static gboolean attach_key_pressed(GtkWidget *widget, GdkEventKey *event,
10046 Compose *compose = (Compose *)data;
10048 if (!event) return FALSE;
10050 switch (event->keyval) {
10051 case GDK_KEY_Delete:
10052 compose_attach_remove_selected(NULL, compose);
10058 static void compose_allow_user_actions (Compose *compose, gboolean allow)
10060 toolbar_comp_set_sensitive(compose, allow);
10061 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message", allow);
10062 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", allow);
10064 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", allow);
10066 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", allow);
10067 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools", allow);
10068 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Help", allow);
10070 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), allow);
10074 static void compose_send_cb(GtkAction *action, gpointer data)
10076 Compose *compose = (Compose *)data;
10079 if (compose->exteditor_tag != -1) {
10080 debug_print("ignoring send: external editor still open\n");
10084 if (prefs_common.work_offline &&
10085 !inc_offline_should_override(TRUE,
10086 _("Claws Mail needs network access in order "
10087 "to send this email.")))
10090 if (compose->draft_timeout_tag >= 0) { /* CLAWS: disable draft timeout */
10091 g_source_remove(compose->draft_timeout_tag);
10092 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
10095 compose_send(compose);
10098 static void compose_send_later_cb(GtkAction *action, gpointer data)
10100 Compose *compose = (Compose *)data;
10104 compose_allow_user_actions(compose, FALSE);
10105 val = compose_queue_sub(compose, NULL, NULL, NULL, TRUE, TRUE);
10106 compose_allow_user_actions(compose, TRUE);
10110 compose_close(compose);
10111 } else if (val == -1) {
10112 alertpanel_error(_("Could not queue message."));
10113 } else if (val == -2) {
10114 alertpanel_error(_("Could not queue message:\n\n%s."), g_strerror(errno));
10115 } else if (val == -3) {
10116 if (privacy_peek_error())
10117 alertpanel_error(_("Could not queue message for sending:\n\n"
10118 "Signature failed: %s"), privacy_get_error());
10119 } else if (val == -4) {
10120 alertpanel_error(_("Could not queue message for sending:\n\n"
10121 "Charset conversion failed."));
10122 } else if (val == -5) {
10123 alertpanel_error(_("Could not queue message for sending:\n\n"
10124 "Couldn't get recipient encryption key."));
10125 } else if (val == -6) {
10128 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
10131 #define DRAFTED_AT_EXIT "drafted_at_exit"
10132 static void compose_register_draft(MsgInfo *info)
10134 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10135 DRAFTED_AT_EXIT, NULL);
10136 FILE *fp = g_fopen(filepath, "ab");
10139 fprintf(fp, "%s\t%d\n", folder_item_get_identifier(info->folder),
10147 gboolean compose_draft (gpointer data, guint action)
10149 Compose *compose = (Compose *)data;
10154 MsgFlags flag = {0, 0};
10155 static gboolean lock = FALSE;
10156 MsgInfo *newmsginfo;
10158 gboolean target_locked = FALSE;
10159 gboolean err = FALSE;
10161 if (lock) return FALSE;
10163 if (compose->sending)
10166 draft = account_get_special_folder(compose->account, F_DRAFT);
10167 cm_return_val_if_fail(draft != NULL, FALSE);
10169 if (!g_mutex_trylock(compose->mutex)) {
10170 /* we don't want to lock the mutex once it's available,
10171 * because as the only other part of compose.c locking
10172 * it is compose_close - which means once unlocked,
10173 * the compose struct will be freed */
10174 debug_print("couldn't lock mutex, probably sending\n");
10180 tmp = g_strdup_printf("%s%cdraft.%p", get_tmp_dir(),
10181 G_DIR_SEPARATOR, compose);
10182 if ((fp = g_fopen(tmp, "wb")) == NULL) {
10183 FILE_OP_ERROR(tmp, "fopen");
10187 /* chmod for security */
10188 if (change_file_mode_rw(fp, tmp) < 0) {
10189 FILE_OP_ERROR(tmp, "chmod");
10190 g_warning("can't change file mode");
10193 /* Save draft infos */
10194 err |= (fprintf(fp, "X-Claws-Account-Id:%d\n", compose->account->account_id) < 0);
10195 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
10197 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
10198 gchar *savefolderid;
10200 savefolderid = compose_get_save_to(compose);
10201 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
10202 g_free(savefolderid);
10204 if (compose->return_receipt) {
10205 err |= (fprintf(fp, "RRCPT:1\n") < 0);
10207 if (compose->privacy_system) {
10208 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
10209 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
10210 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
10213 /* Message-ID of message replying to */
10214 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
10215 gchar *folderid = NULL;
10217 if (compose->replyinfo->folder)
10218 folderid = folder_item_get_identifier(compose->replyinfo->folder);
10219 if (folderid == NULL)
10220 folderid = g_strdup("NULL");
10222 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
10225 /* Message-ID of message forwarding to */
10226 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
10227 gchar *folderid = NULL;
10229 if (compose->fwdinfo->folder)
10230 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
10231 if (folderid == NULL)
10232 folderid = g_strdup("NULL");
10234 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
10238 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
10239 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
10241 sheaders = compose_get_manual_headers_info(compose);
10242 err |= (fprintf(fp, "X-Claws-Manual-Headers:%s\n", sheaders) < 0);
10245 /* end of headers */
10246 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
10253 if (compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_STORE, action != COMPOSE_AUTO_SAVE) < 0) {
10257 if (fclose(fp) == EOF) {
10261 flag.perm_flags = MSG_NEW|MSG_UNREAD;
10262 if (compose->targetinfo) {
10263 target_locked = MSG_IS_LOCKED(compose->targetinfo->flags);
10265 flag.perm_flags |= MSG_LOCKED;
10267 flag.tmp_flags = MSG_DRAFT;
10269 folder_item_scan(draft);
10270 if ((msgnum = folder_item_add_msg(draft, tmp, &flag, TRUE)) < 0) {
10271 MsgInfo *tmpinfo = NULL;
10272 debug_print("didn't get msgnum after adding draft [%s]\n", compose->msgid?compose->msgid:"no msgid");
10273 if (compose->msgid) {
10274 tmpinfo = folder_item_get_msginfo_by_msgid(draft, compose->msgid);
10277 msgnum = tmpinfo->msgnum;
10278 procmsg_msginfo_free(&tmpinfo);
10279 debug_print("got draft msgnum %d from scanning\n", msgnum);
10281 debug_print("didn't get draft msgnum after scanning\n");
10284 debug_print("got draft msgnum %d from adding\n", msgnum);
10290 if (action != COMPOSE_AUTO_SAVE) {
10291 if (action != COMPOSE_DRAFT_FOR_EXIT)
10292 alertpanel_error(_("Could not save draft."));
10295 gtkut_window_popup(compose->window);
10296 val = alertpanel_full(_("Could not save draft"),
10297 _("Could not save draft.\n"
10298 "Do you want to cancel exit or discard this email?"),
10299 _("_Cancel exit"), _("_Discard email"), NULL,
10300 FALSE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
10301 if (val == G_ALERTALTERNATE) {
10303 g_mutex_unlock(compose->mutex); /* must be done before closing */
10304 compose_close(compose);
10308 g_mutex_unlock(compose->mutex); /* must be done before closing */
10317 if (compose->mode == COMPOSE_REEDIT) {
10318 compose_remove_reedit_target(compose, TRUE);
10321 newmsginfo = folder_item_get_msginfo(draft, msgnum);
10324 procmsg_msginfo_unset_flags(newmsginfo, ~0, ~0);
10326 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD|MSG_LOCKED, MSG_DRAFT);
10328 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD, MSG_DRAFT);
10329 if (compose_use_attach(compose) && action != COMPOSE_AUTO_SAVE)
10330 procmsg_msginfo_set_flags(newmsginfo, 0,
10331 MSG_HAS_ATTACHMENT);
10333 if (action == COMPOSE_DRAFT_FOR_EXIT) {
10334 compose_register_draft(newmsginfo);
10336 procmsg_msginfo_free(&newmsginfo);
10339 folder_item_scan(draft);
10341 if (action == COMPOSE_QUIT_EDITING || action == COMPOSE_DRAFT_FOR_EXIT) {
10343 g_mutex_unlock(compose->mutex); /* must be done before closing */
10344 compose_close(compose);
10350 path = folder_item_fetch_msg(draft, msgnum);
10351 if (path == NULL) {
10352 debug_print("can't fetch %s:%d\n", draft->path, msgnum);
10355 if (g_stat(path, &s) < 0) {
10356 FILE_OP_ERROR(path, "stat");
10362 procmsg_msginfo_free(&(compose->targetinfo));
10363 compose->targetinfo = procmsg_msginfo_new();
10364 compose->targetinfo->msgnum = msgnum;
10365 compose->targetinfo->size = (goffset)s.st_size;
10366 compose->targetinfo->mtime = s.st_mtime;
10367 compose->targetinfo->folder = draft;
10369 procmsg_msginfo_set_flags(compose->targetinfo, MSG_LOCKED, 0);
10370 compose->mode = COMPOSE_REEDIT;
10372 if (action == COMPOSE_AUTO_SAVE) {
10373 compose->autosaved_draft = compose->targetinfo;
10375 compose->modified = FALSE;
10376 compose_set_title(compose);
10380 g_mutex_unlock(compose->mutex);
10384 void compose_clear_exit_drafts(void)
10386 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10387 DRAFTED_AT_EXIT, NULL);
10388 if (is_file_exist(filepath))
10389 claws_unlink(filepath);
10394 void compose_reopen_exit_drafts(void)
10396 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10397 DRAFTED_AT_EXIT, NULL);
10398 FILE *fp = g_fopen(filepath, "rb");
10402 while (fgets(buf, sizeof(buf), fp)) {
10403 gchar **parts = g_strsplit(buf, "\t", 2);
10404 const gchar *folder = parts[0];
10405 int msgnum = parts[1] ? atoi(parts[1]):-1;
10407 if (folder && *folder && msgnum > -1) {
10408 FolderItem *item = folder_find_item_from_identifier(folder);
10409 MsgInfo *info = folder_item_get_msginfo(item, msgnum);
10411 compose_reedit(info, FALSE);
10418 compose_clear_exit_drafts();
10421 static void compose_save_cb(GtkAction *action, gpointer data)
10423 Compose *compose = (Compose *)data;
10424 compose_draft(compose, COMPOSE_KEEP_EDITING);
10425 compose->rmode = COMPOSE_REEDIT;
10428 void compose_attach_from_list(Compose *compose, GList *file_list, gboolean free_data)
10430 if (compose && file_list) {
10433 for ( tmp = file_list; tmp; tmp = tmp->next) {
10434 gchar *file = (gchar *) tmp->data;
10435 gchar *utf8_filename = conv_filename_to_utf8(file);
10436 compose_attach_append(compose, file, utf8_filename, NULL, NULL);
10437 compose_changed_cb(NULL, compose);
10442 g_free(utf8_filename);
10447 static void compose_attach_cb(GtkAction *action, gpointer data)
10449 Compose *compose = (Compose *)data;
10452 if (compose->redirect_filename != NULL)
10455 /* Set focus_window properly, in case we were called via popup menu,
10456 * which unsets it (via focus_out_event callback on compose window). */
10457 manage_window_focus_in(compose->window, NULL, NULL);
10459 file_list = filesel_select_multiple_files_open(_("Select file"), NULL);
10462 compose_attach_from_list(compose, file_list, TRUE);
10463 g_list_free(file_list);
10467 static void compose_insert_file_cb(GtkAction *action, gpointer data)
10469 Compose *compose = (Compose *)data;
10471 gint files_inserted = 0;
10473 file_list = filesel_select_multiple_files_open(_("Select file"), NULL);
10478 for ( tmp = file_list; tmp; tmp = tmp->next) {
10479 gchar *file = (gchar *) tmp->data;
10480 gchar *filedup = g_strdup(file);
10481 gchar *shortfile = g_path_get_basename(filedup);
10482 ComposeInsertResult res;
10483 /* insert the file if the file is short or if the user confirmed that
10484 he/she wants to insert the large file */
10485 res = compose_insert_file(compose, file);
10486 if (res == COMPOSE_INSERT_READ_ERROR) {
10487 alertpanel_error(_("File '%s' could not be read."), shortfile);
10488 } else if (res == COMPOSE_INSERT_INVALID_CHARACTER) {
10489 alertpanel_error(_("File '%s' contained invalid characters\n"
10490 "for the current encoding, insertion may be incorrect."),
10492 } else if (res == COMPOSE_INSERT_SUCCESS)
10499 g_list_free(file_list);
10503 if (files_inserted > 0 && compose->gtkaspell &&
10504 compose->gtkaspell->check_while_typing)
10505 gtkaspell_highlight_all(compose->gtkaspell);
10509 static void compose_insert_sig_cb(GtkAction *action, gpointer data)
10511 Compose *compose = (Compose *)data;
10513 compose_insert_sig(compose, FALSE);
10516 static void compose_replace_sig_cb(GtkAction *action, gpointer data)
10518 Compose *compose = (Compose *)data;
10520 compose_insert_sig(compose, TRUE);
10523 static gint compose_delete_cb(GtkWidget *widget, GdkEventAny *event,
10527 Compose *compose = (Compose *)data;
10529 gtkut_widget_get_uposition(widget, &x, &y);
10530 if (!compose->batch) {
10531 prefs_common.compose_x = x;
10532 prefs_common.compose_y = y;
10534 if (compose->sending || compose->updating)
10536 compose_close_cb(NULL, compose);
10540 void compose_close_toolbar(Compose *compose)
10542 compose_close_cb(NULL, compose);
10545 static void compose_close_cb(GtkAction *action, gpointer data)
10547 Compose *compose = (Compose *)data;
10551 if (compose->exteditor_tag != -1) {
10552 if (!compose_ext_editor_kill(compose))
10557 if (compose->modified) {
10558 gboolean reedit = (compose->rmode == COMPOSE_REEDIT);
10559 if (!g_mutex_trylock(compose->mutex)) {
10560 /* we don't want to lock the mutex once it's available,
10561 * because as the only other part of compose.c locking
10562 * it is compose_close - which means once unlocked,
10563 * the compose struct will be freed */
10564 debug_print("couldn't lock mutex, probably sending\n");
10568 val = alertpanel(_("Discard message"),
10569 _("This message has been modified. Discard it?"),
10570 _("_Discard"), _("_Save to Drafts"), GTK_STOCK_CANCEL);
10572 val = alertpanel(_("Save changes"),
10573 _("This message has been modified. Save the latest changes?"),
10574 _("_Don't save"), g_strconcat("+", _("_Save to Drafts"), NULL),
10577 g_mutex_unlock(compose->mutex);
10579 case G_ALERTDEFAULT:
10580 if (compose_can_autosave(compose) && !reedit)
10581 compose_remove_draft(compose);
10583 case G_ALERTALTERNATE:
10584 compose_draft(data, COMPOSE_QUIT_EDITING);
10591 compose_close(compose);
10594 static void compose_print_cb(GtkAction *action, gpointer data)
10596 Compose *compose = (Compose *) data;
10598 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
10599 if (compose->targetinfo)
10600 messageview_print(compose->targetinfo, FALSE, -1, -1, 0);
10603 static void compose_set_encoding_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
10605 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
10606 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
10607 Compose *compose = (Compose *) data;
10610 compose->out_encoding = (CharSet)value;
10613 static void compose_address_cb(GtkAction *action, gpointer data)
10615 Compose *compose = (Compose *)data;
10617 #ifndef USE_ALT_ADDRBOOK
10618 addressbook_open(compose);
10620 GError* error = NULL;
10621 addressbook_connect_signals(compose);
10622 addressbook_dbus_open(TRUE, &error);
10624 g_warning("%s", error->message);
10625 g_error_free(error);
10630 static void about_show_cb(GtkAction *action, gpointer data)
10635 static void compose_template_activate_cb(GtkWidget *widget, gpointer data)
10637 Compose *compose = (Compose *)data;
10642 tmpl = g_object_get_data(G_OBJECT(widget), "template");
10643 cm_return_if_fail(tmpl != NULL);
10645 msg = g_strdup_printf(_("Do you want to apply the template '%s'?"),
10647 val = alertpanel(_("Apply template"), msg,
10648 _("_Replace"), _("_Insert"), GTK_STOCK_CANCEL);
10651 if (val == G_ALERTDEFAULT)
10652 compose_template_apply(compose, tmpl, TRUE);
10653 else if (val == G_ALERTALTERNATE)
10654 compose_template_apply(compose, tmpl, FALSE);
10657 static void compose_ext_editor_cb(GtkAction *action, gpointer data)
10659 Compose *compose = (Compose *)data;
10662 if (compose->exteditor_tag != -1) {
10663 debug_print("ignoring open external editor: external editor still open\n");
10667 compose_exec_ext_editor(compose);
10670 static void compose_undo_cb(GtkAction *action, gpointer data)
10672 Compose *compose = (Compose *)data;
10673 gboolean prev_autowrap = compose->autowrap;
10675 compose->autowrap = FALSE;
10676 undo_undo(compose->undostruct);
10677 compose->autowrap = prev_autowrap;
10680 static void compose_redo_cb(GtkAction *action, gpointer data)
10682 Compose *compose = (Compose *)data;
10683 gboolean prev_autowrap = compose->autowrap;
10685 compose->autowrap = FALSE;
10686 undo_redo(compose->undostruct);
10687 compose->autowrap = prev_autowrap;
10690 static void entry_cut_clipboard(GtkWidget *entry)
10692 if (GTK_IS_EDITABLE(entry))
10693 gtk_editable_cut_clipboard (GTK_EDITABLE(entry));
10694 else if (GTK_IS_TEXT_VIEW(entry))
10695 gtk_text_buffer_cut_clipboard(
10696 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10697 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
10701 static void entry_copy_clipboard(GtkWidget *entry)
10703 if (GTK_IS_EDITABLE(entry))
10704 gtk_editable_copy_clipboard (GTK_EDITABLE(entry));
10705 else if (GTK_IS_TEXT_VIEW(entry))
10706 gtk_text_buffer_copy_clipboard(
10707 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10708 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
10711 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry,
10712 gboolean wrap, GdkAtom clip, GtkTextIter *insert_place)
10714 if (GTK_IS_TEXT_VIEW(entry)) {
10715 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10716 GtkTextMark *mark_start = gtk_text_buffer_get_insert(buffer);
10717 GtkTextIter start_iter, end_iter;
10719 gchar *contents = gtk_clipboard_wait_for_text(gtk_clipboard_get(clip));
10721 if (contents == NULL)
10724 /* we shouldn't delete the selection when middle-click-pasting, or we
10725 * can't mid-click-paste our own selection */
10726 if (clip != GDK_SELECTION_PRIMARY) {
10727 undo_paste_clipboard(GTK_TEXT_VIEW(compose->text), compose->undostruct);
10728 gtk_text_buffer_delete_selection(buffer, FALSE, TRUE);
10731 if (insert_place == NULL) {
10732 /* if insert_place isn't specified, insert at the cursor.
10733 * used for Ctrl-V pasting */
10734 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10735 start = gtk_text_iter_get_offset(&start_iter);
10736 gtk_text_buffer_insert(buffer, &start_iter, contents, strlen(contents));
10738 /* if insert_place is specified, paste here.
10739 * used for mid-click-pasting */
10740 start = gtk_text_iter_get_offset(insert_place);
10741 gtk_text_buffer_insert(buffer, insert_place, contents, strlen(contents));
10742 if (prefs_common.primary_paste_unselects)
10743 gtk_text_buffer_select_range(buffer, insert_place, insert_place);
10747 /* paste unwrapped: mark the paste so it's not wrapped later */
10748 end = start + strlen(contents);
10749 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, start);
10750 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, end);
10751 gtk_text_buffer_apply_tag_by_name(buffer, "no_wrap", &start_iter, &end_iter);
10752 } else if (wrap && clip == GDK_SELECTION_PRIMARY) {
10753 /* rewrap paragraph now (after a mid-click-paste) */
10754 mark_start = gtk_text_buffer_get_insert(buffer);
10755 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10756 gtk_text_iter_backward_char(&start_iter);
10757 compose_beautify_paragraph(compose, &start_iter, TRUE);
10759 } else if (GTK_IS_EDITABLE(entry))
10760 gtk_editable_paste_clipboard (GTK_EDITABLE(entry));
10762 compose->modified = TRUE;
10765 static void entry_allsel(GtkWidget *entry)
10767 if (GTK_IS_EDITABLE(entry))
10768 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
10769 else if (GTK_IS_TEXT_VIEW(entry)) {
10770 GtkTextIter startiter, enditer;
10771 GtkTextBuffer *textbuf;
10773 textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10774 gtk_text_buffer_get_start_iter(textbuf, &startiter);
10775 gtk_text_buffer_get_end_iter(textbuf, &enditer);
10777 gtk_text_buffer_move_mark_by_name(textbuf,
10778 "selection_bound", &startiter);
10779 gtk_text_buffer_move_mark_by_name(textbuf,
10780 "insert", &enditer);
10784 static void compose_cut_cb(GtkAction *action, gpointer data)
10786 Compose *compose = (Compose *)data;
10787 if (compose->focused_editable
10788 #ifndef GENERIC_UMPC
10789 && gtk_widget_has_focus(compose->focused_editable)
10792 entry_cut_clipboard(compose->focused_editable);
10795 static void compose_copy_cb(GtkAction *action, gpointer data)
10797 Compose *compose = (Compose *)data;
10798 if (compose->focused_editable
10799 #ifndef GENERIC_UMPC
10800 && gtk_widget_has_focus(compose->focused_editable)
10803 entry_copy_clipboard(compose->focused_editable);
10806 static void compose_paste_cb(GtkAction *action, gpointer data)
10808 Compose *compose = (Compose *)data;
10809 gint prev_autowrap;
10810 GtkTextBuffer *buffer;
10812 if (compose->focused_editable &&
10813 #ifndef GENERIC_UMPC
10814 gtk_widget_has_focus(compose->focused_editable)
10817 entry_paste_clipboard(compose, compose->focused_editable,
10818 prefs_common.linewrap_pastes,
10819 GDK_SELECTION_CLIPBOARD, NULL);
10824 #ifndef GENERIC_UMPC
10825 gtk_widget_has_focus(compose->text) &&
10827 compose->gtkaspell &&
10828 compose->gtkaspell->check_while_typing)
10829 gtkaspell_highlight_all(compose->gtkaspell);
10833 static void compose_paste_as_quote_cb(GtkAction *action, gpointer data)
10835 Compose *compose = (Compose *)data;
10836 gint wrap_quote = prefs_common.linewrap_quote;
10837 if (compose->focused_editable
10838 #ifndef GENERIC_UMPC
10839 && gtk_widget_has_focus(compose->focused_editable)
10842 /* let text_insert() (called directly or at a later time
10843 * after the gtk_editable_paste_clipboard) know that
10844 * text is to be inserted as a quotation. implemented
10845 * by using a simple refcount... */
10846 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data(
10847 G_OBJECT(compose->focused_editable),
10848 "paste_as_quotation"));
10849 g_object_set_data(G_OBJECT(compose->focused_editable),
10850 "paste_as_quotation",
10851 GINT_TO_POINTER(paste_as_quotation + 1));
10852 prefs_common.linewrap_quote = prefs_common.linewrap_pastes;
10853 entry_paste_clipboard(compose, compose->focused_editable,
10854 prefs_common.linewrap_pastes,
10855 GDK_SELECTION_CLIPBOARD, NULL);
10856 prefs_common.linewrap_quote = wrap_quote;
10860 static void compose_paste_no_wrap_cb(GtkAction *action, gpointer data)
10862 Compose *compose = (Compose *)data;
10863 gint prev_autowrap;
10864 GtkTextBuffer *buffer;
10866 if (compose->focused_editable
10867 #ifndef GENERIC_UMPC
10868 && gtk_widget_has_focus(compose->focused_editable)
10871 entry_paste_clipboard(compose, compose->focused_editable, FALSE,
10872 GDK_SELECTION_CLIPBOARD, NULL);
10877 #ifndef GENERIC_UMPC
10878 gtk_widget_has_focus(compose->text) &&
10880 compose->gtkaspell &&
10881 compose->gtkaspell->check_while_typing)
10882 gtkaspell_highlight_all(compose->gtkaspell);
10886 static void compose_paste_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, TRUE,
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_allsel_cb(GtkAction *action, gpointer data)
10914 Compose *compose = (Compose *)data;
10915 if (compose->focused_editable
10916 #ifndef GENERIC_UMPC
10917 && gtk_widget_has_focus(compose->focused_editable)
10920 entry_allsel(compose->focused_editable);
10923 static void textview_move_beginning_of_line (GtkTextView *text)
10925 GtkTextBuffer *buffer;
10929 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10931 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10932 mark = gtk_text_buffer_get_insert(buffer);
10933 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10934 gtk_text_iter_set_line_offset(&ins, 0);
10935 gtk_text_buffer_place_cursor(buffer, &ins);
10938 static void textview_move_forward_character (GtkTextView *text)
10940 GtkTextBuffer *buffer;
10944 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10946 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10947 mark = gtk_text_buffer_get_insert(buffer);
10948 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10949 if (gtk_text_iter_forward_cursor_position(&ins))
10950 gtk_text_buffer_place_cursor(buffer, &ins);
10953 static void textview_move_backward_character (GtkTextView *text)
10955 GtkTextBuffer *buffer;
10959 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10961 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10962 mark = gtk_text_buffer_get_insert(buffer);
10963 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10964 if (gtk_text_iter_backward_cursor_position(&ins))
10965 gtk_text_buffer_place_cursor(buffer, &ins);
10968 static void textview_move_forward_word (GtkTextView *text)
10970 GtkTextBuffer *buffer;
10975 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10977 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10978 mark = gtk_text_buffer_get_insert(buffer);
10979 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10980 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
10981 if (gtk_text_iter_forward_word_ends(&ins, count)) {
10982 gtk_text_iter_backward_word_start(&ins);
10983 gtk_text_buffer_place_cursor(buffer, &ins);
10987 static void textview_move_backward_word (GtkTextView *text)
10989 GtkTextBuffer *buffer;
10993 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10995 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10996 mark = gtk_text_buffer_get_insert(buffer);
10997 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10998 if (gtk_text_iter_backward_word_starts(&ins, 1))
10999 gtk_text_buffer_place_cursor(buffer, &ins);
11002 static void textview_move_end_of_line (GtkTextView *text)
11004 GtkTextBuffer *buffer;
11008 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11010 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11011 mark = gtk_text_buffer_get_insert(buffer);
11012 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11013 if (gtk_text_iter_forward_to_line_end(&ins))
11014 gtk_text_buffer_place_cursor(buffer, &ins);
11017 static void textview_move_next_line (GtkTextView *text)
11019 GtkTextBuffer *buffer;
11024 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11026 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11027 mark = gtk_text_buffer_get_insert(buffer);
11028 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11029 offset = gtk_text_iter_get_line_offset(&ins);
11030 if (gtk_text_iter_forward_line(&ins)) {
11031 gtk_text_iter_set_line_offset(&ins, offset);
11032 gtk_text_buffer_place_cursor(buffer, &ins);
11036 static void textview_move_previous_line (GtkTextView *text)
11038 GtkTextBuffer *buffer;
11043 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11045 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11046 mark = gtk_text_buffer_get_insert(buffer);
11047 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11048 offset = gtk_text_iter_get_line_offset(&ins);
11049 if (gtk_text_iter_backward_line(&ins)) {
11050 gtk_text_iter_set_line_offset(&ins, offset);
11051 gtk_text_buffer_place_cursor(buffer, &ins);
11055 static void textview_delete_forward_character (GtkTextView *text)
11057 GtkTextBuffer *buffer;
11059 GtkTextIter ins, end_iter;
11061 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11063 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11064 mark = gtk_text_buffer_get_insert(buffer);
11065 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11067 if (gtk_text_iter_forward_char(&end_iter)) {
11068 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11072 static void textview_delete_backward_character (GtkTextView *text)
11074 GtkTextBuffer *buffer;
11076 GtkTextIter ins, end_iter;
11078 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11080 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11081 mark = gtk_text_buffer_get_insert(buffer);
11082 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11084 if (gtk_text_iter_backward_char(&end_iter)) {
11085 gtk_text_buffer_delete(buffer, &end_iter, &ins);
11089 static void textview_delete_forward_word (GtkTextView *text)
11091 GtkTextBuffer *buffer;
11093 GtkTextIter ins, end_iter;
11095 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11097 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11098 mark = gtk_text_buffer_get_insert(buffer);
11099 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11101 if (gtk_text_iter_forward_word_end(&end_iter)) {
11102 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11106 static void textview_delete_backward_word (GtkTextView *text)
11108 GtkTextBuffer *buffer;
11110 GtkTextIter ins, end_iter;
11112 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11114 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11115 mark = gtk_text_buffer_get_insert(buffer);
11116 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11118 if (gtk_text_iter_backward_word_start(&end_iter)) {
11119 gtk_text_buffer_delete(buffer, &end_iter, &ins);
11123 static void textview_delete_line (GtkTextView *text)
11125 GtkTextBuffer *buffer;
11127 GtkTextIter ins, start_iter, end_iter;
11129 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11131 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11132 mark = gtk_text_buffer_get_insert(buffer);
11133 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11136 gtk_text_iter_set_line_offset(&start_iter, 0);
11139 if (gtk_text_iter_ends_line(&end_iter)){
11140 if (!gtk_text_iter_forward_char(&end_iter))
11141 gtk_text_iter_backward_char(&start_iter);
11144 gtk_text_iter_forward_to_line_end(&end_iter);
11145 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
11148 static void textview_delete_to_line_end (GtkTextView *text)
11150 GtkTextBuffer *buffer;
11152 GtkTextIter ins, end_iter;
11154 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11156 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11157 mark = gtk_text_buffer_get_insert(buffer);
11158 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11160 if (gtk_text_iter_ends_line(&end_iter))
11161 gtk_text_iter_forward_char(&end_iter);
11163 gtk_text_iter_forward_to_line_end(&end_iter);
11164 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11167 #define DO_ACTION(name, act) { \
11168 if(!strcmp(name, a_name)) { \
11172 static ComposeCallAdvancedAction compose_call_advanced_action_from_path(GtkAction *action)
11174 const gchar *a_name = gtk_action_get_name(action);
11175 DO_ACTION("Edit/Advanced/BackChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER);
11176 DO_ACTION("Edit/Advanced/ForwChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER);
11177 DO_ACTION("Edit/Advanced/BackWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD);
11178 DO_ACTION("Edit/Advanced/ForwWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD);
11179 DO_ACTION("Edit/Advanced/BegLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE);
11180 DO_ACTION("Edit/Advanced/EndLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE);
11181 DO_ACTION("Edit/Advanced/PrevLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE);
11182 DO_ACTION("Edit/Advanced/NextLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE);
11183 DO_ACTION("Edit/Advanced/DelBackChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER);
11184 DO_ACTION("Edit/Advanced/DelForwChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER);
11185 DO_ACTION("Edit/Advanced/DelBackWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD);
11186 DO_ACTION("Edit/Advanced/DelForwWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD);
11187 DO_ACTION("Edit/Advanced/DelLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE);
11188 DO_ACTION("Edit/Advanced/DelEndLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END);
11189 return COMPOSE_CALL_ADVANCED_ACTION_UNDEFINED;
11192 static void compose_advanced_action_cb(GtkAction *gaction, gpointer data)
11194 Compose *compose = (Compose *)data;
11195 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11196 ComposeCallAdvancedAction action = COMPOSE_CALL_ADVANCED_ACTION_UNDEFINED;
11198 action = compose_call_advanced_action_from_path(gaction);
11201 void (*do_action) (GtkTextView *text);
11202 } action_table[] = {
11203 {textview_move_beginning_of_line},
11204 {textview_move_forward_character},
11205 {textview_move_backward_character},
11206 {textview_move_forward_word},
11207 {textview_move_backward_word},
11208 {textview_move_end_of_line},
11209 {textview_move_next_line},
11210 {textview_move_previous_line},
11211 {textview_delete_forward_character},
11212 {textview_delete_backward_character},
11213 {textview_delete_forward_word},
11214 {textview_delete_backward_word},
11215 {textview_delete_line},
11216 {textview_delete_to_line_end}
11219 if (!gtk_widget_has_focus(GTK_WIDGET(text))) return;
11221 if (action >= COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE &&
11222 action <= COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END) {
11223 if (action_table[action].do_action)
11224 action_table[action].do_action(text);
11226 g_warning("Not implemented yet.");
11230 static void compose_grab_focus_cb(GtkWidget *widget, Compose *compose)
11232 GtkAllocation allocation;
11236 if (GTK_IS_EDITABLE(widget)) {
11237 str = gtk_editable_get_chars(GTK_EDITABLE(widget), 0, -1);
11238 gtk_editable_set_position(GTK_EDITABLE(widget),
11241 if ((parent = gtk_widget_get_parent(widget))
11242 && (parent = gtk_widget_get_parent(parent))
11243 && (parent = gtk_widget_get_parent(parent))) {
11244 if (GTK_IS_SCROLLED_WINDOW(parent)) {
11245 gtk_widget_get_allocation(widget, &allocation);
11246 gint y = allocation.y;
11247 gint height = allocation.height;
11248 GtkAdjustment *shown = gtk_scrolled_window_get_vadjustment
11249 (GTK_SCROLLED_WINDOW(parent));
11251 gfloat value = gtk_adjustment_get_value(shown);
11252 gfloat upper = gtk_adjustment_get_upper(shown);
11253 gfloat page_size = gtk_adjustment_get_page_size(shown);
11254 if (y < (int)value) {
11255 gtk_adjustment_set_value(shown, y - 1);
11257 if ((y + height) > ((int)value + (int)page_size)) {
11258 if ((y - height - 1) < ((int)upper - (int)page_size)) {
11259 gtk_adjustment_set_value(shown,
11260 y + height - (int)page_size - 1);
11262 gtk_adjustment_set_value(shown,
11263 (int)upper - (int)page_size - 1);
11270 if (GTK_IS_EDITABLE(widget) || GTK_IS_TEXT_VIEW(widget))
11271 compose->focused_editable = widget;
11273 #ifdef GENERIC_UMPC
11274 if (GTK_IS_TEXT_VIEW(widget)
11275 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->edit_vbox) {
11276 g_object_ref(compose->notebook);
11277 g_object_ref(compose->edit_vbox);
11278 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
11279 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
11280 gtk_paned_add1(GTK_PANED(compose->paned), compose->edit_vbox);
11281 gtk_paned_add2(GTK_PANED(compose->paned), compose->notebook);
11282 g_object_unref(compose->notebook);
11283 g_object_unref(compose->edit_vbox);
11284 g_signal_handlers_block_by_func(G_OBJECT(widget),
11285 G_CALLBACK(compose_grab_focus_cb),
11287 gtk_widget_grab_focus(widget);
11288 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
11289 G_CALLBACK(compose_grab_focus_cb),
11291 } else if (!GTK_IS_TEXT_VIEW(widget)
11292 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->notebook) {
11293 g_object_ref(compose->notebook);
11294 g_object_ref(compose->edit_vbox);
11295 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
11296 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
11297 gtk_paned_add1(GTK_PANED(compose->paned), compose->notebook);
11298 gtk_paned_add2(GTK_PANED(compose->paned), compose->edit_vbox);
11299 g_object_unref(compose->notebook);
11300 g_object_unref(compose->edit_vbox);
11301 g_signal_handlers_block_by_func(G_OBJECT(widget),
11302 G_CALLBACK(compose_grab_focus_cb),
11304 gtk_widget_grab_focus(widget);
11305 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
11306 G_CALLBACK(compose_grab_focus_cb),
11312 static void compose_changed_cb(GtkTextBuffer *textbuf, Compose *compose)
11314 compose->modified = TRUE;
11315 /* compose_beautify_paragraph(compose, NULL, TRUE); */
11316 #ifndef GENERIC_UMPC
11317 compose_set_title(compose);
11321 static void compose_wrap_cb(GtkAction *action, gpointer data)
11323 Compose *compose = (Compose *)data;
11324 compose_beautify_paragraph(compose, NULL, TRUE);
11327 static void compose_wrap_all_cb(GtkAction *action, gpointer data)
11329 Compose *compose = (Compose *)data;
11330 compose_wrap_all_full(compose, TRUE);
11333 static void compose_find_cb(GtkAction *action, gpointer data)
11335 Compose *compose = (Compose *)data;
11337 message_search_compose(compose);
11340 static void compose_toggle_autowrap_cb(GtkToggleAction *action,
11343 Compose *compose = (Compose *)data;
11344 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11345 if (compose->autowrap)
11346 compose_wrap_all_full(compose, TRUE);
11347 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11350 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
11353 Compose *compose = (Compose *)data;
11354 compose->autoindent = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11357 static void compose_toggle_sign_cb(GtkToggleAction *action, gpointer data)
11359 Compose *compose = (Compose *)data;
11361 compose->use_signing = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11364 static void compose_toggle_encrypt_cb(GtkToggleAction *action, gpointer data)
11366 Compose *compose = (Compose *)data;
11368 compose->use_encryption = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11371 static void activate_privacy_system(Compose *compose, PrefsAccount *account, gboolean warn)
11373 g_free(compose->privacy_system);
11374 g_free(compose->encdata);
11376 compose->privacy_system = g_strdup(account->default_privacy_system);
11377 compose_update_privacy_system_menu_item(compose, warn);
11380 static void compose_toggle_ruler_cb(GtkToggleAction *action, gpointer data)
11382 Compose *compose = (Compose *)data;
11384 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action))) {
11385 gtk_widget_show(compose->ruler_hbox);
11386 prefs_common.show_ruler = TRUE;
11388 gtk_widget_hide(compose->ruler_hbox);
11389 gtk_widget_queue_resize(compose->edit_vbox);
11390 prefs_common.show_ruler = FALSE;
11394 static void compose_attach_drag_received_cb (GtkWidget *widget,
11395 GdkDragContext *context,
11398 GtkSelectionData *data,
11401 gpointer user_data)
11403 Compose *compose = (Compose *)user_data;
11407 type = gtk_selection_data_get_data_type(data);
11408 if ((gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list"))
11409 && gtk_drag_get_source_widget(context) !=
11410 summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
11411 list = uri_list_extract_filenames(
11412 (const gchar *)gtk_selection_data_get_data(data));
11413 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11414 gchar *utf8_filename = conv_filename_to_utf8((const gchar *)tmp->data);
11415 compose_attach_append
11416 (compose, (const gchar *)tmp->data,
11417 utf8_filename, NULL, NULL);
11418 g_free(utf8_filename);
11420 if (list) compose_changed_cb(NULL, compose);
11421 list_free_strings(list);
11423 } else if (gtk_drag_get_source_widget(context)
11424 == summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
11425 /* comes from our summaryview */
11426 SummaryView * summaryview = NULL;
11427 GSList * list = NULL, *cur = NULL;
11429 if (mainwindow_get_mainwindow())
11430 summaryview = mainwindow_get_mainwindow()->summaryview;
11433 list = summary_get_selected_msg_list(summaryview);
11435 for (cur = list; cur; cur = cur->next) {
11436 MsgInfo *msginfo = (MsgInfo *)cur->data;
11437 gchar *file = NULL;
11439 file = procmsg_get_message_file_full(msginfo,
11442 compose_attach_append(compose, (const gchar *)file,
11443 (const gchar *)file, "message/rfc822", NULL);
11447 g_slist_free(list);
11451 static gboolean compose_drag_drop(GtkWidget *widget,
11452 GdkDragContext *drag_context,
11454 guint time, gpointer user_data)
11456 /* not handling this signal makes compose_insert_drag_received_cb
11461 static gboolean completion_set_focus_to_subject
11462 (GtkWidget *widget,
11463 GdkEventKey *event,
11466 cm_return_val_if_fail(compose != NULL, FALSE);
11468 /* make backtab move to subject field */
11469 if(event->keyval == GDK_KEY_ISO_Left_Tab) {
11470 gtk_widget_grab_focus(compose->subject_entry);
11476 static void compose_insert_drag_received_cb (GtkWidget *widget,
11477 GdkDragContext *drag_context,
11480 GtkSelectionData *data,
11483 gpointer user_data)
11485 Compose *compose = (Compose *)user_data;
11491 /* strangely, testing data->type == gdk_atom_intern("text/uri-list", TRUE)
11493 type = gtk_selection_data_get_data_type(data);
11494 if (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list")) {
11495 AlertValue val = G_ALERTDEFAULT;
11496 const gchar* ddata = (const gchar *)gtk_selection_data_get_data(data);
11498 list = uri_list_extract_filenames(ddata);
11499 num_files = g_list_length(list);
11500 if (list == NULL && strstr(ddata, "://")) {
11501 /* Assume a list of no files, and data has ://, is a remote link */
11502 gchar *tmpdata = g_strstrip(g_strdup(ddata));
11503 gchar *tmpfile = get_tmp_file();
11504 str_write_to_file(tmpdata, tmpfile);
11506 compose_insert_file(compose, tmpfile);
11507 claws_unlink(tmpfile);
11509 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11510 compose_beautify_paragraph(compose, NULL, TRUE);
11513 switch (prefs_common.compose_dnd_mode) {
11514 case COMPOSE_DND_ASK:
11515 msg = g_strdup_printf(
11517 "Do you want to insert the contents of the file "
11518 "into the message body, or attach it to the email?",
11519 "Do you want to insert the contents of the %d files "
11520 "into the message body, or attach them to the email?",
11523 val = alertpanel_full(_("Insert or attach?"), msg,
11524 GTK_STOCK_CANCEL, g_strconcat("+", _("_Insert"), NULL), _("_Attach"),
11525 TRUE, NULL, ALERT_QUESTION, G_ALERTALTERNATE);
11528 case COMPOSE_DND_INSERT:
11529 val = G_ALERTALTERNATE;
11531 case COMPOSE_DND_ATTACH:
11532 val = G_ALERTOTHER;
11535 /* unexpected case */
11536 g_warning("error: unexpected compose_dnd_mode option value in compose_insert_drag_received_cb()");
11539 if (val & G_ALERTDISABLE) {
11540 val &= ~G_ALERTDISABLE;
11541 /* remember what action to perform by default, only if we don't click Cancel */
11542 if (val == G_ALERTALTERNATE)
11543 prefs_common.compose_dnd_mode = COMPOSE_DND_INSERT;
11544 else if (val == G_ALERTOTHER)
11545 prefs_common.compose_dnd_mode = COMPOSE_DND_ATTACH;
11548 if (val == G_ALERTDEFAULT || val == G_ALERTCANCEL) {
11549 gtk_drag_finish(drag_context, FALSE, FALSE, time);
11550 list_free_strings(list);
11553 } else if (val == G_ALERTOTHER) {
11554 compose_attach_drag_received_cb(widget, drag_context, x, y, data, info, time, user_data);
11555 list_free_strings(list);
11560 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11561 compose_insert_file(compose, (const gchar *)tmp->data);
11563 list_free_strings(list);
11565 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11570 static void compose_header_drag_received_cb (GtkWidget *widget,
11571 GdkDragContext *drag_context,
11574 GtkSelectionData *data,
11577 gpointer user_data)
11579 GtkEditable *entry = (GtkEditable *)user_data;
11580 const gchar *email = (const gchar *)gtk_selection_data_get_data(data);
11582 /* strangely, testing data->type == gdk_atom_intern("text/plain", TRUE)
11585 if (!strncmp(email, "mailto:", strlen("mailto:"))) {
11586 gchar *decoded=g_new(gchar, strlen(email));
11589 decode_uri(decoded, email + strlen("mailto:")); /* will fit */
11590 gtk_editable_delete_text(entry, 0, -1);
11591 gtk_editable_insert_text(entry, decoded, strlen(decoded), &start);
11592 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11596 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11599 static void compose_toggle_return_receipt_cb(GtkToggleAction *action, gpointer data)
11601 Compose *compose = (Compose *)data;
11603 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11604 compose->return_receipt = TRUE;
11606 compose->return_receipt = FALSE;
11609 static void compose_toggle_remove_refs_cb(GtkToggleAction *action, gpointer data)
11611 Compose *compose = (Compose *)data;
11613 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11614 compose->remove_references = TRUE;
11616 compose->remove_references = FALSE;
11619 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
11620 ComposeHeaderEntry *headerentry)
11622 gtk_entry_set_text(GTK_ENTRY(headerentry->entry), "");
11626 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
11627 GdkEventKey *event,
11628 ComposeHeaderEntry *headerentry)
11630 if ((g_slist_length(headerentry->compose->header_list) > 0) &&
11631 ((headerentry->headernum + 1) != headerentry->compose->header_nextrow) &&
11632 !(event->state & GDK_MODIFIER_MASK) &&
11633 (event->keyval == GDK_KEY_BackSpace) &&
11634 (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) == 0)) {
11635 gtk_container_remove
11636 (GTK_CONTAINER(headerentry->compose->header_table),
11637 headerentry->combo);
11638 gtk_container_remove
11639 (GTK_CONTAINER(headerentry->compose->header_table),
11640 headerentry->entry);
11641 headerentry->compose->header_list =
11642 g_slist_remove(headerentry->compose->header_list,
11644 g_free(headerentry);
11645 } else if (event->keyval == GDK_KEY_Tab) {
11646 if (headerentry->compose->header_last == headerentry) {
11647 /* Override default next focus, and give it to subject_entry
11648 * instead of notebook tabs
11650 g_signal_stop_emission_by_name(G_OBJECT(entry), "key-press-event");
11651 gtk_widget_grab_focus(headerentry->compose->subject_entry);
11658 static gboolean scroll_postpone(gpointer data)
11660 Compose *compose = (Compose *)data;
11662 if (compose->batch)
11665 GTK_EVENTS_FLUSH();
11666 compose_show_first_last_header(compose, FALSE);
11670 static void compose_headerentry_changed_cb(GtkWidget *entry,
11671 ComposeHeaderEntry *headerentry)
11673 if (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) != 0) {
11674 compose_create_header_entry(headerentry->compose);
11675 g_signal_handlers_disconnect_matched
11676 (G_OBJECT(entry), G_SIGNAL_MATCH_DATA,
11677 0, 0, NULL, NULL, headerentry);
11679 if (!headerentry->compose->batch)
11680 g_timeout_add(0, scroll_postpone, headerentry->compose);
11684 static gboolean compose_defer_auto_save_draft(Compose *compose)
11686 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
11687 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
11691 static void compose_show_first_last_header(Compose *compose, gboolean show_first)
11693 GtkAdjustment *vadj;
11695 cm_return_if_fail(compose);
11700 cm_return_if_fail(GTK_IS_WIDGET(compose->header_table));
11701 cm_return_if_fail(GTK_IS_VIEWPORT(gtk_widget_get_parent(compose->header_table)));
11702 vadj = gtk_viewport_get_vadjustment(GTK_VIEWPORT(
11703 gtk_widget_get_parent(compose->header_table)));
11704 gtk_adjustment_set_value(vadj, (show_first ?
11705 gtk_adjustment_get_lower(vadj) :
11706 (gtk_adjustment_get_upper(vadj) -
11707 gtk_adjustment_get_page_size(vadj))));
11708 gtk_adjustment_changed(vadj);
11711 static void text_inserted(GtkTextBuffer *buffer, GtkTextIter *iter,
11712 const gchar *text, gint len, Compose *compose)
11714 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data
11715 (G_OBJECT(compose->text), "paste_as_quotation"));
11718 cm_return_if_fail(text != NULL);
11720 g_signal_handlers_block_by_func(G_OBJECT(buffer),
11721 G_CALLBACK(text_inserted),
11723 if (paste_as_quotation) {
11725 const gchar *qmark;
11727 GtkTextIter start_iter;
11730 len = strlen(text);
11732 new_text = g_strndup(text, len);
11734 qmark = compose_quote_char_from_context(compose);
11736 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11737 gtk_text_buffer_place_cursor(buffer, iter);
11739 pos = gtk_text_iter_get_offset(iter);
11741 compose_quote_fmt(compose, NULL, "%Q", qmark, new_text, TRUE, FALSE,
11742 _("Quote format error at line %d."));
11743 quote_fmt_reset_vartable();
11745 g_object_set_data(G_OBJECT(compose->text), "paste_as_quotation",
11746 GINT_TO_POINTER(paste_as_quotation - 1));
11748 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11749 gtk_text_buffer_place_cursor(buffer, iter);
11750 gtk_text_buffer_delete_mark(buffer, mark);
11752 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, pos);
11753 mark = gtk_text_buffer_create_mark(buffer, NULL, &start_iter, FALSE);
11754 compose_beautify_paragraph(compose, &start_iter, FALSE);
11755 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark);
11756 gtk_text_buffer_delete_mark(buffer, mark);
11758 if (strcmp(text, "\n") || compose->automatic_break
11759 || gtk_text_iter_starts_line(iter)) {
11760 GtkTextIter before_ins;
11761 gtk_text_buffer_insert(buffer, iter, text, len);
11762 if (!strstr(text, "\n") && gtk_text_iter_has_tag(iter, compose->no_join_tag)) {
11763 before_ins = *iter;
11764 gtk_text_iter_backward_chars(&before_ins, len);
11765 gtk_text_buffer_remove_tag_by_name(buffer, "no_join", &before_ins, iter);
11768 /* check if the preceding is just whitespace or quote */
11769 GtkTextIter start_line;
11770 gchar *tmp = NULL, *quote = NULL;
11771 gint quote_len = 0, is_normal = 0;
11772 start_line = *iter;
11773 gtk_text_iter_set_line_offset(&start_line, 0);
11774 tmp = gtk_text_buffer_get_text(buffer, &start_line, iter, FALSE);
11777 if (*tmp == '\0') {
11780 quote = compose_get_quote_str(buffer, &start_line, "e_len);
11788 gtk_text_buffer_insert(buffer, iter, text, len);
11790 gtk_text_buffer_insert_with_tags_by_name(buffer,
11791 iter, text, len, "no_join", NULL);
11796 if (!paste_as_quotation) {
11797 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11798 compose_beautify_paragraph(compose, iter, FALSE);
11799 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11800 gtk_text_buffer_delete_mark(buffer, mark);
11803 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
11804 G_CALLBACK(text_inserted),
11806 g_signal_stop_emission_by_name(G_OBJECT(buffer), "insert-text");
11808 if (compose_can_autosave(compose) &&
11809 gtk_text_buffer_get_char_count(buffer) % prefs_common.autosave_length == 0 &&
11810 compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN /* disabled while loading */)
11811 compose->draft_timeout_tag = g_timeout_add
11812 (500, (GSourceFunc) compose_defer_auto_save_draft, compose);
11816 static void compose_check_all(GtkAction *action, gpointer data)
11818 Compose *compose = (Compose *)data;
11819 if (!compose->gtkaspell)
11822 if (gtk_widget_has_focus(compose->subject_entry))
11823 claws_spell_entry_check_all(
11824 CLAWS_SPELL_ENTRY(compose->subject_entry));
11826 gtkaspell_check_all(compose->gtkaspell);
11829 static void compose_highlight_all(GtkAction *action, gpointer data)
11831 Compose *compose = (Compose *)data;
11832 if (compose->gtkaspell) {
11833 claws_spell_entry_recheck_all(
11834 CLAWS_SPELL_ENTRY(compose->subject_entry));
11835 gtkaspell_highlight_all(compose->gtkaspell);
11839 static void compose_check_backwards(GtkAction *action, gpointer data)
11841 Compose *compose = (Compose *)data;
11842 if (!compose->gtkaspell) {
11843 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
11847 if (gtk_widget_has_focus(compose->subject_entry))
11848 claws_spell_entry_check_backwards(
11849 CLAWS_SPELL_ENTRY(compose->subject_entry));
11851 gtkaspell_check_backwards(compose->gtkaspell);
11854 static void compose_check_forwards_go(GtkAction *action, gpointer data)
11856 Compose *compose = (Compose *)data;
11857 if (!compose->gtkaspell) {
11858 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
11862 if (gtk_widget_has_focus(compose->subject_entry))
11863 claws_spell_entry_check_forwards_go(
11864 CLAWS_SPELL_ENTRY(compose->subject_entry));
11866 gtkaspell_check_forwards_go(compose->gtkaspell);
11871 *\brief Guess originating forward account from MsgInfo and several
11872 * "common preference" settings. Return NULL if no guess.
11874 static PrefsAccount *compose_find_account(MsgInfo *msginfo)
11876 PrefsAccount *account = NULL;
11878 cm_return_val_if_fail(msginfo, NULL);
11879 cm_return_val_if_fail(msginfo->folder, NULL);
11880 cm_return_val_if_fail(msginfo->folder->prefs, NULL);
11882 if (msginfo->folder->prefs->enable_default_account)
11883 account = account_find_from_id(msginfo->folder->prefs->default_account);
11885 if (!account && msginfo->to && prefs_common.forward_account_autosel) {
11887 Xstrdup_a(to, msginfo->to, return NULL);
11888 extract_address(to);
11889 account = account_find_from_address(to, FALSE);
11892 if (!account && prefs_common.forward_account_autosel) {
11894 if (!procheader_get_header_from_msginfo
11895 (msginfo, &cc, "Cc:")) {
11896 gchar *buf = cc + strlen("Cc:");
11897 extract_address(buf);
11898 account = account_find_from_address(buf, FALSE);
11903 if (!account && prefs_common.forward_account_autosel) {
11904 gchar *deliveredto = NULL;
11905 if (!procheader_get_header_from_msginfo
11906 (msginfo, &deliveredto, "Delivered-To:")) {
11907 gchar *buf = deliveredto + strlen("Delivered-To:");
11908 extract_address(buf);
11909 account = account_find_from_address(buf, FALSE);
11910 g_free(deliveredto);
11915 account = msginfo->folder->folder->account;
11920 gboolean compose_close(Compose *compose)
11924 cm_return_val_if_fail(compose, FALSE);
11926 if (!g_mutex_trylock(compose->mutex)) {
11927 /* we have to wait for the (possibly deferred by auto-save)
11928 * drafting to be done, before destroying the compose under
11930 debug_print("waiting for drafting to finish...\n");
11931 compose_allow_user_actions(compose, FALSE);
11932 if (compose->close_timeout_tag == 0) {
11933 compose->close_timeout_tag =
11934 g_timeout_add (500, (GSourceFunc) compose_close,
11940 if (compose->draft_timeout_tag >= 0) {
11941 g_source_remove(compose->draft_timeout_tag);
11942 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN;
11945 gtkut_widget_get_uposition(compose->window, &x, &y);
11946 if (!compose->batch) {
11947 prefs_common.compose_x = x;
11948 prefs_common.compose_y = y;
11950 g_mutex_unlock(compose->mutex);
11951 compose_destroy(compose);
11956 * Add entry field for each address in list.
11957 * \param compose E-Mail composition object.
11958 * \param listAddress List of (formatted) E-Mail addresses.
11960 static void compose_add_field_list( Compose *compose, GList *listAddress ) {
11963 node = listAddress;
11965 addr = ( gchar * ) node->data;
11966 compose_entry_append( compose, addr, COMPOSE_TO, PREF_NONE );
11967 node = g_list_next( node );
11971 static void compose_reply_from_messageview_real(MessageView *msgview, GSList *msginfo_list,
11972 guint action, gboolean opening_multiple)
11974 gchar *body = NULL;
11975 GSList *new_msglist = NULL;
11976 MsgInfo *tmp_msginfo = NULL;
11977 gboolean originally_enc = FALSE;
11978 gboolean originally_sig = FALSE;
11979 Compose *compose = NULL;
11980 gchar *s_system = NULL;
11982 cm_return_if_fail(msgview != NULL);
11984 cm_return_if_fail(msginfo_list != NULL);
11986 if (g_slist_length(msginfo_list) == 1 && !opening_multiple) {
11987 MimeInfo *mimeinfo = messageview_get_selected_mime_part(msgview);
11988 MsgInfo *orig_msginfo = (MsgInfo *)msginfo_list->data;
11990 if (mimeinfo != NULL && mimeinfo->type == MIMETYPE_MESSAGE &&
11991 !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
11992 tmp_msginfo = procmsg_msginfo_new_from_mimeinfo(
11993 orig_msginfo, mimeinfo);
11994 if (tmp_msginfo != NULL) {
11995 new_msglist = g_slist_append(NULL, tmp_msginfo);
11997 originally_enc = MSG_IS_ENCRYPTED(orig_msginfo->flags);
11998 privacy_msginfo_get_signed_state(orig_msginfo, &s_system);
11999 originally_sig = MSG_IS_SIGNED(orig_msginfo->flags);
12001 tmp_msginfo->folder = orig_msginfo->folder;
12002 tmp_msginfo->msgnum = orig_msginfo->msgnum;
12003 if (orig_msginfo->tags) {
12004 tmp_msginfo->tags = g_slist_copy(orig_msginfo->tags);
12005 tmp_msginfo->folder->tags_dirty = TRUE;
12011 if (!opening_multiple)
12012 body = messageview_get_selection(msgview);
12015 compose = compose_reply_mode((ComposeMode)action, new_msglist, body);
12016 procmsg_msginfo_free(&tmp_msginfo);
12017 g_slist_free(new_msglist);
12019 compose = compose_reply_mode((ComposeMode)action, msginfo_list, body);
12021 if (compose && originally_enc) {
12022 compose_force_encryption(compose, compose->account, FALSE, s_system);
12025 if (compose && originally_sig && compose->account->default_sign_reply) {
12026 compose_force_signing(compose, compose->account, s_system);
12030 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
12033 void compose_reply_from_messageview(MessageView *msgview, GSList *msginfo_list,
12036 if ((!prefs_common.forward_as_attachment || action != COMPOSE_FORWARD)
12037 && action != COMPOSE_FORWARD_AS_ATTACH && g_slist_length(msginfo_list) > 1) {
12038 GSList *cur = msginfo_list;
12039 gchar *msg = g_strdup_printf(_("You are about to reply to %d "
12040 "messages. Opening the windows "
12041 "could take some time. Do you "
12042 "want to continue?"),
12043 g_slist_length(msginfo_list));
12044 if (g_slist_length(msginfo_list) > 9
12045 && alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, "+" GTK_STOCK_YES, NULL)
12046 != G_ALERTALTERNATE) {
12051 /* We'll open multiple compose windows */
12052 /* let the WM place the next windows */
12053 compose_force_window_origin = FALSE;
12054 for (; cur; cur = cur->next) {
12056 tmplist.data = cur->data;
12057 tmplist.next = NULL;
12058 compose_reply_from_messageview_real(msgview, &tmplist, action, TRUE);
12060 compose_force_window_origin = TRUE;
12062 /* forwarding multiple mails as attachments is done via a
12063 * single compose window */
12064 compose_reply_from_messageview_real(msgview, msginfo_list, action, FALSE);
12068 void compose_check_for_email_account(Compose *compose)
12070 PrefsAccount *ac = NULL, *curr = NULL;
12076 if (compose->account && compose->account->protocol == A_NNTP) {
12077 ac = account_get_cur_account();
12078 if (ac->protocol == A_NNTP) {
12079 list = account_get_list();
12081 for( ; list != NULL ; list = g_list_next(list)) {
12082 curr = (PrefsAccount *) list->data;
12083 if (curr->protocol != A_NNTP) {
12089 combobox_select_by_data(GTK_COMBO_BOX(compose->account_combo),
12094 void compose_reply_to_address(MessageView *msgview, MsgInfo *msginfo,
12095 const gchar *address)
12097 GSList *msginfo_list = NULL;
12098 gchar *body = messageview_get_selection(msgview);
12101 msginfo_list = g_slist_prepend(msginfo_list, msginfo);
12103 compose = compose_reply_mode(COMPOSE_REPLY_TO_ADDRESS, msginfo_list, body);
12104 compose_check_for_email_account(compose);
12105 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
12106 compose_entry_append(compose, address, COMPOSE_TO, PREF_NONE);
12107 compose_reply_set_subject(compose, msginfo);
12110 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
12113 void compose_set_position(Compose *compose, gint pos)
12115 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
12117 gtkut_text_view_set_position(text, pos);
12120 gboolean compose_search_string(Compose *compose,
12121 const gchar *str, gboolean case_sens)
12123 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
12125 return gtkut_text_view_search_string(text, str, case_sens);
12128 gboolean compose_search_string_backward(Compose *compose,
12129 const gchar *str, gboolean case_sens)
12131 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
12133 return gtkut_text_view_search_string_backward(text, str, case_sens);
12136 /* allocate a msginfo structure and populate its data from a compose data structure */
12137 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose)
12139 MsgInfo *newmsginfo;
12141 gchar date[RFC822_DATE_BUFFSIZE];
12143 cm_return_val_if_fail( compose != NULL, NULL );
12145 newmsginfo = procmsg_msginfo_new();
12148 get_rfc822_date(date, sizeof(date));
12149 newmsginfo->date = g_strdup(date);
12152 if (compose->from_name) {
12153 newmsginfo->from = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
12154 newmsginfo->fromname = procheader_get_fromname(newmsginfo->from);
12158 if (compose->subject_entry)
12159 newmsginfo->subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
12161 /* to, cc, reply-to, newsgroups */
12162 for (list = compose->header_list; list; list = list->next) {
12163 gchar *header = gtk_editable_get_chars(
12165 gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
12166 gchar *entry = gtk_editable_get_chars(
12167 GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
12169 if ( strcasecmp(header, prefs_common_translated_header_name("To:")) == 0 ) {
12170 if ( newmsginfo->to == NULL ) {
12171 newmsginfo->to = g_strdup(entry);
12172 } else if (entry && *entry) {
12173 gchar *tmp = g_strconcat(newmsginfo->to, ", ", entry, NULL);
12174 g_free(newmsginfo->to);
12175 newmsginfo->to = tmp;
12178 if ( strcasecmp(header, prefs_common_translated_header_name("Cc:")) == 0 ) {
12179 if ( newmsginfo->cc == NULL ) {
12180 newmsginfo->cc = g_strdup(entry);
12181 } else if (entry && *entry) {
12182 gchar *tmp = g_strconcat(newmsginfo->cc, ", ", entry, NULL);
12183 g_free(newmsginfo->cc);
12184 newmsginfo->cc = tmp;
12187 if ( strcasecmp(header,
12188 prefs_common_translated_header_name("Newsgroups:")) == 0 ) {
12189 if ( newmsginfo->newsgroups == NULL ) {
12190 newmsginfo->newsgroups = g_strdup(entry);
12191 } else if (entry && *entry) {
12192 gchar *tmp = g_strconcat(newmsginfo->newsgroups, ", ", entry, NULL);
12193 g_free(newmsginfo->newsgroups);
12194 newmsginfo->newsgroups = tmp;
12202 /* other data is unset */
12208 /* update compose's dictionaries from folder dict settings */
12209 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
12210 FolderItem *folder_item)
12212 cm_return_if_fail(compose != NULL);
12214 if (compose->gtkaspell && folder_item && folder_item->prefs) {
12215 FolderItemPrefs *prefs = folder_item->prefs;
12217 if (prefs->enable_default_dictionary)
12218 gtkaspell_change_dict(compose->gtkaspell,
12219 prefs->default_dictionary, FALSE);
12220 if (folder_item->prefs->enable_default_alt_dictionary)
12221 gtkaspell_change_alt_dict(compose->gtkaspell,
12222 prefs->default_alt_dictionary);
12223 if (prefs->enable_default_dictionary
12224 || prefs->enable_default_alt_dictionary)
12225 compose_spell_menu_changed(compose);
12230 static void compose_subject_entry_activated(GtkWidget *widget, gpointer data)
12232 Compose *compose = (Compose *)data;
12234 cm_return_if_fail(compose != NULL);
12236 gtk_widget_grab_focus(compose->text);
12239 static void from_name_activate_cb(GtkWidget *widget, gpointer data)
12241 gtk_combo_box_popup(GTK_COMBO_BOX(data));