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 or permission denied\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_num > 0
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_sending_many_recipients_num = 0;
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, tmp;
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 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5647 gtk_text_buffer_get_end_iter(buffer, &end);
5650 /* We make sure that there is a newline at the end. */
5651 if (action == COMPOSE_WRITE_FOR_SEND && gtk_text_iter_backward_char(&tmp)) {
5652 chars = gtk_text_buffer_get_text(buffer, &tmp, &end, FALSE);
5653 if (*chars != '\n') {
5654 gtk_text_buffer_insert(buffer, &end, "\n", 1);
5658 /* get all composed text */
5659 gtk_text_buffer_get_start_iter(buffer, &start);
5660 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5662 out_codeset = conv_get_charset_str(compose->out_encoding);
5664 if (!out_codeset && is_ascii_str(chars)) {
5665 out_codeset = CS_US_ASCII;
5666 } else if (prefs_common.outgoing_fallback_to_ascii &&
5667 is_ascii_str(chars)) {
5668 out_codeset = CS_US_ASCII;
5669 encoding = ENC_7BIT;
5673 gchar *test_conv_global_out = NULL;
5674 gchar *test_conv_reply = NULL;
5676 /* automatic mode. be automatic. */
5677 codeconv_set_strict(TRUE);
5679 out_codeset = conv_get_outgoing_charset_str();
5681 debug_print("trying to convert to %s\n", out_codeset);
5682 test_conv_global_out = conv_codeset_strdup(chars, src_codeset, out_codeset);
5685 if (!test_conv_global_out && compose->orig_charset
5686 && strcmp(compose->orig_charset, CS_US_ASCII)) {
5687 out_codeset = compose->orig_charset;
5688 debug_print("failure; trying to convert to %s\n", out_codeset);
5689 test_conv_reply = conv_codeset_strdup(chars, src_codeset, out_codeset);
5692 if (!test_conv_global_out && !test_conv_reply) {
5694 out_codeset = CS_INTERNAL;
5695 debug_print("failure; finally using %s\n", out_codeset);
5697 g_free(test_conv_global_out);
5698 g_free(test_conv_reply);
5699 codeconv_set_strict(FALSE);
5702 if (encoding == ENC_UNKNOWN) {
5703 if (prefs_common.encoding_method == CTE_BASE64)
5704 encoding = ENC_BASE64;
5705 else if (prefs_common.encoding_method == CTE_QUOTED_PRINTABLE)
5706 encoding = ENC_QUOTED_PRINTABLE;
5707 else if (prefs_common.encoding_method == CTE_8BIT)
5708 encoding = ENC_8BIT;
5710 encoding = procmime_get_encoding_for_charset(out_codeset);
5713 debug_print("src encoding = %s, out encoding = %s, transfer encoding = %s\n",
5714 src_codeset, out_codeset, procmime_get_encoding_str(encoding));
5716 if (action == COMPOSE_WRITE_FOR_SEND) {
5717 codeconv_set_strict(TRUE);
5718 buf = conv_codeset_strdup(chars, src_codeset, out_codeset);
5719 codeconv_set_strict(FALSE);
5724 msg = g_strdup_printf(_("Can't convert the character encoding of the message \n"
5725 "to the specified %s charset.\n"
5726 "Send it as %s?"), out_codeset, src_codeset);
5727 aval = alertpanel_full(_("Error"), msg, GTK_STOCK_CANCEL,
5728 g_strconcat("+", _("_Send"), NULL), NULL, FALSE,
5729 NULL, ALERT_ERROR, G_ALERTDEFAULT);
5732 if (aval != G_ALERTALTERNATE) {
5737 out_codeset = src_codeset;
5743 out_codeset = src_codeset;
5748 if (prefs_common.rewrite_first_from && (encoding == ENC_8BIT || encoding == ENC_7BIT)) {
5749 if (!strncmp(buf, "From ", sizeof("From ")-1) ||
5750 strstr(buf, "\nFrom ") != NULL) {
5751 encoding = ENC_QUOTED_PRINTABLE;
5755 mimetext = procmime_mimeinfo_new();
5756 mimetext->content = MIMECONTENT_MEM;
5757 mimetext->tmp = TRUE; /* must free content later */
5758 /* dup'ed because procmime_encode_content can turn it into a tmpfile
5759 * and free the data, which we need later. */
5760 mimetext->data.mem = g_strdup(buf);
5761 mimetext->type = MIMETYPE_TEXT;
5762 mimetext->subtype = g_strdup("plain");
5763 g_hash_table_insert(mimetext->typeparameters, g_strdup("charset"),
5764 g_strdup(out_codeset));
5766 /* protect trailing spaces when signing message */
5767 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5768 privacy_system_can_sign(compose->privacy_system)) {
5769 encoding = ENC_QUOTED_PRINTABLE;
5773 debug_print("main text: %Id bytes encoded as %s in %d\n",
5775 debug_print("main text: %zd bytes encoded as %s in %d\n",
5777 strlen(buf), out_codeset, encoding);
5779 /* check for line length limit */
5780 if (action == COMPOSE_WRITE_FOR_SEND &&
5781 encoding != ENC_QUOTED_PRINTABLE && encoding != ENC_BASE64 &&
5782 check_line_length(buf, 1000, &line) < 0) {
5785 msg = g_strdup_printf
5786 (_("Line %d exceeds the line length limit (998 bytes).\n"
5787 "The contents of the message might be broken on the way to the delivery.\n"
5789 "Send it anyway?"), line + 1);
5790 aval = alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, GTK_STOCK_OK, NULL);
5792 if (aval != G_ALERTALTERNATE) {
5798 if (encoding != ENC_UNKNOWN)
5799 procmime_encode_content(mimetext, encoding);
5801 /* append attachment parts */
5802 if (compose_use_attach(compose) && attach_parts) {
5803 MimeInfo *mimempart;
5804 gchar *boundary = NULL;
5805 mimempart = procmime_mimeinfo_new();
5806 mimempart->content = MIMECONTENT_EMPTY;
5807 mimempart->type = MIMETYPE_MULTIPART;
5808 mimempart->subtype = g_strdup("mixed");
5812 boundary = generate_mime_boundary(NULL);
5813 } while (strstr(buf, boundary) != NULL);
5815 g_hash_table_insert(mimempart->typeparameters, g_strdup("boundary"),
5818 mimetext->disposition = DISPOSITIONTYPE_INLINE;
5820 g_node_append(mimempart->node, mimetext->node);
5821 g_node_append(mimemsg->node, mimempart->node);
5823 if (compose_add_attachments(compose, mimempart) < 0)
5826 g_node_append(mimemsg->node, mimetext->node);
5830 if (strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) != 0) {
5831 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
5832 /* extract name and address */
5833 if (strstr(spec, " <") && strstr(spec, ">")) {
5834 from_addr = g_strdup(strrchr(spec, '<')+1);
5835 *(strrchr(from_addr, '>')) = '\0';
5836 from_name = g_strdup(spec);
5837 *(strrchr(from_name, '<')) = '\0';
5844 /* sign message if sending */
5845 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5846 privacy_system_can_sign(compose->privacy_system))
5847 if (!privacy_sign(compose->privacy_system, mimemsg,
5848 compose->account, from_addr)) {
5856 if (compose->use_encryption) {
5857 if (compose->encdata != NULL &&
5858 strcmp(compose->encdata, "_DONT_ENCRYPT_")) {
5860 /* First, write an unencrypted copy and save it to outbox, if
5861 * user wants that. */
5862 if (compose->account->save_encrypted_as_clear_text) {
5863 debug_print("saving sent message unencrypted...\n");
5864 FILE *tmpfp = get_tmpfile_in_dir(get_mime_tmp_dir(), &tmp_enc_file);
5868 /* fp now points to a file with headers written,
5869 * let's make a copy. */
5871 content = file_read_stream_to_str(fp);
5873 str_write_to_file(content, tmp_enc_file);
5876 /* Now write the unencrypted body. */
5877 if ((tmpfp = g_fopen(tmp_enc_file, "a")) != NULL) {
5878 procmime_write_mimeinfo(mimemsg, tmpfp);
5881 outbox = folder_find_item_from_identifier(compose_get_save_to(compose));
5883 outbox = folder_get_default_outbox();
5885 procmsg_save_to_outbox(outbox, tmp_enc_file, TRUE);
5886 claws_unlink(tmp_enc_file);
5888 g_warning("Can't open file '%s'", tmp_enc_file);
5891 g_warning("couldn't get tempfile");
5894 if (!privacy_encrypt(compose->privacy_system, mimemsg, compose->encdata)) {
5895 debug_print("Couldn't encrypt mime structure: %s.\n",
5896 privacy_get_error());
5897 alertpanel_error(_("Couldn't encrypt the email: %s"),
5898 privacy_get_error());
5903 procmime_write_mimeinfo(mimemsg, fp);
5905 procmime_mimeinfo_free_all(&mimemsg);
5910 static gint compose_write_body_to_file(Compose *compose, const gchar *file)
5912 GtkTextBuffer *buffer;
5913 GtkTextIter start, end;
5918 if ((fp = g_fopen(file, "wb")) == NULL) {
5919 FILE_OP_ERROR(file, "fopen");
5923 /* chmod for security */
5924 if (change_file_mode_rw(fp, file) < 0) {
5925 FILE_OP_ERROR(file, "chmod");
5926 g_warning("can't change file mode");
5929 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5930 gtk_text_buffer_get_start_iter(buffer, &start);
5931 gtk_text_buffer_get_end_iter(buffer, &end);
5932 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5934 chars = conv_codeset_strdup
5935 (tmp, CS_INTERNAL, conv_get_locale_charset_str());
5944 len = strlen(chars);
5945 if (fwrite(chars, sizeof(gchar), len, fp) != len) {
5946 FILE_OP_ERROR(file, "fwrite");
5955 if (fclose(fp) == EOF) {
5956 FILE_OP_ERROR(file, "fclose");
5963 static gint compose_remove_reedit_target(Compose *compose, gboolean force)
5966 MsgInfo *msginfo = compose->targetinfo;
5968 cm_return_val_if_fail(compose->mode == COMPOSE_REEDIT, -1);
5969 if (!msginfo) return -1;
5971 if (!force && MSG_IS_LOCKED(msginfo->flags))
5974 item = msginfo->folder;
5975 cm_return_val_if_fail(item != NULL, -1);
5977 if (procmsg_msg_exist(msginfo) &&
5978 (folder_has_parent_of_type(item, F_QUEUE) ||
5979 folder_has_parent_of_type(item, F_DRAFT)
5980 || msginfo == compose->autosaved_draft)) {
5981 if (folder_item_remove_msg(item, msginfo->msgnum) < 0) {
5982 g_warning("can't remove the old message");
5985 debug_print("removed reedit target %d\n", msginfo->msgnum);
5992 static void compose_remove_draft(Compose *compose)
5995 MsgInfo *msginfo = compose->targetinfo;
5996 drafts = account_get_special_folder(compose->account, F_DRAFT);
5998 if (procmsg_msg_exist(msginfo)) {
5999 folder_item_remove_msg(drafts, msginfo->msgnum);
6004 gint compose_queue(Compose *compose, gint *msgnum, FolderItem **item, gchar **msgpath,
6005 gboolean remove_reedit_target)
6007 return compose_queue_sub (compose, msgnum, item, msgpath, FALSE, remove_reedit_target);
6010 static gboolean compose_warn_encryption(Compose *compose)
6012 const gchar *warning = privacy_get_encrypt_warning(compose->privacy_system);
6013 AlertValue val = G_ALERTALTERNATE;
6015 if (warning == NULL)
6018 val = alertpanel_full(_("Encryption warning"), warning,
6019 GTK_STOCK_CANCEL, g_strconcat("+", _("C_ontinue"), NULL), NULL,
6020 TRUE, NULL, ALERT_WARNING, G_ALERTALTERNATE);
6021 if (val & G_ALERTDISABLE) {
6022 val &= ~G_ALERTDISABLE;
6023 if (val == G_ALERTALTERNATE)
6024 privacy_inhibit_encrypt_warning(compose->privacy_system,
6028 if (val == G_ALERTALTERNATE) {
6035 static gint compose_queue_sub(Compose *compose, gint *msgnum, FolderItem **item,
6036 gchar **msgpath, gboolean perform_checks,
6037 gboolean remove_reedit_target)
6044 PrefsAccount *mailac = NULL, *newsac = NULL;
6045 gboolean err = FALSE;
6047 debug_print("queueing message...\n");
6048 cm_return_val_if_fail(compose->account != NULL, -1);
6050 if (compose_check_entries(compose, perform_checks) == FALSE) {
6051 if (compose->batch) {
6052 gtk_widget_show_all(compose->window);
6057 if (!compose->to_list && !compose->newsgroup_list) {
6058 g_warning("can't get recipient list.");
6062 if (compose->to_list) {
6063 if (compose->account->protocol != A_NNTP)
6064 mailac = compose->account;
6065 else if (cur_account && cur_account->protocol != A_NNTP)
6066 mailac = cur_account;
6067 else if (!(mailac = compose_current_mail_account())) {
6068 alertpanel_error(_("No account for sending mails available!"));
6073 if (compose->newsgroup_list) {
6074 if (compose->account->protocol == A_NNTP)
6075 newsac = compose->account;
6077 alertpanel_error(_("Selected account isn't NNTP: Posting is impossible."));
6082 /* write queue header */
6083 tmp = g_strdup_printf("%s%cqueue.%p%08x", get_tmp_dir(),
6084 G_DIR_SEPARATOR, compose, (guint) rand());
6085 debug_print("queuing to %s\n", tmp);
6086 if ((fp = g_fopen(tmp, "w+b")) == NULL) {
6087 FILE_OP_ERROR(tmp, "fopen");
6092 if (change_file_mode_rw(fp, tmp) < 0) {
6093 FILE_OP_ERROR(tmp, "chmod");
6094 g_warning("can't change file mode");
6097 /* queueing variables */
6098 err |= (fprintf(fp, "AF:\n") < 0);
6099 err |= (fprintf(fp, "NF:0\n") < 0);
6100 err |= (fprintf(fp, "PS:10\n") < 0);
6101 err |= (fprintf(fp, "SRH:1\n") < 0);
6102 err |= (fprintf(fp, "SFN:\n") < 0);
6103 err |= (fprintf(fp, "DSR:\n") < 0);
6105 err |= (fprintf(fp, "MID:<%s>\n", compose->msgid) < 0);
6107 err |= (fprintf(fp, "MID:\n") < 0);
6108 err |= (fprintf(fp, "CFG:\n") < 0);
6109 err |= (fprintf(fp, "PT:0\n") < 0);
6110 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
6111 err |= (fprintf(fp, "RQ:\n") < 0);
6113 err |= (fprintf(fp, "SSV:%s\n", mailac->smtp_server) < 0);
6115 err |= (fprintf(fp, "SSV:\n") < 0);
6117 err |= (fprintf(fp, "NSV:%s\n", newsac->nntp_server) < 0);
6119 err |= (fprintf(fp, "NSV:\n") < 0);
6120 err |= (fprintf(fp, "SSH:\n") < 0);
6121 /* write recepient list */
6122 if (compose->to_list) {
6123 err |= (fprintf(fp, "R:<%s>", (gchar *)compose->to_list->data) < 0);
6124 for (cur = compose->to_list->next; cur != NULL;
6126 err |= (fprintf(fp, ",<%s>", (gchar *)cur->data) < 0);
6127 err |= (fprintf(fp, "\n") < 0);
6129 /* write newsgroup list */
6130 if (compose->newsgroup_list) {
6131 err |= (fprintf(fp, "NG:") < 0);
6132 err |= (fprintf(fp, "%s", (gchar *)compose->newsgroup_list->data) < 0);
6133 for (cur = compose->newsgroup_list->next; cur != NULL; cur = cur->next)
6134 err |= (fprintf(fp, ",%s", (gchar *)cur->data) < 0);
6135 err |= (fprintf(fp, "\n") < 0);
6137 /* Sylpheed account IDs */
6139 err |= (fprintf(fp, "MAID:%d\n", mailac->account_id) < 0);
6141 err |= (fprintf(fp, "NAID:%d\n", newsac->account_id) < 0);
6144 if (compose->privacy_system != NULL) {
6145 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
6146 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
6147 if (compose->use_encryption) {
6148 if (!compose_warn_encryption(compose)) {
6154 if (mailac && mailac->encrypt_to_self) {
6155 GSList *tmp_list = g_slist_copy(compose->to_list);
6156 tmp_list = g_slist_append(tmp_list, compose->account->address);
6157 compose->encdata = privacy_get_encrypt_data(compose->privacy_system, tmp_list);
6158 g_slist_free(tmp_list);
6160 compose->encdata = privacy_get_encrypt_data(compose->privacy_system, compose->to_list);
6162 if (compose->encdata != NULL) {
6163 if (strcmp(compose->encdata, "_DONT_ENCRYPT_")) {
6164 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
6165 err |= (fprintf(fp, "X-Claws-Encrypt-Data:%s\n",
6166 compose->encdata) < 0);
6167 } /* else we finally dont want to encrypt */
6169 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
6170 /* and if encdata was null, it means there's been a problem in
6173 g_warning("failed to write queue message");
6182 /* Save copy folder */
6183 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
6184 gchar *savefolderid;
6186 savefolderid = compose_get_save_to(compose);
6187 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
6188 g_free(savefolderid);
6190 /* Save copy folder */
6191 if (compose->return_receipt) {
6192 err |= (fprintf(fp, "RRCPT:1\n") < 0);
6194 /* Message-ID of message replying to */
6195 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
6196 gchar *folderid = NULL;
6198 if (compose->replyinfo->folder)
6199 folderid = folder_item_get_identifier(compose->replyinfo->folder);
6200 if (folderid == NULL)
6201 folderid = g_strdup("NULL");
6203 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
6206 /* Message-ID of message forwarding to */
6207 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
6208 gchar *folderid = NULL;
6210 if (compose->fwdinfo->folder)
6211 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
6212 if (folderid == NULL)
6213 folderid = g_strdup("NULL");
6215 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
6219 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
6220 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
6222 /* end of headers */
6223 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
6225 if (compose->redirect_filename != NULL) {
6226 if (compose_redirect_write_to_file(compose, fp) < 0) {
6234 if ((result = compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_SEND, TRUE)) < 0) {
6238 return result - 1; /* -2 for a generic error, -3 for signing error, -4 for encoding */
6242 g_warning("failed to write queue message");
6248 if (fclose(fp) == EOF) {
6249 FILE_OP_ERROR(tmp, "fclose");
6255 if (item && *item) {
6258 queue = account_get_special_folder(compose->account, F_QUEUE);
6261 g_warning("can't find queue folder");
6266 folder_item_scan(queue);
6267 if ((num = folder_item_add_msg(queue, tmp, NULL, FALSE)) < 0) {
6268 g_warning("can't queue the message");
6274 if (msgpath == NULL) {
6280 if (compose->mode == COMPOSE_REEDIT && remove_reedit_target) {
6281 compose_remove_reedit_target(compose, FALSE);
6284 if ((msgnum != NULL) && (item != NULL)) {
6292 static int compose_add_attachments(Compose *compose, MimeInfo *parent)
6295 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
6298 gchar *type, *subtype;
6299 GtkTreeModel *model;
6302 model = gtk_tree_view_get_model(tree_view);
6304 if (!gtk_tree_model_get_iter_first(model, &iter))
6307 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
6309 if (!is_file_exist(ainfo->file)) {
6310 gchar *msg = g_strdup_printf(_("Attachment %s doesn't exist anymore. Ignore?"), ainfo->file);
6311 AlertValue val = alertpanel_full(_("Warning"), msg, _("Cancel sending"), _("Ignore attachment"),
6312 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
6314 if (val == G_ALERTDEFAULT) {
6319 if (g_stat(ainfo->file, &statbuf) < 0)
6322 mimepart = procmime_mimeinfo_new();
6323 mimepart->content = MIMECONTENT_FILE;
6324 mimepart->data.filename = g_strdup(ainfo->file);
6325 mimepart->tmp = FALSE; /* or we destroy our attachment */
6326 mimepart->offset = 0;
6327 mimepart->length = statbuf.st_size;
6329 type = g_strdup(ainfo->content_type);
6331 if (!strchr(type, '/')) {
6333 type = g_strdup("application/octet-stream");
6336 subtype = strchr(type, '/') + 1;
6337 *(subtype - 1) = '\0';
6338 mimepart->type = procmime_get_media_type(type);
6339 mimepart->subtype = g_strdup(subtype);
6342 if (mimepart->type == MIMETYPE_MESSAGE &&
6343 !g_ascii_strcasecmp(mimepart->subtype, "rfc822")) {
6344 mimepart->disposition = DISPOSITIONTYPE_INLINE;
6345 } else if (mimepart->type == MIMETYPE_TEXT) {
6346 if (!ainfo->name && g_ascii_strcasecmp(mimepart->subtype, "plain")) {
6347 /* Text parts with no name come from multipart/alternative
6348 * forwards. Make sure the recipient won't look at the
6349 * original HTML part by mistake. */
6350 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6351 ainfo->name = g_strdup_printf(_("Original %s part"),
6355 g_hash_table_insert(mimepart->typeparameters,
6356 g_strdup("charset"), g_strdup(ainfo->charset));
6358 if (ainfo->name && mimepart->type != MIMETYPE_MESSAGE) {
6359 if (mimepart->type == MIMETYPE_APPLICATION &&
6360 !strcmp2(mimepart->subtype, "octet-stream"))
6361 g_hash_table_insert(mimepart->typeparameters,
6362 g_strdup("name"), g_strdup(ainfo->name));
6363 g_hash_table_insert(mimepart->dispositionparameters,
6364 g_strdup("filename"), g_strdup(ainfo->name));
6365 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6368 if (mimepart->type == MIMETYPE_MESSAGE
6369 || mimepart->type == MIMETYPE_MULTIPART)
6370 ainfo->encoding = ENC_BINARY;
6371 else if (compose->use_signing) {
6372 if (ainfo->encoding == ENC_7BIT)
6373 ainfo->encoding = ENC_QUOTED_PRINTABLE;
6374 else if (ainfo->encoding == ENC_8BIT)
6375 ainfo->encoding = ENC_BASE64;
6378 procmime_encode_content(mimepart, ainfo->encoding);
6380 g_node_append(parent->node, mimepart->node);
6381 } while (gtk_tree_model_iter_next(model, &iter));
6386 static gchar *compose_quote_list_of_addresses(gchar *str)
6388 GSList *list = NULL, *item = NULL;
6389 gchar *qname = NULL, *faddr = NULL, *result = NULL;
6391 list = address_list_append_with_comments(list, str);
6392 for (item = list; item != NULL; item = item->next) {
6393 gchar *spec = item->data;
6394 gchar *endofname = strstr(spec, " <");
6395 if (endofname != NULL) {
6398 QUOTE_IF_REQUIRED_NORMAL(qname, spec, return NULL);
6399 qqname = escape_internal_quotes(qname, '"');
6401 if (*qname != *spec || qqname != qname) { /* has been quoted, compute new */
6402 gchar *addr = g_strdup(endofname);
6403 gchar *name = (qqname != qname)? qqname: g_strdup(qname);
6404 faddr = g_strconcat(name, addr, NULL);
6407 debug_print("new auto-quoted address: '%s'\n", faddr);
6411 result = g_strdup((faddr != NULL)? faddr: spec);
6413 result = g_strconcat(result,
6415 (faddr != NULL)? faddr: spec,
6418 if (faddr != NULL) {
6423 slist_free_strings_full(list);
6428 #define IS_IN_CUSTOM_HEADER(header) \
6429 (compose->account->add_customhdr && \
6430 custom_header_find(compose->account->customhdr_list, header) != NULL)
6432 static const gchar *compose_untranslated_header_name(gchar *header_name)
6434 /* return the untranslated header name, if header_name is a known
6435 header name, in either its translated or untranslated form, with
6436 or without trailing colon. otherwise, returns header_name. */
6437 gchar *translated_header_name;
6438 gchar *translated_header_name_wcolon;
6439 const gchar *untranslated_header_name;
6440 const gchar *untranslated_header_name_wcolon;
6443 cm_return_val_if_fail(header_name != NULL, NULL);
6445 for (i = 0; HEADERS[i].header_name != NULL; i++) {
6446 untranslated_header_name = HEADERS[i].header_name;
6447 untranslated_header_name_wcolon = HEADERS[i].header_name_w_colon;
6449 translated_header_name = gettext(untranslated_header_name);
6450 translated_header_name_wcolon = gettext(untranslated_header_name_wcolon);
6452 if (!strcmp(header_name, untranslated_header_name) ||
6453 !strcmp(header_name, translated_header_name)) {
6454 return untranslated_header_name;
6456 if (!strcmp(header_name, untranslated_header_name_wcolon) ||
6457 !strcmp(header_name, translated_header_name_wcolon)) {
6458 return untranslated_header_name_wcolon;
6462 debug_print("compose_untranslated_header_name: unknown header '%s'\n", header_name);
6466 static void compose_add_headerfield_from_headerlist(Compose *compose,
6468 const gchar *fieldname,
6469 const gchar *seperator)
6471 gchar *str, *fieldname_w_colon;
6472 gboolean add_field = FALSE;
6474 ComposeHeaderEntry *headerentry;
6475 const gchar *headerentryname;
6476 const gchar *trans_fieldname;
6479 if (IS_IN_CUSTOM_HEADER(fieldname))
6482 debug_print("Adding %s-fields\n", fieldname);
6484 fieldstr = g_string_sized_new(64);
6486 fieldname_w_colon = g_strconcat(fieldname, ":", NULL);
6487 trans_fieldname = prefs_common_translated_header_name(fieldname_w_colon);
6489 for (list = compose->header_list; list; list = list->next) {
6490 headerentry = ((ComposeHeaderEntry *)list->data);
6491 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
6493 if (!g_utf8_collate(trans_fieldname, headerentryname)) {
6494 gchar * ustr = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
6496 str = compose_quote_list_of_addresses(ustr);
6498 if (str != NULL && str[0] != '\0') {
6500 g_string_append(fieldstr, seperator);
6501 g_string_append(fieldstr, str);
6510 buf = g_new0(gchar, fieldstr->len * 4 + 256);
6511 compose_convert_header
6512 (compose, buf, fieldstr->len * 4 + 256, fieldstr->str,
6513 strlen(fieldname) + 2, TRUE);
6514 g_string_append_printf(header, "%s: %s\n", fieldname, buf);
6518 g_free(fieldname_w_colon);
6519 g_string_free(fieldstr, TRUE);
6524 static gchar *compose_get_manual_headers_info(Compose *compose)
6526 GString *sh_header = g_string_new(" ");
6528 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6530 for (list = compose->header_list; list; list = list->next) {
6531 ComposeHeaderEntry *headerentry;
6534 gchar *headername_wcolon;
6535 const gchar *headername_trans;
6537 gboolean standard_header = FALSE;
6539 headerentry = ((ComposeHeaderEntry *)list->data);
6541 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6543 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6548 if (!strstr(tmp, ":")) {
6549 headername_wcolon = g_strconcat(tmp, ":", NULL);
6550 headername = g_strdup(tmp);
6552 headername_wcolon = g_strdup(tmp);
6553 headername = g_strdup(strtok(tmp, ":"));
6557 string = std_headers;
6558 while (*string != NULL) {
6559 headername_trans = prefs_common_translated_header_name(*string);
6560 if (!strcmp(headername_trans, headername_wcolon))
6561 standard_header = TRUE;
6564 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
6565 g_string_append_printf(sh_header, "%s ", headername);
6567 g_free(headername_wcolon);
6569 g_string_truncate(sh_header, strlen(sh_header->str) - 1); /* remove last space */
6570 return g_string_free(sh_header, FALSE);
6573 static gchar *compose_get_header(Compose *compose)
6575 gchar date[RFC822_DATE_BUFFSIZE];
6576 gchar buf[BUFFSIZE];
6577 const gchar *entry_str;
6581 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6583 gchar *from_name = NULL, *from_address = NULL;
6586 cm_return_val_if_fail(compose->account != NULL, NULL);
6587 cm_return_val_if_fail(compose->account->address != NULL, NULL);
6589 header = g_string_sized_new(64);
6592 if (prefs_common.hide_timezone)
6593 get_rfc822_date_hide_tz(date, sizeof(date));
6595 get_rfc822_date(date, sizeof(date));
6596 g_string_append_printf(header, "Date: %s\n", date);
6600 if (compose->account->name && *compose->account->name) {
6602 QUOTE_IF_REQUIRED(buf, compose->account->name);
6603 tmp = g_strdup_printf("%s <%s>",
6604 buf, compose->account->address);
6606 tmp = g_strdup_printf("%s",
6607 compose->account->address);
6609 if (!strcmp(gtk_entry_get_text(GTK_ENTRY(compose->from_name)), tmp)
6610 || strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) == 0) {
6612 from_name = compose->account->name ? g_strdup(compose->account->name):NULL;
6613 from_address = g_strdup(compose->account->address);
6615 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
6616 /* extract name and address */
6617 if (strstr(spec, " <") && strstr(spec, ">")) {
6618 from_address = g_strdup(strrchr(spec, '<')+1);
6619 *(strrchr(from_address, '>')) = '\0';
6620 from_name = g_strdup(spec);
6621 *(strrchr(from_name, '<')) = '\0';
6624 from_address = g_strdup(spec);
6631 if (from_name && *from_name) {
6633 compose_convert_header
6634 (compose, buf, sizeof(buf), from_name,
6635 strlen("From: "), TRUE);
6636 QUOTE_IF_REQUIRED(name, buf);
6637 qname = escape_internal_quotes(name, '"');
6639 g_string_append_printf(header, "From: %s <%s>\n",
6640 qname, from_address);
6641 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To") &&
6642 compose->return_receipt) {
6643 compose_convert_header(compose, buf, sizeof(buf), from_name,
6644 strlen("Disposition-Notification-To: "),
6646 g_string_append_printf(header, "Disposition-Notification-To: %s <%s>\n", buf, from_address);
6651 g_string_append_printf(header, "From: %s\n", from_address);
6652 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To") &&
6653 compose->return_receipt)
6654 g_string_append_printf(header, "Disposition-Notification-To: %s\n", from_address);
6658 g_free(from_address);
6661 compose_add_headerfield_from_headerlist(compose, header, "To", ", ");
6664 compose_add_headerfield_from_headerlist(compose, header, "Newsgroups", ",");
6667 compose_add_headerfield_from_headerlist(compose, header, "Cc", ", ");
6671 * If this account is a NNTP account remove Bcc header from
6672 * message body since it otherwise will be publicly shown
6674 if (compose->account->protocol != A_NNTP)
6675 compose_add_headerfield_from_headerlist(compose, header, "Bcc", ", ");
6678 str = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
6680 if (*str != '\0' && !IS_IN_CUSTOM_HEADER("Subject")) {
6683 compose_convert_header(compose, buf, sizeof(buf), str,
6684 strlen("Subject: "), FALSE);
6685 g_string_append_printf(header, "Subject: %s\n", buf);
6691 if (compose->msgid != NULL && strlen(compose->msgid) > 0) {
6692 g_string_append_printf(header, "Message-ID: <%s>\n",
6696 if (compose->remove_references == FALSE) {
6698 if (compose->inreplyto && compose->to_list)
6699 g_string_append_printf(header, "In-Reply-To: <%s>\n", compose->inreplyto);
6702 if (compose->references)
6703 g_string_append_printf(header, "References: %s\n", compose->references);
6707 compose_add_headerfield_from_headerlist(compose, header, "Followup-To", ",");
6710 compose_add_headerfield_from_headerlist(compose, header, "Reply-To", ", ");
6713 if (compose->account->organization &&
6714 strlen(compose->account->organization) &&
6715 !IS_IN_CUSTOM_HEADER("Organization")) {
6716 compose_convert_header(compose, buf, sizeof(buf),
6717 compose->account->organization,
6718 strlen("Organization: "), FALSE);
6719 g_string_append_printf(header, "Organization: %s\n", buf);
6722 /* Program version and system info */
6723 if (compose->account->gen_xmailer &&
6724 g_slist_length(compose->to_list) && !IS_IN_CUSTOM_HEADER("X-Mailer") &&
6725 !compose->newsgroup_list) {
6726 g_string_append_printf(header, "X-Mailer: %s (GTK+ %d.%d.%d; %s)\n",
6728 gtk_major_version, gtk_minor_version, gtk_micro_version,
6731 if (compose->account->gen_xmailer &&
6732 g_slist_length(compose->newsgroup_list) && !IS_IN_CUSTOM_HEADER("X-Newsreader")) {
6733 g_string_append_printf(header, "X-Newsreader: %s (GTK+ %d.%d.%d; %s)\n",
6735 gtk_major_version, gtk_minor_version, gtk_micro_version,
6739 /* custom headers */
6740 if (compose->account->add_customhdr) {
6743 for (cur = compose->account->customhdr_list; cur != NULL;
6745 CustomHeader *chdr = (CustomHeader *)cur->data;
6747 if (custom_header_is_allowed(chdr->name)
6748 && chdr->value != NULL
6749 && *(chdr->value) != '\0') {
6750 compose_convert_header
6751 (compose, buf, sizeof(buf),
6753 strlen(chdr->name) + 2, FALSE);
6754 g_string_append_printf(header, "%s: %s\n", chdr->name, buf);
6759 /* Automatic Faces and X-Faces */
6760 if (get_account_xface (buf, sizeof(buf), compose->account->account_name) == 0) {
6761 g_string_append_printf(header, "X-Face: %s\n", buf);
6763 else if (get_default_xface (buf, sizeof(buf)) == 0) {
6764 g_string_append_printf(header, "X-Face: %s\n", buf);
6766 if (get_account_face (buf, sizeof(buf), compose->account->account_name) == 0) {
6767 g_string_append_printf(header, "Face: %s\n", buf);
6769 else if (get_default_face (buf, sizeof(buf)) == 0) {
6770 g_string_append_printf(header, "Face: %s\n", buf);
6774 switch (compose->priority) {
6775 case PRIORITY_HIGHEST: g_string_append_printf(header, "Importance: high\n"
6776 "X-Priority: 1 (Highest)\n");
6778 case PRIORITY_HIGH: g_string_append_printf(header, "Importance: high\n"
6779 "X-Priority: 2 (High)\n");
6781 case PRIORITY_NORMAL: break;
6782 case PRIORITY_LOW: g_string_append_printf(header, "Importance: low\n"
6783 "X-Priority: 4 (Low)\n");
6785 case PRIORITY_LOWEST: g_string_append_printf(header, "Importance: low\n"
6786 "X-Priority: 5 (Lowest)\n");
6788 default: debug_print("compose: priority unknown : %d\n",
6792 /* get special headers */
6793 for (list = compose->header_list; list; list = list->next) {
6794 ComposeHeaderEntry *headerentry;
6797 gchar *headername_wcolon;
6798 const gchar *headername_trans;
6801 gboolean standard_header = FALSE;
6803 headerentry = ((ComposeHeaderEntry *)list->data);
6805 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6807 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6812 if (!strstr(tmp, ":")) {
6813 headername_wcolon = g_strconcat(tmp, ":", NULL);
6814 headername = g_strdup(tmp);
6816 headername_wcolon = g_strdup(tmp);
6817 headername = g_strdup(strtok(tmp, ":"));
6821 entry_str = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
6822 Xstrdup_a(headervalue, entry_str, return NULL);
6823 subst_char(headervalue, '\r', ' ');
6824 subst_char(headervalue, '\n', ' ');
6825 g_strstrip(headervalue);
6826 if (*headervalue != '\0') {
6827 string = std_headers;
6828 while (*string != NULL && !standard_header) {
6829 headername_trans = prefs_common_translated_header_name(*string);
6830 /* support mixed translated and untranslated headers */
6831 if (!strcmp(headername_trans, headername_wcolon) || !strcmp(*string, headername_wcolon))
6832 standard_header = TRUE;
6835 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername)) {
6836 /* store untranslated header name */
6837 g_string_append_printf(header, "%s %s\n",
6838 compose_untranslated_header_name(headername_wcolon), headervalue);
6842 g_free(headername_wcolon);
6846 g_string_free(header, FALSE);
6851 #undef IS_IN_CUSTOM_HEADER
6853 static void compose_convert_header(Compose *compose, gchar *dest, gint len, gchar *src,
6854 gint header_len, gboolean addr_field)
6856 gchar *tmpstr = NULL;
6857 const gchar *out_codeset = NULL;
6859 cm_return_if_fail(src != NULL);
6860 cm_return_if_fail(dest != NULL);
6862 if (len < 1) return;
6864 tmpstr = g_strdup(src);
6866 subst_char(tmpstr, '\n', ' ');
6867 subst_char(tmpstr, '\r', ' ');
6870 if (!g_utf8_validate(tmpstr, -1, NULL)) {
6871 gchar *mybuf = g_malloc(strlen(tmpstr)*2 +1);
6872 conv_localetodisp(mybuf, strlen(tmpstr)*2 +1, tmpstr);
6877 codeconv_set_strict(TRUE);
6878 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6879 conv_get_charset_str(compose->out_encoding));
6880 codeconv_set_strict(FALSE);
6882 if (!dest || *dest == '\0') {
6883 gchar *test_conv_global_out = NULL;
6884 gchar *test_conv_reply = NULL;
6886 /* automatic mode. be automatic. */
6887 codeconv_set_strict(TRUE);
6889 out_codeset = conv_get_outgoing_charset_str();
6891 debug_print("trying to convert to %s\n", out_codeset);
6892 test_conv_global_out = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6895 if (!test_conv_global_out && compose->orig_charset
6896 && strcmp(compose->orig_charset, CS_US_ASCII)) {
6897 out_codeset = compose->orig_charset;
6898 debug_print("failure; trying to convert to %s\n", out_codeset);
6899 test_conv_reply = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6902 if (!test_conv_global_out && !test_conv_reply) {
6904 out_codeset = CS_INTERNAL;
6905 debug_print("finally using %s\n", out_codeset);
6907 g_free(test_conv_global_out);
6908 g_free(test_conv_reply);
6909 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6911 codeconv_set_strict(FALSE);
6916 static void compose_add_to_addressbook_cb(GtkMenuItem *menuitem, gpointer user_data)
6920 cm_return_if_fail(user_data != NULL);
6922 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(user_data)));
6923 g_strstrip(address);
6924 if (*address != '\0') {
6925 gchar *name = procheader_get_fromname(address);
6926 extract_address(address);
6927 #ifndef USE_ALT_ADDRBOOK
6928 addressbook_add_contact(name, address, NULL, NULL);
6930 debug_print("%s: %s\n", name, address);
6931 if (addressadd_selection(name, address, NULL, NULL)) {
6932 debug_print( "addressbook_add_contact - added\n" );
6939 static void compose_entry_popup_extend(GtkEntry *entry, GtkMenu *menu, gpointer user_data)
6941 GtkWidget *menuitem;
6944 cm_return_if_fail(menu != NULL);
6945 cm_return_if_fail(GTK_IS_MENU_SHELL(menu));
6947 menuitem = gtk_separator_menu_item_new();
6948 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6949 gtk_widget_show(menuitem);
6951 menuitem = gtk_menu_item_new_with_mnemonic(_("Add to address _book"));
6952 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6954 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
6955 g_strstrip(address);
6956 if (*address == '\0') {
6957 gtk_widget_set_sensitive(GTK_WIDGET(menuitem), FALSE);
6960 g_signal_connect(G_OBJECT(menuitem), "activate",
6961 G_CALLBACK(compose_add_to_addressbook_cb), entry);
6962 gtk_widget_show(menuitem);
6965 void compose_add_extra_header(gchar *header, GtkListStore *model)
6968 if (strcmp(header, "")) {
6969 COMBOBOX_ADD(model, header, COMPOSE_TO);
6973 void compose_add_extra_header_entries(GtkListStore *model)
6977 gchar buf[BUFFSIZE];
6980 if (extra_headers == NULL) {
6981 exhrc = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, "extraheaderrc", NULL);
6982 if ((exh = g_fopen(exhrc, "rb")) == NULL) {
6983 debug_print("extra headers file not found\n");
6984 goto extra_headers_done;
6986 while (fgets(buf, BUFFSIZE, exh) != NULL) {
6987 lastc = strlen(buf) - 1; /* remove trailing control chars */
6988 while (lastc >= 0 && buf[lastc] != ':')
6989 buf[lastc--] = '\0';
6990 if (lastc > 0 && buf[0] != '#' && buf[lastc] == ':') {
6991 buf[lastc] = '\0'; /* remove trailing : for comparison */
6992 if (custom_header_is_allowed(buf)) {
6994 extra_headers = g_slist_prepend(extra_headers, g_strdup(buf));
6997 g_message("disallowed extra header line: %s\n", buf);
7001 g_message("invalid extra header line: %s\n", buf);
7007 extra_headers = g_slist_prepend(extra_headers, g_strdup("")); /* end of list */
7008 extra_headers = g_slist_reverse(extra_headers);
7010 g_slist_foreach(extra_headers, (GFunc)compose_add_extra_header, (gpointer)model);
7013 static void compose_create_header_entry(Compose *compose)
7015 gchar *headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
7022 const gchar *header = NULL;
7023 ComposeHeaderEntry *headerentry;
7024 gboolean standard_header = FALSE;
7025 GtkListStore *model;
7028 headerentry = g_new0(ComposeHeaderEntry, 1);
7030 /* Combo box model */
7031 model = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_BOOLEAN);
7032 #if !GTK_CHECK_VERSION(2, 24, 0)
7033 combo = gtk_combo_box_entry_new_with_model(GTK_TREE_MODEL(model), 0);
7035 COMBOBOX_ADD(model, prefs_common_translated_header_name("To:"),
7037 COMBOBOX_ADD(model, prefs_common_translated_header_name("Cc:"),
7039 COMBOBOX_ADD(model, prefs_common_translated_header_name("Bcc:"),
7041 COMBOBOX_ADD(model, prefs_common_translated_header_name("Newsgroups:"),
7042 COMPOSE_NEWSGROUPS);
7043 COMBOBOX_ADD(model, prefs_common_translated_header_name("Reply-To:"),
7045 COMBOBOX_ADD(model, prefs_common_translated_header_name("Followup-To:"),
7046 COMPOSE_FOLLOWUPTO);
7047 compose_add_extra_header_entries(model);
7050 #if GTK_CHECK_VERSION(2, 24, 0)
7051 combo = gtk_combo_box_new_with_model_and_entry(GTK_TREE_MODEL(model));
7052 GtkCellRenderer *cell = gtk_cell_renderer_text_new();
7053 gtk_cell_renderer_set_alignment(cell, 0.0, 0.5);
7054 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), cell, TRUE);
7055 gtk_combo_box_set_entry_text_column(GTK_COMBO_BOX(combo), 0);
7057 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
7058 g_signal_connect(G_OBJECT(gtk_bin_get_child(GTK_BIN(combo))), "grab_focus",
7059 G_CALLBACK(compose_grab_focus_cb), compose);
7060 gtk_widget_show(combo);
7062 /* Putting only the combobox child into focus chain of its parent causes
7063 * the parent to be skipped when changing focus via Tab or Shift+Tab.
7064 * This eliminates need to pres Tab twice in order to really get from the
7065 * combobox to next widget. */
7067 l = g_list_prepend(l, gtk_bin_get_child(GTK_BIN(combo)));
7068 gtk_container_set_focus_chain(GTK_CONTAINER(combo), l);
7071 gtk_table_attach(GTK_TABLE(compose->header_table), combo, 0, 1,
7072 compose->header_nextrow, compose->header_nextrow+1,
7073 GTK_SHRINK, GTK_FILL, 0, 0);
7074 if (compose->header_last && (compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN)) {
7075 const gchar *last_header_entry = gtk_entry_get_text(
7076 GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
7078 while (*string != NULL) {
7079 if (!strcmp(prefs_common_translated_header_name(*string), last_header_entry))
7080 standard_header = TRUE;
7083 if (standard_header)
7084 header = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
7086 if (!compose->header_last || !standard_header) {
7087 switch(compose->account->protocol) {
7089 header = prefs_common_translated_header_name("Newsgroups:");
7092 header = prefs_common_translated_header_name("To:");
7097 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((combo)))), header);
7099 gtk_editable_set_editable(
7100 GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((combo)))),
7101 prefs_common.type_any_header);
7103 g_signal_connect_after(G_OBJECT(gtk_bin_get_child(GTK_BIN((combo)))), "grab_focus",
7104 G_CALLBACK(compose_grab_focus_cb), compose);
7106 /* Entry field with cleanup button */
7107 button = gtk_button_new();
7108 gtk_button_set_image(GTK_BUTTON(button),
7109 gtk_image_new_from_stock(GTK_STOCK_CLEAR, GTK_ICON_SIZE_MENU));
7110 gtk_widget_show(button);
7111 CLAWS_SET_TIP(button,
7112 _("Delete entry contents"));
7113 entry = gtk_entry_new();
7114 gtk_widget_show(entry);
7115 CLAWS_SET_TIP(entry,
7116 _("Use <tab> to autocomplete from addressbook"));
7117 hbox = gtk_hbox_new (FALSE, 0);
7118 gtk_widget_show(hbox);
7119 gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, TRUE, 0);
7120 gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
7121 gtk_table_attach(GTK_TABLE(compose->header_table), hbox, 1, 2,
7122 compose->header_nextrow, compose->header_nextrow+1,
7123 GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
7125 g_signal_connect(G_OBJECT(entry), "key-press-event",
7126 G_CALLBACK(compose_headerentry_key_press_event_cb),
7128 g_signal_connect(G_OBJECT(entry), "changed",
7129 G_CALLBACK(compose_headerentry_changed_cb),
7131 g_signal_connect_after(G_OBJECT(entry), "grab_focus",
7132 G_CALLBACK(compose_grab_focus_cb), compose);
7134 g_signal_connect(G_OBJECT(button), "clicked",
7135 G_CALLBACK(compose_headerentry_button_clicked_cb),
7139 gtk_drag_dest_set(entry, GTK_DEST_DEFAULT_ALL, compose_mime_types,
7140 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7141 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7142 g_signal_connect(G_OBJECT(entry), "drag_data_received",
7143 G_CALLBACK(compose_header_drag_received_cb),
7145 g_signal_connect(G_OBJECT(entry), "drag-drop",
7146 G_CALLBACK(compose_drag_drop),
7148 g_signal_connect(G_OBJECT(entry), "populate-popup",
7149 G_CALLBACK(compose_entry_popup_extend),
7152 address_completion_register_entry(GTK_ENTRY(entry), TRUE);
7154 headerentry->compose = compose;
7155 headerentry->combo = combo;
7156 headerentry->entry = entry;
7157 headerentry->button = button;
7158 headerentry->hbox = hbox;
7159 headerentry->headernum = compose->header_nextrow;
7160 headerentry->type = PREF_NONE;
7162 compose->header_nextrow++;
7163 compose->header_last = headerentry;
7164 compose->header_list =
7165 g_slist_append(compose->header_list,
7169 static void compose_add_header_entry(Compose *compose, const gchar *header,
7170 gchar *text, ComposePrefType pref_type)
7172 ComposeHeaderEntry *last_header = compose->header_last;
7173 gchar *tmp = g_strdup(text), *email;
7174 gboolean replyto_hdr;
7176 replyto_hdr = (!strcasecmp(header,
7177 prefs_common_translated_header_name("Reply-To:")) ||
7179 prefs_common_translated_header_name("Followup-To:")) ||
7181 prefs_common_translated_header_name("In-Reply-To:")));
7183 extract_address(tmp);
7184 email = g_utf8_strdown(tmp, -1);
7186 if (replyto_hdr == FALSE &&
7187 g_hash_table_lookup(compose->email_hashtable, email) != NULL)
7189 debug_print("Ignoring duplicate address - %s %s, pref_type: %d\n",
7190 header, text, (gint) pref_type);
7196 if (!strcasecmp(header, prefs_common_translated_header_name("In-Reply-To:")))
7197 gtk_entry_set_text(GTK_ENTRY(
7198 gtk_bin_get_child(GTK_BIN(last_header->combo))), header);
7200 combobox_select_by_text(GTK_COMBO_BOX(last_header->combo), header);
7201 gtk_entry_set_text(GTK_ENTRY(last_header->entry), text);
7202 last_header->type = pref_type;
7204 if (replyto_hdr == FALSE)
7205 g_hash_table_insert(compose->email_hashtable, email,
7206 GUINT_TO_POINTER(1));
7213 static void compose_destroy_headerentry(Compose *compose,
7214 ComposeHeaderEntry *headerentry)
7216 gchar *text = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
7219 extract_address(text);
7220 email = g_utf8_strdown(text, -1);
7221 g_hash_table_remove(compose->email_hashtable, email);
7225 gtk_widget_destroy(headerentry->combo);
7226 gtk_widget_destroy(headerentry->entry);
7227 gtk_widget_destroy(headerentry->button);
7228 gtk_widget_destroy(headerentry->hbox);
7229 g_free(headerentry);
7232 static void compose_remove_header_entries(Compose *compose)
7235 for (list = compose->header_list; list; list = list->next)
7236 compose_destroy_headerentry(compose, (ComposeHeaderEntry *)list->data);
7238 compose->header_last = NULL;
7239 g_slist_free(compose->header_list);
7240 compose->header_list = NULL;
7241 compose->header_nextrow = 1;
7242 compose_create_header_entry(compose);
7245 static GtkWidget *compose_create_header(Compose *compose)
7247 GtkWidget *from_optmenu_hbox;
7248 GtkWidget *header_table_main;
7249 GtkWidget *header_scrolledwin;
7250 GtkWidget *header_table;
7252 /* parent with account selection and from header */
7253 header_table_main = gtk_table_new(2, 2, FALSE);
7254 gtk_widget_show(header_table_main);
7255 gtk_container_set_border_width(GTK_CONTAINER(header_table_main), BORDER_WIDTH);
7257 from_optmenu_hbox = compose_account_option_menu_create(compose);
7258 gtk_table_attach(GTK_TABLE(header_table_main), from_optmenu_hbox,
7259 0, 2, 0, 1, GTK_EXPAND | GTK_FILL, GTK_SHRINK, 0, 0);
7261 /* child with header labels and entries */
7262 header_scrolledwin = gtk_scrolled_window_new(NULL, NULL);
7263 gtk_widget_show(header_scrolledwin);
7264 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
7266 header_table = gtk_table_new(2, 2, FALSE);
7267 gtk_widget_show(header_table);
7268 gtk_container_set_border_width(GTK_CONTAINER(header_table), 0);
7269 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin), header_table);
7270 gtk_container_set_focus_vadjustment(GTK_CONTAINER(header_table),
7271 gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(header_scrolledwin)));
7272 gtk_viewport_set_shadow_type(GTK_VIEWPORT(gtk_bin_get_child(GTK_BIN(header_scrolledwin))), GTK_SHADOW_NONE);
7274 gtk_table_attach(GTK_TABLE(header_table_main), header_scrolledwin,
7275 0, 2, 1, 2, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 2);
7277 compose->header_table = header_table;
7278 compose->header_list = NULL;
7279 compose->header_nextrow = 0;
7281 compose_create_header_entry(compose);
7283 compose->table = NULL;
7285 return header_table_main;
7288 static gboolean popup_attach_button_pressed(GtkWidget *widget, gpointer data)
7290 Compose *compose = (Compose *)data;
7291 GdkEventButton event;
7294 event.time = gtk_get_current_event_time();
7296 return attach_button_pressed(compose->attach_clist, &event, compose);
7299 static GtkWidget *compose_create_attach(Compose *compose)
7301 GtkWidget *attach_scrwin;
7302 GtkWidget *attach_clist;
7304 GtkListStore *store;
7305 GtkCellRenderer *renderer;
7306 GtkTreeViewColumn *column;
7307 GtkTreeSelection *selection;
7309 /* attachment list */
7310 attach_scrwin = gtk_scrolled_window_new(NULL, NULL);
7311 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(attach_scrwin),
7312 GTK_POLICY_AUTOMATIC,
7313 GTK_POLICY_AUTOMATIC);
7314 gtk_widget_set_size_request(attach_scrwin, -1, 80);
7316 store = gtk_list_store_new(N_ATTACH_COLS,
7322 G_TYPE_AUTO_POINTER,
7324 attach_clist = GTK_WIDGET(gtk_tree_view_new_with_model
7325 (GTK_TREE_MODEL(store)));
7326 gtk_container_add(GTK_CONTAINER(attach_scrwin), attach_clist);
7327 g_object_unref(store);
7329 renderer = gtk_cell_renderer_text_new();
7330 column = gtk_tree_view_column_new_with_attributes
7331 (_("Mime type"), renderer, "text",
7332 COL_MIMETYPE, NULL);
7333 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7335 renderer = gtk_cell_renderer_text_new();
7336 column = gtk_tree_view_column_new_with_attributes
7337 (_("Size"), renderer, "text",
7339 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7341 renderer = gtk_cell_renderer_text_new();
7342 column = gtk_tree_view_column_new_with_attributes
7343 (_("Name"), renderer, "text",
7345 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7347 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(attach_clist),
7348 prefs_common.use_stripes_everywhere);
7349 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(attach_clist));
7350 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
7352 g_signal_connect(G_OBJECT(attach_clist), "row_activated",
7353 G_CALLBACK(attach_selected), compose);
7354 g_signal_connect(G_OBJECT(attach_clist), "button_press_event",
7355 G_CALLBACK(attach_button_pressed), compose);
7356 g_signal_connect(G_OBJECT(attach_clist), "popup-menu",
7357 G_CALLBACK(popup_attach_button_pressed), compose);
7358 g_signal_connect(G_OBJECT(attach_clist), "key_press_event",
7359 G_CALLBACK(attach_key_pressed), compose);
7362 gtk_drag_dest_set(attach_clist,
7363 GTK_DEST_DEFAULT_ALL, compose_mime_types,
7364 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7365 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7366 g_signal_connect(G_OBJECT(attach_clist), "drag_data_received",
7367 G_CALLBACK(compose_attach_drag_received_cb),
7369 g_signal_connect(G_OBJECT(attach_clist), "drag-drop",
7370 G_CALLBACK(compose_drag_drop),
7373 compose->attach_scrwin = attach_scrwin;
7374 compose->attach_clist = attach_clist;
7376 return attach_scrwin;
7379 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose);
7380 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose);
7382 static GtkWidget *compose_create_others(Compose *compose)
7385 GtkWidget *savemsg_checkbtn;
7386 GtkWidget *savemsg_combo;
7387 GtkWidget *savemsg_select;
7390 gchar *folderidentifier;
7392 /* Table for settings */
7393 table = gtk_table_new(3, 1, FALSE);
7394 gtk_container_set_border_width(GTK_CONTAINER(table), BORDER_WIDTH);
7395 gtk_widget_show(table);
7396 gtk_table_set_row_spacings(GTK_TABLE(table), VSPACING_NARROW);
7399 /* Save Message to folder */
7400 savemsg_checkbtn = gtk_check_button_new_with_label(_("Save Message to "));
7401 gtk_widget_show(savemsg_checkbtn);
7402 gtk_table_attach(GTK_TABLE(table), savemsg_checkbtn, 0, 1, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7403 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7404 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), prefs_common.savemsg);
7406 g_signal_connect(G_OBJECT(savemsg_checkbtn), "toggled",
7407 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
7409 #if !GTK_CHECK_VERSION(2, 24, 0)
7410 savemsg_combo = gtk_combo_box_entry_new_text();
7412 savemsg_combo = gtk_combo_box_text_new_with_entry();
7414 compose->savemsg_checkbtn = savemsg_checkbtn;
7415 compose->savemsg_combo = savemsg_combo;
7416 gtk_widget_show(savemsg_combo);
7418 if (prefs_common.compose_save_to_history)
7419 #if !GTK_CHECK_VERSION(2, 24, 0)
7420 combobox_set_popdown_strings(GTK_COMBO_BOX(savemsg_combo),
7421 prefs_common.compose_save_to_history);
7423 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(savemsg_combo),
7424 prefs_common.compose_save_to_history);
7426 gtk_table_attach(GTK_TABLE(table), savemsg_combo, 1, 2, rowcount, rowcount + 1, GTK_FILL|GTK_EXPAND, GTK_SHRINK, 0, 0);
7427 gtk_widget_set_sensitive(GTK_WIDGET(savemsg_combo), prefs_common.savemsg);
7428 g_signal_connect_after(G_OBJECT(savemsg_combo), "grab_focus",
7429 G_CALLBACK(compose_grab_focus_cb), compose);
7430 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7431 folderidentifier = folder_item_get_identifier(account_get_special_folder
7432 (compose->account, F_OUTBOX));
7433 compose_set_save_to(compose, folderidentifier);
7434 g_free(folderidentifier);
7437 savemsg_select = gtkut_get_browse_file_btn(_("_Browse"));
7438 gtk_widget_show(savemsg_select);
7439 gtk_table_attach(GTK_TABLE(table), savemsg_select, 2, 3, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7440 g_signal_connect(G_OBJECT(savemsg_select), "clicked",
7441 G_CALLBACK(compose_savemsg_select_cb),
7447 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose)
7449 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo),
7450 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn)));
7453 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose)
7458 dest = foldersel_folder_sel(NULL, FOLDER_SEL_COPY, NULL, FALSE,
7459 _("Select folder to save message to"));
7462 path = folder_item_get_identifier(dest);
7464 compose_set_save_to(compose, path);
7468 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry, gboolean wrap,
7469 GdkAtom clip, GtkTextIter *insert_place);
7472 static gboolean text_clicked(GtkWidget *text, GdkEventButton *event,
7476 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
7478 if (event->button == 3) {
7480 GtkTextIter sel_start, sel_end;
7481 gboolean stuff_selected;
7483 /* move the cursor to allow GtkAspell to check the word
7484 * under the mouse */
7485 if (event->x && event->y) {
7486 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7487 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7489 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7492 GtkTextMark *mark = gtk_text_buffer_get_insert(buffer);
7493 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7496 stuff_selected = gtk_text_buffer_get_selection_bounds(
7498 &sel_start, &sel_end);
7500 gtk_text_buffer_place_cursor (buffer, &iter);
7501 /* reselect stuff */
7503 && gtk_text_iter_in_range(&iter, &sel_start, &sel_end)) {
7504 gtk_text_buffer_select_range(buffer,
7505 &sel_start, &sel_end);
7507 return FALSE; /* pass the event so that the right-click goes through */
7510 if (event->button == 2) {
7515 /* get the middle-click position to paste at the correct place */
7516 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7517 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7519 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7522 entry_paste_clipboard(compose, text,
7523 prefs_common.linewrap_pastes,
7524 GDK_SELECTION_PRIMARY, &iter);
7532 static void compose_spell_menu_changed(void *data)
7534 Compose *compose = (Compose *)data;
7536 GtkWidget *menuitem;
7537 GtkWidget *parent_item;
7538 GtkMenu *menu = GTK_MENU(gtk_menu_new());
7541 if (compose->gtkaspell == NULL)
7544 parent_item = gtk_ui_manager_get_widget(compose->ui_manager,
7545 "/Menu/Spelling/Options");
7547 /* setting the submenu removes /Spelling/Options from the factory
7548 * so we need to save it */
7550 if (parent_item == NULL) {
7551 parent_item = compose->aspell_options_menu;
7552 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), NULL);
7554 compose->aspell_options_menu = parent_item;
7556 spell_menu = gtkaspell_make_config_menu(compose->gtkaspell);
7558 spell_menu = g_slist_reverse(spell_menu);
7559 for (items = spell_menu;
7560 items; items = items->next) {
7561 menuitem = GTK_WIDGET(GTK_MENU_ITEM(items->data));
7562 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), GTK_WIDGET(menuitem));
7563 gtk_widget_show(GTK_WIDGET(menuitem));
7565 g_slist_free(spell_menu);
7567 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), GTK_WIDGET(menu));
7568 gtk_widget_show(parent_item);
7571 static void compose_dict_changed(void *data)
7573 Compose *compose = (Compose *) data;
7575 if(!compose->gtkaspell)
7577 if(compose->gtkaspell->recheck_when_changing_dict == FALSE)
7580 gtkaspell_highlight_all(compose->gtkaspell);
7581 claws_spell_entry_recheck_all(CLAWS_SPELL_ENTRY(compose->subject_entry));
7585 static gboolean compose_popup_menu(GtkWidget *widget, gpointer data)
7587 Compose *compose = (Compose *)data;
7588 GdkEventButton event;
7591 event.time = gtk_get_current_event_time();
7595 return text_clicked(compose->text, &event, compose);
7598 static gboolean compose_force_window_origin = TRUE;
7599 static Compose *compose_create(PrefsAccount *account,
7608 GtkWidget *handlebox;
7610 GtkWidget *notebook;
7612 GtkWidget *attach_hbox;
7613 GtkWidget *attach_lab1;
7614 GtkWidget *attach_lab2;
7619 GtkWidget *subject_hbox;
7620 GtkWidget *subject_frame;
7621 GtkWidget *subject_entry;
7625 GtkWidget *edit_vbox;
7626 GtkWidget *ruler_hbox;
7628 GtkWidget *scrolledwin;
7630 GtkTextBuffer *buffer;
7631 GtkClipboard *clipboard;
7633 UndoMain *undostruct;
7635 GtkWidget *popupmenu;
7636 GtkWidget *tmpl_menu;
7637 GtkActionGroup *action_group = NULL;
7640 GtkAspell * gtkaspell = NULL;
7643 static GdkGeometry geometry;
7645 cm_return_val_if_fail(account != NULL, NULL);
7647 gtkut_convert_int_to_gdk_color(prefs_common.default_header_bgcolor,
7648 &default_header_bgcolor);
7649 gtkut_convert_int_to_gdk_color(prefs_common.default_header_color,
7650 &default_header_color);
7652 debug_print("Creating compose window...\n");
7653 compose = g_new0(Compose, 1);
7655 compose->batch = batch;
7656 compose->account = account;
7657 compose->folder = folder;
7659 compose->mutex = cm_mutex_new();
7660 compose->set_cursor_pos = -1;
7662 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose");
7664 gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
7665 gtk_widget_set_size_request(window, prefs_common.compose_width,
7666 prefs_common.compose_height);
7668 if (!geometry.max_width) {
7669 geometry.max_width = gdk_screen_width();
7670 geometry.max_height = gdk_screen_height();
7673 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7674 &geometry, GDK_HINT_MAX_SIZE);
7675 if (!geometry.min_width) {
7676 geometry.min_width = 600;
7677 geometry.min_height = 440;
7679 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7680 &geometry, GDK_HINT_MIN_SIZE);
7682 #ifndef GENERIC_UMPC
7683 if (compose_force_window_origin)
7684 gtk_window_move(GTK_WINDOW(window), prefs_common.compose_x,
7685 prefs_common.compose_y);
7687 g_signal_connect(G_OBJECT(window), "delete_event",
7688 G_CALLBACK(compose_delete_cb), compose);
7689 MANAGE_WINDOW_SIGNALS_CONNECT(window);
7690 gtk_widget_realize(window);
7692 gtkut_widget_set_composer_icon(window);
7694 vbox = gtk_vbox_new(FALSE, 0);
7695 gtk_container_add(GTK_CONTAINER(window), vbox);
7697 compose->ui_manager = gtk_ui_manager_new();
7698 action_group = cm_menu_create_action_group_full(compose->ui_manager,"Menu", compose_entries,
7699 G_N_ELEMENTS(compose_entries), (gpointer)compose);
7700 gtk_action_group_add_toggle_actions(action_group, compose_toggle_entries,
7701 G_N_ELEMENTS(compose_toggle_entries), (gpointer)compose);
7702 gtk_action_group_add_radio_actions(action_group, compose_radio_rm_entries,
7703 G_N_ELEMENTS(compose_radio_rm_entries), COMPOSE_REPLY, G_CALLBACK(compose_reply_change_mode_cb), (gpointer)compose);
7704 gtk_action_group_add_radio_actions(action_group, compose_radio_prio_entries,
7705 G_N_ELEMENTS(compose_radio_prio_entries), PRIORITY_NORMAL, G_CALLBACK(compose_set_priority_cb), (gpointer)compose);
7706 gtk_action_group_add_radio_actions(action_group, compose_radio_enc_entries,
7707 G_N_ELEMENTS(compose_radio_enc_entries), C_AUTO, G_CALLBACK(compose_set_encoding_cb), (gpointer)compose);
7709 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Menu", NULL, GTK_UI_MANAGER_MENUBAR)
7711 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Message", "Message", GTK_UI_MANAGER_MENU)
7712 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Edit", "Edit", GTK_UI_MANAGER_MENU)
7714 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Spelling", "Spelling", GTK_UI_MANAGER_MENU)
7716 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Options", "Options", GTK_UI_MANAGER_MENU)
7717 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Tools", "Tools", GTK_UI_MANAGER_MENU)
7718 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Help", "Help", GTK_UI_MANAGER_MENU)
7721 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Send", "Message/Send", GTK_UI_MANAGER_MENUITEM)
7722 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "SendLater", "Message/SendLater", GTK_UI_MANAGER_MENUITEM)
7723 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator1", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7724 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "AttachFile", "Message/AttachFile", GTK_UI_MANAGER_MENUITEM)
7725 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertFile", "Message/InsertFile", GTK_UI_MANAGER_MENUITEM)
7726 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertSig", "Message/InsertSig", GTK_UI_MANAGER_MENUITEM)
7727 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "ReplaceSig", "Message/ReplaceSig", GTK_UI_MANAGER_MENUITEM)
7728 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator2", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7729 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Save", "Message/Save", GTK_UI_MANAGER_MENUITEM)
7730 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator3", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7731 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Print", "Message/Print", GTK_UI_MANAGER_MENUITEM)
7732 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator4", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7733 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Close", "Message/Close", GTK_UI_MANAGER_MENUITEM)
7736 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Undo", "Edit/Undo", GTK_UI_MANAGER_MENUITEM)
7737 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Redo", "Edit/Redo", GTK_UI_MANAGER_MENUITEM)
7738 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator1", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7740 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Cut", "Edit/Cut", GTK_UI_MANAGER_MENUITEM)
7741 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Copy", "Edit/Copy", GTK_UI_MANAGER_MENUITEM)
7742 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Paste", "Edit/Paste", GTK_UI_MANAGER_MENUITEM)
7744 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SpecialPaste", "Edit/SpecialPaste", GTK_UI_MANAGER_MENU)
7745 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "AsQuotation", "Edit/SpecialPaste/AsQuotation", GTK_UI_MANAGER_MENUITEM)
7746 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Wrapped", "Edit/SpecialPaste/Wrapped", GTK_UI_MANAGER_MENUITEM)
7747 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Unwrapped", "Edit/SpecialPaste/Unwrapped", GTK_UI_MANAGER_MENUITEM)
7749 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SelectAll", "Edit/SelectAll", GTK_UI_MANAGER_MENUITEM)
7751 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Advanced", "Edit/Advanced", GTK_UI_MANAGER_MENU)
7752 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackChar", "Edit/Advanced/BackChar", GTK_UI_MANAGER_MENUITEM)
7753 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwChar", "Edit/Advanced/ForwChar", GTK_UI_MANAGER_MENUITEM)
7754 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackWord", "Edit/Advanced/BackWord", GTK_UI_MANAGER_MENUITEM)
7755 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwWord", "Edit/Advanced/ForwWord", GTK_UI_MANAGER_MENUITEM)
7756 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BegLine", "Edit/Advanced/BegLine", GTK_UI_MANAGER_MENUITEM)
7757 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "EndLine", "Edit/Advanced/EndLine", GTK_UI_MANAGER_MENUITEM)
7758 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "PrevLine", "Edit/Advanced/PrevLine", GTK_UI_MANAGER_MENUITEM)
7759 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "NextLine", "Edit/Advanced/NextLine", GTK_UI_MANAGER_MENUITEM)
7760 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackChar", "Edit/Advanced/DelBackChar", GTK_UI_MANAGER_MENUITEM)
7761 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwChar", "Edit/Advanced/DelForwChar", GTK_UI_MANAGER_MENUITEM)
7762 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackWord", "Edit/Advanced/DelBackWord", GTK_UI_MANAGER_MENUITEM)
7763 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwWord", "Edit/Advanced/DelForwWord", GTK_UI_MANAGER_MENUITEM)
7764 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelLine", "Edit/Advanced/DelLine", GTK_UI_MANAGER_MENUITEM)
7765 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelEndLine", "Edit/Advanced/DelEndLine", GTK_UI_MANAGER_MENUITEM)
7767 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator2", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7769 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Find", "Edit/Find", GTK_UI_MANAGER_MENUITEM)
7770 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapPara", "Edit/WrapPara", GTK_UI_MANAGER_MENUITEM)
7771 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapAllLines", "Edit/WrapAllLines", GTK_UI_MANAGER_MENUITEM)
7772 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoWrap", "Edit/AutoWrap", GTK_UI_MANAGER_MENUITEM)
7773 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoIndent", "Edit/AutoIndent", GTK_UI_MANAGER_MENUITEM)
7775 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator3", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7777 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "ExtEditor", "Edit/ExtEditor", GTK_UI_MANAGER_MENUITEM)
7781 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckAllSel", "Spelling/CheckAllSel", GTK_UI_MANAGER_MENUITEM)
7782 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "HighlightAll", "Spelling/HighlightAll", GTK_UI_MANAGER_MENUITEM)
7783 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckBackwards", "Spelling/CheckBackwards", GTK_UI_MANAGER_MENUITEM)
7784 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "ForwardNext", "Spelling/ForwardNext", GTK_UI_MANAGER_MENUITEM)
7785 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Separator1", "Spelling/---", GTK_UI_MANAGER_SEPARATOR)
7786 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Options", "Spelling/Options", GTK_UI_MANAGER_MENU)
7790 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "ReplyMode", "Options/ReplyMode", GTK_UI_MANAGER_MENU)
7791 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Normal", "Options/ReplyMode/Normal", GTK_UI_MANAGER_MENUITEM)
7792 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "All", "Options/ReplyMode/All", GTK_UI_MANAGER_MENUITEM)
7793 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Sender", "Options/ReplyMode/Sender", GTK_UI_MANAGER_MENUITEM)
7794 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "List", "Options/ReplyMode/List", GTK_UI_MANAGER_MENUITEM)
7796 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator1", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7797 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "PrivacySystem", "Options/PrivacySystem", GTK_UI_MANAGER_MENU)
7798 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/PrivacySystem", "PlaceHolder", "Options/PrivacySystem/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7799 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Sign", "Options/Sign", GTK_UI_MANAGER_MENUITEM)
7800 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encrypt", "Options/Encrypt", GTK_UI_MANAGER_MENUITEM)
7803 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator2", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7804 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Priority", "Options/Priority", GTK_UI_MANAGER_MENU)
7805 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Highest", "Options/Priority/Highest", GTK_UI_MANAGER_MENUITEM)
7806 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "High", "Options/Priority/High", GTK_UI_MANAGER_MENUITEM)
7807 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Normal", "Options/Priority/Normal", GTK_UI_MANAGER_MENUITEM)
7808 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Low", "Options/Priority/Low", GTK_UI_MANAGER_MENUITEM)
7809 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Lowest", "Options/Priority/Lowest", GTK_UI_MANAGER_MENUITEM)
7811 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator3", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7812 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RequestRetRcpt", "Options/RequestRetRcpt", GTK_UI_MANAGER_MENUITEM)
7813 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator4", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7814 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RemoveReferences", "Options/RemoveReferences", GTK_UI_MANAGER_MENUITEM)
7815 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator5", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7817 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encoding", "Options/Encoding", GTK_UI_MANAGER_MENU)
7819 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_AUTO, "Options/Encoding/"CS_AUTO, GTK_UI_MANAGER_MENUITEM)
7820 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator1", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7821 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_US_ASCII, "Options/Encoding/"CS_US_ASCII, GTK_UI_MANAGER_MENUITEM)
7822 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_UTF_8, "Options/Encoding/"CS_UTF_8, GTK_UI_MANAGER_MENUITEM)
7823 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator2", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7825 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Western", "Options/Encoding/Western", GTK_UI_MANAGER_MENU)
7826 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)
7827 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)
7828 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Western", CS_WINDOWS_1252, "Options/Encoding/Western/"CS_WINDOWS_1252, GTK_UI_MANAGER_MENUITEM)
7830 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_2, "Options/Encoding/"CS_ISO_8859_2, GTK_UI_MANAGER_MENUITEM)
7832 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Baltic", "Options/Encoding/Baltic", GTK_UI_MANAGER_MENU)
7833 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)
7834 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)
7836 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_7, "Options/Encoding/"CS_ISO_8859_7, GTK_UI_MANAGER_MENUITEM)
7838 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Hebrew", "Options/Encoding/Hebrew", GTK_UI_MANAGER_MENU)
7839 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)
7840 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Hebrew", CS_WINDOWS_1255, "Options/Encoding/Hebrew/"CS_WINDOWS_1255, GTK_UI_MANAGER_MENUITEM)
7842 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Arabic", "Options/Encoding/Arabic", GTK_UI_MANAGER_MENU)
7843 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)
7844 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Arabic", CS_WINDOWS_1256, "Options/Encoding/Arabic/"CS_WINDOWS_1256, GTK_UI_MANAGER_MENUITEM)
7846 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_9, "Options/Encoding/"CS_ISO_8859_9, GTK_UI_MANAGER_MENUITEM)
7848 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Cyrillic", "Options/Encoding/Cyrillic", GTK_UI_MANAGER_MENU)
7849 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)
7850 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_R, "Options/Encoding/Cyrillic/"CS_KOI8_R, GTK_UI_MANAGER_MENUITEM)
7851 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_MACCYR, "Options/Encoding/Cyrillic/"CS_MACCYR, GTK_UI_MANAGER_MENUITEM)
7852 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_U, "Options/Encoding/Cyrillic/"CS_KOI8_U, GTK_UI_MANAGER_MENUITEM)
7853 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_WINDOWS_1251, "Options/Encoding/Cyrillic/"CS_WINDOWS_1251, GTK_UI_MANAGER_MENUITEM)
7855 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Japanese", "Options/Encoding/Japanese", GTK_UI_MANAGER_MENU)
7856 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)
7857 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)
7858 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_EUC_JP, "Options/Encoding/Japanese/"CS_EUC_JP, GTK_UI_MANAGER_MENUITEM)
7859 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_SHIFT_JIS, "Options/Encoding/Japanese/"CS_SHIFT_JIS, GTK_UI_MANAGER_MENUITEM)
7861 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Chinese", "Options/Encoding/Chinese", GTK_UI_MANAGER_MENU)
7862 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB18030, "Options/Encoding/Chinese/"CS_GB18030, GTK_UI_MANAGER_MENUITEM)
7863 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB2312, "Options/Encoding/Chinese/"CS_GB2312, GTK_UI_MANAGER_MENUITEM)
7864 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GBK, "Options/Encoding/Chinese/"CS_GBK, GTK_UI_MANAGER_MENUITEM)
7865 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_BIG5, "Options/Encoding/Chinese/"CS_BIG5, GTK_UI_MANAGER_MENUITEM)
7866 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_EUC_TW, "Options/Encoding/Chinese/"CS_EUC_TW, GTK_UI_MANAGER_MENUITEM)
7868 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Korean", "Options/Encoding/Korean", GTK_UI_MANAGER_MENU)
7869 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Korean", CS_EUC_KR, "Options/Encoding/Korean/"CS_EUC_KR, GTK_UI_MANAGER_MENUITEM)
7870 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)
7872 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Thai", "Options/Encoding/Thai", GTK_UI_MANAGER_MENU)
7873 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_TIS_620, "Options/Encoding/Thai/"CS_TIS_620, GTK_UI_MANAGER_MENUITEM)
7874 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_WINDOWS_874, "Options/Encoding/Thai/"CS_WINDOWS_874, GTK_UI_MANAGER_MENUITEM)
7878 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "ShowRuler", "Tools/ShowRuler", GTK_UI_MANAGER_MENUITEM)
7879 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "AddressBook", "Tools/AddressBook", GTK_UI_MANAGER_MENUITEM)
7880 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Template", "Tools/Template", GTK_UI_MANAGER_MENU)
7881 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Template", "PlaceHolder", "Tools/Template/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7882 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Actions", "Tools/Actions", GTK_UI_MANAGER_MENU)
7883 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Actions", "PlaceHolder", "Tools/Actions/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7886 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Help", "About", "Help/About", GTK_UI_MANAGER_MENUITEM)
7888 menubar = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu");
7889 gtk_widget_show_all(menubar);
7891 gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(compose->ui_manager));
7892 gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, TRUE, 0);
7894 if (prefs_common.toolbar_detachable) {
7895 handlebox = gtk_handle_box_new();
7897 handlebox = gtk_hbox_new(FALSE, 0);
7899 gtk_box_pack_start(GTK_BOX(vbox), handlebox, FALSE, FALSE, 0);
7901 gtk_widget_realize(handlebox);
7902 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, handlebox,
7905 vbox2 = gtk_vbox_new(FALSE, 2);
7906 gtk_box_pack_start(GTK_BOX(vbox), vbox2, TRUE, TRUE, 0);
7907 gtk_container_set_border_width(GTK_CONTAINER(vbox2), 0);
7910 notebook = gtk_notebook_new();
7911 gtk_widget_show(notebook);
7913 /* header labels and entries */
7914 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7915 compose_create_header(compose),
7916 gtk_label_new_with_mnemonic(_("Hea_der")));
7917 /* attachment list */
7918 attach_hbox = gtk_hbox_new(FALSE, 0);
7919 gtk_widget_show(attach_hbox);
7921 attach_lab1 = gtk_label_new_with_mnemonic(_("_Attachments"));
7922 gtk_widget_show(attach_lab1);
7923 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab1, TRUE, TRUE, 0);
7925 attach_lab2 = gtk_label_new("");
7926 gtk_widget_show(attach_lab2);
7927 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab2, FALSE, FALSE, 0);
7929 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7930 compose_create_attach(compose),
7933 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7934 compose_create_others(compose),
7935 gtk_label_new_with_mnemonic(_("Othe_rs")));
7938 subject_hbox = gtk_hbox_new(FALSE, 0);
7939 gtk_widget_show(subject_hbox);
7941 subject_frame = gtk_frame_new(NULL);
7942 gtk_frame_set_shadow_type(GTK_FRAME(subject_frame), GTK_SHADOW_NONE);
7943 gtk_box_pack_start(GTK_BOX(subject_hbox), subject_frame, TRUE, TRUE, 0);
7944 gtk_widget_show(subject_frame);
7946 subject = gtk_hbox_new(FALSE, HSPACING_NARROW);
7947 gtk_container_set_border_width(GTK_CONTAINER(subject), 0);
7948 gtk_widget_show(subject);
7950 label = gtk_label_new_with_mnemonic(_("S_ubject:"));
7951 gtk_box_pack_start(GTK_BOX(subject), label, FALSE, FALSE, 0);
7952 gtk_widget_show(label);
7955 subject_entry = claws_spell_entry_new();
7957 subject_entry = gtk_entry_new();
7959 gtk_box_pack_start(GTK_BOX(subject), subject_entry, TRUE, TRUE, 0);
7960 g_signal_connect_after(G_OBJECT(subject_entry), "grab_focus",
7961 G_CALLBACK(compose_grab_focus_cb), compose);
7962 gtk_label_set_mnemonic_widget(GTK_LABEL(label), subject_entry);
7963 gtk_widget_show(subject_entry);
7964 compose->subject_entry = subject_entry;
7965 gtk_container_add(GTK_CONTAINER(subject_frame), subject);
7967 edit_vbox = gtk_vbox_new(FALSE, 0);
7969 gtk_box_pack_start(GTK_BOX(edit_vbox), subject_hbox, FALSE, FALSE, 0);
7972 ruler_hbox = gtk_hbox_new(FALSE, 0);
7973 gtk_box_pack_start(GTK_BOX(edit_vbox), ruler_hbox, FALSE, FALSE, 0);
7975 ruler = gtk_shruler_new(GTK_ORIENTATION_HORIZONTAL);
7976 gtk_shruler_set_range(GTK_SHRULER(ruler), 0.0, 100.0, 1.0);
7977 gtk_box_pack_start(GTK_BOX(ruler_hbox), ruler, TRUE, TRUE,
7981 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
7982 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
7983 GTK_POLICY_AUTOMATIC,
7984 GTK_POLICY_AUTOMATIC);
7985 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
7987 gtk_box_pack_start(GTK_BOX(edit_vbox), scrolledwin, TRUE, TRUE, 0);
7989 text = gtk_text_view_new();
7990 if (prefs_common.show_compose_margin) {
7991 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text), 6);
7992 gtk_text_view_set_right_margin(GTK_TEXT_VIEW(text), 6);
7994 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
7995 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
7996 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), TRUE);
7997 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
7998 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
8000 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
8001 g_signal_connect_after(G_OBJECT(text), "size_allocate",
8002 G_CALLBACK(compose_edit_size_alloc),
8004 g_signal_connect(G_OBJECT(buffer), "changed",
8005 G_CALLBACK(compose_changed_cb), compose);
8006 g_signal_connect(G_OBJECT(text), "grab_focus",
8007 G_CALLBACK(compose_grab_focus_cb), compose);
8008 g_signal_connect(G_OBJECT(buffer), "insert_text",
8009 G_CALLBACK(text_inserted), compose);
8010 g_signal_connect(G_OBJECT(text), "button_press_event",
8011 G_CALLBACK(text_clicked), compose);
8012 g_signal_connect(G_OBJECT(text), "popup-menu",
8013 G_CALLBACK(compose_popup_menu), compose);
8014 g_signal_connect(G_OBJECT(subject_entry), "changed",
8015 G_CALLBACK(compose_changed_cb), compose);
8016 g_signal_connect(G_OBJECT(subject_entry), "activate",
8017 G_CALLBACK(compose_subject_entry_activated), compose);
8020 gtk_drag_dest_set(text, GTK_DEST_DEFAULT_ALL, compose_mime_types,
8021 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
8022 GDK_ACTION_COPY | GDK_ACTION_MOVE);
8023 g_signal_connect(G_OBJECT(text), "drag_data_received",
8024 G_CALLBACK(compose_insert_drag_received_cb),
8026 g_signal_connect(G_OBJECT(text), "drag-drop",
8027 G_CALLBACK(compose_drag_drop),
8029 g_signal_connect(G_OBJECT(text), "key-press-event",
8030 G_CALLBACK(completion_set_focus_to_subject),
8032 gtk_widget_show_all(vbox);
8034 /* pane between attach clist and text */
8035 paned = gtk_vpaned_new();
8036 gtk_container_add(GTK_CONTAINER(vbox2), paned);
8037 gtk_paned_pack1(GTK_PANED(paned), notebook, FALSE, FALSE);
8038 gtk_paned_pack2(GTK_PANED(paned), edit_vbox, TRUE, FALSE);
8039 gtk_paned_set_position(GTK_PANED(paned), prefs_common.compose_notebook_height);
8040 g_signal_connect(G_OBJECT(notebook), "size_allocate",
8041 G_CALLBACK(compose_notebook_size_alloc), paned);
8043 gtk_widget_show_all(paned);
8046 if (prefs_common.textfont) {
8047 PangoFontDescription *font_desc;
8049 font_desc = pango_font_description_from_string
8050 (prefs_common.textfont);
8052 gtk_widget_modify_font(text, font_desc);
8053 pango_font_description_free(font_desc);
8057 gtk_action_group_add_actions(action_group, compose_popup_entries,
8058 G_N_ELEMENTS(compose_popup_entries), (gpointer)compose);
8059 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Popup", NULL, GTK_UI_MANAGER_MENUBAR)
8060 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup", "Compose", "Compose", GTK_UI_MANAGER_MENU)
8061 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Add", "Compose/Add", GTK_UI_MANAGER_MENUITEM)
8062 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Remove", "Compose/Remove", GTK_UI_MANAGER_MENUITEM)
8063 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Separator1", "Compose/---", GTK_UI_MANAGER_SEPARATOR)
8064 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Properties", "Compose/Properties", GTK_UI_MANAGER_MENUITEM)
8066 popupmenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(gtk_ui_manager_get_widget(compose->ui_manager, "/Popup/Compose")));
8068 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
8069 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
8070 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
8072 tmpl_menu = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu/Tools/Template");
8074 undostruct = undo_init(text);
8075 undo_set_change_state_func(undostruct, &compose_undo_state_changed,
8078 address_completion_start(window);
8080 compose->window = window;
8081 compose->vbox = vbox;
8082 compose->menubar = menubar;
8083 compose->handlebox = handlebox;
8085 compose->vbox2 = vbox2;
8087 compose->paned = paned;
8089 compose->attach_label = attach_lab2;
8091 compose->notebook = notebook;
8092 compose->edit_vbox = edit_vbox;
8093 compose->ruler_hbox = ruler_hbox;
8094 compose->ruler = ruler;
8095 compose->scrolledwin = scrolledwin;
8096 compose->text = text;
8098 compose->focused_editable = NULL;
8100 compose->popupmenu = popupmenu;
8102 compose->tmpl_menu = tmpl_menu;
8104 compose->mode = mode;
8105 compose->rmode = mode;
8107 compose->targetinfo = NULL;
8108 compose->replyinfo = NULL;
8109 compose->fwdinfo = NULL;
8111 compose->email_hashtable = g_hash_table_new_full(g_str_hash,
8112 g_str_equal, (GDestroyNotify) g_free, NULL);
8114 compose->replyto = NULL;
8116 compose->bcc = NULL;
8117 compose->followup_to = NULL;
8119 compose->ml_post = NULL;
8121 compose->inreplyto = NULL;
8122 compose->references = NULL;
8123 compose->msgid = NULL;
8124 compose->boundary = NULL;
8126 compose->autowrap = prefs_common.autowrap;
8127 compose->autoindent = prefs_common.auto_indent;
8128 compose->use_signing = FALSE;
8129 compose->use_encryption = FALSE;
8130 compose->privacy_system = NULL;
8131 compose->encdata = NULL;
8133 compose->modified = FALSE;
8135 compose->return_receipt = FALSE;
8137 compose->to_list = NULL;
8138 compose->newsgroup_list = NULL;
8140 compose->undostruct = undostruct;
8142 compose->sig_str = NULL;
8144 compose->exteditor_file = NULL;
8145 compose->exteditor_pid = -1;
8146 compose->exteditor_tag = -1;
8147 compose->exteditor_socket = NULL;
8148 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN; /* inhibit auto-drafting while loading */
8150 compose->folder_update_callback_id =
8151 hooks_register_hook(FOLDER_UPDATE_HOOKLIST,
8152 compose_update_folder_hook,
8153 (gpointer) compose);
8156 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
8157 if (mode != COMPOSE_REDIRECT) {
8158 if (prefs_common.enable_aspell && prefs_common.dictionary &&
8159 strcmp(prefs_common.dictionary, "")) {
8160 gtkaspell = gtkaspell_new(prefs_common.dictionary,
8161 prefs_common.alt_dictionary,
8162 conv_get_locale_charset_str(),
8163 prefs_common.misspelled_col,
8164 prefs_common.check_while_typing,
8165 prefs_common.recheck_when_changing_dict,
8166 prefs_common.use_alternate,
8167 prefs_common.use_both_dicts,
8168 GTK_TEXT_VIEW(text),
8169 GTK_WINDOW(compose->window),
8170 compose_dict_changed,
8171 compose_spell_menu_changed,
8174 alertpanel_error(_("Spell checker could not "
8176 gtkaspell_checkers_strerror());
8177 gtkaspell_checkers_reset_error();
8179 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", TRUE);
8183 compose->gtkaspell = gtkaspell;
8184 compose_spell_menu_changed(compose);
8185 claws_spell_entry_set_gtkaspell(CLAWS_SPELL_ENTRY(subject_entry), gtkaspell);
8188 compose_select_account(compose, account, TRUE);
8190 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", prefs_common.autowrap);
8191 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", prefs_common.auto_indent);
8193 if (account->set_autocc && account->auto_cc && mode != COMPOSE_REEDIT)
8194 compose_entry_append(compose, account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8196 if (account->set_autobcc && account->auto_bcc && mode != COMPOSE_REEDIT)
8197 compose_entry_append(compose, account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8199 if (account->set_autoreplyto && account->auto_replyto && mode != COMPOSE_REEDIT)
8200 compose_entry_append(compose, account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8202 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", compose->mode == COMPOSE_REPLY);
8203 if (account->protocol != A_NNTP)
8204 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
8205 prefs_common_translated_header_name("To:"));
8207 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
8208 prefs_common_translated_header_name("Newsgroups:"));
8210 #ifndef USE_ALT_ADDRBOOK
8211 addressbook_set_target_compose(compose);
8213 if (mode != COMPOSE_REDIRECT)
8214 compose_set_template_menu(compose);
8216 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Template", FALSE);
8219 compose_list = g_list_append(compose_list, compose);
8221 if (!prefs_common.show_ruler)
8222 gtk_widget_hide(ruler_hbox);
8224 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Tools/ShowRuler", prefs_common.show_ruler);
8227 compose->priority = PRIORITY_NORMAL;
8228 compose_update_priority_menu_item(compose);
8230 compose_set_out_encoding(compose);
8233 compose_update_actions_menu(compose);
8235 /* Privacy Systems menu */
8236 compose_update_privacy_systems_menu(compose);
8238 activate_privacy_system(compose, account, TRUE);
8239 toolbar_set_style(compose->toolbar->toolbar, compose->handlebox, prefs_common.toolbar_style);
8241 gtk_widget_realize(window);
8243 gtk_widget_show(window);
8249 static GtkWidget *compose_account_option_menu_create(Compose *compose)
8254 GtkWidget *optmenubox;
8255 GtkWidget *fromlabel;
8258 GtkWidget *from_name = NULL;
8260 gint num = 0, def_menu = 0;
8262 accounts = account_get_list();
8263 cm_return_val_if_fail(accounts != NULL, NULL);
8265 optmenubox = gtk_event_box_new();
8266 optmenu = gtkut_sc_combobox_create(optmenubox, FALSE);
8267 menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
8269 hbox = gtk_hbox_new(FALSE, 4);
8270 from_name = gtk_entry_new();
8272 g_signal_connect_after(G_OBJECT(from_name), "grab_focus",
8273 G_CALLBACK(compose_grab_focus_cb), compose);
8274 g_signal_connect_after(G_OBJECT(from_name), "activate",
8275 G_CALLBACK(from_name_activate_cb), optmenu);
8277 for (; accounts != NULL; accounts = accounts->next, num++) {
8278 PrefsAccount *ac = (PrefsAccount *)accounts->data;
8279 gchar *name, *from = NULL;
8281 if (ac == compose->account) def_menu = num;
8283 name = g_markup_printf_escaped("<i>%s</i>",
8286 if (ac == compose->account) {
8287 if (ac->name && *ac->name) {
8289 QUOTE_IF_REQUIRED_NORMAL(buf, ac->name, return NULL);
8290 from = g_strdup_printf("%s <%s>",
8292 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8294 from = g_strdup_printf("%s",
8296 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8298 if (cur_account != compose->account) {
8299 gtk_widget_modify_base(
8300 GTK_WIDGET(from_name),
8301 GTK_STATE_NORMAL, &default_header_bgcolor);
8302 gtk_widget_modify_text(
8303 GTK_WIDGET(from_name),
8304 GTK_STATE_NORMAL, &default_header_color);
8307 COMBOBOX_ADD(menu, name, ac->account_id);
8312 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), def_menu);
8314 g_signal_connect(G_OBJECT(optmenu), "changed",
8315 G_CALLBACK(account_activated),
8317 g_signal_connect(G_OBJECT(from_name), "populate-popup",
8318 G_CALLBACK(compose_entry_popup_extend),
8321 fromlabel = gtk_label_new_with_mnemonic(_("_From:"));
8322 gtk_label_set_mnemonic_widget(GTK_LABEL(fromlabel), from_name);
8324 gtk_box_pack_start(GTK_BOX(hbox), fromlabel, FALSE, FALSE, 4);
8325 gtk_box_pack_start(GTK_BOX(hbox), optmenubox, FALSE, FALSE, 0);
8326 gtk_box_pack_start(GTK_BOX(hbox), from_name, TRUE, TRUE, 0);
8328 /* Putting only the GtkEntry into focus chain of parent hbox causes
8329 * the account selector combobox next to it to be unreachable when
8330 * navigating widgets in GtkTable with up/down arrow keys.
8331 * Note: gtk_widget_set_can_focus() was not enough. */
8333 l = g_list_prepend(l, from_name);
8334 gtk_container_set_focus_chain(GTK_CONTAINER(hbox), l);
8337 CLAWS_SET_TIP(optmenubox,
8338 _("Account to use for this email"));
8339 CLAWS_SET_TIP(from_name,
8340 _("Sender address to be used"));
8342 compose->account_combo = optmenu;
8343 compose->from_name = from_name;
8348 static void compose_set_priority_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8350 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8351 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8352 Compose *compose = (Compose *) data;
8354 compose->priority = value;
8358 static void compose_reply_change_mode(Compose *compose,
8361 gboolean was_modified = compose->modified;
8363 gboolean all = FALSE, ml = FALSE, sender = FALSE, followup = FALSE;
8365 cm_return_if_fail(compose->replyinfo != NULL);
8367 if (action == COMPOSE_REPLY && prefs_common.default_reply_list)
8369 if (action == COMPOSE_REPLY && compose->rmode == COMPOSE_FOLLOWUP_AND_REPLY_TO)
8371 if (action == COMPOSE_REPLY_TO_ALL)
8373 if (action == COMPOSE_REPLY_TO_SENDER)
8375 if (action == COMPOSE_REPLY_TO_LIST)
8378 compose_remove_header_entries(compose);
8379 compose_reply_set_entry(compose, compose->replyinfo, all, ml, sender, followup);
8380 if (compose->account->set_autocc && compose->account->auto_cc)
8381 compose_entry_append(compose, compose->account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8383 if (compose->account->set_autobcc && compose->account->auto_bcc)
8384 compose_entry_append(compose, compose->account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8386 if (compose->account->set_autoreplyto && compose->account->auto_replyto)
8387 compose_entry_append(compose, compose->account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8388 compose_show_first_last_header(compose, TRUE);
8389 compose->modified = was_modified;
8390 compose_set_title(compose);
8393 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8395 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8396 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8397 Compose *compose = (Compose *) data;
8400 compose_reply_change_mode(compose, value);
8403 static void compose_update_priority_menu_item(Compose * compose)
8405 GtkWidget *menuitem = NULL;
8406 switch (compose->priority) {
8407 case PRIORITY_HIGHEST:
8408 menuitem = gtk_ui_manager_get_widget
8409 (compose->ui_manager, "/Menu/Options/Priority/Highest");
8412 menuitem = gtk_ui_manager_get_widget
8413 (compose->ui_manager, "/Menu/Options/Priority/High");
8415 case PRIORITY_NORMAL:
8416 menuitem = gtk_ui_manager_get_widget
8417 (compose->ui_manager, "/Menu/Options/Priority/Normal");
8420 menuitem = gtk_ui_manager_get_widget
8421 (compose->ui_manager, "/Menu/Options/Priority/Low");
8423 case PRIORITY_LOWEST:
8424 menuitem = gtk_ui_manager_get_widget
8425 (compose->ui_manager, "/Menu/Options/Priority/Lowest");
8428 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8431 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data)
8433 Compose *compose = (Compose *) data;
8435 gboolean can_sign = FALSE, can_encrypt = FALSE;
8437 cm_return_if_fail(GTK_IS_CHECK_MENU_ITEM(widget));
8439 if (!gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget)))
8442 systemid = g_object_get_data(G_OBJECT(widget), "privacy_system");
8443 g_free(compose->privacy_system);
8444 compose->privacy_system = NULL;
8445 g_free(compose->encdata);
8446 compose->encdata = NULL;
8447 if (systemid != NULL) {
8448 compose->privacy_system = g_strdup(systemid);
8450 can_sign = privacy_system_can_sign(systemid);
8451 can_encrypt = privacy_system_can_encrypt(systemid);
8454 debug_print("activated privacy system: %s\n", systemid != NULL ? systemid : "None");
8456 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8457 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8460 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn)
8462 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8463 GtkWidget *menuitem = NULL;
8464 GList *children, *amenu;
8465 gboolean can_sign = FALSE, can_encrypt = FALSE;
8466 gboolean found = FALSE;
8468 if (compose->privacy_system != NULL) {
8470 menuitem = gtk_menu_item_get_submenu(GTK_MENU_ITEM(
8471 gtk_ui_manager_get_widget(compose->ui_manager, branch_path)));
8472 cm_return_if_fail(menuitem != NULL);
8474 children = gtk_container_get_children(GTK_CONTAINER(GTK_MENU_SHELL(menuitem)));
8477 while (amenu != NULL) {
8478 systemid = g_object_get_data(G_OBJECT(amenu->data), "privacy_system");
8479 if (systemid != NULL) {
8480 if (strcmp(systemid, compose->privacy_system) == 0 &&
8481 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8482 menuitem = GTK_WIDGET(amenu->data);
8484 can_sign = privacy_system_can_sign(systemid);
8485 can_encrypt = privacy_system_can_encrypt(systemid);
8489 } else if (strlen(compose->privacy_system) == 0 &&
8490 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8491 menuitem = GTK_WIDGET(amenu->data);
8494 can_encrypt = FALSE;
8499 amenu = amenu->next;
8501 g_list_free(children);
8502 if (menuitem != NULL)
8503 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8505 if (warn && !found && strlen(compose->privacy_system)) {
8506 alertpanel_warning(_("The privacy system '%s' cannot be loaded. You "
8507 "will not be able to sign or encrypt this message."),
8508 compose->privacy_system);
8512 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8513 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8516 static void compose_set_out_encoding(Compose *compose)
8518 CharSet out_encoding;
8519 const gchar *branch = NULL;
8520 out_encoding = conv_get_charset_from_str(prefs_common.outgoing_charset);
8522 switch(out_encoding) {
8523 case C_AUTO: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8524 case C_US_ASCII: branch = "Menu/Options/Encoding/" CS_US_ASCII; break;
8525 case C_UTF_8: branch = "Menu/Options/Encoding/" CS_UTF_8; break;
8526 case C_ISO_8859_2: branch = "Menu/Options/Encoding/" CS_ISO_8859_2; break;
8527 case C_ISO_8859_7: branch = "Menu/Options/Encoding/" CS_ISO_8859_7; break;
8528 case C_ISO_8859_9: branch = "Menu/Options/Encoding/" CS_ISO_8859_9; break;
8529 case C_ISO_8859_1: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_1; break;
8530 case C_ISO_8859_15: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_15; break;
8531 case C_WINDOWS_1252: branch = "Menu/Options/Encoding/Western/" CS_WINDOWS_1252; break;
8532 case C_ISO_8859_13: branch = "Menu/Options/Encoding/Baltic/" CS_ISO_8859_13; break;
8533 case C_ISO_8859_4: branch = "Menu/Options/Encoding/Baltic" CS_ISO_8859_4; break;
8534 case C_ISO_8859_8: branch = "Menu/Options/Encoding/Hebrew/" CS_ISO_8859_8; break;
8535 case C_WINDOWS_1255: branch = "Menu/Options/Encoding/Hebrew/" CS_WINDOWS_1255; break;
8536 case C_ISO_8859_6: branch = "Menu/Options/Encoding/Arabic/" CS_ISO_8859_6; break;
8537 case C_WINDOWS_1256: branch = "Menu/Options/Encoding/Arabic/" CS_WINDOWS_1256; break;
8538 case C_ISO_8859_5: branch = "Menu/Options/Encoding/Cyrillic/" CS_ISO_8859_5; break;
8539 case C_KOI8_R: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_R; break;
8540 case C_MACCYR: branch = "Menu/Options/Encoding/Cyrillic/" CS_MACCYR; break;
8541 case C_KOI8_U: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_U; break;
8542 case C_WINDOWS_1251: branch = "Menu/Options/Encoding/Cyrillic/" CS_WINDOWS_1251; break;
8543 case C_ISO_2022_JP: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP; break;
8544 case C_ISO_2022_JP_2: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP_2; break;
8545 case C_EUC_JP: branch = "Menu/Options/Encoding/Japanese/" CS_EUC_JP; break;
8546 case C_SHIFT_JIS: branch = "Menu/Options/Encoding/Japanese/" CS_SHIFT_JIS; break;
8547 case C_GB18030: branch = "Menu/Options/Encoding/Chinese/" CS_GB18030; break;
8548 case C_GB2312: branch = "Menu/Options/Encoding/Chinese/" CS_GB2312; break;
8549 case C_GBK: branch = "Menu/Options/Encoding/Chinese/" CS_GBK; break;
8550 case C_BIG5: branch = "Menu/Options/Encoding/Chinese/" CS_BIG5; break;
8551 case C_EUC_TW: branch = "Menu/Options/Encoding/Chinese/" CS_EUC_TW; break;
8552 case C_EUC_KR: branch = "Menu/Options/Encoding/Korean/" CS_EUC_KR; break;
8553 case C_ISO_2022_KR: branch = "Menu/Options/Encoding/Korean/" CS_ISO_2022_KR; break;
8554 case C_TIS_620: branch = "Menu/Options/Encoding/Thai/" CS_TIS_620; break;
8555 case C_WINDOWS_874: branch = "Menu/Options/Encoding/Thai/" CS_WINDOWS_874; break;
8556 default: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8558 cm_toggle_menu_set_active_full(compose->ui_manager, (gchar *)branch, TRUE);
8561 static void compose_set_template_menu(Compose *compose)
8563 GSList *tmpl_list, *cur;
8567 tmpl_list = template_get_config();
8569 menu = gtk_menu_new();
8571 gtk_menu_set_accel_group (GTK_MENU (menu),
8572 gtk_ui_manager_get_accel_group(compose->ui_manager));
8573 for (cur = tmpl_list; cur != NULL; cur = cur->next) {
8574 Template *tmpl = (Template *)cur->data;
8575 gchar *accel_path = NULL;
8576 item = gtk_menu_item_new_with_label(tmpl->name);
8577 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
8578 g_signal_connect(G_OBJECT(item), "activate",
8579 G_CALLBACK(compose_template_activate_cb),
8581 g_object_set_data(G_OBJECT(item), "template", tmpl);
8582 gtk_widget_show(item);
8583 accel_path = g_strconcat("<ComposeTemplates>" , "/", tmpl->name, NULL);
8584 gtk_menu_item_set_accel_path(GTK_MENU_ITEM(item), accel_path);
8588 gtk_widget_show(menu);
8589 gtk_menu_item_set_submenu(GTK_MENU_ITEM(compose->tmpl_menu), menu);
8592 void compose_update_actions_menu(Compose *compose)
8594 action_update_compose_menu(compose->ui_manager, "/Menu/Tools/Actions", compose);
8597 static void compose_update_privacy_systems_menu(Compose *compose)
8599 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8600 GSList *systems, *cur;
8602 GtkWidget *system_none;
8604 GtkWidget *privacy_menuitem = gtk_ui_manager_get_widget(compose->ui_manager, branch_path);
8605 GtkWidget *privacy_menu = gtk_menu_new();
8607 system_none = gtk_radio_menu_item_new_with_mnemonic(NULL, _("_None"));
8608 g_object_set_data_full(G_OBJECT(system_none), "privacy_system", NULL, NULL);
8610 g_signal_connect(G_OBJECT(system_none), "activate",
8611 G_CALLBACK(compose_set_privacy_system_cb), compose);
8613 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), system_none);
8614 gtk_widget_show(system_none);
8616 systems = privacy_get_system_ids();
8617 for (cur = systems; cur != NULL; cur = g_slist_next(cur)) {
8618 gchar *systemid = cur->data;
8620 group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(system_none));
8621 widget = gtk_radio_menu_item_new_with_label(group,
8622 privacy_system_get_name(systemid));
8623 g_object_set_data_full(G_OBJECT(widget), "privacy_system",
8624 g_strdup(systemid), g_free);
8625 g_signal_connect(G_OBJECT(widget), "activate",
8626 G_CALLBACK(compose_set_privacy_system_cb), compose);
8628 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), widget);
8629 gtk_widget_show(widget);
8632 g_slist_free(systems);
8633 gtk_menu_item_set_submenu(GTK_MENU_ITEM(privacy_menuitem), privacy_menu);
8634 gtk_widget_show_all(privacy_menu);
8635 gtk_widget_show_all(privacy_menuitem);
8638 void compose_reflect_prefs_all(void)
8643 for (cur = compose_list; cur != NULL; cur = cur->next) {
8644 compose = (Compose *)cur->data;
8645 compose_set_template_menu(compose);
8649 void compose_reflect_prefs_pixmap_theme(void)
8654 for (cur = compose_list; cur != NULL; cur = cur->next) {
8655 compose = (Compose *)cur->data;
8656 toolbar_update(TOOLBAR_COMPOSE, compose);
8660 static const gchar *compose_quote_char_from_context(Compose *compose)
8662 const gchar *qmark = NULL;
8664 cm_return_val_if_fail(compose != NULL, NULL);
8666 switch (compose->mode) {
8667 /* use forward-specific quote char */
8668 case COMPOSE_FORWARD:
8669 case COMPOSE_FORWARD_AS_ATTACH:
8670 case COMPOSE_FORWARD_INLINE:
8671 if (compose->folder && compose->folder->prefs &&
8672 compose->folder->prefs->forward_with_format)
8673 qmark = compose->folder->prefs->forward_quotemark;
8674 else if (compose->account->forward_with_format)
8675 qmark = compose->account->forward_quotemark;
8677 qmark = prefs_common.fw_quotemark;
8680 /* use reply-specific quote char in all other modes */
8682 if (compose->folder && compose->folder->prefs &&
8683 compose->folder->prefs->reply_with_format)
8684 qmark = compose->folder->prefs->reply_quotemark;
8685 else if (compose->account->reply_with_format)
8686 qmark = compose->account->reply_quotemark;
8688 qmark = prefs_common.quotemark;
8692 if (qmark == NULL || *qmark == '\0')
8698 static void compose_template_apply(Compose *compose, Template *tmpl,
8702 GtkTextBuffer *buffer;
8706 gchar *parsed_str = NULL;
8707 gint cursor_pos = 0;
8708 const gchar *err_msg = _("The body of the template has an error at line %d.");
8711 /* process the body */
8713 text = GTK_TEXT_VIEW(compose->text);
8714 buffer = gtk_text_view_get_buffer(text);
8717 qmark = compose_quote_char_from_context(compose);
8719 if (compose->replyinfo != NULL) {
8722 gtk_text_buffer_set_text(buffer, "", -1);
8723 mark = gtk_text_buffer_get_insert(buffer);
8724 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8726 parsed_str = compose_quote_fmt(compose, compose->replyinfo,
8727 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8729 } else if (compose->fwdinfo != NULL) {
8732 gtk_text_buffer_set_text(buffer, "", -1);
8733 mark = gtk_text_buffer_get_insert(buffer);
8734 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8736 parsed_str = compose_quote_fmt(compose, compose->fwdinfo,
8737 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8740 MsgInfo* dummyinfo = compose_msginfo_new_from_compose(compose);
8742 GtkTextIter start, end;
8745 gtk_text_buffer_get_start_iter(buffer, &start);
8746 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
8747 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
8749 /* clear the buffer now */
8751 gtk_text_buffer_set_text(buffer, "", -1);
8753 parsed_str = compose_quote_fmt(compose, dummyinfo,
8754 tmpl->value, qmark, tmp, FALSE, FALSE, err_msg);
8755 procmsg_msginfo_free( &dummyinfo );
8761 gtk_text_buffer_set_text(buffer, "", -1);
8762 mark = gtk_text_buffer_get_insert(buffer);
8763 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8766 if (replace && parsed_str && compose->account->auto_sig)
8767 compose_insert_sig(compose, FALSE);
8769 if (replace && parsed_str) {
8770 gtk_text_buffer_get_start_iter(buffer, &iter);
8771 gtk_text_buffer_place_cursor(buffer, &iter);
8775 cursor_pos = quote_fmt_get_cursor_pos();
8776 compose->set_cursor_pos = cursor_pos;
8777 if (cursor_pos == -1)
8779 gtk_text_buffer_get_start_iter(buffer, &iter);
8780 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
8781 gtk_text_buffer_place_cursor(buffer, &iter);
8784 /* process the other fields */
8786 compose_template_apply_fields(compose, tmpl);
8787 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
8788 quote_fmt_reset_vartable();
8789 compose_changed_cb(NULL, compose);
8792 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
8793 gtkaspell_highlight_all(compose->gtkaspell);
8797 static void compose_template_apply_fields_error(const gchar *header)
8802 tr = g_strdup(C_("'%s' stands for a header name",
8803 "Template '%s' format error."));
8804 text = g_strdup_printf(tr, prefs_common_translated_header_name(header));
8805 alertpanel_error("%s", text);
8811 static void compose_template_apply_fields(Compose *compose, Template *tmpl)
8813 MsgInfo* dummyinfo = NULL;
8814 MsgInfo *msginfo = NULL;
8817 if (compose->replyinfo != NULL)
8818 msginfo = compose->replyinfo;
8819 else if (compose->fwdinfo != NULL)
8820 msginfo = compose->fwdinfo;
8822 dummyinfo = compose_msginfo_new_from_compose(compose);
8823 msginfo = dummyinfo;
8826 if (tmpl->from && *tmpl->from != '\0') {
8828 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8829 compose->gtkaspell);
8831 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8833 quote_fmt_scan_string(tmpl->from);
8836 buf = quote_fmt_get_buffer();
8838 compose_template_apply_fields_error("From");
8840 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
8844 if (tmpl->to && *tmpl->to != '\0') {
8846 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8847 compose->gtkaspell);
8849 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8851 quote_fmt_scan_string(tmpl->to);
8854 buf = quote_fmt_get_buffer();
8856 compose_template_apply_fields_error("To");
8858 compose_entry_append(compose, buf, COMPOSE_TO, PREF_TEMPLATE);
8862 if (tmpl->cc && *tmpl->cc != '\0') {
8864 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8865 compose->gtkaspell);
8867 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8869 quote_fmt_scan_string(tmpl->cc);
8872 buf = quote_fmt_get_buffer();
8874 compose_template_apply_fields_error("Cc");
8876 compose_entry_append(compose, buf, COMPOSE_CC, PREF_TEMPLATE);
8880 if (tmpl->bcc && *tmpl->bcc != '\0') {
8882 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8883 compose->gtkaspell);
8885 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8887 quote_fmt_scan_string(tmpl->bcc);
8890 buf = quote_fmt_get_buffer();
8892 compose_template_apply_fields_error("Bcc");
8894 compose_entry_append(compose, buf, COMPOSE_BCC, PREF_TEMPLATE);
8898 if (tmpl->replyto && *tmpl->replyto != '\0') {
8900 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8901 compose->gtkaspell);
8903 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8905 quote_fmt_scan_string(tmpl->replyto);
8908 buf = quote_fmt_get_buffer();
8910 compose_template_apply_fields_error("Reply-To");
8912 compose_entry_append(compose, buf, COMPOSE_REPLYTO, PREF_TEMPLATE);
8916 /* process the subject */
8917 if (tmpl->subject && *tmpl->subject != '\0') {
8919 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8920 compose->gtkaspell);
8922 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8924 quote_fmt_scan_string(tmpl->subject);
8927 buf = quote_fmt_get_buffer();
8929 compose_template_apply_fields_error("Subject");
8931 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
8935 procmsg_msginfo_free( &dummyinfo );
8938 static void compose_destroy(Compose *compose)
8940 GtkAllocation allocation;
8941 GtkTextBuffer *buffer;
8942 GtkClipboard *clipboard;
8944 compose_list = g_list_remove(compose_list, compose);
8946 if (compose->updating) {
8947 debug_print("danger, not destroying anything now\n");
8948 compose->deferred_destroy = TRUE;
8952 /* NOTE: address_completion_end() does nothing with the window
8953 * however this may change. */
8954 address_completion_end(compose->window);
8956 slist_free_strings_full(compose->to_list);
8957 slist_free_strings_full(compose->newsgroup_list);
8958 slist_free_strings_full(compose->header_list);
8960 slist_free_strings_full(extra_headers);
8961 extra_headers = NULL;
8963 compose->header_list = compose->newsgroup_list = compose->to_list = NULL;
8965 g_hash_table_destroy(compose->email_hashtable);
8967 hooks_unregister_hook(FOLDER_UPDATE_HOOKLIST,
8968 compose->folder_update_callback_id);
8970 procmsg_msginfo_free(&(compose->targetinfo));
8971 procmsg_msginfo_free(&(compose->replyinfo));
8972 procmsg_msginfo_free(&(compose->fwdinfo));
8974 g_free(compose->replyto);
8975 g_free(compose->cc);
8976 g_free(compose->bcc);
8977 g_free(compose->newsgroups);
8978 g_free(compose->followup_to);
8980 g_free(compose->ml_post);
8982 g_free(compose->inreplyto);
8983 g_free(compose->references);
8984 g_free(compose->msgid);
8985 g_free(compose->boundary);
8987 g_free(compose->redirect_filename);
8988 if (compose->undostruct)
8989 undo_destroy(compose->undostruct);
8991 g_free(compose->sig_str);
8993 g_free(compose->exteditor_file);
8995 g_free(compose->orig_charset);
8997 g_free(compose->privacy_system);
8998 g_free(compose->encdata);
9000 #ifndef USE_ALT_ADDRBOOK
9001 if (addressbook_get_target_compose() == compose)
9002 addressbook_set_target_compose(NULL);
9005 if (compose->gtkaspell) {
9006 gtkaspell_delete(compose->gtkaspell);
9007 compose->gtkaspell = NULL;
9011 if (!compose->batch) {
9012 gtk_widget_get_allocation(compose->window, &allocation);
9013 prefs_common.compose_width = allocation.width;
9014 prefs_common.compose_height = allocation.height;
9017 if (!gtk_widget_get_parent(compose->paned))
9018 gtk_widget_destroy(compose->paned);
9019 gtk_widget_destroy(compose->popupmenu);
9021 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
9022 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
9023 gtk_text_buffer_remove_selection_clipboard(buffer, clipboard);
9025 gtk_widget_destroy(compose->window);
9026 toolbar_destroy(compose->toolbar);
9027 g_free(compose->toolbar);
9028 cm_mutex_free(compose->mutex);
9032 static void compose_attach_info_free(AttachInfo *ainfo)
9034 g_free(ainfo->file);
9035 g_free(ainfo->content_type);
9036 g_free(ainfo->name);
9037 g_free(ainfo->charset);
9041 static void compose_attach_update_label(Compose *compose)
9046 GtkTreeModel *model;
9050 if (compose == NULL)
9053 model = gtk_tree_view_get_model(GTK_TREE_VIEW(compose->attach_clist));
9054 if (!gtk_tree_model_get_iter_first(model, &iter)) {
9055 gtk_label_set_text(GTK_LABEL(compose->attach_label), "");
9059 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
9060 total_size = ainfo->size;
9061 while(gtk_tree_model_iter_next(model, &iter)) {
9062 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
9063 total_size += ainfo->size;
9066 text = g_strdup_printf(" (%d/%s)", i, to_human_readable(total_size));
9067 gtk_label_set_text(GTK_LABEL(compose->attach_label), text);
9071 static void compose_attach_remove_selected(GtkAction *action, gpointer data)
9073 Compose *compose = (Compose *)data;
9074 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
9075 GtkTreeSelection *selection;
9077 GtkTreeModel *model;
9079 selection = gtk_tree_view_get_selection(tree_view);
9080 sel = gtk_tree_selection_get_selected_rows(selection, &model);
9085 for (cur = sel; cur != NULL; cur = cur->next) {
9086 GtkTreePath *path = cur->data;
9087 GtkTreeRowReference *ref = gtk_tree_row_reference_new
9090 gtk_tree_path_free(path);
9093 for (cur = sel; cur != NULL; cur = cur->next) {
9094 GtkTreeRowReference *ref = cur->data;
9095 GtkTreePath *path = gtk_tree_row_reference_get_path(ref);
9098 if (gtk_tree_model_get_iter(model, &iter, path))
9099 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
9101 gtk_tree_path_free(path);
9102 gtk_tree_row_reference_free(ref);
9106 compose_attach_update_label(compose);
9109 static struct _AttachProperty
9112 GtkWidget *mimetype_entry;
9113 GtkWidget *encoding_optmenu;
9114 GtkWidget *path_entry;
9115 GtkWidget *filename_entry;
9117 GtkWidget *cancel_btn;
9120 static void gtk_tree_path_free_(gpointer ptr, gpointer data)
9122 gtk_tree_path_free((GtkTreePath *)ptr);
9125 static void compose_attach_property(GtkAction *action, gpointer data)
9127 Compose *compose = (Compose *)data;
9128 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
9130 GtkComboBox *optmenu;
9131 GtkTreeSelection *selection;
9133 GtkTreeModel *model;
9136 static gboolean cancelled;
9138 /* only if one selected */
9139 selection = gtk_tree_view_get_selection(tree_view);
9140 if (gtk_tree_selection_count_selected_rows(selection) != 1)
9143 sel = gtk_tree_selection_get_selected_rows(selection, &model);
9147 path = (GtkTreePath *) sel->data;
9148 gtk_tree_model_get_iter(model, &iter, path);
9149 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
9152 g_list_foreach(sel, gtk_tree_path_free_, NULL);
9158 if (!attach_prop.window)
9159 compose_attach_property_create(&cancelled);
9160 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), TRUE);
9161 gtk_widget_grab_focus(attach_prop.ok_btn);
9162 gtk_widget_show(attach_prop.window);
9163 gtk_window_set_transient_for(GTK_WINDOW(attach_prop.window),
9164 GTK_WINDOW(compose->window));
9166 optmenu = GTK_COMBO_BOX(attach_prop.encoding_optmenu);
9167 if (ainfo->encoding == ENC_UNKNOWN)
9168 combobox_select_by_data(optmenu, ENC_BASE64);
9170 combobox_select_by_data(optmenu, ainfo->encoding);
9172 gtk_entry_set_text(GTK_ENTRY(attach_prop.mimetype_entry),
9173 ainfo->content_type ? ainfo->content_type : "");
9174 gtk_entry_set_text(GTK_ENTRY(attach_prop.path_entry),
9175 ainfo->file ? ainfo->file : "");
9176 gtk_entry_set_text(GTK_ENTRY(attach_prop.filename_entry),
9177 ainfo->name ? ainfo->name : "");
9180 const gchar *entry_text;
9182 gchar *cnttype = NULL;
9189 gtk_widget_hide(attach_prop.window);
9190 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), FALSE);
9195 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.mimetype_entry));
9196 if (*entry_text != '\0') {
9199 text = g_strstrip(g_strdup(entry_text));
9200 if ((p = strchr(text, '/')) && !strchr(p + 1, '/')) {
9201 cnttype = g_strdup(text);
9204 alertpanel_error(_("Invalid MIME type."));
9210 ainfo->encoding = combobox_get_active_data(optmenu);
9212 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.path_entry));
9213 if (*entry_text != '\0') {
9214 if (is_file_exist(entry_text) &&
9215 (size = get_file_size(entry_text)) > 0)
9216 file = g_strdup(entry_text);
9219 (_("File doesn't exist or is empty."));
9225 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.filename_entry));
9226 if (*entry_text != '\0') {
9227 g_free(ainfo->name);
9228 ainfo->name = g_strdup(entry_text);
9232 g_free(ainfo->content_type);
9233 ainfo->content_type = cnttype;
9236 g_free(ainfo->file);
9240 ainfo->size = (goffset)size;
9242 /* update tree store */
9243 text = to_human_readable(ainfo->size);
9244 gtk_tree_model_get_iter(model, &iter, path);
9245 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
9246 COL_MIMETYPE, ainfo->content_type,
9248 COL_NAME, ainfo->name,
9249 COL_CHARSET, ainfo->charset,
9255 gtk_tree_path_free(path);
9258 #define SET_LABEL_AND_ENTRY(str, entry, top) \
9260 label = gtk_label_new(str); \
9261 gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), \
9262 GTK_FILL, 0, 0, 0); \
9263 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); \
9265 entry = gtk_entry_new(); \
9266 gtk_table_attach(GTK_TABLE(table), entry, 1, 2, top, (top + 1), \
9267 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); \
9270 static void compose_attach_property_create(gboolean *cancelled)
9276 GtkWidget *mimetype_entry;
9279 GtkListStore *optmenu_menu;
9280 GtkWidget *path_entry;
9281 GtkWidget *filename_entry;
9284 GtkWidget *cancel_btn;
9285 GList *mime_type_list, *strlist;
9288 debug_print("Creating attach_property window...\n");
9290 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose_attach_property");
9291 gtk_widget_set_size_request(window, 480, -1);
9292 gtk_container_set_border_width(GTK_CONTAINER(window), 8);
9293 gtk_window_set_title(GTK_WINDOW(window), _("Properties"));
9294 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
9295 g_signal_connect(G_OBJECT(window), "delete_event",
9296 G_CALLBACK(attach_property_delete_event),
9298 g_signal_connect(G_OBJECT(window), "key_press_event",
9299 G_CALLBACK(attach_property_key_pressed),
9302 vbox = gtk_vbox_new(FALSE, 8);
9303 gtk_container_add(GTK_CONTAINER(window), vbox);
9305 table = gtk_table_new(4, 2, FALSE);
9306 gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
9307 gtk_table_set_row_spacings(GTK_TABLE(table), 8);
9308 gtk_table_set_col_spacings(GTK_TABLE(table), 8);
9310 label = gtk_label_new(_("MIME type"));
9311 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, (0 + 1),
9313 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
9314 #if !GTK_CHECK_VERSION(2, 24, 0)
9315 mimetype_entry = gtk_combo_box_entry_new_text();
9317 mimetype_entry = gtk_combo_box_text_new_with_entry();
9319 gtk_table_attach(GTK_TABLE(table), mimetype_entry, 1, 2, 0, (0 + 1),
9320 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
9322 /* stuff with list */
9323 mime_type_list = procmime_get_mime_type_list();
9325 for (; mime_type_list != NULL; mime_type_list = mime_type_list->next) {
9326 MimeType *type = (MimeType *) mime_type_list->data;
9329 tmp = g_strdup_printf("%s/%s", type->type, type->sub_type);
9331 if (g_list_find_custom(strlist, tmp, (GCompareFunc)strcmp2))
9334 strlist = g_list_insert_sorted(strlist, (gpointer)tmp,
9335 (GCompareFunc)strcmp2);
9338 for (mime_type_list = strlist; mime_type_list != NULL;
9339 mime_type_list = mime_type_list->next) {
9340 #if !GTK_CHECK_VERSION(2, 24, 0)
9341 gtk_combo_box_append_text(GTK_COMBO_BOX(mimetype_entry), mime_type_list->data);
9343 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(mimetype_entry), mime_type_list->data);
9345 g_free(mime_type_list->data);
9347 g_list_free(strlist);
9348 gtk_combo_box_set_active(GTK_COMBO_BOX(mimetype_entry), 0);
9349 mimetype_entry = gtk_bin_get_child(GTK_BIN((mimetype_entry)));
9351 label = gtk_label_new(_("Encoding"));
9352 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2,
9354 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
9356 hbox = gtk_hbox_new(FALSE, 0);
9357 gtk_table_attach(GTK_TABLE(table), hbox, 1, 2, 1, 2,
9358 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
9360 optmenu = gtkut_sc_combobox_create(NULL, TRUE);
9361 optmenu_menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
9363 COMBOBOX_ADD(optmenu_menu, "7bit", ENC_7BIT);
9364 COMBOBOX_ADD(optmenu_menu, "8bit", ENC_8BIT);
9365 COMBOBOX_ADD(optmenu_menu, "quoted-printable", ENC_QUOTED_PRINTABLE);
9366 COMBOBOX_ADD(optmenu_menu, "base64", ENC_BASE64);
9367 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), 0);
9369 gtk_box_pack_start(GTK_BOX(hbox), optmenu, TRUE, TRUE, 0);
9371 SET_LABEL_AND_ENTRY(_("Path"), path_entry, 2);
9372 SET_LABEL_AND_ENTRY(_("File name"), filename_entry, 3);
9374 gtkut_stock_button_set_create(&hbbox, &cancel_btn, GTK_STOCK_CANCEL,
9375 &ok_btn, GTK_STOCK_OK,
9377 gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
9378 gtk_widget_grab_default(ok_btn);
9380 g_signal_connect(G_OBJECT(ok_btn), "clicked",
9381 G_CALLBACK(attach_property_ok),
9383 g_signal_connect(G_OBJECT(cancel_btn), "clicked",
9384 G_CALLBACK(attach_property_cancel),
9387 gtk_widget_show_all(vbox);
9389 attach_prop.window = window;
9390 attach_prop.mimetype_entry = mimetype_entry;
9391 attach_prop.encoding_optmenu = optmenu;
9392 attach_prop.path_entry = path_entry;
9393 attach_prop.filename_entry = filename_entry;
9394 attach_prop.ok_btn = ok_btn;
9395 attach_prop.cancel_btn = cancel_btn;
9398 #undef SET_LABEL_AND_ENTRY
9400 static void attach_property_ok(GtkWidget *widget, gboolean *cancelled)
9406 static void attach_property_cancel(GtkWidget *widget, gboolean *cancelled)
9412 static gint attach_property_delete_event(GtkWidget *widget, GdkEventAny *event,
9413 gboolean *cancelled)
9421 static gboolean attach_property_key_pressed(GtkWidget *widget,
9423 gboolean *cancelled)
9425 if (event && event->keyval == GDK_KEY_Escape) {
9429 if (event && event->keyval == GDK_KEY_Return) {
9437 static void compose_exec_ext_editor(Compose *compose)
9442 GdkNativeWindow socket_wid = 0;
9446 tmp = g_strdup_printf("%s%ctmpmsg.%p", get_tmp_dir(),
9447 G_DIR_SEPARATOR, compose);
9449 if (compose_get_ext_editor_uses_socket()) {
9450 /* Only allow one socket */
9451 if (compose->exteditor_socket != NULL) {
9452 if (gtk_widget_is_focus(compose->exteditor_socket)) {
9453 /* Move the focus off of the socket */
9454 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9459 /* Create the receiving GtkSocket */
9460 socket = gtk_socket_new ();
9461 g_signal_connect (GTK_OBJECT(socket), "plug-removed",
9462 G_CALLBACK(compose_ext_editor_plug_removed_cb),
9464 gtk_box_pack_start(GTK_BOX(compose->edit_vbox), socket, TRUE, TRUE, 0);
9465 gtk_widget_set_size_request(socket, prefs_common.compose_width, -1);
9466 /* Realize the socket so that we can use its ID */
9467 gtk_widget_realize(socket);
9468 socket_wid = gtk_socket_get_id(GTK_SOCKET (socket));
9469 compose->exteditor_socket = socket;
9472 if (pipe(pipe_fds) < 0) {
9478 if ((pid = fork()) < 0) {
9485 /* close the write side of the pipe */
9488 compose->exteditor_file = g_strdup(tmp);
9489 compose->exteditor_pid = pid;
9491 compose_set_ext_editor_sensitive(compose, FALSE);
9494 compose->exteditor_ch = g_io_channel_unix_new(pipe_fds[0]);
9496 compose->exteditor_ch = g_io_channel_win32_new_fd(pipe_fds[0]);
9498 compose->exteditor_tag = g_io_add_watch(compose->exteditor_ch,
9502 } else { /* process-monitoring process */
9508 /* close the read side of the pipe */
9511 if (compose_write_body_to_file(compose, tmp) < 0) {
9512 fd_write_all(pipe_fds[1], "2\n", 2);
9516 pid_ed = compose_exec_ext_editor_real(tmp, socket_wid);
9518 fd_write_all(pipe_fds[1], "1\n", 2);
9522 /* wait until editor is terminated */
9523 waitpid(pid_ed, NULL, 0);
9525 fd_write_all(pipe_fds[1], "0\n", 2);
9532 #endif /* G_OS_UNIX */
9535 static gboolean compose_can_autosave(Compose *compose)
9537 if (compose->privacy_system && compose->use_encryption)
9538 return prefs_common.autosave && prefs_common.autosave_encrypted;
9540 return prefs_common.autosave;
9544 static gboolean compose_get_ext_editor_cmd_valid()
9546 gboolean has_s = FALSE;
9547 gboolean has_w = FALSE;
9548 const gchar *p = prefs_common_get_ext_editor_cmd();
9551 while ((p = strchr(p, '%'))) {
9557 } else if (*p == 'w') {
9568 static gint compose_exec_ext_editor_real(const gchar *file, GdkNativeWindow socket_wid)
9575 cm_return_val_if_fail(file != NULL, -1);
9577 if ((pid = fork()) < 0) {
9582 if (pid != 0) return pid;
9584 /* grandchild process */
9586 if (setpgid(0, getppid()))
9589 if (compose_get_ext_editor_cmd_valid()) {
9590 if (compose_get_ext_editor_uses_socket()) {
9591 p = g_strdup(prefs_common_get_ext_editor_cmd());
9592 s = strstr(p, "%w");
9594 if (strstr(p, "%s") < s)
9595 buf = g_strdup_printf(p, file, socket_wid);
9597 buf = g_strdup_printf(p, socket_wid, file);
9600 buf = g_strdup_printf(prefs_common_get_ext_editor_cmd(), file);
9603 if (prefs_common_get_ext_editor_cmd())
9604 g_warning("External editor command-line is invalid: '%s'",
9605 prefs_common_get_ext_editor_cmd());
9606 buf = g_strdup_printf(DEFAULT_EDITOR_CMD, file);
9609 cmdline = strsplit_with_quote(buf, " ", 0);
9611 execvp(cmdline[0], cmdline);
9614 g_strfreev(cmdline);
9619 static gboolean compose_ext_editor_kill(Compose *compose)
9621 pid_t pgid = compose->exteditor_pid * -1;
9624 ret = kill(pgid, 0);
9626 if (ret == 0 || (ret == -1 && EPERM == errno)) {
9630 msg = g_strdup_printf
9631 (_("The external editor is still working.\n"
9632 "Force terminating the process?\n"
9633 "process group id: %d"), -pgid);
9634 val = alertpanel_full(_("Notice"), msg, GTK_STOCK_NO, GTK_STOCK_YES,
9635 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
9639 if (val == G_ALERTALTERNATE) {
9640 g_source_remove(compose->exteditor_tag);
9641 g_io_channel_shutdown(compose->exteditor_ch,
9643 g_io_channel_unref(compose->exteditor_ch);
9645 if (kill(pgid, SIGTERM) < 0) perror("kill");
9646 waitpid(compose->exteditor_pid, NULL, 0);
9648 g_warning("Terminated process group id: %d. "
9649 "Temporary file: %s", -pgid, compose->exteditor_file);
9651 compose_set_ext_editor_sensitive(compose, TRUE);
9653 g_free(compose->exteditor_file);
9654 compose->exteditor_file = NULL;
9655 compose->exteditor_pid = -1;
9656 compose->exteditor_ch = NULL;
9657 compose->exteditor_tag = -1;
9665 static gboolean compose_input_cb(GIOChannel *source, GIOCondition condition,
9669 Compose *compose = (Compose *)data;
9672 debug_print("Compose: input from monitoring process\n");
9674 if (g_io_channel_read_chars(source, buf, sizeof(buf), &bytes_read, NULL) != G_IO_STATUS_NORMAL) {
9679 g_io_channel_shutdown(source, FALSE, NULL);
9680 g_io_channel_unref(source);
9682 waitpid(compose->exteditor_pid, NULL, 0);
9684 if (buf[0] == '0') { /* success */
9685 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9686 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
9687 GtkTextIter start, end;
9690 gtk_text_buffer_set_text(buffer, "", -1);
9691 compose_insert_file(compose, compose->exteditor_file);
9692 compose_changed_cb(NULL, compose);
9694 /* Check if we should save the draft or not */
9695 if (compose_can_autosave(compose))
9696 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
9698 if (claws_unlink(compose->exteditor_file) < 0)
9699 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9701 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
9702 gtk_text_buffer_get_start_iter(buffer, &start);
9703 gtk_text_buffer_get_end_iter(buffer, &end);
9704 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
9705 if (chars && strlen(chars) > 0)
9706 compose->modified = TRUE;
9708 } else if (buf[0] == '1') { /* failed */
9709 g_warning("Couldn't exec external editor");
9710 if (claws_unlink(compose->exteditor_file) < 0)
9711 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9712 } else if (buf[0] == '2') {
9713 g_warning("Couldn't write to file");
9714 } else if (buf[0] == '3') {
9715 g_warning("Pipe read failed");
9718 compose_set_ext_editor_sensitive(compose, TRUE);
9720 g_free(compose->exteditor_file);
9721 compose->exteditor_file = NULL;
9722 compose->exteditor_pid = -1;
9723 compose->exteditor_ch = NULL;
9724 compose->exteditor_tag = -1;
9725 if (compose->exteditor_socket) {
9726 gtk_widget_destroy(compose->exteditor_socket);
9727 compose->exteditor_socket = NULL;
9734 static char *ext_editor_menu_entries[] = {
9735 "Menu/Message/Send",
9736 "Menu/Message/SendLater",
9737 "Menu/Message/InsertFile",
9738 "Menu/Message/InsertSig",
9739 "Menu/Message/ReplaceSig",
9740 "Menu/Message/Save",
9741 "Menu/Message/Print",
9746 "Menu/Tools/ShowRuler",
9747 "Menu/Tools/Actions",
9752 static void compose_set_ext_editor_sensitive(Compose *compose,
9757 for (i = 0; ext_editor_menu_entries[i]; ++i) {
9758 cm_menu_set_sensitive_full(compose->ui_manager,
9759 ext_editor_menu_entries[i], sensitive);
9762 if (compose_get_ext_editor_uses_socket()) {
9764 if (compose->exteditor_socket)
9765 gtk_widget_hide(compose->exteditor_socket);
9766 gtk_widget_show(compose->scrolledwin);
9767 if (prefs_common.show_ruler)
9768 gtk_widget_show(compose->ruler_hbox);
9769 /* Fix the focus, as it doesn't go anywhere when the
9770 * socket is hidden or destroyed */
9771 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9773 g_assert (compose->exteditor_socket != NULL);
9774 /* Fix the focus, as it doesn't go anywhere when the
9775 * edit box is hidden */
9776 if (gtk_widget_is_focus(compose->text))
9777 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9778 gtk_widget_hide(compose->scrolledwin);
9779 gtk_widget_hide(compose->ruler_hbox);
9780 gtk_widget_show(compose->exteditor_socket);
9783 gtk_widget_set_sensitive(compose->text, sensitive);
9785 if (compose->toolbar->send_btn)
9786 gtk_widget_set_sensitive(compose->toolbar->send_btn, sensitive);
9787 if (compose->toolbar->sendl_btn)
9788 gtk_widget_set_sensitive(compose->toolbar->sendl_btn, sensitive);
9789 if (compose->toolbar->draft_btn)
9790 gtk_widget_set_sensitive(compose->toolbar->draft_btn, sensitive);
9791 if (compose->toolbar->insert_btn)
9792 gtk_widget_set_sensitive(compose->toolbar->insert_btn, sensitive);
9793 if (compose->toolbar->sig_btn)
9794 gtk_widget_set_sensitive(compose->toolbar->sig_btn, sensitive);
9795 if (compose->toolbar->exteditor_btn)
9796 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, sensitive);
9797 if (compose->toolbar->linewrap_current_btn)
9798 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, sensitive);
9799 if (compose->toolbar->linewrap_all_btn)
9800 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, sensitive);
9803 static gboolean compose_get_ext_editor_uses_socket()
9805 return (prefs_common_get_ext_editor_cmd() &&
9806 strstr(prefs_common_get_ext_editor_cmd(), "%w"));
9809 static gboolean compose_ext_editor_plug_removed_cb(GtkSocket *socket, Compose *compose)
9811 compose->exteditor_socket = NULL;
9812 /* returning FALSE allows destruction of the socket */
9815 #endif /* G_OS_UNIX */
9818 * compose_undo_state_changed:
9820 * Change the sensivity of the menuentries undo and redo
9822 static void compose_undo_state_changed(UndoMain *undostruct, gint undo_state,
9823 gint redo_state, gpointer data)
9825 Compose *compose = (Compose *)data;
9827 switch (undo_state) {
9828 case UNDO_STATE_TRUE:
9829 if (!undostruct->undo_state) {
9830 undostruct->undo_state = TRUE;
9831 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", TRUE);
9834 case UNDO_STATE_FALSE:
9835 if (undostruct->undo_state) {
9836 undostruct->undo_state = FALSE;
9837 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
9840 case UNDO_STATE_UNCHANGED:
9842 case UNDO_STATE_REFRESH:
9843 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", undostruct->undo_state);
9846 g_warning("Undo state not recognized");
9850 switch (redo_state) {
9851 case UNDO_STATE_TRUE:
9852 if (!undostruct->redo_state) {
9853 undostruct->redo_state = TRUE;
9854 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", TRUE);
9857 case UNDO_STATE_FALSE:
9858 if (undostruct->redo_state) {
9859 undostruct->redo_state = FALSE;
9860 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
9863 case UNDO_STATE_UNCHANGED:
9865 case UNDO_STATE_REFRESH:
9866 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", undostruct->redo_state);
9869 g_warning("Redo state not recognized");
9874 /* callback functions */
9876 static void compose_notebook_size_alloc(GtkNotebook *notebook,
9877 GtkAllocation *allocation,
9880 prefs_common.compose_notebook_height = gtk_paned_get_position(paned);
9883 /* compose_edit_size_alloc() - called when resized. don't know whether Gtk
9884 * includes "non-client" (windows-izm) in calculation, so this calculation
9885 * may not be accurate.
9887 static gboolean compose_edit_size_alloc(GtkEditable *widget,
9888 GtkAllocation *allocation,
9889 GtkSHRuler *shruler)
9891 if (prefs_common.show_ruler) {
9892 gint char_width = 0, char_height = 0;
9893 gint line_width_in_chars;
9895 gtkut_get_font_size(GTK_WIDGET(widget),
9896 &char_width, &char_height);
9897 line_width_in_chars =
9898 (allocation->width - allocation->x) / char_width;
9900 /* got the maximum */
9901 gtk_shruler_set_range(GTK_SHRULER(shruler),
9902 0.0, line_width_in_chars, 0);
9911 ComposePrefType type;
9912 gboolean entry_marked;
9915 static void account_activated(GtkComboBox *optmenu, gpointer data)
9917 Compose *compose = (Compose *)data;
9920 gchar *folderidentifier;
9921 gint account_id = 0;
9924 GSList *list, *saved_list = NULL;
9925 HeaderEntryState *state;
9927 /* Get ID of active account in the combo box */
9928 menu = gtk_combo_box_get_model(optmenu);
9929 cm_return_if_fail(gtk_combo_box_get_active_iter(optmenu, &iter));
9930 gtk_tree_model_get(menu, &iter, 1, &account_id, -1);
9932 ac = account_find_from_id(account_id);
9933 cm_return_if_fail(ac != NULL);
9935 if (ac != compose->account) {
9936 compose_select_account(compose, ac, FALSE);
9938 for (list = compose->header_list; list; list = list->next) {
9939 ComposeHeaderEntry *hentry=(ComposeHeaderEntry *)list->data;
9941 if (hentry->type == PREF_ACCOUNT || !list->next) {
9942 compose_destroy_headerentry(compose, hentry);
9945 state = g_malloc0(sizeof(HeaderEntryState));
9946 state->header = gtk_editable_get_chars(GTK_EDITABLE(
9947 gtk_bin_get_child(GTK_BIN(hentry->combo))), 0, -1);
9948 state->entry = gtk_editable_get_chars(
9949 GTK_EDITABLE(hentry->entry), 0, -1);
9950 state->type = hentry->type;
9952 saved_list = g_slist_append(saved_list, state);
9953 compose_destroy_headerentry(compose, hentry);
9956 compose->header_last = NULL;
9957 g_slist_free(compose->header_list);
9958 compose->header_list = NULL;
9959 compose->header_nextrow = 1;
9960 compose_create_header_entry(compose);
9962 if (ac->set_autocc && ac->auto_cc)
9963 compose_entry_append(compose, ac->auto_cc,
9964 COMPOSE_CC, PREF_ACCOUNT);
9965 if (ac->set_autobcc && ac->auto_bcc)
9966 compose_entry_append(compose, ac->auto_bcc,
9967 COMPOSE_BCC, PREF_ACCOUNT);
9968 if (ac->set_autoreplyto && ac->auto_replyto)
9969 compose_entry_append(compose, ac->auto_replyto,
9970 COMPOSE_REPLYTO, PREF_ACCOUNT);
9972 for (list = saved_list; list; list = list->next) {
9973 state = (HeaderEntryState *) list->data;
9975 compose_add_header_entry(compose, state->header,
9976 state->entry, state->type);
9978 g_free(state->header);
9979 g_free(state->entry);
9982 g_slist_free(saved_list);
9984 combobox_select_by_data(GTK_COMBO_BOX(compose->header_last->combo),
9985 (ac->protocol == A_NNTP) ?
9986 COMPOSE_NEWSGROUPS : COMPOSE_TO);
9989 /* Set message save folder */
9990 if (account_get_special_folder(compose->account, F_OUTBOX)) {
9991 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
9993 g_signal_connect(G_OBJECT(compose->savemsg_checkbtn), "toggled",
9994 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
9996 compose_set_save_to(compose, NULL);
9997 if (account_get_special_folder(compose->account, F_OUTBOX)) {
9998 folderidentifier = folder_item_get_identifier(account_get_special_folder
9999 (compose->account, F_OUTBOX));
10000 compose_set_save_to(compose, folderidentifier);
10001 g_free(folderidentifier);
10005 static void attach_selected(GtkTreeView *tree_view, GtkTreePath *tree_path,
10006 GtkTreeViewColumn *column, Compose *compose)
10008 compose_attach_property(NULL, compose);
10011 static gboolean attach_button_pressed(GtkWidget *widget, GdkEventButton *event,
10014 Compose *compose = (Compose *)data;
10015 GtkTreeSelection *attach_selection;
10016 gint attach_nr_selected;
10019 if (!event) return FALSE;
10021 if (event->button == 3) {
10022 attach_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
10023 attach_nr_selected = gtk_tree_selection_count_selected_rows(attach_selection);
10025 /* If no rows, or just one row is selected, right-click should
10026 * open menu relevant to the row being right-clicked on. We
10027 * achieve that by selecting the clicked row first. If more
10028 * than one row is selected, we shouldn't modify the selection,
10029 * as user may want to remove selected rows (attachments). */
10030 if (attach_nr_selected < 2) {
10031 gtk_tree_selection_unselect_all(attach_selection);
10032 attach_nr_selected = 0;
10033 gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget),
10034 event->x, event->y, &path, NULL, NULL, NULL);
10035 if (path != NULL) {
10036 gtk_tree_selection_select_path(attach_selection, path);
10037 gtk_tree_path_free(path);
10038 attach_nr_selected++;
10042 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", (attach_nr_selected > 0));
10043 /* Properties menu item makes no sense with more than one row
10044 * selected, the properties dialog can only edit one attachment. */
10045 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", (attach_nr_selected == 1));
10047 gtk_menu_popup(GTK_MENU(compose->popupmenu), NULL, NULL,
10048 NULL, NULL, event->button, event->time);
10055 static gboolean attach_key_pressed(GtkWidget *widget, GdkEventKey *event,
10058 Compose *compose = (Compose *)data;
10060 if (!event) return FALSE;
10062 switch (event->keyval) {
10063 case GDK_KEY_Delete:
10064 compose_attach_remove_selected(NULL, compose);
10070 static void compose_allow_user_actions (Compose *compose, gboolean allow)
10072 toolbar_comp_set_sensitive(compose, allow);
10073 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message", allow);
10074 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", allow);
10076 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", allow);
10078 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", allow);
10079 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools", allow);
10080 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Help", allow);
10082 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), allow);
10086 static void compose_send_cb(GtkAction *action, gpointer data)
10088 Compose *compose = (Compose *)data;
10091 if (compose->exteditor_tag != -1) {
10092 debug_print("ignoring send: external editor still open\n");
10096 if (prefs_common.work_offline &&
10097 !inc_offline_should_override(TRUE,
10098 _("Claws Mail needs network access in order "
10099 "to send this email.")))
10102 if (compose->draft_timeout_tag >= 0) { /* CLAWS: disable draft timeout */
10103 g_source_remove(compose->draft_timeout_tag);
10104 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
10107 compose_send(compose);
10110 static void compose_send_later_cb(GtkAction *action, gpointer data)
10112 Compose *compose = (Compose *)data;
10116 compose_allow_user_actions(compose, FALSE);
10117 val = compose_queue_sub(compose, NULL, NULL, NULL, TRUE, TRUE);
10118 compose_allow_user_actions(compose, TRUE);
10122 compose_close(compose);
10123 } else if (val == -1) {
10124 alertpanel_error(_("Could not queue message."));
10125 } else if (val == -2) {
10126 alertpanel_error(_("Could not queue message:\n\n%s."), g_strerror(errno));
10127 } else if (val == -3) {
10128 if (privacy_peek_error())
10129 alertpanel_error(_("Could not queue message for sending:\n\n"
10130 "Signature failed: %s"), privacy_get_error());
10131 } else if (val == -4) {
10132 alertpanel_error(_("Could not queue message for sending:\n\n"
10133 "Charset conversion failed."));
10134 } else if (val == -5) {
10135 alertpanel_error(_("Could not queue message for sending:\n\n"
10136 "Couldn't get recipient encryption key."));
10137 } else if (val == -6) {
10140 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
10143 #define DRAFTED_AT_EXIT "drafted_at_exit"
10144 static void compose_register_draft(MsgInfo *info)
10146 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10147 DRAFTED_AT_EXIT, NULL);
10148 FILE *fp = g_fopen(filepath, "ab");
10151 fprintf(fp, "%s\t%d\n", folder_item_get_identifier(info->folder),
10159 gboolean compose_draft (gpointer data, guint action)
10161 Compose *compose = (Compose *)data;
10166 MsgFlags flag = {0, 0};
10167 static gboolean lock = FALSE;
10168 MsgInfo *newmsginfo;
10170 gboolean target_locked = FALSE;
10171 gboolean err = FALSE;
10173 if (lock) return FALSE;
10175 if (compose->sending)
10178 draft = account_get_special_folder(compose->account, F_DRAFT);
10179 cm_return_val_if_fail(draft != NULL, FALSE);
10181 if (!g_mutex_trylock(compose->mutex)) {
10182 /* we don't want to lock the mutex once it's available,
10183 * because as the only other part of compose.c locking
10184 * it is compose_close - which means once unlocked,
10185 * the compose struct will be freed */
10186 debug_print("couldn't lock mutex, probably sending\n");
10192 tmp = g_strdup_printf("%s%cdraft.%p", get_tmp_dir(),
10193 G_DIR_SEPARATOR, compose);
10194 if ((fp = g_fopen(tmp, "wb")) == NULL) {
10195 FILE_OP_ERROR(tmp, "fopen");
10199 /* chmod for security */
10200 if (change_file_mode_rw(fp, tmp) < 0) {
10201 FILE_OP_ERROR(tmp, "chmod");
10202 g_warning("can't change file mode");
10205 /* Save draft infos */
10206 err |= (fprintf(fp, "X-Claws-Account-Id:%d\n", compose->account->account_id) < 0);
10207 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
10209 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
10210 gchar *savefolderid;
10212 savefolderid = compose_get_save_to(compose);
10213 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
10214 g_free(savefolderid);
10216 if (compose->return_receipt) {
10217 err |= (fprintf(fp, "RRCPT:1\n") < 0);
10219 if (compose->privacy_system) {
10220 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
10221 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
10222 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
10225 /* Message-ID of message replying to */
10226 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
10227 gchar *folderid = NULL;
10229 if (compose->replyinfo->folder)
10230 folderid = folder_item_get_identifier(compose->replyinfo->folder);
10231 if (folderid == NULL)
10232 folderid = g_strdup("NULL");
10234 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
10237 /* Message-ID of message forwarding to */
10238 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
10239 gchar *folderid = NULL;
10241 if (compose->fwdinfo->folder)
10242 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
10243 if (folderid == NULL)
10244 folderid = g_strdup("NULL");
10246 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
10250 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
10251 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
10253 sheaders = compose_get_manual_headers_info(compose);
10254 err |= (fprintf(fp, "X-Claws-Manual-Headers:%s\n", sheaders) < 0);
10257 /* end of headers */
10258 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
10265 if (compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_STORE, action != COMPOSE_AUTO_SAVE) < 0) {
10269 if (fclose(fp) == EOF) {
10273 flag.perm_flags = MSG_NEW|MSG_UNREAD;
10274 if (compose->targetinfo) {
10275 target_locked = MSG_IS_LOCKED(compose->targetinfo->flags);
10277 flag.perm_flags |= MSG_LOCKED;
10279 flag.tmp_flags = MSG_DRAFT;
10281 folder_item_scan(draft);
10282 if ((msgnum = folder_item_add_msg(draft, tmp, &flag, TRUE)) < 0) {
10283 MsgInfo *tmpinfo = NULL;
10284 debug_print("didn't get msgnum after adding draft [%s]\n", compose->msgid?compose->msgid:"no msgid");
10285 if (compose->msgid) {
10286 tmpinfo = folder_item_get_msginfo_by_msgid(draft, compose->msgid);
10289 msgnum = tmpinfo->msgnum;
10290 procmsg_msginfo_free(&tmpinfo);
10291 debug_print("got draft msgnum %d from scanning\n", msgnum);
10293 debug_print("didn't get draft msgnum after scanning\n");
10296 debug_print("got draft msgnum %d from adding\n", msgnum);
10302 if (action != COMPOSE_AUTO_SAVE) {
10303 if (action != COMPOSE_DRAFT_FOR_EXIT)
10304 alertpanel_error(_("Could not save draft."));
10307 gtkut_window_popup(compose->window);
10308 val = alertpanel_full(_("Could not save draft"),
10309 _("Could not save draft.\n"
10310 "Do you want to cancel exit or discard this email?"),
10311 _("_Cancel exit"), _("_Discard email"), NULL,
10312 FALSE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
10313 if (val == G_ALERTALTERNATE) {
10315 g_mutex_unlock(compose->mutex); /* must be done before closing */
10316 compose_close(compose);
10320 g_mutex_unlock(compose->mutex); /* must be done before closing */
10329 if (compose->mode == COMPOSE_REEDIT) {
10330 compose_remove_reedit_target(compose, TRUE);
10333 newmsginfo = folder_item_get_msginfo(draft, msgnum);
10336 procmsg_msginfo_unset_flags(newmsginfo, ~0, ~0);
10338 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD|MSG_LOCKED, MSG_DRAFT);
10340 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD, MSG_DRAFT);
10341 if (compose_use_attach(compose) && action != COMPOSE_AUTO_SAVE)
10342 procmsg_msginfo_set_flags(newmsginfo, 0,
10343 MSG_HAS_ATTACHMENT);
10345 if (action == COMPOSE_DRAFT_FOR_EXIT) {
10346 compose_register_draft(newmsginfo);
10348 procmsg_msginfo_free(&newmsginfo);
10351 folder_item_scan(draft);
10353 if (action == COMPOSE_QUIT_EDITING || action == COMPOSE_DRAFT_FOR_EXIT) {
10355 g_mutex_unlock(compose->mutex); /* must be done before closing */
10356 compose_close(compose);
10362 path = folder_item_fetch_msg(draft, msgnum);
10363 if (path == NULL) {
10364 debug_print("can't fetch %s:%d\n", draft->path, msgnum);
10367 if (g_stat(path, &s) < 0) {
10368 FILE_OP_ERROR(path, "stat");
10374 procmsg_msginfo_free(&(compose->targetinfo));
10375 compose->targetinfo = procmsg_msginfo_new();
10376 compose->targetinfo->msgnum = msgnum;
10377 compose->targetinfo->size = (goffset)s.st_size;
10378 compose->targetinfo->mtime = s.st_mtime;
10379 compose->targetinfo->folder = draft;
10381 procmsg_msginfo_set_flags(compose->targetinfo, MSG_LOCKED, 0);
10382 compose->mode = COMPOSE_REEDIT;
10384 if (action == COMPOSE_AUTO_SAVE) {
10385 compose->autosaved_draft = compose->targetinfo;
10387 compose->modified = FALSE;
10388 compose_set_title(compose);
10392 g_mutex_unlock(compose->mutex);
10396 void compose_clear_exit_drafts(void)
10398 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10399 DRAFTED_AT_EXIT, NULL);
10400 if (is_file_exist(filepath))
10401 claws_unlink(filepath);
10406 void compose_reopen_exit_drafts(void)
10408 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10409 DRAFTED_AT_EXIT, NULL);
10410 FILE *fp = g_fopen(filepath, "rb");
10414 while (fgets(buf, sizeof(buf), fp)) {
10415 gchar **parts = g_strsplit(buf, "\t", 2);
10416 const gchar *folder = parts[0];
10417 int msgnum = parts[1] ? atoi(parts[1]):-1;
10419 if (folder && *folder && msgnum > -1) {
10420 FolderItem *item = folder_find_item_from_identifier(folder);
10421 MsgInfo *info = folder_item_get_msginfo(item, msgnum);
10423 compose_reedit(info, FALSE);
10430 compose_clear_exit_drafts();
10433 static void compose_save_cb(GtkAction *action, gpointer data)
10435 Compose *compose = (Compose *)data;
10436 compose_draft(compose, COMPOSE_KEEP_EDITING);
10437 compose->rmode = COMPOSE_REEDIT;
10440 void compose_attach_from_list(Compose *compose, GList *file_list, gboolean free_data)
10442 if (compose && file_list) {
10445 for ( tmp = file_list; tmp; tmp = tmp->next) {
10446 gchar *file = (gchar *) tmp->data;
10447 gchar *utf8_filename = conv_filename_to_utf8(file);
10448 compose_attach_append(compose, file, utf8_filename, NULL, NULL);
10449 compose_changed_cb(NULL, compose);
10454 g_free(utf8_filename);
10459 static void compose_attach_cb(GtkAction *action, gpointer data)
10461 Compose *compose = (Compose *)data;
10464 if (compose->redirect_filename != NULL)
10467 /* Set focus_window properly, in case we were called via popup menu,
10468 * which unsets it (via focus_out_event callback on compose window). */
10469 manage_window_focus_in(compose->window, NULL, NULL);
10471 file_list = filesel_select_multiple_files_open(_("Select file"), NULL);
10474 compose_attach_from_list(compose, file_list, TRUE);
10475 g_list_free(file_list);
10479 static void compose_insert_file_cb(GtkAction *action, gpointer data)
10481 Compose *compose = (Compose *)data;
10483 gint files_inserted = 0;
10485 file_list = filesel_select_multiple_files_open(_("Select file"), NULL);
10490 for ( tmp = file_list; tmp; tmp = tmp->next) {
10491 gchar *file = (gchar *) tmp->data;
10492 gchar *filedup = g_strdup(file);
10493 gchar *shortfile = g_path_get_basename(filedup);
10494 ComposeInsertResult res;
10495 /* insert the file if the file is short or if the user confirmed that
10496 he/she wants to insert the large file */
10497 res = compose_insert_file(compose, file);
10498 if (res == COMPOSE_INSERT_READ_ERROR) {
10499 alertpanel_error(_("File '%s' could not be read."), shortfile);
10500 } else if (res == COMPOSE_INSERT_INVALID_CHARACTER) {
10501 alertpanel_error(_("File '%s' contained invalid characters\n"
10502 "for the current encoding, insertion may be incorrect."),
10504 } else if (res == COMPOSE_INSERT_SUCCESS)
10511 g_list_free(file_list);
10515 if (files_inserted > 0 && compose->gtkaspell &&
10516 compose->gtkaspell->check_while_typing)
10517 gtkaspell_highlight_all(compose->gtkaspell);
10521 static void compose_insert_sig_cb(GtkAction *action, gpointer data)
10523 Compose *compose = (Compose *)data;
10525 compose_insert_sig(compose, FALSE);
10528 static void compose_replace_sig_cb(GtkAction *action, gpointer data)
10530 Compose *compose = (Compose *)data;
10532 compose_insert_sig(compose, TRUE);
10535 static gint compose_delete_cb(GtkWidget *widget, GdkEventAny *event,
10539 Compose *compose = (Compose *)data;
10541 gtkut_widget_get_uposition(widget, &x, &y);
10542 if (!compose->batch) {
10543 prefs_common.compose_x = x;
10544 prefs_common.compose_y = y;
10546 if (compose->sending || compose->updating)
10548 compose_close_cb(NULL, compose);
10552 void compose_close_toolbar(Compose *compose)
10554 compose_close_cb(NULL, compose);
10557 static void compose_close_cb(GtkAction *action, gpointer data)
10559 Compose *compose = (Compose *)data;
10563 if (compose->exteditor_tag != -1) {
10564 if (!compose_ext_editor_kill(compose))
10569 if (compose->modified) {
10570 gboolean reedit = (compose->rmode == COMPOSE_REEDIT);
10571 if (!g_mutex_trylock(compose->mutex)) {
10572 /* we don't want to lock the mutex once it's available,
10573 * because as the only other part of compose.c locking
10574 * it is compose_close - which means once unlocked,
10575 * the compose struct will be freed */
10576 debug_print("couldn't lock mutex, probably sending\n");
10580 val = alertpanel(_("Discard message"),
10581 _("This message has been modified. Discard it?"),
10582 _("_Discard"), _("_Save to Drafts"), GTK_STOCK_CANCEL);
10584 val = alertpanel(_("Save changes"),
10585 _("This message has been modified. Save the latest changes?"),
10586 _("_Don't save"), g_strconcat("+", _("_Save to Drafts"), NULL),
10589 g_mutex_unlock(compose->mutex);
10591 case G_ALERTDEFAULT:
10592 if (compose_can_autosave(compose) && !reedit)
10593 compose_remove_draft(compose);
10595 case G_ALERTALTERNATE:
10596 compose_draft(data, COMPOSE_QUIT_EDITING);
10603 compose_close(compose);
10606 static void compose_print_cb(GtkAction *action, gpointer data)
10608 Compose *compose = (Compose *) data;
10610 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
10611 if (compose->targetinfo)
10612 messageview_print(compose->targetinfo, FALSE, -1, -1, 0);
10615 static void compose_set_encoding_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
10617 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
10618 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
10619 Compose *compose = (Compose *) data;
10622 compose->out_encoding = (CharSet)value;
10625 static void compose_address_cb(GtkAction *action, gpointer data)
10627 Compose *compose = (Compose *)data;
10629 #ifndef USE_ALT_ADDRBOOK
10630 addressbook_open(compose);
10632 GError* error = NULL;
10633 addressbook_connect_signals(compose);
10634 addressbook_dbus_open(TRUE, &error);
10636 g_warning("%s", error->message);
10637 g_error_free(error);
10642 static void about_show_cb(GtkAction *action, gpointer data)
10647 static void compose_template_activate_cb(GtkWidget *widget, gpointer data)
10649 Compose *compose = (Compose *)data;
10654 tmpl = g_object_get_data(G_OBJECT(widget), "template");
10655 cm_return_if_fail(tmpl != NULL);
10657 msg = g_strdup_printf(_("Do you want to apply the template '%s'?"),
10659 val = alertpanel(_("Apply template"), msg,
10660 _("_Replace"), _("_Insert"), GTK_STOCK_CANCEL);
10663 if (val == G_ALERTDEFAULT)
10664 compose_template_apply(compose, tmpl, TRUE);
10665 else if (val == G_ALERTALTERNATE)
10666 compose_template_apply(compose, tmpl, FALSE);
10669 static void compose_ext_editor_cb(GtkAction *action, gpointer data)
10671 Compose *compose = (Compose *)data;
10674 if (compose->exteditor_tag != -1) {
10675 debug_print("ignoring open external editor: external editor still open\n");
10679 compose_exec_ext_editor(compose);
10682 static void compose_undo_cb(GtkAction *action, gpointer data)
10684 Compose *compose = (Compose *)data;
10685 gboolean prev_autowrap = compose->autowrap;
10687 compose->autowrap = FALSE;
10688 undo_undo(compose->undostruct);
10689 compose->autowrap = prev_autowrap;
10692 static void compose_redo_cb(GtkAction *action, gpointer data)
10694 Compose *compose = (Compose *)data;
10695 gboolean prev_autowrap = compose->autowrap;
10697 compose->autowrap = FALSE;
10698 undo_redo(compose->undostruct);
10699 compose->autowrap = prev_autowrap;
10702 static void entry_cut_clipboard(GtkWidget *entry)
10704 if (GTK_IS_EDITABLE(entry))
10705 gtk_editable_cut_clipboard (GTK_EDITABLE(entry));
10706 else if (GTK_IS_TEXT_VIEW(entry))
10707 gtk_text_buffer_cut_clipboard(
10708 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10709 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
10713 static void entry_copy_clipboard(GtkWidget *entry)
10715 if (GTK_IS_EDITABLE(entry))
10716 gtk_editable_copy_clipboard (GTK_EDITABLE(entry));
10717 else if (GTK_IS_TEXT_VIEW(entry))
10718 gtk_text_buffer_copy_clipboard(
10719 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10720 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
10723 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry,
10724 gboolean wrap, GdkAtom clip, GtkTextIter *insert_place)
10726 if (GTK_IS_TEXT_VIEW(entry)) {
10727 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10728 GtkTextMark *mark_start = gtk_text_buffer_get_insert(buffer);
10729 GtkTextIter start_iter, end_iter;
10731 gchar *contents = gtk_clipboard_wait_for_text(gtk_clipboard_get(clip));
10733 if (contents == NULL)
10736 /* we shouldn't delete the selection when middle-click-pasting, or we
10737 * can't mid-click-paste our own selection */
10738 if (clip != GDK_SELECTION_PRIMARY) {
10739 undo_paste_clipboard(GTK_TEXT_VIEW(compose->text), compose->undostruct);
10740 gtk_text_buffer_delete_selection(buffer, FALSE, TRUE);
10743 if (insert_place == NULL) {
10744 /* if insert_place isn't specified, insert at the cursor.
10745 * used for Ctrl-V pasting */
10746 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10747 start = gtk_text_iter_get_offset(&start_iter);
10748 gtk_text_buffer_insert(buffer, &start_iter, contents, strlen(contents));
10750 /* if insert_place is specified, paste here.
10751 * used for mid-click-pasting */
10752 start = gtk_text_iter_get_offset(insert_place);
10753 gtk_text_buffer_insert(buffer, insert_place, contents, strlen(contents));
10754 if (prefs_common.primary_paste_unselects)
10755 gtk_text_buffer_select_range(buffer, insert_place, insert_place);
10759 /* paste unwrapped: mark the paste so it's not wrapped later */
10760 end = start + strlen(contents);
10761 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, start);
10762 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, end);
10763 gtk_text_buffer_apply_tag_by_name(buffer, "no_wrap", &start_iter, &end_iter);
10764 } else if (wrap && clip == GDK_SELECTION_PRIMARY) {
10765 /* rewrap paragraph now (after a mid-click-paste) */
10766 mark_start = gtk_text_buffer_get_insert(buffer);
10767 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10768 gtk_text_iter_backward_char(&start_iter);
10769 compose_beautify_paragraph(compose, &start_iter, TRUE);
10771 } else if (GTK_IS_EDITABLE(entry))
10772 gtk_editable_paste_clipboard (GTK_EDITABLE(entry));
10774 compose->modified = TRUE;
10777 static void entry_allsel(GtkWidget *entry)
10779 if (GTK_IS_EDITABLE(entry))
10780 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
10781 else if (GTK_IS_TEXT_VIEW(entry)) {
10782 GtkTextIter startiter, enditer;
10783 GtkTextBuffer *textbuf;
10785 textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10786 gtk_text_buffer_get_start_iter(textbuf, &startiter);
10787 gtk_text_buffer_get_end_iter(textbuf, &enditer);
10789 gtk_text_buffer_move_mark_by_name(textbuf,
10790 "selection_bound", &startiter);
10791 gtk_text_buffer_move_mark_by_name(textbuf,
10792 "insert", &enditer);
10796 static void compose_cut_cb(GtkAction *action, gpointer data)
10798 Compose *compose = (Compose *)data;
10799 if (compose->focused_editable
10800 #ifndef GENERIC_UMPC
10801 && gtk_widget_has_focus(compose->focused_editable)
10804 entry_cut_clipboard(compose->focused_editable);
10807 static void compose_copy_cb(GtkAction *action, gpointer data)
10809 Compose *compose = (Compose *)data;
10810 if (compose->focused_editable
10811 #ifndef GENERIC_UMPC
10812 && gtk_widget_has_focus(compose->focused_editable)
10815 entry_copy_clipboard(compose->focused_editable);
10818 static void compose_paste_cb(GtkAction *action, gpointer data)
10820 Compose *compose = (Compose *)data;
10821 gint prev_autowrap;
10822 GtkTextBuffer *buffer;
10824 if (compose->focused_editable &&
10825 #ifndef GENERIC_UMPC
10826 gtk_widget_has_focus(compose->focused_editable)
10829 entry_paste_clipboard(compose, compose->focused_editable,
10830 prefs_common.linewrap_pastes,
10831 GDK_SELECTION_CLIPBOARD, NULL);
10836 #ifndef GENERIC_UMPC
10837 gtk_widget_has_focus(compose->text) &&
10839 compose->gtkaspell &&
10840 compose->gtkaspell->check_while_typing)
10841 gtkaspell_highlight_all(compose->gtkaspell);
10845 static void compose_paste_as_quote_cb(GtkAction *action, gpointer data)
10847 Compose *compose = (Compose *)data;
10848 gint wrap_quote = prefs_common.linewrap_quote;
10849 if (compose->focused_editable
10850 #ifndef GENERIC_UMPC
10851 && gtk_widget_has_focus(compose->focused_editable)
10854 /* let text_insert() (called directly or at a later time
10855 * after the gtk_editable_paste_clipboard) know that
10856 * text is to be inserted as a quotation. implemented
10857 * by using a simple refcount... */
10858 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data(
10859 G_OBJECT(compose->focused_editable),
10860 "paste_as_quotation"));
10861 g_object_set_data(G_OBJECT(compose->focused_editable),
10862 "paste_as_quotation",
10863 GINT_TO_POINTER(paste_as_quotation + 1));
10864 prefs_common.linewrap_quote = prefs_common.linewrap_pastes;
10865 entry_paste_clipboard(compose, compose->focused_editable,
10866 prefs_common.linewrap_pastes,
10867 GDK_SELECTION_CLIPBOARD, NULL);
10868 prefs_common.linewrap_quote = wrap_quote;
10872 static void compose_paste_no_wrap_cb(GtkAction *action, gpointer data)
10874 Compose *compose = (Compose *)data;
10875 gint prev_autowrap;
10876 GtkTextBuffer *buffer;
10878 if (compose->focused_editable
10879 #ifndef GENERIC_UMPC
10880 && gtk_widget_has_focus(compose->focused_editable)
10883 entry_paste_clipboard(compose, compose->focused_editable, FALSE,
10884 GDK_SELECTION_CLIPBOARD, NULL);
10889 #ifndef GENERIC_UMPC
10890 gtk_widget_has_focus(compose->text) &&
10892 compose->gtkaspell &&
10893 compose->gtkaspell->check_while_typing)
10894 gtkaspell_highlight_all(compose->gtkaspell);
10898 static void compose_paste_wrap_cb(GtkAction *action, gpointer data)
10900 Compose *compose = (Compose *)data;
10901 gint prev_autowrap;
10902 GtkTextBuffer *buffer;
10904 if (compose->focused_editable
10905 #ifndef GENERIC_UMPC
10906 && gtk_widget_has_focus(compose->focused_editable)
10909 entry_paste_clipboard(compose, compose->focused_editable, TRUE,
10910 GDK_SELECTION_CLIPBOARD, NULL);
10915 #ifndef GENERIC_UMPC
10916 gtk_widget_has_focus(compose->text) &&
10918 compose->gtkaspell &&
10919 compose->gtkaspell->check_while_typing)
10920 gtkaspell_highlight_all(compose->gtkaspell);
10924 static void compose_allsel_cb(GtkAction *action, gpointer data)
10926 Compose *compose = (Compose *)data;
10927 if (compose->focused_editable
10928 #ifndef GENERIC_UMPC
10929 && gtk_widget_has_focus(compose->focused_editable)
10932 entry_allsel(compose->focused_editable);
10935 static void textview_move_beginning_of_line (GtkTextView *text)
10937 GtkTextBuffer *buffer;
10941 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10943 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10944 mark = gtk_text_buffer_get_insert(buffer);
10945 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10946 gtk_text_iter_set_line_offset(&ins, 0);
10947 gtk_text_buffer_place_cursor(buffer, &ins);
10950 static void textview_move_forward_character (GtkTextView *text)
10952 GtkTextBuffer *buffer;
10956 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10958 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10959 mark = gtk_text_buffer_get_insert(buffer);
10960 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10961 if (gtk_text_iter_forward_cursor_position(&ins))
10962 gtk_text_buffer_place_cursor(buffer, &ins);
10965 static void textview_move_backward_character (GtkTextView *text)
10967 GtkTextBuffer *buffer;
10971 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10973 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10974 mark = gtk_text_buffer_get_insert(buffer);
10975 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10976 if (gtk_text_iter_backward_cursor_position(&ins))
10977 gtk_text_buffer_place_cursor(buffer, &ins);
10980 static void textview_move_forward_word (GtkTextView *text)
10982 GtkTextBuffer *buffer;
10987 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10989 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10990 mark = gtk_text_buffer_get_insert(buffer);
10991 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10992 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
10993 if (gtk_text_iter_forward_word_ends(&ins, count)) {
10994 gtk_text_iter_backward_word_start(&ins);
10995 gtk_text_buffer_place_cursor(buffer, &ins);
10999 static void textview_move_backward_word (GtkTextView *text)
11001 GtkTextBuffer *buffer;
11005 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11007 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11008 mark = gtk_text_buffer_get_insert(buffer);
11009 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11010 if (gtk_text_iter_backward_word_starts(&ins, 1))
11011 gtk_text_buffer_place_cursor(buffer, &ins);
11014 static void textview_move_end_of_line (GtkTextView *text)
11016 GtkTextBuffer *buffer;
11020 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11022 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11023 mark = gtk_text_buffer_get_insert(buffer);
11024 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11025 if (gtk_text_iter_forward_to_line_end(&ins))
11026 gtk_text_buffer_place_cursor(buffer, &ins);
11029 static void textview_move_next_line (GtkTextView *text)
11031 GtkTextBuffer *buffer;
11036 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11038 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11039 mark = gtk_text_buffer_get_insert(buffer);
11040 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11041 offset = gtk_text_iter_get_line_offset(&ins);
11042 if (gtk_text_iter_forward_line(&ins)) {
11043 gtk_text_iter_set_line_offset(&ins, offset);
11044 gtk_text_buffer_place_cursor(buffer, &ins);
11048 static void textview_move_previous_line (GtkTextView *text)
11050 GtkTextBuffer *buffer;
11055 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11057 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11058 mark = gtk_text_buffer_get_insert(buffer);
11059 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11060 offset = gtk_text_iter_get_line_offset(&ins);
11061 if (gtk_text_iter_backward_line(&ins)) {
11062 gtk_text_iter_set_line_offset(&ins, offset);
11063 gtk_text_buffer_place_cursor(buffer, &ins);
11067 static void textview_delete_forward_character (GtkTextView *text)
11069 GtkTextBuffer *buffer;
11071 GtkTextIter ins, end_iter;
11073 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11075 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11076 mark = gtk_text_buffer_get_insert(buffer);
11077 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11079 if (gtk_text_iter_forward_char(&end_iter)) {
11080 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11084 static void textview_delete_backward_character (GtkTextView *text)
11086 GtkTextBuffer *buffer;
11088 GtkTextIter ins, end_iter;
11090 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11092 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11093 mark = gtk_text_buffer_get_insert(buffer);
11094 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11096 if (gtk_text_iter_backward_char(&end_iter)) {
11097 gtk_text_buffer_delete(buffer, &end_iter, &ins);
11101 static void textview_delete_forward_word (GtkTextView *text)
11103 GtkTextBuffer *buffer;
11105 GtkTextIter ins, end_iter;
11107 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11109 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11110 mark = gtk_text_buffer_get_insert(buffer);
11111 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11113 if (gtk_text_iter_forward_word_end(&end_iter)) {
11114 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11118 static void textview_delete_backward_word (GtkTextView *text)
11120 GtkTextBuffer *buffer;
11122 GtkTextIter ins, end_iter;
11124 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11126 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11127 mark = gtk_text_buffer_get_insert(buffer);
11128 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11130 if (gtk_text_iter_backward_word_start(&end_iter)) {
11131 gtk_text_buffer_delete(buffer, &end_iter, &ins);
11135 static void textview_delete_line (GtkTextView *text)
11137 GtkTextBuffer *buffer;
11139 GtkTextIter ins, start_iter, end_iter;
11141 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11143 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11144 mark = gtk_text_buffer_get_insert(buffer);
11145 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11148 gtk_text_iter_set_line_offset(&start_iter, 0);
11151 if (gtk_text_iter_ends_line(&end_iter)){
11152 if (!gtk_text_iter_forward_char(&end_iter))
11153 gtk_text_iter_backward_char(&start_iter);
11156 gtk_text_iter_forward_to_line_end(&end_iter);
11157 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
11160 static void textview_delete_to_line_end (GtkTextView *text)
11162 GtkTextBuffer *buffer;
11164 GtkTextIter ins, end_iter;
11166 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11168 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11169 mark = gtk_text_buffer_get_insert(buffer);
11170 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11172 if (gtk_text_iter_ends_line(&end_iter))
11173 gtk_text_iter_forward_char(&end_iter);
11175 gtk_text_iter_forward_to_line_end(&end_iter);
11176 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11179 #define DO_ACTION(name, act) { \
11180 if(!strcmp(name, a_name)) { \
11184 static ComposeCallAdvancedAction compose_call_advanced_action_from_path(GtkAction *action)
11186 const gchar *a_name = gtk_action_get_name(action);
11187 DO_ACTION("Edit/Advanced/BackChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER);
11188 DO_ACTION("Edit/Advanced/ForwChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER);
11189 DO_ACTION("Edit/Advanced/BackWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD);
11190 DO_ACTION("Edit/Advanced/ForwWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD);
11191 DO_ACTION("Edit/Advanced/BegLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE);
11192 DO_ACTION("Edit/Advanced/EndLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE);
11193 DO_ACTION("Edit/Advanced/PrevLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE);
11194 DO_ACTION("Edit/Advanced/NextLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE);
11195 DO_ACTION("Edit/Advanced/DelBackChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER);
11196 DO_ACTION("Edit/Advanced/DelForwChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER);
11197 DO_ACTION("Edit/Advanced/DelBackWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD);
11198 DO_ACTION("Edit/Advanced/DelForwWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD);
11199 DO_ACTION("Edit/Advanced/DelLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE);
11200 DO_ACTION("Edit/Advanced/DelEndLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END);
11201 return COMPOSE_CALL_ADVANCED_ACTION_UNDEFINED;
11204 static void compose_advanced_action_cb(GtkAction *gaction, gpointer data)
11206 Compose *compose = (Compose *)data;
11207 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11208 ComposeCallAdvancedAction action = COMPOSE_CALL_ADVANCED_ACTION_UNDEFINED;
11210 action = compose_call_advanced_action_from_path(gaction);
11213 void (*do_action) (GtkTextView *text);
11214 } action_table[] = {
11215 {textview_move_beginning_of_line},
11216 {textview_move_forward_character},
11217 {textview_move_backward_character},
11218 {textview_move_forward_word},
11219 {textview_move_backward_word},
11220 {textview_move_end_of_line},
11221 {textview_move_next_line},
11222 {textview_move_previous_line},
11223 {textview_delete_forward_character},
11224 {textview_delete_backward_character},
11225 {textview_delete_forward_word},
11226 {textview_delete_backward_word},
11227 {textview_delete_line},
11228 {textview_delete_to_line_end}
11231 if (!gtk_widget_has_focus(GTK_WIDGET(text))) return;
11233 if (action >= COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE &&
11234 action <= COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END) {
11235 if (action_table[action].do_action)
11236 action_table[action].do_action(text);
11238 g_warning("Not implemented yet.");
11242 static void compose_grab_focus_cb(GtkWidget *widget, Compose *compose)
11244 GtkAllocation allocation;
11248 if (GTK_IS_EDITABLE(widget)) {
11249 str = gtk_editable_get_chars(GTK_EDITABLE(widget), 0, -1);
11250 gtk_editable_set_position(GTK_EDITABLE(widget),
11253 if ((parent = gtk_widget_get_parent(widget))
11254 && (parent = gtk_widget_get_parent(parent))
11255 && (parent = gtk_widget_get_parent(parent))) {
11256 if (GTK_IS_SCROLLED_WINDOW(parent)) {
11257 gtk_widget_get_allocation(widget, &allocation);
11258 gint y = allocation.y;
11259 gint height = allocation.height;
11260 GtkAdjustment *shown = gtk_scrolled_window_get_vadjustment
11261 (GTK_SCROLLED_WINDOW(parent));
11263 gfloat value = gtk_adjustment_get_value(shown);
11264 gfloat upper = gtk_adjustment_get_upper(shown);
11265 gfloat page_size = gtk_adjustment_get_page_size(shown);
11266 if (y < (int)value) {
11267 gtk_adjustment_set_value(shown, y - 1);
11269 if ((y + height) > ((int)value + (int)page_size)) {
11270 if ((y - height - 1) < ((int)upper - (int)page_size)) {
11271 gtk_adjustment_set_value(shown,
11272 y + height - (int)page_size - 1);
11274 gtk_adjustment_set_value(shown,
11275 (int)upper - (int)page_size - 1);
11282 if (GTK_IS_EDITABLE(widget) || GTK_IS_TEXT_VIEW(widget))
11283 compose->focused_editable = widget;
11285 #ifdef GENERIC_UMPC
11286 if (GTK_IS_TEXT_VIEW(widget)
11287 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->edit_vbox) {
11288 g_object_ref(compose->notebook);
11289 g_object_ref(compose->edit_vbox);
11290 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
11291 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
11292 gtk_paned_add1(GTK_PANED(compose->paned), compose->edit_vbox);
11293 gtk_paned_add2(GTK_PANED(compose->paned), compose->notebook);
11294 g_object_unref(compose->notebook);
11295 g_object_unref(compose->edit_vbox);
11296 g_signal_handlers_block_by_func(G_OBJECT(widget),
11297 G_CALLBACK(compose_grab_focus_cb),
11299 gtk_widget_grab_focus(widget);
11300 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
11301 G_CALLBACK(compose_grab_focus_cb),
11303 } else if (!GTK_IS_TEXT_VIEW(widget)
11304 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->notebook) {
11305 g_object_ref(compose->notebook);
11306 g_object_ref(compose->edit_vbox);
11307 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
11308 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
11309 gtk_paned_add1(GTK_PANED(compose->paned), compose->notebook);
11310 gtk_paned_add2(GTK_PANED(compose->paned), compose->edit_vbox);
11311 g_object_unref(compose->notebook);
11312 g_object_unref(compose->edit_vbox);
11313 g_signal_handlers_block_by_func(G_OBJECT(widget),
11314 G_CALLBACK(compose_grab_focus_cb),
11316 gtk_widget_grab_focus(widget);
11317 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
11318 G_CALLBACK(compose_grab_focus_cb),
11324 static void compose_changed_cb(GtkTextBuffer *textbuf, Compose *compose)
11326 compose->modified = TRUE;
11327 /* compose_beautify_paragraph(compose, NULL, TRUE); */
11328 #ifndef GENERIC_UMPC
11329 compose_set_title(compose);
11333 static void compose_wrap_cb(GtkAction *action, gpointer data)
11335 Compose *compose = (Compose *)data;
11336 compose_beautify_paragraph(compose, NULL, TRUE);
11339 static void compose_wrap_all_cb(GtkAction *action, gpointer data)
11341 Compose *compose = (Compose *)data;
11342 compose_wrap_all_full(compose, TRUE);
11345 static void compose_find_cb(GtkAction *action, gpointer data)
11347 Compose *compose = (Compose *)data;
11349 message_search_compose(compose);
11352 static void compose_toggle_autowrap_cb(GtkToggleAction *action,
11355 Compose *compose = (Compose *)data;
11356 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11357 if (compose->autowrap)
11358 compose_wrap_all_full(compose, TRUE);
11359 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11362 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
11365 Compose *compose = (Compose *)data;
11366 compose->autoindent = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11369 static void compose_toggle_sign_cb(GtkToggleAction *action, gpointer data)
11371 Compose *compose = (Compose *)data;
11373 compose->use_signing = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11376 static void compose_toggle_encrypt_cb(GtkToggleAction *action, gpointer data)
11378 Compose *compose = (Compose *)data;
11380 compose->use_encryption = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11383 static void activate_privacy_system(Compose *compose, PrefsAccount *account, gboolean warn)
11385 g_free(compose->privacy_system);
11386 g_free(compose->encdata);
11388 compose->privacy_system = g_strdup(account->default_privacy_system);
11389 compose_update_privacy_system_menu_item(compose, warn);
11392 static void compose_toggle_ruler_cb(GtkToggleAction *action, gpointer data)
11394 Compose *compose = (Compose *)data;
11396 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action))) {
11397 gtk_widget_show(compose->ruler_hbox);
11398 prefs_common.show_ruler = TRUE;
11400 gtk_widget_hide(compose->ruler_hbox);
11401 gtk_widget_queue_resize(compose->edit_vbox);
11402 prefs_common.show_ruler = FALSE;
11406 static void compose_attach_drag_received_cb (GtkWidget *widget,
11407 GdkDragContext *context,
11410 GtkSelectionData *data,
11413 gpointer user_data)
11415 Compose *compose = (Compose *)user_data;
11419 type = gtk_selection_data_get_data_type(data);
11420 if ((gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list"))
11421 && gtk_drag_get_source_widget(context) !=
11422 summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
11423 list = uri_list_extract_filenames(
11424 (const gchar *)gtk_selection_data_get_data(data));
11425 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11426 gchar *utf8_filename = conv_filename_to_utf8((const gchar *)tmp->data);
11427 compose_attach_append
11428 (compose, (const gchar *)tmp->data,
11429 utf8_filename, NULL, NULL);
11430 g_free(utf8_filename);
11432 if (list) compose_changed_cb(NULL, compose);
11433 list_free_strings(list);
11435 } else if (gtk_drag_get_source_widget(context)
11436 == summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
11437 /* comes from our summaryview */
11438 SummaryView * summaryview = NULL;
11439 GSList * list = NULL, *cur = NULL;
11441 if (mainwindow_get_mainwindow())
11442 summaryview = mainwindow_get_mainwindow()->summaryview;
11445 list = summary_get_selected_msg_list(summaryview);
11447 for (cur = list; cur; cur = cur->next) {
11448 MsgInfo *msginfo = (MsgInfo *)cur->data;
11449 gchar *file = NULL;
11451 file = procmsg_get_message_file_full(msginfo,
11454 compose_attach_append(compose, (const gchar *)file,
11455 (const gchar *)file, "message/rfc822", NULL);
11459 g_slist_free(list);
11463 static gboolean compose_drag_drop(GtkWidget *widget,
11464 GdkDragContext *drag_context,
11466 guint time, gpointer user_data)
11468 /* not handling this signal makes compose_insert_drag_received_cb
11473 static gboolean completion_set_focus_to_subject
11474 (GtkWidget *widget,
11475 GdkEventKey *event,
11478 cm_return_val_if_fail(compose != NULL, FALSE);
11480 /* make backtab move to subject field */
11481 if(event->keyval == GDK_KEY_ISO_Left_Tab) {
11482 gtk_widget_grab_focus(compose->subject_entry);
11488 static void compose_insert_drag_received_cb (GtkWidget *widget,
11489 GdkDragContext *drag_context,
11492 GtkSelectionData *data,
11495 gpointer user_data)
11497 Compose *compose = (Compose *)user_data;
11503 /* strangely, testing data->type == gdk_atom_intern("text/uri-list", TRUE)
11505 type = gtk_selection_data_get_data_type(data);
11506 if (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list")) {
11507 AlertValue val = G_ALERTDEFAULT;
11508 const gchar* ddata = (const gchar *)gtk_selection_data_get_data(data);
11510 list = uri_list_extract_filenames(ddata);
11511 num_files = g_list_length(list);
11512 if (list == NULL && strstr(ddata, "://")) {
11513 /* Assume a list of no files, and data has ://, is a remote link */
11514 gchar *tmpdata = g_strstrip(g_strdup(ddata));
11515 gchar *tmpfile = get_tmp_file();
11516 str_write_to_file(tmpdata, tmpfile);
11518 compose_insert_file(compose, tmpfile);
11519 claws_unlink(tmpfile);
11521 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11522 compose_beautify_paragraph(compose, NULL, TRUE);
11525 switch (prefs_common.compose_dnd_mode) {
11526 case COMPOSE_DND_ASK:
11527 msg = g_strdup_printf(
11529 "Do you want to insert the contents of the file "
11530 "into the message body, or attach it to the email?",
11531 "Do you want to insert the contents of the %d files "
11532 "into the message body, or attach them to the email?",
11535 val = alertpanel_full(_("Insert or attach?"), msg,
11536 GTK_STOCK_CANCEL, g_strconcat("+", _("_Insert"), NULL), _("_Attach"),
11537 TRUE, NULL, ALERT_QUESTION, G_ALERTALTERNATE);
11540 case COMPOSE_DND_INSERT:
11541 val = G_ALERTALTERNATE;
11543 case COMPOSE_DND_ATTACH:
11544 val = G_ALERTOTHER;
11547 /* unexpected case */
11548 g_warning("error: unexpected compose_dnd_mode option value in compose_insert_drag_received_cb()");
11551 if (val & G_ALERTDISABLE) {
11552 val &= ~G_ALERTDISABLE;
11553 /* remember what action to perform by default, only if we don't click Cancel */
11554 if (val == G_ALERTALTERNATE)
11555 prefs_common.compose_dnd_mode = COMPOSE_DND_INSERT;
11556 else if (val == G_ALERTOTHER)
11557 prefs_common.compose_dnd_mode = COMPOSE_DND_ATTACH;
11560 if (val == G_ALERTDEFAULT || val == G_ALERTCANCEL) {
11561 gtk_drag_finish(drag_context, FALSE, FALSE, time);
11562 list_free_strings(list);
11565 } else if (val == G_ALERTOTHER) {
11566 compose_attach_drag_received_cb(widget, drag_context, x, y, data, info, time, user_data);
11567 list_free_strings(list);
11572 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11573 compose_insert_file(compose, (const gchar *)tmp->data);
11575 list_free_strings(list);
11577 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11582 static void compose_header_drag_received_cb (GtkWidget *widget,
11583 GdkDragContext *drag_context,
11586 GtkSelectionData *data,
11589 gpointer user_data)
11591 GtkEditable *entry = (GtkEditable *)user_data;
11592 const gchar *email = (const gchar *)gtk_selection_data_get_data(data);
11594 /* strangely, testing data->type == gdk_atom_intern("text/plain", TRUE)
11597 if (!strncmp(email, "mailto:", strlen("mailto:"))) {
11598 gchar *decoded=g_new(gchar, strlen(email));
11601 decode_uri(decoded, email + strlen("mailto:")); /* will fit */
11602 gtk_editable_delete_text(entry, 0, -1);
11603 gtk_editable_insert_text(entry, decoded, strlen(decoded), &start);
11604 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11608 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11611 static void compose_toggle_return_receipt_cb(GtkToggleAction *action, gpointer data)
11613 Compose *compose = (Compose *)data;
11615 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11616 compose->return_receipt = TRUE;
11618 compose->return_receipt = FALSE;
11621 static void compose_toggle_remove_refs_cb(GtkToggleAction *action, gpointer data)
11623 Compose *compose = (Compose *)data;
11625 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11626 compose->remove_references = TRUE;
11628 compose->remove_references = FALSE;
11631 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
11632 ComposeHeaderEntry *headerentry)
11634 gtk_entry_set_text(GTK_ENTRY(headerentry->entry), "");
11638 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
11639 GdkEventKey *event,
11640 ComposeHeaderEntry *headerentry)
11642 if ((g_slist_length(headerentry->compose->header_list) > 0) &&
11643 ((headerentry->headernum + 1) != headerentry->compose->header_nextrow) &&
11644 !(event->state & GDK_MODIFIER_MASK) &&
11645 (event->keyval == GDK_KEY_BackSpace) &&
11646 (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) == 0)) {
11647 gtk_container_remove
11648 (GTK_CONTAINER(headerentry->compose->header_table),
11649 headerentry->combo);
11650 gtk_container_remove
11651 (GTK_CONTAINER(headerentry->compose->header_table),
11652 headerentry->entry);
11653 headerentry->compose->header_list =
11654 g_slist_remove(headerentry->compose->header_list,
11656 g_free(headerentry);
11657 } else if (event->keyval == GDK_KEY_Tab) {
11658 if (headerentry->compose->header_last == headerentry) {
11659 /* Override default next focus, and give it to subject_entry
11660 * instead of notebook tabs
11662 g_signal_stop_emission_by_name(G_OBJECT(entry), "key-press-event");
11663 gtk_widget_grab_focus(headerentry->compose->subject_entry);
11670 static gboolean scroll_postpone(gpointer data)
11672 Compose *compose = (Compose *)data;
11674 if (compose->batch)
11677 GTK_EVENTS_FLUSH();
11678 compose_show_first_last_header(compose, FALSE);
11682 static void compose_headerentry_changed_cb(GtkWidget *entry,
11683 ComposeHeaderEntry *headerentry)
11685 if (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) != 0) {
11686 compose_create_header_entry(headerentry->compose);
11687 g_signal_handlers_disconnect_matched
11688 (G_OBJECT(entry), G_SIGNAL_MATCH_DATA,
11689 0, 0, NULL, NULL, headerentry);
11691 if (!headerentry->compose->batch)
11692 g_timeout_add(0, scroll_postpone, headerentry->compose);
11696 static gboolean compose_defer_auto_save_draft(Compose *compose)
11698 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
11699 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
11703 static void compose_show_first_last_header(Compose *compose, gboolean show_first)
11705 GtkAdjustment *vadj;
11707 cm_return_if_fail(compose);
11712 cm_return_if_fail(GTK_IS_WIDGET(compose->header_table));
11713 cm_return_if_fail(GTK_IS_VIEWPORT(gtk_widget_get_parent(compose->header_table)));
11714 vadj = gtk_viewport_get_vadjustment(GTK_VIEWPORT(
11715 gtk_widget_get_parent(compose->header_table)));
11716 gtk_adjustment_set_value(vadj, (show_first ?
11717 gtk_adjustment_get_lower(vadj) :
11718 (gtk_adjustment_get_upper(vadj) -
11719 gtk_adjustment_get_page_size(vadj))));
11720 gtk_adjustment_changed(vadj);
11723 static void text_inserted(GtkTextBuffer *buffer, GtkTextIter *iter,
11724 const gchar *text, gint len, Compose *compose)
11726 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data
11727 (G_OBJECT(compose->text), "paste_as_quotation"));
11730 cm_return_if_fail(text != NULL);
11732 g_signal_handlers_block_by_func(G_OBJECT(buffer),
11733 G_CALLBACK(text_inserted),
11735 if (paste_as_quotation) {
11737 const gchar *qmark;
11739 GtkTextIter start_iter;
11742 len = strlen(text);
11744 new_text = g_strndup(text, len);
11746 qmark = compose_quote_char_from_context(compose);
11748 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11749 gtk_text_buffer_place_cursor(buffer, iter);
11751 pos = gtk_text_iter_get_offset(iter);
11753 compose_quote_fmt(compose, NULL, "%Q", qmark, new_text, TRUE, FALSE,
11754 _("Quote format error at line %d."));
11755 quote_fmt_reset_vartable();
11757 g_object_set_data(G_OBJECT(compose->text), "paste_as_quotation",
11758 GINT_TO_POINTER(paste_as_quotation - 1));
11760 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11761 gtk_text_buffer_place_cursor(buffer, iter);
11762 gtk_text_buffer_delete_mark(buffer, mark);
11764 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, pos);
11765 mark = gtk_text_buffer_create_mark(buffer, NULL, &start_iter, FALSE);
11766 compose_beautify_paragraph(compose, &start_iter, FALSE);
11767 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark);
11768 gtk_text_buffer_delete_mark(buffer, mark);
11770 if (strcmp(text, "\n") || compose->automatic_break
11771 || gtk_text_iter_starts_line(iter)) {
11772 GtkTextIter before_ins;
11773 gtk_text_buffer_insert(buffer, iter, text, len);
11774 if (!strstr(text, "\n") && gtk_text_iter_has_tag(iter, compose->no_join_tag)) {
11775 before_ins = *iter;
11776 gtk_text_iter_backward_chars(&before_ins, len);
11777 gtk_text_buffer_remove_tag_by_name(buffer, "no_join", &before_ins, iter);
11780 /* check if the preceding is just whitespace or quote */
11781 GtkTextIter start_line;
11782 gchar *tmp = NULL, *quote = NULL;
11783 gint quote_len = 0, is_normal = 0;
11784 start_line = *iter;
11785 gtk_text_iter_set_line_offset(&start_line, 0);
11786 tmp = gtk_text_buffer_get_text(buffer, &start_line, iter, FALSE);
11789 if (*tmp == '\0') {
11792 quote = compose_get_quote_str(buffer, &start_line, "e_len);
11800 gtk_text_buffer_insert(buffer, iter, text, len);
11802 gtk_text_buffer_insert_with_tags_by_name(buffer,
11803 iter, text, len, "no_join", NULL);
11808 if (!paste_as_quotation) {
11809 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11810 compose_beautify_paragraph(compose, iter, FALSE);
11811 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11812 gtk_text_buffer_delete_mark(buffer, mark);
11815 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
11816 G_CALLBACK(text_inserted),
11818 g_signal_stop_emission_by_name(G_OBJECT(buffer), "insert-text");
11820 if (compose_can_autosave(compose) &&
11821 gtk_text_buffer_get_char_count(buffer) % prefs_common.autosave_length == 0 &&
11822 compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN /* disabled while loading */)
11823 compose->draft_timeout_tag = g_timeout_add
11824 (500, (GSourceFunc) compose_defer_auto_save_draft, compose);
11828 static void compose_check_all(GtkAction *action, gpointer data)
11830 Compose *compose = (Compose *)data;
11831 if (!compose->gtkaspell)
11834 if (gtk_widget_has_focus(compose->subject_entry))
11835 claws_spell_entry_check_all(
11836 CLAWS_SPELL_ENTRY(compose->subject_entry));
11838 gtkaspell_check_all(compose->gtkaspell);
11841 static void compose_highlight_all(GtkAction *action, gpointer data)
11843 Compose *compose = (Compose *)data;
11844 if (compose->gtkaspell) {
11845 claws_spell_entry_recheck_all(
11846 CLAWS_SPELL_ENTRY(compose->subject_entry));
11847 gtkaspell_highlight_all(compose->gtkaspell);
11851 static void compose_check_backwards(GtkAction *action, gpointer data)
11853 Compose *compose = (Compose *)data;
11854 if (!compose->gtkaspell) {
11855 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
11859 if (gtk_widget_has_focus(compose->subject_entry))
11860 claws_spell_entry_check_backwards(
11861 CLAWS_SPELL_ENTRY(compose->subject_entry));
11863 gtkaspell_check_backwards(compose->gtkaspell);
11866 static void compose_check_forwards_go(GtkAction *action, gpointer data)
11868 Compose *compose = (Compose *)data;
11869 if (!compose->gtkaspell) {
11870 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
11874 if (gtk_widget_has_focus(compose->subject_entry))
11875 claws_spell_entry_check_forwards_go(
11876 CLAWS_SPELL_ENTRY(compose->subject_entry));
11878 gtkaspell_check_forwards_go(compose->gtkaspell);
11883 *\brief Guess originating forward account from MsgInfo and several
11884 * "common preference" settings. Return NULL if no guess.
11886 static PrefsAccount *compose_find_account(MsgInfo *msginfo)
11888 PrefsAccount *account = NULL;
11890 cm_return_val_if_fail(msginfo, NULL);
11891 cm_return_val_if_fail(msginfo->folder, NULL);
11892 cm_return_val_if_fail(msginfo->folder->prefs, NULL);
11894 if (msginfo->folder->prefs->enable_default_account)
11895 account = account_find_from_id(msginfo->folder->prefs->default_account);
11897 if (!account && msginfo->to && prefs_common.forward_account_autosel) {
11899 Xstrdup_a(to, msginfo->to, return NULL);
11900 extract_address(to);
11901 account = account_find_from_address(to, FALSE);
11904 if (!account && prefs_common.forward_account_autosel) {
11906 if (!procheader_get_header_from_msginfo
11907 (msginfo, &cc, "Cc:")) {
11908 gchar *buf = cc + strlen("Cc:");
11909 extract_address(buf);
11910 account = account_find_from_address(buf, FALSE);
11915 if (!account && prefs_common.forward_account_autosel) {
11916 gchar *deliveredto = NULL;
11917 if (!procheader_get_header_from_msginfo
11918 (msginfo, &deliveredto, "Delivered-To:")) {
11919 gchar *buf = deliveredto + strlen("Delivered-To:");
11920 extract_address(buf);
11921 account = account_find_from_address(buf, FALSE);
11922 g_free(deliveredto);
11927 account = msginfo->folder->folder->account;
11932 gboolean compose_close(Compose *compose)
11936 cm_return_val_if_fail(compose, FALSE);
11938 if (!g_mutex_trylock(compose->mutex)) {
11939 /* we have to wait for the (possibly deferred by auto-save)
11940 * drafting to be done, before destroying the compose under
11942 debug_print("waiting for drafting to finish...\n");
11943 compose_allow_user_actions(compose, FALSE);
11944 if (compose->close_timeout_tag == 0) {
11945 compose->close_timeout_tag =
11946 g_timeout_add (500, (GSourceFunc) compose_close,
11952 if (compose->draft_timeout_tag >= 0) {
11953 g_source_remove(compose->draft_timeout_tag);
11954 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN;
11957 gtkut_widget_get_uposition(compose->window, &x, &y);
11958 if (!compose->batch) {
11959 prefs_common.compose_x = x;
11960 prefs_common.compose_y = y;
11962 g_mutex_unlock(compose->mutex);
11963 compose_destroy(compose);
11968 * Add entry field for each address in list.
11969 * \param compose E-Mail composition object.
11970 * \param listAddress List of (formatted) E-Mail addresses.
11972 static void compose_add_field_list( Compose *compose, GList *listAddress ) {
11975 node = listAddress;
11977 addr = ( gchar * ) node->data;
11978 compose_entry_append( compose, addr, COMPOSE_TO, PREF_NONE );
11979 node = g_list_next( node );
11983 static void compose_reply_from_messageview_real(MessageView *msgview, GSList *msginfo_list,
11984 guint action, gboolean opening_multiple)
11986 gchar *body = NULL;
11987 GSList *new_msglist = NULL;
11988 MsgInfo *tmp_msginfo = NULL;
11989 gboolean originally_enc = FALSE;
11990 gboolean originally_sig = FALSE;
11991 Compose *compose = NULL;
11992 gchar *s_system = NULL;
11994 cm_return_if_fail(msgview != NULL);
11996 cm_return_if_fail(msginfo_list != NULL);
11998 if (g_slist_length(msginfo_list) == 1 && !opening_multiple) {
11999 MimeInfo *mimeinfo = messageview_get_selected_mime_part(msgview);
12000 MsgInfo *orig_msginfo = (MsgInfo *)msginfo_list->data;
12002 if (mimeinfo != NULL && mimeinfo->type == MIMETYPE_MESSAGE &&
12003 !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
12004 tmp_msginfo = procmsg_msginfo_new_from_mimeinfo(
12005 orig_msginfo, mimeinfo);
12006 if (tmp_msginfo != NULL) {
12007 new_msglist = g_slist_append(NULL, tmp_msginfo);
12009 originally_enc = MSG_IS_ENCRYPTED(orig_msginfo->flags);
12010 privacy_msginfo_get_signed_state(orig_msginfo, &s_system);
12011 originally_sig = MSG_IS_SIGNED(orig_msginfo->flags);
12013 tmp_msginfo->folder = orig_msginfo->folder;
12014 tmp_msginfo->msgnum = orig_msginfo->msgnum;
12015 if (orig_msginfo->tags) {
12016 tmp_msginfo->tags = g_slist_copy(orig_msginfo->tags);
12017 tmp_msginfo->folder->tags_dirty = TRUE;
12023 if (!opening_multiple)
12024 body = messageview_get_selection(msgview);
12027 compose = compose_reply_mode((ComposeMode)action, new_msglist, body);
12028 procmsg_msginfo_free(&tmp_msginfo);
12029 g_slist_free(new_msglist);
12031 compose = compose_reply_mode((ComposeMode)action, msginfo_list, body);
12033 if (compose && originally_enc) {
12034 compose_force_encryption(compose, compose->account, FALSE, s_system);
12037 if (compose && originally_sig && compose->account->default_sign_reply) {
12038 compose_force_signing(compose, compose->account, s_system);
12042 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
12045 void compose_reply_from_messageview(MessageView *msgview, GSList *msginfo_list,
12048 if ((!prefs_common.forward_as_attachment || action != COMPOSE_FORWARD)
12049 && action != COMPOSE_FORWARD_AS_ATTACH && g_slist_length(msginfo_list) > 1) {
12050 GSList *cur = msginfo_list;
12051 gchar *msg = g_strdup_printf(_("You are about to reply to %d "
12052 "messages. Opening the windows "
12053 "could take some time. Do you "
12054 "want to continue?"),
12055 g_slist_length(msginfo_list));
12056 if (g_slist_length(msginfo_list) > 9
12057 && alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, "+" GTK_STOCK_YES, NULL)
12058 != G_ALERTALTERNATE) {
12063 /* We'll open multiple compose windows */
12064 /* let the WM place the next windows */
12065 compose_force_window_origin = FALSE;
12066 for (; cur; cur = cur->next) {
12068 tmplist.data = cur->data;
12069 tmplist.next = NULL;
12070 compose_reply_from_messageview_real(msgview, &tmplist, action, TRUE);
12072 compose_force_window_origin = TRUE;
12074 /* forwarding multiple mails as attachments is done via a
12075 * single compose window */
12076 compose_reply_from_messageview_real(msgview, msginfo_list, action, FALSE);
12080 void compose_check_for_email_account(Compose *compose)
12082 PrefsAccount *ac = NULL, *curr = NULL;
12088 if (compose->account && compose->account->protocol == A_NNTP) {
12089 ac = account_get_cur_account();
12090 if (ac->protocol == A_NNTP) {
12091 list = account_get_list();
12093 for( ; list != NULL ; list = g_list_next(list)) {
12094 curr = (PrefsAccount *) list->data;
12095 if (curr->protocol != A_NNTP) {
12101 combobox_select_by_data(GTK_COMBO_BOX(compose->account_combo),
12106 void compose_reply_to_address(MessageView *msgview, MsgInfo *msginfo,
12107 const gchar *address)
12109 GSList *msginfo_list = NULL;
12110 gchar *body = messageview_get_selection(msgview);
12113 msginfo_list = g_slist_prepend(msginfo_list, msginfo);
12115 compose = compose_reply_mode(COMPOSE_REPLY_TO_ADDRESS, msginfo_list, body);
12116 compose_check_for_email_account(compose);
12117 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
12118 compose_entry_append(compose, address, COMPOSE_TO, PREF_NONE);
12119 compose_reply_set_subject(compose, msginfo);
12122 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
12125 void compose_set_position(Compose *compose, gint pos)
12127 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
12129 gtkut_text_view_set_position(text, pos);
12132 gboolean compose_search_string(Compose *compose,
12133 const gchar *str, gboolean case_sens)
12135 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
12137 return gtkut_text_view_search_string(text, str, case_sens);
12140 gboolean compose_search_string_backward(Compose *compose,
12141 const gchar *str, gboolean case_sens)
12143 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
12145 return gtkut_text_view_search_string_backward(text, str, case_sens);
12148 /* allocate a msginfo structure and populate its data from a compose data structure */
12149 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose)
12151 MsgInfo *newmsginfo;
12153 gchar date[RFC822_DATE_BUFFSIZE];
12155 cm_return_val_if_fail( compose != NULL, NULL );
12157 newmsginfo = procmsg_msginfo_new();
12160 get_rfc822_date(date, sizeof(date));
12161 newmsginfo->date = g_strdup(date);
12164 if (compose->from_name) {
12165 newmsginfo->from = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
12166 newmsginfo->fromname = procheader_get_fromname(newmsginfo->from);
12170 if (compose->subject_entry)
12171 newmsginfo->subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
12173 /* to, cc, reply-to, newsgroups */
12174 for (list = compose->header_list; list; list = list->next) {
12175 gchar *header = gtk_editable_get_chars(
12177 gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
12178 gchar *entry = gtk_editable_get_chars(
12179 GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
12181 if ( strcasecmp(header, prefs_common_translated_header_name("To:")) == 0 ) {
12182 if ( newmsginfo->to == NULL ) {
12183 newmsginfo->to = g_strdup(entry);
12184 } else if (entry && *entry) {
12185 gchar *tmp = g_strconcat(newmsginfo->to, ", ", entry, NULL);
12186 g_free(newmsginfo->to);
12187 newmsginfo->to = tmp;
12190 if ( strcasecmp(header, prefs_common_translated_header_name("Cc:")) == 0 ) {
12191 if ( newmsginfo->cc == NULL ) {
12192 newmsginfo->cc = g_strdup(entry);
12193 } else if (entry && *entry) {
12194 gchar *tmp = g_strconcat(newmsginfo->cc, ", ", entry, NULL);
12195 g_free(newmsginfo->cc);
12196 newmsginfo->cc = tmp;
12199 if ( strcasecmp(header,
12200 prefs_common_translated_header_name("Newsgroups:")) == 0 ) {
12201 if ( newmsginfo->newsgroups == NULL ) {
12202 newmsginfo->newsgroups = g_strdup(entry);
12203 } else if (entry && *entry) {
12204 gchar *tmp = g_strconcat(newmsginfo->newsgroups, ", ", entry, NULL);
12205 g_free(newmsginfo->newsgroups);
12206 newmsginfo->newsgroups = tmp;
12214 /* other data is unset */
12220 /* update compose's dictionaries from folder dict settings */
12221 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
12222 FolderItem *folder_item)
12224 cm_return_if_fail(compose != NULL);
12226 if (compose->gtkaspell && folder_item && folder_item->prefs) {
12227 FolderItemPrefs *prefs = folder_item->prefs;
12229 if (prefs->enable_default_dictionary)
12230 gtkaspell_change_dict(compose->gtkaspell,
12231 prefs->default_dictionary, FALSE);
12232 if (folder_item->prefs->enable_default_alt_dictionary)
12233 gtkaspell_change_alt_dict(compose->gtkaspell,
12234 prefs->default_alt_dictionary);
12235 if (prefs->enable_default_dictionary
12236 || prefs->enable_default_alt_dictionary)
12237 compose_spell_menu_changed(compose);
12242 static void compose_subject_entry_activated(GtkWidget *widget, gpointer data)
12244 Compose *compose = (Compose *)data;
12246 cm_return_if_fail(compose != NULL);
12248 gtk_widget_grab_focus(compose->text);
12251 static void from_name_activate_cb(GtkWidget *widget, gpointer data)
12253 gtk_combo_box_popup(GTK_COMBO_BOX(data));