2 * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2018 the Claws Mail team and Hiroyuki Yamamoto
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"
110 #include "file-utils.h"
113 #include "password.h"
114 #include "ldapserver.h"
128 #define N_ATTACH_COLS (N_COL_COLUMNS)
132 COMPOSE_CALL_ADVANCED_ACTION_UNDEFINED = -1,
133 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE = 0,
134 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER,
135 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER,
136 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD,
137 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD,
138 COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE,
139 COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE,
140 COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE,
141 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER,
142 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER,
143 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD,
144 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD,
145 COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE,
146 COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END
147 } ComposeCallAdvancedAction;
151 PRIORITY_HIGHEST = 1,
160 COMPOSE_INSERT_SUCCESS,
161 COMPOSE_INSERT_READ_ERROR,
162 COMPOSE_INSERT_INVALID_CHARACTER,
163 COMPOSE_INSERT_NO_FILE
164 } ComposeInsertResult;
168 COMPOSE_WRITE_FOR_SEND,
169 COMPOSE_WRITE_FOR_STORE
174 COMPOSE_QUOTE_FORCED,
181 SUBJECT_FIELD_PRESENT,
186 #define B64_LINE_SIZE 57
187 #define B64_BUFFSIZE 77
189 #define MAX_REFERENCES_LEN 999
191 #define COMPOSE_DRAFT_TIMEOUT_UNSET -1
192 #define COMPOSE_DRAFT_TIMEOUT_FORBIDDEN -2
194 static GdkColor default_header_bgcolor = {
201 static GdkColor default_header_color = {
208 static GList *compose_list = NULL;
209 static GSList *extra_headers = NULL;
211 static Compose *compose_generic_new (PrefsAccount *account,
215 GList *listAddress );
217 static Compose *compose_create (PrefsAccount *account,
222 static void compose_entry_indicate (Compose *compose,
223 const gchar *address);
224 static Compose *compose_followup_and_reply_to (MsgInfo *msginfo,
225 ComposeQuoteMode quote_mode,
229 static Compose *compose_forward_multiple (PrefsAccount *account,
230 GSList *msginfo_list);
231 static Compose *compose_reply (MsgInfo *msginfo,
232 ComposeQuoteMode quote_mode,
237 static Compose *compose_reply_mode (ComposeMode mode,
238 GSList *msginfo_list,
240 static void compose_template_apply_fields(Compose *compose, Template *tmpl);
241 static void compose_update_privacy_systems_menu(Compose *compose);
243 static GtkWidget *compose_account_option_menu_create
245 static void compose_set_out_encoding (Compose *compose);
246 static void compose_set_template_menu (Compose *compose);
247 static void compose_destroy (Compose *compose);
249 static MailField compose_entries_set (Compose *compose,
251 ComposeEntryType to_type);
252 static gint compose_parse_header (Compose *compose,
254 static gint compose_parse_manual_headers (Compose *compose,
256 HeaderEntry *entries);
257 static gchar *compose_parse_references (const gchar *ref,
260 static gchar *compose_quote_fmt (Compose *compose,
266 gboolean need_unescape,
267 const gchar *err_msg);
269 static void compose_reply_set_entry (Compose *compose,
275 followup_and_reply_to);
276 static void compose_reedit_set_entry (Compose *compose,
279 static void compose_insert_sig (Compose *compose,
281 static ComposeInsertResult compose_insert_file (Compose *compose,
284 static gboolean compose_attach_append (Compose *compose,
287 const gchar *content_type,
288 const gchar *charset);
289 static void compose_attach_parts (Compose *compose,
292 static gboolean compose_beautify_paragraph (Compose *compose,
293 GtkTextIter *par_iter,
295 static void compose_wrap_all (Compose *compose);
296 static void compose_wrap_all_full (Compose *compose,
299 static void compose_set_title (Compose *compose);
300 static void compose_select_account (Compose *compose,
301 PrefsAccount *account,
304 static PrefsAccount *compose_current_mail_account(void);
305 /* static gint compose_send (Compose *compose); */
306 static gboolean compose_check_for_valid_recipient
308 static gboolean compose_check_entries (Compose *compose,
309 gboolean check_everything);
310 static gint compose_write_to_file (Compose *compose,
313 gboolean attach_parts);
314 static gint compose_write_body_to_file (Compose *compose,
316 static gint compose_remove_reedit_target (Compose *compose,
318 static void compose_remove_draft (Compose *compose);
319 static ComposeQueueResult compose_queue_sub (Compose *compose,
323 gboolean perform_checks,
324 gboolean remove_reedit_target);
325 static int compose_add_attachments (Compose *compose,
327 static gchar *compose_get_header (Compose *compose);
328 static gchar *compose_get_manual_headers_info (Compose *compose);
330 static void compose_convert_header (Compose *compose,
335 gboolean addr_field);
337 static void compose_attach_info_free (AttachInfo *ainfo);
338 static void compose_attach_remove_selected (GtkAction *action,
341 static void compose_template_apply (Compose *compose,
344 static void compose_attach_property (GtkAction *action,
346 static void compose_attach_property_create (gboolean *cancelled);
347 static void attach_property_ok (GtkWidget *widget,
348 gboolean *cancelled);
349 static void attach_property_cancel (GtkWidget *widget,
350 gboolean *cancelled);
351 static gint attach_property_delete_event (GtkWidget *widget,
353 gboolean *cancelled);
354 static gboolean attach_property_key_pressed (GtkWidget *widget,
356 gboolean *cancelled);
358 static void compose_exec_ext_editor (Compose *compose);
360 static gint compose_exec_ext_editor_real (const gchar *file,
361 GdkNativeWindow socket_wid);
362 static gboolean compose_ext_editor_kill (Compose *compose);
363 static gboolean compose_input_cb (GIOChannel *source,
364 GIOCondition condition,
366 static void compose_set_ext_editor_sensitive (Compose *compose,
368 static gboolean compose_get_ext_editor_cmd_valid();
369 static gboolean compose_get_ext_editor_uses_socket();
370 static gboolean compose_ext_editor_plug_removed_cb
373 #endif /* G_OS_UNIX */
375 static void compose_undo_state_changed (UndoMain *undostruct,
380 static void compose_create_header_entry (Compose *compose);
381 static void compose_add_header_entry (Compose *compose, const gchar *header,
382 gchar *text, ComposePrefType pref_type);
383 static void compose_remove_header_entries(Compose *compose);
385 static void compose_update_priority_menu_item(Compose * compose);
387 static void compose_spell_menu_changed (void *data);
388 static void compose_dict_changed (void *data);
390 static void compose_add_field_list ( Compose *compose,
391 GList *listAddress );
393 /* callback functions */
395 static void compose_notebook_size_alloc (GtkNotebook *notebook,
396 GtkAllocation *allocation,
398 static gboolean compose_edit_size_alloc (GtkEditable *widget,
399 GtkAllocation *allocation,
400 GtkSHRuler *shruler);
401 static void account_activated (GtkComboBox *optmenu,
403 static void attach_selected (GtkTreeView *tree_view,
404 GtkTreePath *tree_path,
405 GtkTreeViewColumn *column,
407 static gboolean attach_button_pressed (GtkWidget *widget,
408 GdkEventButton *event,
410 static gboolean attach_key_pressed (GtkWidget *widget,
413 static void compose_send_cb (GtkAction *action, gpointer data);
414 static void compose_send_later_cb (GtkAction *action, gpointer data);
416 static void compose_save_cb (GtkAction *action,
419 static void compose_attach_cb (GtkAction *action,
421 static void compose_insert_file_cb (GtkAction *action,
423 static void compose_insert_sig_cb (GtkAction *action,
425 static void compose_replace_sig_cb (GtkAction *action,
428 static void compose_close_cb (GtkAction *action,
430 static void compose_print_cb (GtkAction *action,
433 static void compose_set_encoding_cb (GtkAction *action, GtkRadioAction *current, gpointer data);
435 static void compose_address_cb (GtkAction *action,
437 static void about_show_cb (GtkAction *action,
439 static void compose_template_activate_cb(GtkWidget *widget,
442 static void compose_ext_editor_cb (GtkAction *action,
445 static gint compose_delete_cb (GtkWidget *widget,
449 static void compose_undo_cb (GtkAction *action,
451 static void compose_redo_cb (GtkAction *action,
453 static void compose_cut_cb (GtkAction *action,
455 static void compose_copy_cb (GtkAction *action,
457 static void compose_paste_cb (GtkAction *action,
459 static void compose_paste_as_quote_cb (GtkAction *action,
461 static void compose_paste_no_wrap_cb (GtkAction *action,
463 static void compose_paste_wrap_cb (GtkAction *action,
465 static void compose_allsel_cb (GtkAction *action,
468 static void compose_advanced_action_cb (GtkAction *action,
471 static void compose_grab_focus_cb (GtkWidget *widget,
474 static void compose_changed_cb (GtkTextBuffer *textbuf,
477 static void compose_wrap_cb (GtkAction *action,
479 static void compose_wrap_all_cb (GtkAction *action,
481 static void compose_find_cb (GtkAction *action,
483 static void compose_toggle_autowrap_cb (GtkToggleAction *action,
485 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
488 static void compose_toggle_ruler_cb (GtkToggleAction *action,
490 static void compose_toggle_sign_cb (GtkToggleAction *action,
492 static void compose_toggle_encrypt_cb (GtkToggleAction *action,
494 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data);
495 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn);
496 static void compose_activate_privacy_system (Compose *compose,
497 PrefsAccount *account,
499 static void compose_apply_folder_privacy_settings(Compose *compose, FolderItem *folder_item);
500 static void compose_toggle_return_receipt_cb(GtkToggleAction *action,
502 static void compose_toggle_remove_refs_cb(GtkToggleAction *action,
504 static void compose_set_priority_cb (GtkAction *action, GtkRadioAction *current, gpointer data);
505 static void compose_reply_change_mode (Compose *compose, ComposeMode action);
506 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data);
508 static void compose_attach_drag_received_cb (GtkWidget *widget,
509 GdkDragContext *drag_context,
512 GtkSelectionData *data,
516 static void compose_insert_drag_received_cb (GtkWidget *widget,
517 GdkDragContext *drag_context,
520 GtkSelectionData *data,
524 static void compose_header_drag_received_cb (GtkWidget *widget,
525 GdkDragContext *drag_context,
528 GtkSelectionData *data,
533 static gboolean compose_drag_drop (GtkWidget *widget,
534 GdkDragContext *drag_context,
536 guint time, gpointer user_data);
537 static gboolean completion_set_focus_to_subject
542 static void text_inserted (GtkTextBuffer *buffer,
547 static Compose *compose_generic_reply(MsgInfo *msginfo,
548 ComposeQuoteMode quote_mode,
552 gboolean followup_and_reply_to,
555 static void compose_headerentry_changed_cb (GtkWidget *entry,
556 ComposeHeaderEntry *headerentry);
557 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
559 ComposeHeaderEntry *headerentry);
560 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
561 ComposeHeaderEntry *headerentry);
563 static void compose_show_first_last_header (Compose *compose, gboolean show_first);
565 static void compose_allow_user_actions (Compose *compose, gboolean allow);
567 static void compose_nothing_cb (GtkAction *action, gpointer data)
573 static void compose_check_all (GtkAction *action, gpointer data);
574 static void compose_highlight_all (GtkAction *action, gpointer data);
575 static void compose_check_backwards (GtkAction *action, gpointer data);
576 static void compose_check_forwards_go (GtkAction *action, gpointer data);
579 static PrefsAccount *compose_find_account (MsgInfo *msginfo);
581 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose);
584 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
585 FolderItem *folder_item);
587 static void compose_attach_update_label(Compose *compose);
588 static void compose_set_folder_prefs(Compose *compose, FolderItem *folder,
589 gboolean respect_default_to);
590 static void compose_subject_entry_activated(GtkWidget *widget, gpointer data);
591 static void from_name_activate_cb(GtkWidget *widget, gpointer data);
593 static GtkActionEntry compose_popup_entries[] =
595 {"Compose", NULL, "Compose", NULL, NULL, NULL },
596 {"Compose/Add", NULL, N_("_Add..."), NULL, NULL, G_CALLBACK(compose_attach_cb) },
597 {"Compose/Remove", NULL, N_("_Remove"), NULL, NULL, G_CALLBACK(compose_attach_remove_selected) },
598 {"Compose/---", NULL, "---", NULL, NULL, NULL },
599 {"Compose/Properties", NULL, N_("_Properties..."), NULL, NULL, G_CALLBACK(compose_attach_property) },
602 static GtkActionEntry compose_entries[] =
604 {"Menu", NULL, "Menu", NULL, NULL, NULL },
606 {"Message", NULL, N_("_Message"), NULL, NULL, NULL },
607 {"Edit", NULL, N_("_Edit"), NULL, NULL, NULL },
609 {"Spelling", NULL, N_("_Spelling"), NULL, NULL, NULL },
611 {"Options", NULL, N_("_Options"), NULL, NULL, NULL },
612 {"Tools", NULL, N_("_Tools"), NULL, NULL, NULL },
613 {"Help", NULL, N_("_Help"), NULL, NULL, NULL },
615 {"Message/Send", NULL, N_("S_end"), "<control>Return", NULL, G_CALLBACK(compose_send_cb) },
616 {"Message/SendLater", NULL, N_("Send _later"), "<shift><control>S", NULL, G_CALLBACK(compose_send_later_cb) },
617 {"Message/---", NULL, "---", NULL, NULL, NULL },
619 {"Message/AttachFile", NULL, N_("_Attach file"), "<control>M", NULL, G_CALLBACK(compose_attach_cb) },
620 {"Message/InsertFile", NULL, N_("_Insert file"), "<control>I", NULL, G_CALLBACK(compose_insert_file_cb) },
621 {"Message/InsertSig", NULL, N_("Insert si_gnature"), "<control>G", NULL, G_CALLBACK(compose_insert_sig_cb) },
622 {"Message/ReplaceSig", NULL, N_("_Replace signature"), NULL, NULL, G_CALLBACK(compose_replace_sig_cb) },
623 /* {"Message/---", NULL, "---", NULL, NULL, NULL }, */
624 {"Message/Save", NULL, N_("_Save"), "<control>S", NULL, G_CALLBACK(compose_save_cb) }, /*COMPOSE_KEEP_EDITING*/
625 /* {"Message/---", NULL, "---", NULL, NULL, NULL }, */
626 {"Message/Print", NULL, N_("_Print"), NULL, NULL, G_CALLBACK(compose_print_cb) },
627 /* {"Message/---", NULL, "---", NULL, NULL, NULL }, */
628 {"Message/Close", NULL, N_("_Close"), "<control>W", NULL, G_CALLBACK(compose_close_cb) },
631 {"Edit/Undo", NULL, N_("_Undo"), "<control>Z", NULL, G_CALLBACK(compose_undo_cb) },
632 {"Edit/Redo", NULL, N_("_Redo"), "<control>Y", NULL, G_CALLBACK(compose_redo_cb) },
633 {"Edit/---", NULL, "---", NULL, NULL, NULL },
635 {"Edit/Cut", NULL, N_("Cu_t"), "<control>X", NULL, G_CALLBACK(compose_cut_cb) },
636 {"Edit/Copy", NULL, N_("_Copy"), "<control>C", NULL, G_CALLBACK(compose_copy_cb) },
637 {"Edit/Paste", NULL, N_("_Paste"), "<control>V", NULL, G_CALLBACK(compose_paste_cb) },
639 {"Edit/SpecialPaste", NULL, N_("_Special paste"), NULL, NULL, NULL },
640 {"Edit/SpecialPaste/AsQuotation", NULL, N_("As _quotation"), NULL, NULL, G_CALLBACK(compose_paste_as_quote_cb) },
641 {"Edit/SpecialPaste/Wrapped", NULL, N_("_Wrapped"), NULL, NULL, G_CALLBACK(compose_paste_wrap_cb) },
642 {"Edit/SpecialPaste/Unwrapped", NULL, N_("_Unwrapped"), NULL, NULL, G_CALLBACK(compose_paste_no_wrap_cb) },
644 {"Edit/SelectAll", NULL, N_("Select _all"), "<control>A", NULL, G_CALLBACK(compose_allsel_cb) },
646 {"Edit/Advanced", NULL, N_("A_dvanced"), NULL, NULL, NULL },
647 {"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*/
648 {"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*/
649 {"Edit/Advanced/BackWord", NULL, N_("Move a word backward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD*/
650 {"Edit/Advanced/ForwWord", NULL, N_("Move a word forward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD*/
651 {"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*/
652 {"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*/
653 {"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*/
654 {"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*/
655 {"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*/
656 {"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*/
657 {"Edit/Advanced/DelBackWord", NULL, N_("Delete a word backward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD*/
658 {"Edit/Advanced/DelForwWord", NULL, N_("Delete a word forward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD*/
659 {"Edit/Advanced/DelLine", NULL, N_("Delete line"), "<control>U", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE*/
660 {"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*/
662 /* {"Edit/---", NULL, "---", NULL, NULL, NULL }, */
663 {"Edit/Find", NULL, N_("_Find"), "<control>F", NULL, G_CALLBACK(compose_find_cb) },
665 /* {"Edit/---", NULL, "---", NULL, NULL, NULL }, */
666 {"Edit/WrapPara", NULL, N_("_Wrap current paragraph"), "<control>L", NULL, G_CALLBACK(compose_wrap_cb) }, /* 0 */
667 {"Edit/WrapAllLines", NULL, N_("Wrap all long _lines"), "<control><alt>L", NULL, G_CALLBACK(compose_wrap_all_cb) }, /* 1 */
668 /* {"Edit/---", NULL, "---", NULL, NULL, NULL }, */
669 {"Edit/ExtEditor", NULL, N_("Edit with e_xternal editor"), "<shift><control>X", NULL, G_CALLBACK(compose_ext_editor_cb) },
672 {"Spelling/CheckAllSel", NULL, N_("_Check all or check selection"), NULL, NULL, G_CALLBACK(compose_check_all) },
673 {"Spelling/HighlightAll", NULL, N_("_Highlight all misspelled words"), NULL, NULL, G_CALLBACK(compose_highlight_all) },
674 {"Spelling/CheckBackwards", NULL, N_("Check _backwards misspelled word"), NULL, NULL, G_CALLBACK(compose_check_backwards) },
675 {"Spelling/ForwardNext", NULL, N_("_Forward to next misspelled word"), NULL, NULL, G_CALLBACK(compose_check_forwards_go) },
677 {"Spelling/---", NULL, "---", NULL, NULL, NULL },
678 {"Spelling/Options", NULL, N_("_Options"), NULL, NULL, NULL },
682 {"Options/ReplyMode", NULL, N_("Reply _mode"), NULL, NULL, NULL },
683 {"Options/---", NULL, "---", NULL, NULL, NULL },
684 {"Options/PrivacySystem", NULL, N_("Privacy _System"), NULL, NULL, NULL },
685 {"Options/PrivacySystem/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
687 /* {"Options/---", NULL, "---", NULL, NULL, NULL }, */
688 {"Options/Priority", NULL, N_("_Priority"), NULL, NULL, NULL },
690 {"Options/Encoding", NULL, N_("Character _encoding"), NULL, NULL, NULL },
691 {"Options/Encoding/---", NULL, "---", NULL, NULL, NULL },
692 #define ENC_ACTION(cs_char,c_char,string) \
693 {"Options/Encoding/" cs_char, NULL, N_(string), NULL, NULL, c_char }
695 {"Options/Encoding/Western", NULL, N_("Western European"), NULL, NULL, NULL },
696 {"Options/Encoding/Baltic", NULL, N_("Baltic"), NULL, NULL, NULL },
697 {"Options/Encoding/Hebrew", NULL, N_("Hebrew"), NULL, NULL, NULL },
698 {"Options/Encoding/Arabic", NULL, N_("Arabic"), NULL, NULL, NULL },
699 {"Options/Encoding/Cyrillic", NULL, N_("Cyrillic"), NULL, NULL, NULL },
700 {"Options/Encoding/Japanese", NULL, N_("Japanese"), NULL, NULL, NULL },
701 {"Options/Encoding/Chinese", NULL, N_("Chinese"), NULL, NULL, NULL },
702 {"Options/Encoding/Korean", NULL, N_("Korean"), NULL, NULL, NULL },
703 {"Options/Encoding/Thai", NULL, N_("Thai"), NULL, NULL, NULL },
706 {"Tools/AddressBook", NULL, N_("_Address book"), NULL, NULL, G_CALLBACK(compose_address_cb) },
708 {"Tools/Template", NULL, N_("_Template"), NULL, NULL, NULL },
709 {"Tools/Template/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
710 {"Tools/Actions", NULL, N_("Actio_ns"), NULL, NULL, NULL },
711 {"Tools/Actions/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
714 {"Help/About", NULL, N_("_About"), NULL, NULL, G_CALLBACK(about_show_cb) },
717 static GtkToggleActionEntry compose_toggle_entries[] =
719 {"Edit/AutoWrap", NULL, N_("Aut_o wrapping"), "<shift><control>L", NULL, G_CALLBACK(compose_toggle_autowrap_cb), FALSE }, /* Toggle */
720 {"Edit/AutoIndent", NULL, N_("Auto _indent"), NULL, NULL, G_CALLBACK(compose_toggle_autoindent_cb), FALSE }, /* Toggle */
721 {"Options/Sign", NULL, N_("Si_gn"), NULL, NULL, G_CALLBACK(compose_toggle_sign_cb), FALSE }, /* Toggle */
722 {"Options/Encrypt", NULL, N_("_Encrypt"), NULL, NULL, G_CALLBACK(compose_toggle_encrypt_cb), FALSE }, /* Toggle */
723 {"Options/RequestRetRcpt", NULL, N_("_Request Return Receipt"), NULL, NULL, G_CALLBACK(compose_toggle_return_receipt_cb), FALSE }, /* Toggle */
724 {"Options/RemoveReferences", NULL, N_("Remo_ve references"), NULL, NULL, G_CALLBACK(compose_toggle_remove_refs_cb), FALSE }, /* Toggle */
725 {"Tools/ShowRuler", NULL, N_("Show _ruler"), NULL, NULL, G_CALLBACK(compose_toggle_ruler_cb), FALSE }, /* Toggle */
728 static GtkRadioActionEntry compose_radio_rm_entries[] =
730 {"Options/ReplyMode/Normal", NULL, N_("_Normal"), NULL, NULL, COMPOSE_REPLY }, /* RADIO compose_reply_change_mode_cb */
731 {"Options/ReplyMode/All", NULL, N_("_All"), NULL, NULL, COMPOSE_REPLY_TO_ALL }, /* RADIO compose_reply_change_mode_cb */
732 {"Options/ReplyMode/Sender", NULL, N_("_Sender"), NULL, NULL, COMPOSE_REPLY_TO_SENDER }, /* RADIO compose_reply_change_mode_cb */
733 {"Options/ReplyMode/List", NULL, N_("_Mailing-list"), NULL, NULL, COMPOSE_REPLY_TO_LIST }, /* RADIO compose_reply_change_mode_cb */
736 static GtkRadioActionEntry compose_radio_prio_entries[] =
738 {"Options/Priority/Highest", NULL, N_("_Highest"), NULL, NULL, PRIORITY_HIGHEST }, /* RADIO compose_set_priority_cb */
739 {"Options/Priority/High", NULL, N_("Hi_gh"), NULL, NULL, PRIORITY_HIGH }, /* RADIO compose_set_priority_cb */
740 {"Options/Priority/Normal", NULL, N_("_Normal"), NULL, NULL, PRIORITY_NORMAL }, /* RADIO compose_set_priority_cb */
741 {"Options/Priority/Low", NULL, N_("Lo_w"), NULL, NULL, PRIORITY_LOW }, /* RADIO compose_set_priority_cb */
742 {"Options/Priority/Lowest", NULL, N_("_Lowest"), NULL, NULL, PRIORITY_LOWEST }, /* RADIO compose_set_priority_cb */
745 static GtkRadioActionEntry compose_radio_enc_entries[] =
747 ENC_ACTION(CS_AUTO, C_AUTO, N_("_Automatic")), /* RADIO compose_set_encoding_cb */
748 ENC_ACTION(CS_US_ASCII, C_US_ASCII, N_("7bit ASCII (US-ASC_II)")), /* RADIO compose_set_encoding_cb */
749 ENC_ACTION(CS_UTF_8, C_UTF_8, N_("Unicode (_UTF-8)")), /* RADIO compose_set_encoding_cb */
750 ENC_ACTION("Western/"CS_ISO_8859_1, C_ISO_8859_1, "ISO-8859-_1"), /* RADIO compose_set_encoding_cb */
751 ENC_ACTION("Western/"CS_ISO_8859_15, C_ISO_8859_15, "ISO-8859-15"), /* RADIO compose_set_encoding_cb */
752 ENC_ACTION("Western/"CS_WINDOWS_1252, C_WINDOWS_1252, "Windows-1252"), /* RADIO compose_set_encoding_cb */
753 ENC_ACTION(CS_ISO_8859_2, C_ISO_8859_2, N_("Central European (ISO-8859-_2)")), /* RADIO compose_set_encoding_cb */
754 ENC_ACTION("Baltic/"CS_ISO_8859_13, C_ISO_8859_13, "ISO-8859-13"), /* RADIO compose_set_encoding_cb */
755 ENC_ACTION("Baltic/"CS_ISO_8859_4, C_ISO_8859_14, "ISO-8859-_4"), /* RADIO compose_set_encoding_cb */
756 ENC_ACTION(CS_ISO_8859_7, C_ISO_8859_7, N_("Greek (ISO-8859-_7)")), /* RADIO compose_set_encoding_cb */
757 ENC_ACTION("Hebrew/"CS_ISO_8859_8, C_ISO_8859_8, "ISO-8859-_8"), /* RADIO compose_set_encoding_cb */
758 ENC_ACTION("Hebrew/"CS_WINDOWS_1255, C_WINDOWS_1255, "Windows-1255"), /* RADIO compose_set_encoding_cb */
759 ENC_ACTION("Arabic/"CS_ISO_8859_6, C_ISO_8859_6, "ISO-8859-_6"), /* RADIO compose_set_encoding_cb */
760 ENC_ACTION("Arabic/"CS_WINDOWS_1256, C_WINDOWS_1256, "Windows-1256"), /* RADIO compose_set_encoding_cb */
761 ENC_ACTION(CS_ISO_8859_9, C_ISO_8859_9, N_("Turkish (ISO-8859-_9)")), /* RADIO compose_set_encoding_cb */
762 ENC_ACTION("Cyrillic/"CS_ISO_8859_5, C_ISO_8859_5, "ISO-8859-_5"), /* RADIO compose_set_encoding_cb */
763 ENC_ACTION("Cyrillic/"CS_KOI8_R, C_KOI8_R, "KOI8-_R"), /* RADIO compose_set_encoding_cb */
764 ENC_ACTION("Cyrillic/"CS_MACCYR, C_MACCYR, "_Mac-Cyrillic"), /* RADIO compose_set_encoding_cb */
765 ENC_ACTION("Cyrillic/"CS_KOI8_U, C_KOI8_U, "KOI8-_U"), /* RADIO compose_set_encoding_cb */
766 ENC_ACTION("Cyrillic/"CS_WINDOWS_1251, C_WINDOWS_1251, "Windows-1251"), /* RADIO compose_set_encoding_cb */
767 ENC_ACTION("Japanese/"CS_ISO_2022_JP, C_ISO_2022_JP, "ISO-2022-_JP"), /* RADIO compose_set_encoding_cb */
768 ENC_ACTION("Japanese/"CS_ISO_2022_JP_2, C_ISO_2022_JP_2, "ISO-2022-JP-_2"), /* RADIO compose_set_encoding_cb */
769 ENC_ACTION("Japanese/"CS_EUC_JP, C_EUC_JP, "_EUC-JP"), /* RADIO compose_set_encoding_cb */
770 ENC_ACTION("Japanese/"CS_SHIFT_JIS, C_SHIFT_JIS, "_Shift-JIS"), /* RADIO compose_set_encoding_cb */
771 ENC_ACTION("Chinese/"CS_GB18030, C_GB18030, "_GB18030"), /* RADIO compose_set_encoding_cb */
772 ENC_ACTION("Chinese/"CS_GB2312, C_GB2312, "_GB2312"), /* RADIO compose_set_encoding_cb */
773 ENC_ACTION("Chinese/"CS_GBK, C_GBK, "GB_K"), /* RADIO compose_set_encoding_cb */
774 ENC_ACTION("Chinese/"CS_BIG5, C_BIG5, "_Big5-JP"), /* RADIO compose_set_encoding_cb */
775 ENC_ACTION("Chinese/"CS_EUC_TW, C_EUC_TW, "EUC-_TW"), /* RADIO compose_set_encoding_cb */
776 ENC_ACTION("Korean/"CS_EUC_KR, C_EUC_KR, "_EUC-KR"), /* RADIO compose_set_encoding_cb */
777 ENC_ACTION("Korean/"CS_ISO_2022_KR, C_ISO_2022_KR, "_ISO-2022-KR"), /* RADIO compose_set_encoding_cb */
778 ENC_ACTION("Thai/"CS_TIS_620, C_TIS_620, "_TIS-620-KR"), /* RADIO compose_set_encoding_cb */
779 ENC_ACTION("Thai/"CS_WINDOWS_874, C_WINDOWS_874, "_Windows-874"), /* RADIO compose_set_encoding_cb */
782 static GtkTargetEntry compose_mime_types[] =
784 {"text/uri-list", 0, 0},
785 {"UTF8_STRING", 0, 0},
789 static gboolean compose_put_existing_to_front(MsgInfo *info)
791 const GList *compose_list = compose_get_compose_list();
792 const GList *elem = NULL;
795 for (elem = compose_list; elem != NULL && elem->data != NULL;
797 Compose *c = (Compose*)elem->data;
799 if (!c->targetinfo || !c->targetinfo->msgid ||
803 if (!strcmp(c->targetinfo->msgid, info->msgid)) {
804 gtkut_window_popup(c->window);
812 static GdkColor quote_color1 =
813 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
814 static GdkColor quote_color2 =
815 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
816 static GdkColor quote_color3 =
817 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
819 static GdkColor quote_bgcolor1 =
820 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
821 static GdkColor quote_bgcolor2 =
822 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
823 static GdkColor quote_bgcolor3 =
824 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
826 static GdkColor signature_color = {
833 static GdkColor uri_color = {
840 static void compose_create_tags(GtkTextView *text, Compose *compose)
842 GtkTextBuffer *buffer;
843 GdkColor black = {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
845 buffer = gtk_text_view_get_buffer(text);
847 if (prefs_common.enable_color) {
848 /* grab the quote colors, converting from an int to a GdkColor */
849 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_QUOTE_LEVEL1],
851 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_QUOTE_LEVEL2],
853 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_QUOTE_LEVEL3],
855 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_QUOTE_LEVEL1_BG],
857 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_QUOTE_LEVEL2_BG],
859 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_QUOTE_LEVEL3_BG],
861 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_SIGNATURE],
863 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_URI],
866 signature_color = quote_color1 = quote_color2 = quote_color3 =
867 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 = uri_color = black;
870 if (prefs_common.enable_color && prefs_common.enable_bgcolor) {
871 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
872 "foreground-gdk", "e_color1,
873 "paragraph-background-gdk", "e_bgcolor1,
875 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
876 "foreground-gdk", "e_color2,
877 "paragraph-background-gdk", "e_bgcolor2,
879 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
880 "foreground-gdk", "e_color3,
881 "paragraph-background-gdk", "e_bgcolor3,
884 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
885 "foreground-gdk", "e_color1,
887 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
888 "foreground-gdk", "e_color2,
890 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
891 "foreground-gdk", "e_color3,
895 compose->signature_tag = gtk_text_buffer_create_tag(buffer, "signature",
896 "foreground-gdk", &signature_color,
899 compose->uri_tag = gtk_text_buffer_create_tag(buffer, "link",
900 "foreground-gdk", &uri_color,
902 compose->no_wrap_tag = gtk_text_buffer_create_tag(buffer, "no_wrap", NULL);
903 compose->no_join_tag = gtk_text_buffer_create_tag(buffer, "no_join", NULL);
906 Compose *compose_new(PrefsAccount *account, const gchar *mailto,
909 return compose_generic_new(account, mailto, NULL, attach_files, NULL);
912 Compose *compose_new_with_folderitem(PrefsAccount *account, FolderItem *item, const gchar *mailto)
914 return compose_generic_new(account, mailto, item, NULL, NULL);
917 Compose *compose_new_with_list( PrefsAccount *account, GList *listAddress )
919 return compose_generic_new( account, NULL, NULL, NULL, listAddress );
922 #define SCROLL_TO_CURSOR(compose) { \
923 GtkTextMark *cmark = gtk_text_buffer_get_insert( \
924 gtk_text_view_get_buffer( \
925 GTK_TEXT_VIEW(compose->text))); \
926 gtk_text_view_scroll_mark_onscreen( \
927 GTK_TEXT_VIEW(compose->text), \
931 static void compose_set_save_to(Compose *compose, const gchar *folderidentifier)
934 if (folderidentifier) {
935 combobox_unset_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo));
936 prefs_common.compose_save_to_history = add_history(
937 prefs_common.compose_save_to_history, folderidentifier);
938 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo),
939 prefs_common.compose_save_to_history);
942 entry = GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(compose->savemsg_combo)));
943 if (folderidentifier) {
944 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
945 gtk_entry_set_text(GTK_ENTRY(entry), folderidentifier);
947 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), FALSE);
948 gtk_entry_set_text(GTK_ENTRY(entry), "");
952 static gchar *compose_get_save_to(Compose *compose)
955 gchar *result = NULL;
956 entry = GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(compose->savemsg_combo)));
957 result = gtk_editable_get_chars(entry, 0, -1);
960 combobox_unset_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo));
961 prefs_common.compose_save_to_history = add_history(
962 prefs_common.compose_save_to_history, result);
963 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo),
964 prefs_common.compose_save_to_history);
969 Compose *compose_generic_new(PrefsAccount *account, const gchar *mailto, FolderItem *item,
970 GList *attach_files, GList *listAddress )
973 GtkTextView *textview;
974 GtkTextBuffer *textbuf;
976 const gchar *subject_format = NULL;
977 const gchar *body_format = NULL;
978 gchar *mailto_from = NULL;
979 PrefsAccount *mailto_account = NULL;
980 MsgInfo* dummyinfo = NULL;
981 gint cursor_pos = -1;
982 MailField mfield = NO_FIELD_PRESENT;
986 /* check if mailto defines a from */
987 if (mailto && *mailto != '\0') {
988 scan_mailto_url(mailto, &mailto_from, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
989 /* mailto defines a from, check if we can get account prefs from it,
990 if not, the account prefs will be guessed using other ways, but we'll keep
993 mailto_account = account_find_from_address(mailto_from, TRUE);
994 if (mailto_account == NULL) {
996 Xstrdup_a(tmp_from, mailto_from, return NULL);
997 extract_address(tmp_from);
998 mailto_account = account_find_from_address(tmp_from, TRUE);
1002 account = mailto_account;
1005 /* if no account prefs set from mailto, set if from folder prefs (if any) */
1006 if (!mailto_account && item && item->prefs && item->prefs->enable_default_account)
1007 account = account_find_from_id(item->prefs->default_account);
1009 /* if no account prefs set, fallback to the current one */
1010 if (!account) account = cur_account;
1011 cm_return_val_if_fail(account != NULL, NULL);
1013 compose = compose_create(account, item, COMPOSE_NEW, FALSE);
1014 compose_apply_folder_privacy_settings(compose, item);
1016 if (privacy_system_can_sign(compose->privacy_system) == FALSE &&
1017 (account->default_encrypt || account->default_sign))
1018 alertpanel_error(_("You have opted to sign and/or encrypt this "
1019 "message but have not selected a privacy system.\n\n"
1020 "Signing and encrypting have been disabled for this "
1023 /* override from name if mailto asked for it */
1025 gtk_entry_set_text(GTK_ENTRY(compose->from_name), mailto_from);
1026 g_free(mailto_from);
1028 /* override from name according to folder properties */
1029 if (item && item->prefs &&
1030 item->prefs->compose_with_format &&
1031 item->prefs->compose_override_from_format &&
1032 *item->prefs->compose_override_from_format != '\0') {
1037 dummyinfo = compose_msginfo_new_from_compose(compose);
1039 /* decode \-escape sequences in the internal representation of the quote format */
1040 tmp = g_malloc(strlen(item->prefs->compose_override_from_format)+1);
1041 pref_get_unescaped_pref(tmp, item->prefs->compose_override_from_format);
1044 quote_fmt_init(dummyinfo, NULL, NULL, FALSE, compose->account, FALSE,
1045 compose->gtkaspell);
1047 quote_fmt_init(dummyinfo, NULL, NULL, FALSE, compose->account, FALSE);
1049 quote_fmt_scan_string(tmp);
1052 buf = quote_fmt_get_buffer();
1054 alertpanel_error(_("New message From format error."));
1056 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1057 quote_fmt_reset_vartable();
1058 quote_fmtlex_destroy();
1063 compose->replyinfo = NULL;
1064 compose->fwdinfo = NULL;
1066 textview = GTK_TEXT_VIEW(compose->text);
1067 textbuf = gtk_text_view_get_buffer(textview);
1068 compose_create_tags(textview, compose);
1070 undo_block(compose->undostruct);
1072 compose_set_dictionaries_from_folder_prefs(compose, item);
1075 if (account->auto_sig)
1076 compose_insert_sig(compose, FALSE);
1077 gtk_text_buffer_get_start_iter(textbuf, &iter);
1078 gtk_text_buffer_place_cursor(textbuf, &iter);
1080 if (account->protocol != A_NNTP) {
1081 if (mailto && *mailto != '\0') {
1082 mfield = compose_entries_set(compose, mailto, COMPOSE_TO);
1085 compose_set_folder_prefs(compose, item, TRUE);
1087 if (item && item->ret_rcpt) {
1088 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
1091 if (mailto && *mailto != '\0') {
1092 if (!strchr(mailto, '@'))
1093 mfield = compose_entries_set(compose, mailto, COMPOSE_NEWSGROUPS);
1095 mfield = compose_entries_set(compose, mailto, COMPOSE_TO);
1096 } else if (item && FOLDER_CLASS(item->folder) == news_get_class()) {
1097 compose_entry_append(compose, item->path, COMPOSE_NEWSGROUPS, PREF_FOLDER);
1098 mfield = TO_FIELD_PRESENT;
1101 * CLAWS: just don't allow return receipt request, even if the user
1102 * may want to send an email. simple but foolproof.
1104 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", FALSE);
1106 compose_add_field_list( compose, listAddress );
1108 if (item && item->prefs && item->prefs->compose_with_format) {
1109 subject_format = item->prefs->compose_subject_format;
1110 body_format = item->prefs->compose_body_format;
1111 } else if (account->compose_with_format) {
1112 subject_format = account->compose_subject_format;
1113 body_format = account->compose_body_format;
1114 } else if (prefs_common.compose_with_format) {
1115 subject_format = prefs_common.compose_subject_format;
1116 body_format = prefs_common.compose_body_format;
1119 if (subject_format || body_format) {
1122 && *subject_format != '\0' )
1124 gchar *subject = NULL;
1129 dummyinfo = compose_msginfo_new_from_compose(compose);
1131 /* decode \-escape sequences in the internal representation of the quote format */
1132 tmp = g_malloc(strlen(subject_format)+1);
1133 pref_get_unescaped_pref(tmp, subject_format);
1135 subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1137 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account, FALSE,
1138 compose->gtkaspell);
1140 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account, FALSE);
1142 quote_fmt_scan_string(tmp);
1145 buf = quote_fmt_get_buffer();
1147 alertpanel_error(_("New message subject format error."));
1149 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1150 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1151 quote_fmt_reset_vartable();
1152 quote_fmtlex_destroy();
1156 mfield = SUBJECT_FIELD_PRESENT;
1160 && *body_format != '\0' )
1163 GtkTextBuffer *buffer;
1164 GtkTextIter start, end;
1168 dummyinfo = compose_msginfo_new_from_compose(compose);
1170 text = GTK_TEXT_VIEW(compose->text);
1171 buffer = gtk_text_view_get_buffer(text);
1172 gtk_text_buffer_get_start_iter(buffer, &start);
1173 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
1174 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
1176 compose_quote_fmt(compose, dummyinfo,
1178 NULL, tmp, FALSE, TRUE,
1179 _("The body of the \"New message\" template has an error at line %d."));
1180 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1181 quote_fmt_reset_vartable();
1185 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1186 gtkaspell_highlight_all(compose->gtkaspell);
1188 mfield = BODY_FIELD_PRESENT;
1192 procmsg_msginfo_free( &dummyinfo );
1198 for (curr = attach_files ; curr != NULL ; curr = curr->next) {
1199 ainfo = (AttachInfo *) curr->data;
1201 compose_insert_file(compose, ainfo->file);
1203 compose_attach_append(compose, ainfo->file, ainfo->file,
1204 ainfo->content_type, ainfo->charset);
1208 compose_show_first_last_header(compose, TRUE);
1210 /* Set save folder */
1211 if (item && item->prefs && item->prefs->save_copy_to_folder) {
1212 gchar *folderidentifier;
1214 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo), TRUE);
1215 folderidentifier = folder_item_get_identifier(item);
1216 compose_set_save_to(compose, folderidentifier);
1217 g_free(folderidentifier);
1220 /* Place cursor according to provided input (mfield) */
1222 case NO_FIELD_PRESENT:
1223 if (compose->header_last)
1224 gtk_widget_grab_focus(compose->header_last->entry);
1226 case TO_FIELD_PRESENT:
1227 buf = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1229 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1232 gtk_widget_grab_focus(compose->subject_entry);
1234 case SUBJECT_FIELD_PRESENT:
1235 textview = GTK_TEXT_VIEW(compose->text);
1238 textbuf = gtk_text_view_get_buffer(textview);
1241 mark = gtk_text_buffer_get_insert(textbuf);
1242 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
1243 gtk_text_buffer_insert(textbuf, &iter, "", -1);
1245 * SUBJECT_FIELD_PRESENT and BODY_FIELD_PRESENT
1246 * only defers where it comes to the variable body
1247 * is not null. If no body is present compose->text
1248 * will be null in which case you cannot place the
1249 * cursor inside the component so. An empty component
1250 * is therefore created before placing the cursor
1252 case BODY_FIELD_PRESENT:
1253 cursor_pos = quote_fmt_get_cursor_pos();
1254 if (cursor_pos == -1)
1255 gtk_widget_grab_focus(compose->header_last->entry);
1257 gtk_widget_grab_focus(compose->text);
1261 undo_unblock(compose->undostruct);
1263 if (prefs_common.auto_exteditor)
1264 compose_exec_ext_editor(compose);
1266 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
1268 SCROLL_TO_CURSOR(compose);
1270 compose->modified = FALSE;
1271 compose_set_title(compose);
1273 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
1278 static void compose_force_encryption(Compose *compose, PrefsAccount *account,
1279 gboolean override_pref, const gchar *system)
1281 const gchar *privacy = NULL;
1283 cm_return_if_fail(compose != NULL);
1284 cm_return_if_fail(account != NULL);
1286 if (privacy_system_can_encrypt(compose->privacy_system) == FALSE ||
1287 (override_pref == FALSE && account->default_encrypt_reply == FALSE))
1290 if (account->default_privacy_system && strlen(account->default_privacy_system))
1291 privacy = account->default_privacy_system;
1295 GSList *privacy_avail = privacy_get_system_ids();
1296 if (privacy_avail && g_slist_length(privacy_avail)) {
1297 privacy = (gchar *)(privacy_avail->data);
1299 g_slist_free_full(privacy_avail, g_free);
1301 if (privacy != NULL) {
1303 g_free(compose->privacy_system);
1304 compose->privacy_system = NULL;
1305 g_free(compose->encdata);
1306 compose->encdata = NULL;
1308 if (compose->privacy_system == NULL)
1309 compose->privacy_system = g_strdup(privacy);
1310 else if (*(compose->privacy_system) == '\0') {
1311 g_free(compose->privacy_system);
1312 g_free(compose->encdata);
1313 compose->encdata = NULL;
1314 compose->privacy_system = g_strdup(privacy);
1316 compose_update_privacy_system_menu_item(compose, FALSE);
1317 compose_use_encryption(compose, TRUE);
1321 static void compose_force_signing(Compose *compose, PrefsAccount *account, const gchar *system)
1323 const gchar *privacy = NULL;
1324 if (privacy_system_can_sign(compose->privacy_system) == FALSE)
1327 if (account->default_privacy_system && strlen(account->default_privacy_system))
1328 privacy = account->default_privacy_system;
1332 GSList *privacy_avail = privacy_get_system_ids();
1333 if (privacy_avail && g_slist_length(privacy_avail)) {
1334 privacy = (gchar *)(privacy_avail->data);
1338 if (privacy != NULL) {
1340 g_free(compose->privacy_system);
1341 compose->privacy_system = NULL;
1342 g_free(compose->encdata);
1343 compose->encdata = NULL;
1345 if (compose->privacy_system == NULL)
1346 compose->privacy_system = g_strdup(privacy);
1347 compose_update_privacy_system_menu_item(compose, FALSE);
1348 compose_use_signing(compose, TRUE);
1352 static Compose *compose_reply_mode(ComposeMode mode, GSList *msginfo_list, gchar *body)
1356 Compose *compose = NULL;
1358 cm_return_val_if_fail(msginfo_list != NULL, NULL);
1360 msginfo = (MsgInfo*)g_slist_nth_data(msginfo_list, 0);
1361 cm_return_val_if_fail(msginfo != NULL, NULL);
1363 list_len = g_slist_length(msginfo_list);
1367 case COMPOSE_REPLY_TO_ADDRESS:
1368 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1369 FALSE, prefs_common.default_reply_list, FALSE, body);
1371 case COMPOSE_REPLY_WITH_QUOTE:
1372 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1373 FALSE, prefs_common.default_reply_list, FALSE, body);
1375 case COMPOSE_REPLY_WITHOUT_QUOTE:
1376 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1377 FALSE, prefs_common.default_reply_list, FALSE, NULL);
1379 case COMPOSE_REPLY_TO_SENDER:
1380 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1381 FALSE, FALSE, TRUE, body);
1383 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1384 compose = compose_followup_and_reply_to(msginfo,
1385 COMPOSE_QUOTE_CHECK,
1386 FALSE, FALSE, body);
1388 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1389 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1390 FALSE, FALSE, TRUE, body);
1392 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1393 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1394 FALSE, FALSE, TRUE, NULL);
1396 case COMPOSE_REPLY_TO_ALL:
1397 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1398 TRUE, FALSE, FALSE, body);
1400 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1401 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1402 TRUE, FALSE, FALSE, body);
1404 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1405 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1406 TRUE, FALSE, FALSE, NULL);
1408 case COMPOSE_REPLY_TO_LIST:
1409 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1410 FALSE, TRUE, FALSE, body);
1412 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1413 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1414 FALSE, TRUE, FALSE, body);
1416 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1417 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1418 FALSE, TRUE, FALSE, NULL);
1420 case COMPOSE_FORWARD:
1421 if (prefs_common.forward_as_attachment) {
1422 compose = compose_reply_mode(COMPOSE_FORWARD_AS_ATTACH, msginfo_list, body);
1425 compose = compose_reply_mode(COMPOSE_FORWARD_INLINE, msginfo_list, body);
1429 case COMPOSE_FORWARD_INLINE:
1430 /* check if we reply to more than one Message */
1431 if (list_len == 1) {
1432 compose = compose_forward(NULL, msginfo, FALSE, body, FALSE, FALSE);
1435 /* more messages FALL THROUGH */
1436 case COMPOSE_FORWARD_AS_ATTACH:
1437 compose = compose_forward_multiple(NULL, msginfo_list);
1439 case COMPOSE_REDIRECT:
1440 compose = compose_redirect(NULL, msginfo, FALSE);
1443 g_warning("compose_reply_mode(): invalid Compose Mode: %d", mode);
1446 if (compose == NULL) {
1447 alertpanel_error(_("Unable to reply. The original email probably doesn't exist."));
1451 compose->rmode = mode;
1452 switch (compose->rmode) {
1454 case COMPOSE_REPLY_WITH_QUOTE:
1455 case COMPOSE_REPLY_WITHOUT_QUOTE:
1456 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1457 debug_print("reply mode Normal\n");
1458 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/Normal", TRUE);
1459 compose_reply_change_mode(compose, COMPOSE_REPLY); /* force update */
1461 case COMPOSE_REPLY_TO_SENDER:
1462 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1463 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1464 debug_print("reply mode Sender\n");
1465 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/Sender", TRUE);
1467 case COMPOSE_REPLY_TO_ALL:
1468 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1469 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1470 debug_print("reply mode All\n");
1471 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/All", TRUE);
1473 case COMPOSE_REPLY_TO_LIST:
1474 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1475 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1476 debug_print("reply mode List\n");
1477 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/List", TRUE);
1479 case COMPOSE_REPLY_TO_ADDRESS:
1480 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", FALSE);
1488 static Compose *compose_reply(MsgInfo *msginfo,
1489 ComposeQuoteMode quote_mode,
1495 return compose_generic_reply(msginfo, quote_mode, to_all, to_ml,
1496 to_sender, FALSE, body);
1499 static Compose *compose_followup_and_reply_to(MsgInfo *msginfo,
1500 ComposeQuoteMode quote_mode,
1505 return compose_generic_reply(msginfo, quote_mode, to_all, FALSE,
1506 to_sender, TRUE, body);
1509 static void compose_extract_original_charset(Compose *compose)
1511 MsgInfo *info = NULL;
1512 if (compose->replyinfo) {
1513 info = compose->replyinfo;
1514 } else if (compose->fwdinfo) {
1515 info = compose->fwdinfo;
1516 } else if (compose->targetinfo) {
1517 info = compose->targetinfo;
1520 MimeInfo *mimeinfo = procmime_scan_message_short(info);
1521 MimeInfo *partinfo = mimeinfo;
1522 while (partinfo && partinfo->type != MIMETYPE_TEXT)
1523 partinfo = procmime_mimeinfo_next(partinfo);
1525 compose->orig_charset =
1526 g_strdup(procmime_mimeinfo_get_parameter(
1527 partinfo, "charset"));
1529 procmime_mimeinfo_free_all(&mimeinfo);
1533 #define SIGNAL_BLOCK(buffer) { \
1534 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1535 G_CALLBACK(compose_changed_cb), \
1537 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1538 G_CALLBACK(text_inserted), \
1542 #define SIGNAL_UNBLOCK(buffer) { \
1543 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1544 G_CALLBACK(compose_changed_cb), \
1546 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1547 G_CALLBACK(text_inserted), \
1551 static Compose *compose_generic_reply(MsgInfo *msginfo,
1552 ComposeQuoteMode quote_mode,
1553 gboolean to_all, gboolean to_ml,
1555 gboolean followup_and_reply_to,
1559 PrefsAccount *account = NULL;
1560 GtkTextView *textview;
1561 GtkTextBuffer *textbuf;
1562 gboolean quote = FALSE;
1563 const gchar *qmark = NULL;
1564 const gchar *body_fmt = NULL;
1565 gchar *s_system = NULL;
1567 cm_return_val_if_fail(msginfo != NULL, NULL);
1568 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
1570 account = account_get_reply_account(msginfo, prefs_common.reply_account_autosel);
1572 cm_return_val_if_fail(account != NULL, NULL);
1574 compose = compose_create(account, msginfo->folder, COMPOSE_REPLY, FALSE);
1575 compose_apply_folder_privacy_settings(compose, msginfo->folder);
1577 compose->updating = TRUE;
1579 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
1580 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", TRUE);
1582 compose->replyinfo = procmsg_msginfo_get_full_info(msginfo);
1583 if (!compose->replyinfo)
1584 compose->replyinfo = procmsg_msginfo_copy(msginfo);
1586 compose_extract_original_charset(compose);
1588 if (msginfo->folder && msginfo->folder->ret_rcpt)
1589 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
1591 /* Set save folder */
1592 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1593 gchar *folderidentifier;
1595 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo), TRUE);
1596 folderidentifier = folder_item_get_identifier(msginfo->folder);
1597 compose_set_save_to(compose, folderidentifier);
1598 g_free(folderidentifier);
1601 if (compose_parse_header(compose, msginfo) < 0) {
1602 compose->updating = FALSE;
1603 compose_destroy(compose);
1607 /* override from name according to folder properties */
1608 if (msginfo->folder && msginfo->folder->prefs &&
1609 msginfo->folder->prefs->reply_with_format &&
1610 msginfo->folder->prefs->reply_override_from_format &&
1611 *msginfo->folder->prefs->reply_override_from_format != '\0') {
1616 /* decode \-escape sequences in the internal representation of the quote format */
1617 tmp = g_malloc(strlen(msginfo->folder->prefs->reply_override_from_format)+1);
1618 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->reply_override_from_format);
1621 quote_fmt_init(compose->replyinfo, NULL, NULL, FALSE, compose->account, FALSE,
1622 compose->gtkaspell);
1624 quote_fmt_init(compose->replyinfo, NULL, NULL, FALSE, compose->account, FALSE);
1626 quote_fmt_scan_string(tmp);
1629 buf = quote_fmt_get_buffer();
1631 alertpanel_error(_("The \"From\" field of the \"Reply\" template contains an invalid email address."));
1633 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1634 quote_fmt_reset_vartable();
1635 quote_fmtlex_destroy();
1640 textview = (GTK_TEXT_VIEW(compose->text));
1641 textbuf = gtk_text_view_get_buffer(textview);
1642 compose_create_tags(textview, compose);
1644 undo_block(compose->undostruct);
1646 compose_set_dictionaries_from_folder_prefs(compose, msginfo->folder);
1647 gtkaspell_block_check(compose->gtkaspell);
1650 if (quote_mode == COMPOSE_QUOTE_FORCED ||
1651 (quote_mode == COMPOSE_QUOTE_CHECK && prefs_common.reply_with_quote)) {
1652 /* use the reply format of folder (if enabled), or the account's one
1653 (if enabled) or fallback to the global reply format, which is always
1654 enabled (even if empty), and use the relevant quotemark */
1656 if (msginfo->folder && msginfo->folder->prefs &&
1657 msginfo->folder->prefs->reply_with_format) {
1658 qmark = msginfo->folder->prefs->reply_quotemark;
1659 body_fmt = msginfo->folder->prefs->reply_body_format;
1661 } else if (account->reply_with_format) {
1662 qmark = account->reply_quotemark;
1663 body_fmt = account->reply_body_format;
1666 qmark = prefs_common.quotemark;
1667 if (prefs_common.quotefmt && *prefs_common.quotefmt)
1668 body_fmt = gettext(prefs_common.quotefmt);
1675 /* empty quotemark is not allowed */
1676 if (qmark == NULL || *qmark == '\0')
1678 compose_quote_fmt(compose, compose->replyinfo,
1679 body_fmt, qmark, body, FALSE, TRUE,
1680 _("The body of the \"Reply\" template has an error at line %d."));
1681 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1682 quote_fmt_reset_vartable();
1685 if (MSG_IS_ENCRYPTED(compose->replyinfo->flags)) {
1686 compose_force_encryption(compose, account, FALSE, s_system);
1689 privacy_msginfo_get_signed_state(compose->replyinfo, &s_system);
1690 if (MSG_IS_SIGNED(compose->replyinfo->flags) && account->default_sign_reply) {
1691 compose_force_signing(compose, account, s_system);
1695 if (privacy_system_can_sign(compose->privacy_system) == FALSE &&
1696 ((account->default_encrypt_reply && MSG_IS_ENCRYPTED(compose->replyinfo->flags)) ||
1697 (account->default_sign_reply && MSG_IS_SIGNED(compose->replyinfo->flags))))
1698 alertpanel_error(_("You have opted to sign and/or encrypt this "
1699 "message but have not selected a privacy system.\n\n"
1700 "Signing and encrypting have been disabled for this "
1703 SIGNAL_BLOCK(textbuf);
1705 if (account->auto_sig)
1706 compose_insert_sig(compose, FALSE);
1708 compose_wrap_all(compose);
1711 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1712 gtkaspell_highlight_all(compose->gtkaspell);
1713 gtkaspell_unblock_check(compose->gtkaspell);
1715 SIGNAL_UNBLOCK(textbuf);
1717 gtk_widget_grab_focus(compose->text);
1719 undo_unblock(compose->undostruct);
1721 if (prefs_common.auto_exteditor)
1722 compose_exec_ext_editor(compose);
1724 compose->modified = FALSE;
1725 compose_set_title(compose);
1727 compose->updating = FALSE;
1728 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
1729 SCROLL_TO_CURSOR(compose);
1731 if (compose->deferred_destroy) {
1732 compose_destroy(compose);
1740 #define INSERT_FW_HEADER(var, hdr) \
1741 if (msginfo->var && *msginfo->var) { \
1742 gtk_stext_insert(text, NULL, NULL, NULL, hdr, -1); \
1743 gtk_stext_insert(text, NULL, NULL, NULL, msginfo->var, -1); \
1744 gtk_stext_insert(text, NULL, NULL, NULL, "\n", 1); \
1747 Compose *compose_forward(PrefsAccount *account, MsgInfo *msginfo,
1748 gboolean as_attach, const gchar *body,
1749 gboolean no_extedit,
1753 GtkTextView *textview;
1754 GtkTextBuffer *textbuf;
1755 gint cursor_pos = -1;
1758 cm_return_val_if_fail(msginfo != NULL, NULL);
1759 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
1761 if (!account && !(account = compose_find_account(msginfo)))
1762 account = cur_account;
1764 if (!prefs_common.forward_as_attachment)
1765 mode = COMPOSE_FORWARD_INLINE;
1767 mode = COMPOSE_FORWARD;
1768 compose = compose_create(account, msginfo->folder, mode, batch);
1769 compose_apply_folder_privacy_settings(compose, msginfo->folder);
1771 compose->updating = TRUE;
1772 compose->fwdinfo = procmsg_msginfo_get_full_info(msginfo);
1773 if (!compose->fwdinfo)
1774 compose->fwdinfo = procmsg_msginfo_copy(msginfo);
1776 compose_extract_original_charset(compose);
1778 if (msginfo->subject && *msginfo->subject) {
1779 gchar *buf, *buf2, *p;
1781 buf = p = g_strdup(msginfo->subject);
1782 p += subject_get_prefix_length(p);
1783 memmove(buf, p, strlen(p) + 1);
1785 buf2 = g_strdup_printf("Fw: %s", buf);
1786 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
1792 /* override from name according to folder properties */
1793 if (msginfo->folder && msginfo->folder->prefs &&
1794 msginfo->folder->prefs->forward_with_format &&
1795 msginfo->folder->prefs->forward_override_from_format &&
1796 *msginfo->folder->prefs->forward_override_from_format != '\0') {
1800 MsgInfo *full_msginfo = NULL;
1803 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1805 full_msginfo = procmsg_msginfo_copy(msginfo);
1807 /* decode \-escape sequences in the internal representation of the quote format */
1808 tmp = g_malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
1809 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
1812 gtkaspell_block_check(compose->gtkaspell);
1813 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE,
1814 compose->gtkaspell);
1816 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE);
1818 quote_fmt_scan_string(tmp);
1821 buf = quote_fmt_get_buffer();
1823 alertpanel_error(_("The \"From\" field of the \"Forward\" template contains an invalid email address."));
1825 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1826 quote_fmt_reset_vartable();
1827 quote_fmtlex_destroy();
1830 procmsg_msginfo_free(&full_msginfo);
1833 textview = GTK_TEXT_VIEW(compose->text);
1834 textbuf = gtk_text_view_get_buffer(textview);
1835 compose_create_tags(textview, compose);
1837 undo_block(compose->undostruct);
1841 msgfile = procmsg_get_message_file(msginfo);
1842 if (!is_file_exist(msgfile))
1843 g_warning("%s: file does not exist", msgfile);
1845 compose_attach_append(compose, msgfile, msgfile,
1846 "message/rfc822", NULL);
1850 const gchar *qmark = NULL;
1851 const gchar *body_fmt = NULL;
1852 MsgInfo *full_msginfo;
1854 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1856 full_msginfo = procmsg_msginfo_copy(msginfo);
1858 /* use the forward format of folder (if enabled), or the account's one
1859 (if enabled) or fallback to the global forward format, which is always
1860 enabled (even if empty), and use the relevant quotemark */
1861 if (msginfo->folder && msginfo->folder->prefs &&
1862 msginfo->folder->prefs->forward_with_format) {
1863 qmark = msginfo->folder->prefs->forward_quotemark;
1864 body_fmt = msginfo->folder->prefs->forward_body_format;
1866 } else if (account->forward_with_format) {
1867 qmark = account->forward_quotemark;
1868 body_fmt = account->forward_body_format;
1871 qmark = prefs_common.fw_quotemark;
1872 if (prefs_common.fw_quotefmt && *prefs_common.fw_quotefmt)
1873 body_fmt = gettext(prefs_common.fw_quotefmt);
1878 /* empty quotemark is not allowed */
1879 if (qmark == NULL || *qmark == '\0')
1882 compose_quote_fmt(compose, full_msginfo,
1883 body_fmt, qmark, body, FALSE, TRUE,
1884 _("The body of the \"Forward\" template has an error at line %d."));
1885 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1886 quote_fmt_reset_vartable();
1887 compose_attach_parts(compose, msginfo);
1889 procmsg_msginfo_free(&full_msginfo);
1892 SIGNAL_BLOCK(textbuf);
1894 if (account->auto_sig)
1895 compose_insert_sig(compose, FALSE);
1897 compose_wrap_all(compose);
1900 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1901 gtkaspell_highlight_all(compose->gtkaspell);
1902 gtkaspell_unblock_check(compose->gtkaspell);
1904 SIGNAL_UNBLOCK(textbuf);
1906 cursor_pos = quote_fmt_get_cursor_pos();
1907 if (cursor_pos == -1)
1908 gtk_widget_grab_focus(compose->header_last->entry);
1910 gtk_widget_grab_focus(compose->text);
1912 if (!no_extedit && prefs_common.auto_exteditor)
1913 compose_exec_ext_editor(compose);
1916 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1917 gchar *folderidentifier;
1919 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo), TRUE);
1920 folderidentifier = folder_item_get_identifier(msginfo->folder);
1921 compose_set_save_to(compose, folderidentifier);
1922 g_free(folderidentifier);
1925 undo_unblock(compose->undostruct);
1927 compose->modified = FALSE;
1928 compose_set_title(compose);
1930 compose->updating = FALSE;
1931 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
1932 SCROLL_TO_CURSOR(compose);
1934 if (compose->deferred_destroy) {
1935 compose_destroy(compose);
1939 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
1944 #undef INSERT_FW_HEADER
1946 static Compose *compose_forward_multiple(PrefsAccount *account, GSList *msginfo_list)
1949 GtkTextView *textview;
1950 GtkTextBuffer *textbuf;
1954 gboolean single_mail = TRUE;
1956 cm_return_val_if_fail(msginfo_list != NULL, NULL);
1958 if (g_slist_length(msginfo_list) > 1)
1959 single_mail = FALSE;
1961 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next)
1962 if (((MsgInfo *)msginfo->data)->folder == NULL)
1965 /* guess account from first selected message */
1967 !(account = compose_find_account(msginfo_list->data)))
1968 account = cur_account;
1970 cm_return_val_if_fail(account != NULL, NULL);
1972 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
1973 if (msginfo->data) {
1974 MSG_UNSET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_REPLIED);
1975 MSG_SET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_FORWARDED);
1979 if (msginfo_list == NULL || msginfo_list->data == NULL) {
1980 g_warning("no msginfo_list");
1984 compose = compose_create(account, ((MsgInfo *)msginfo_list->data)->folder, COMPOSE_FORWARD, FALSE);
1985 compose_apply_folder_privacy_settings(compose, ((MsgInfo *)msginfo_list->data)->folder);
1987 compose->updating = TRUE;
1989 /* override from name according to folder properties */
1990 if (msginfo_list->data) {
1991 MsgInfo *msginfo = msginfo_list->data;
1993 if (msginfo->folder && msginfo->folder->prefs &&
1994 msginfo->folder->prefs->forward_with_format &&
1995 msginfo->folder->prefs->forward_override_from_format &&
1996 *msginfo->folder->prefs->forward_override_from_format != '\0') {
2001 /* decode \-escape sequences in the internal representation of the quote format */
2002 tmp = g_malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
2003 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
2006 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
2007 compose->gtkaspell);
2009 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
2011 quote_fmt_scan_string(tmp);
2014 buf = quote_fmt_get_buffer();
2016 alertpanel_error(_("The \"From\" field of the \"Forward\" template contains an invalid email address."));
2018 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
2019 quote_fmt_reset_vartable();
2020 quote_fmtlex_destroy();
2026 textview = GTK_TEXT_VIEW(compose->text);
2027 textbuf = gtk_text_view_get_buffer(textview);
2028 compose_create_tags(textview, compose);
2030 undo_block(compose->undostruct);
2031 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
2032 msgfile = procmsg_get_message_file((MsgInfo *)msginfo->data);
2034 if (!is_file_exist(msgfile))
2035 g_warning("%s: file does not exist", msgfile);
2037 compose_attach_append(compose, msgfile, msgfile,
2038 "message/rfc822", NULL);
2043 MsgInfo *info = (MsgInfo *)msginfo_list->data;
2044 if (info->subject && *info->subject) {
2045 gchar *buf, *buf2, *p;
2047 buf = p = g_strdup(info->subject);
2048 p += subject_get_prefix_length(p);
2049 memmove(buf, p, strlen(p) + 1);
2051 buf2 = g_strdup_printf("Fw: %s", buf);
2052 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
2058 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2059 _("Fw: multiple emails"));
2062 SIGNAL_BLOCK(textbuf);
2064 if (account->auto_sig)
2065 compose_insert_sig(compose, FALSE);
2067 compose_wrap_all(compose);
2069 SIGNAL_UNBLOCK(textbuf);
2071 gtk_text_buffer_get_start_iter(textbuf, &iter);
2072 gtk_text_buffer_place_cursor(textbuf, &iter);
2074 if (prefs_common.auto_exteditor)
2075 compose_exec_ext_editor(compose);
2077 gtk_widget_grab_focus(compose->header_last->entry);
2078 undo_unblock(compose->undostruct);
2079 compose->modified = FALSE;
2080 compose_set_title(compose);
2082 compose->updating = FALSE;
2083 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2084 SCROLL_TO_CURSOR(compose);
2086 if (compose->deferred_destroy) {
2087 compose_destroy(compose);
2091 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2096 static gboolean compose_is_sig_separator(Compose *compose, GtkTextBuffer *textbuf, GtkTextIter *iter)
2098 GtkTextIter start = *iter;
2099 GtkTextIter end_iter;
2100 int start_pos = gtk_text_iter_get_offset(&start);
2102 if (!compose->account->sig_sep)
2105 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
2106 start_pos+strlen(compose->account->sig_sep));
2108 /* check sig separator */
2109 str = gtk_text_iter_get_text(&start, &end_iter);
2110 if (!strcmp(str, compose->account->sig_sep)) {
2112 /* check end of line (\n) */
2113 gtk_text_buffer_get_iter_at_offset(textbuf, &start,
2114 start_pos+strlen(compose->account->sig_sep));
2115 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
2116 start_pos+strlen(compose->account->sig_sep)+1);
2117 tmp = gtk_text_iter_get_text(&start, &end_iter);
2118 if (!strcmp(tmp,"\n")) {
2130 static gboolean compose_update_folder_hook(gpointer source, gpointer data)
2132 FolderUpdateData *hookdata = (FolderUpdateData *)source;
2133 Compose *compose = (Compose *)data;
2134 FolderItem *old_item = NULL;
2135 FolderItem *new_item = NULL;
2136 gchar *old_id, *new_id;
2138 if (!(hookdata->update_flags & FOLDER_REMOVE_FOLDERITEM)
2139 && !(hookdata->update_flags & FOLDER_MOVE_FOLDERITEM))
2142 old_item = hookdata->item;
2143 new_item = hookdata->item2;
2145 old_id = folder_item_get_identifier(old_item);
2146 new_id = new_item ? folder_item_get_identifier(new_item) : g_strdup("NULL");
2148 if (compose->targetinfo && compose->targetinfo->folder == old_item) {
2149 debug_print("updating targetinfo folder: %s -> %s\n", old_id, new_id);
2150 compose->targetinfo->folder = new_item;
2153 if (compose->replyinfo && compose->replyinfo->folder == old_item) {
2154 debug_print("updating replyinfo folder: %s -> %s\n", old_id, new_id);
2155 compose->replyinfo->folder = new_item;
2158 if (compose->fwdinfo && compose->fwdinfo->folder == old_item) {
2159 debug_print("updating fwdinfo folder: %s -> %s\n", old_id, new_id);
2160 compose->fwdinfo->folder = new_item;
2168 static void compose_colorize_signature(Compose *compose)
2170 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
2172 GtkTextIter end_iter;
2173 gtk_text_buffer_get_start_iter(buffer, &iter);
2174 while (gtk_text_iter_forward_line(&iter))
2175 if (compose_is_sig_separator(compose, buffer, &iter)) {
2176 gtk_text_buffer_get_end_iter(buffer, &end_iter);
2177 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &end_iter);
2181 #define BLOCK_WRAP() { \
2182 prev_autowrap = compose->autowrap; \
2183 buffer = gtk_text_view_get_buffer( \
2184 GTK_TEXT_VIEW(compose->text)); \
2185 compose->autowrap = FALSE; \
2187 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2188 G_CALLBACK(compose_changed_cb), \
2190 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2191 G_CALLBACK(text_inserted), \
2194 #define UNBLOCK_WRAP() { \
2195 compose->autowrap = prev_autowrap; \
2196 if (compose->autowrap) { \
2197 gint old = compose->draft_timeout_tag; \
2198 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN; \
2199 compose_wrap_all(compose); \
2200 compose->draft_timeout_tag = old; \
2203 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2204 G_CALLBACK(compose_changed_cb), \
2206 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2207 G_CALLBACK(text_inserted), \
2211 Compose *compose_reedit(MsgInfo *msginfo, gboolean batch)
2213 Compose *compose = NULL;
2214 PrefsAccount *account = NULL;
2215 GtkTextView *textview;
2216 GtkTextBuffer *textbuf;
2220 gboolean use_signing = FALSE;
2221 gboolean use_encryption = FALSE;
2222 gchar *privacy_system = NULL;
2223 int priority = PRIORITY_NORMAL;
2224 MsgInfo *replyinfo = NULL, *fwdinfo = NULL;
2225 gboolean autowrap = prefs_common.autowrap;
2226 gboolean autoindent = prefs_common.auto_indent;
2227 HeaderEntry *manual_headers = NULL;
2229 cm_return_val_if_fail(msginfo != NULL, NULL);
2230 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
2232 if (compose_put_existing_to_front(msginfo)) {
2236 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2237 folder_has_parent_of_type(msginfo->folder, F_DRAFT) ||
2238 folder_has_parent_of_type(msginfo->folder, F_OUTBOX)) {
2239 gchar *queueheader_buf = NULL;
2242 /* Select Account from queue headers */
2243 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2244 "X-Claws-Account-Id:")) {
2245 id = atoi(&queueheader_buf[strlen("X-Claws-Account-Id:")]);
2246 account = account_find_from_id(id);
2247 g_free(queueheader_buf);
2249 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2250 "X-Sylpheed-Account-Id:")) {
2251 id = atoi(&queueheader_buf[strlen("X-Sylpheed-Account-Id:")]);
2252 account = account_find_from_id(id);
2253 g_free(queueheader_buf);
2255 if (!account && !procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2257 id = atoi(&queueheader_buf[strlen("NAID:")]);
2258 account = account_find_from_id(id);
2259 g_free(queueheader_buf);
2261 if (!account && !procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2263 id = atoi(&queueheader_buf[strlen("MAID:")]);
2264 account = account_find_from_id(id);
2265 g_free(queueheader_buf);
2267 if (!account && !procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2269 account = account_find_from_address(queueheader_buf, FALSE);
2270 g_free(queueheader_buf);
2272 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2274 param = atoi(&queueheader_buf[strlen("X-Claws-Sign:")]);
2275 use_signing = param;
2276 g_free(queueheader_buf);
2278 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2279 "X-Sylpheed-Sign:")) {
2280 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Sign:")]);
2281 use_signing = param;
2282 g_free(queueheader_buf);
2284 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2285 "X-Claws-Encrypt:")) {
2286 param = atoi(&queueheader_buf[strlen("X-Claws-Encrypt:")]);
2287 use_encryption = param;
2288 g_free(queueheader_buf);
2290 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2291 "X-Sylpheed-Encrypt:")) {
2292 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Encrypt:")]);
2293 use_encryption = param;
2294 g_free(queueheader_buf);
2296 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2297 "X-Claws-Auto-Wrapping:")) {
2298 param = atoi(&queueheader_buf[strlen("X-Claws-Auto-Wrapping:")]);
2300 g_free(queueheader_buf);
2302 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2303 "X-Claws-Auto-Indent:")) {
2304 param = atoi(&queueheader_buf[strlen("X-Claws-Auto-Indent:")]);
2306 g_free(queueheader_buf);
2308 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2309 "X-Claws-Privacy-System:")) {
2310 privacy_system = g_strdup(&queueheader_buf[strlen("X-Claws-Privacy-System:")]);
2311 g_free(queueheader_buf);
2313 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2314 "X-Sylpheed-Privacy-System:")) {
2315 privacy_system = g_strdup(&queueheader_buf[strlen("X-Sylpheed-Privacy-System:")]);
2316 g_free(queueheader_buf);
2318 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2320 param = atoi(&queueheader_buf[strlen("X-Priority: ")]); /* mind the space */
2322 g_free(queueheader_buf);
2324 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2326 gchar **tokens = g_strsplit(&queueheader_buf[strlen("RMID:")], "\t", 0);
2327 if (tokens && tokens[0] && tokens[1] && tokens[2]) {
2328 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2329 if (orig_item != NULL) {
2330 replyinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2334 g_free(queueheader_buf);
2336 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2338 gchar **tokens = g_strsplit(&queueheader_buf[strlen("FMID:")], "\t", 0);
2339 if (tokens && tokens[0] && tokens[1] && tokens[2]) {
2340 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2341 if (orig_item != NULL) {
2342 fwdinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2346 g_free(queueheader_buf);
2348 /* Get manual headers */
2349 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2350 "X-Claws-Manual-Headers:")) {
2351 gchar *listmh = g_strdup(&queueheader_buf[strlen("X-Claws-Manual-Headers:")]);
2352 if (listmh && *listmh != '\0') {
2353 debug_print("Got manual headers: %s\n", listmh);
2354 manual_headers = procheader_entries_from_str(listmh);
2357 g_free(queueheader_buf);
2360 account = msginfo->folder->folder->account;
2363 if (!account && prefs_common.reedit_account_autosel) {
2365 if (!procheader_get_header_from_msginfo(msginfo, &from, "FROM:")) {
2366 extract_address(from);
2367 account = account_find_from_address(from, FALSE);
2372 account = cur_account;
2374 cm_return_val_if_fail(account != NULL, NULL);
2376 compose = compose_create(account, msginfo->folder, COMPOSE_REEDIT, batch);
2378 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", autowrap);
2379 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", autoindent);
2380 compose->autowrap = autowrap;
2381 compose->replyinfo = replyinfo;
2382 compose->fwdinfo = fwdinfo;
2384 compose->updating = TRUE;
2385 compose->priority = priority;
2387 if (privacy_system != NULL) {
2388 compose->privacy_system = privacy_system;
2389 compose_use_signing(compose, use_signing);
2390 compose_use_encryption(compose, use_encryption);
2391 compose_update_privacy_system_menu_item(compose, FALSE);
2393 compose_activate_privacy_system(compose, account, FALSE);
2395 compose_apply_folder_privacy_settings(compose, msginfo->folder);
2397 compose->targetinfo = procmsg_msginfo_copy(msginfo);
2398 compose->targetinfo->tags = g_slist_copy(msginfo->tags);
2400 compose_extract_original_charset(compose);
2402 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2403 folder_has_parent_of_type(msginfo->folder, F_DRAFT) ||
2404 folder_has_parent_of_type(msginfo->folder, F_OUTBOX)) {
2405 gchar *queueheader_buf = NULL;
2407 /* Set message save folder */
2408 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf, "SCF:")) {
2409 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo), TRUE);
2410 compose_set_save_to(compose, &queueheader_buf[4]);
2411 g_free(queueheader_buf);
2413 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf, "RRCPT:")) {
2414 gint active = atoi(&queueheader_buf[strlen("RRCPT:")]);
2416 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
2418 g_free(queueheader_buf);
2422 if (compose_parse_header(compose, msginfo) < 0) {
2423 compose->updating = FALSE;
2424 compose_destroy(compose);
2427 compose_reedit_set_entry(compose, msginfo);
2429 textview = GTK_TEXT_VIEW(compose->text);
2430 textbuf = gtk_text_view_get_buffer(textview);
2431 compose_create_tags(textview, compose);
2433 mark = gtk_text_buffer_get_insert(textbuf);
2434 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
2436 g_signal_handlers_block_by_func(G_OBJECT(textbuf),
2437 G_CALLBACK(compose_changed_cb),
2440 if (MSG_IS_ENCRYPTED(msginfo->flags)) {
2441 fp = procmime_get_first_encrypted_text_content(msginfo);
2443 compose_force_encryption(compose, account, TRUE, NULL);
2446 fp = procmime_get_first_text_content(msginfo);
2449 g_warning("Can't get text part");
2453 gchar buf[BUFFSIZE];
2454 gboolean prev_autowrap;
2455 GtkTextBuffer *buffer;
2457 while (claws_fgets(buf, sizeof(buf), fp) != NULL) {
2459 gtk_text_buffer_insert(textbuf, &iter, buf, -1);
2465 compose_attach_parts(compose, msginfo);
2467 compose_colorize_signature(compose);
2469 g_signal_handlers_unblock_by_func(G_OBJECT(textbuf),
2470 G_CALLBACK(compose_changed_cb),
2473 if (manual_headers != NULL) {
2474 if (compose_parse_manual_headers(compose, msginfo, manual_headers) < 0) {
2475 procheader_entries_free(manual_headers);
2476 compose->updating = FALSE;
2477 compose_destroy(compose);
2480 procheader_entries_free(manual_headers);
2483 gtk_widget_grab_focus(compose->text);
2485 if (prefs_common.auto_exteditor) {
2486 compose_exec_ext_editor(compose);
2488 compose->modified = FALSE;
2489 compose_set_title(compose);
2491 compose->updating = FALSE;
2492 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2493 SCROLL_TO_CURSOR(compose);
2495 if (compose->deferred_destroy) {
2496 compose_destroy(compose);
2500 compose->sig_str = account_get_signature_str(compose->account);
2502 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2507 Compose *compose_redirect(PrefsAccount *account, MsgInfo *msginfo,
2514 cm_return_val_if_fail(msginfo != NULL, NULL);
2517 account = account_get_reply_account(msginfo,
2518 prefs_common.reply_account_autosel);
2519 cm_return_val_if_fail(account != NULL, NULL);
2521 compose = compose_create(account, msginfo->folder, COMPOSE_REDIRECT, batch);
2522 compose_apply_folder_privacy_settings(compose, msginfo->folder);
2524 compose->updating = TRUE;
2526 compose_create_tags(GTK_TEXT_VIEW(compose->text), compose);
2527 compose->replyinfo = NULL;
2528 compose->fwdinfo = NULL;
2530 compose_show_first_last_header(compose, TRUE);
2532 gtk_widget_grab_focus(compose->header_last->entry);
2534 filename = procmsg_get_message_file(msginfo);
2536 if (filename == NULL) {
2537 compose->updating = FALSE;
2538 compose_destroy(compose);
2543 compose->redirect_filename = filename;
2545 /* Set save folder */
2546 item = msginfo->folder;
2547 if (item && item->prefs && item->prefs->save_copy_to_folder) {
2548 gchar *folderidentifier;
2550 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo), TRUE);
2551 folderidentifier = folder_item_get_identifier(item);
2552 compose_set_save_to(compose, folderidentifier);
2553 g_free(folderidentifier);
2556 compose_attach_parts(compose, msginfo);
2558 if (msginfo->subject)
2559 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2561 gtk_editable_set_editable(GTK_EDITABLE(compose->subject_entry), FALSE);
2563 compose_quote_fmt(compose, msginfo, "%M", NULL, NULL, FALSE, FALSE,
2564 _("The body of the \"Redirect\" template has an error at line %d."));
2565 quote_fmt_reset_vartable();
2566 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), FALSE);
2568 compose_colorize_signature(compose);
2571 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Add", FALSE);
2572 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", FALSE);
2573 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", FALSE);
2575 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/Save", FALSE);
2576 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertFile", FALSE);
2577 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/AttachFile", FALSE);
2578 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertSig", FALSE);
2579 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/ReplaceSig", FALSE);
2580 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", FALSE);
2581 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", FALSE);
2582 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/ShowRuler", FALSE);
2583 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Actions", FALSE);
2585 if (compose->toolbar->draft_btn)
2586 gtk_widget_set_sensitive(compose->toolbar->draft_btn, FALSE);
2587 if (compose->toolbar->insert_btn)
2588 gtk_widget_set_sensitive(compose->toolbar->insert_btn, FALSE);
2589 if (compose->toolbar->attach_btn)
2590 gtk_widget_set_sensitive(compose->toolbar->attach_btn, FALSE);
2591 if (compose->toolbar->sig_btn)
2592 gtk_widget_set_sensitive(compose->toolbar->sig_btn, FALSE);
2593 if (compose->toolbar->exteditor_btn)
2594 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, FALSE);
2595 if (compose->toolbar->linewrap_current_btn)
2596 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, FALSE);
2597 if (compose->toolbar->linewrap_all_btn)
2598 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, FALSE);
2600 compose->modified = FALSE;
2601 compose_set_title(compose);
2602 compose->updating = FALSE;
2603 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2604 SCROLL_TO_CURSOR(compose);
2606 if (compose->deferred_destroy) {
2607 compose_destroy(compose);
2611 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2616 const GList *compose_get_compose_list(void)
2618 return compose_list;
2621 void compose_entry_append(Compose *compose, const gchar *address,
2622 ComposeEntryType type, ComposePrefType pref_type)
2624 const gchar *header;
2626 gboolean in_quote = FALSE;
2627 if (!address || *address == '\0') return;
2634 header = N_("Bcc:");
2636 case COMPOSE_REPLYTO:
2637 header = N_("Reply-To:");
2639 case COMPOSE_NEWSGROUPS:
2640 header = N_("Newsgroups:");
2642 case COMPOSE_FOLLOWUPTO:
2643 header = N_( "Followup-To:");
2645 case COMPOSE_INREPLYTO:
2646 header = N_( "In-Reply-To:");
2653 header = prefs_common_translated_header_name(header);
2655 cur = begin = (gchar *)address;
2657 /* we separate the line by commas, but not if we're inside a quoted
2659 while (*cur != '\0') {
2661 in_quote = !in_quote;
2662 if (*cur == ',' && !in_quote) {
2663 gchar *tmp = g_strdup(begin);
2665 tmp[cur-begin]='\0';
2668 while (*tmp == ' ' || *tmp == '\t')
2670 compose_add_header_entry(compose, header, tmp, pref_type);
2671 compose_entry_indicate(compose, tmp);
2678 gchar *tmp = g_strdup(begin);
2680 tmp[cur-begin]='\0';
2681 while (*tmp == ' ' || *tmp == '\t')
2683 compose_add_header_entry(compose, header, tmp, pref_type);
2684 compose_entry_indicate(compose, tmp);
2689 static void compose_entry_indicate(Compose *compose, const gchar *mailto)
2694 for (h_list = compose->header_list; h_list != NULL; h_list = h_list->next) {
2695 entry = GTK_ENTRY(((ComposeHeaderEntry *)h_list->data)->entry);
2696 if (gtk_entry_get_text(entry) &&
2697 !g_utf8_collate(gtk_entry_get_text(entry), mailto)) {
2698 gtk_widget_modify_base(
2699 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2700 GTK_STATE_NORMAL, &default_header_bgcolor);
2701 gtk_widget_modify_text(
2702 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2703 GTK_STATE_NORMAL, &default_header_color);
2708 void compose_toolbar_cb(gint action, gpointer data)
2710 ToolbarItem *toolbar_item = (ToolbarItem*)data;
2711 Compose *compose = (Compose*)toolbar_item->parent;
2713 cm_return_if_fail(compose != NULL);
2717 compose_send_cb(NULL, compose);
2720 compose_send_later_cb(NULL, compose);
2723 compose_draft(compose, COMPOSE_QUIT_EDITING);
2726 compose_insert_file_cb(NULL, compose);
2729 compose_attach_cb(NULL, compose);
2732 compose_insert_sig(compose, FALSE);
2735 compose_insert_sig(compose, TRUE);
2738 compose_ext_editor_cb(NULL, compose);
2740 case A_LINEWRAP_CURRENT:
2741 compose_beautify_paragraph(compose, NULL, TRUE);
2743 case A_LINEWRAP_ALL:
2744 compose_wrap_all_full(compose, TRUE);
2747 compose_address_cb(NULL, compose);
2750 case A_CHECK_SPELLING:
2751 compose_check_all(NULL, compose);
2754 case A_PRIVACY_SIGN:
2756 case A_PRIVACY_ENCRYPT:
2763 static MailField compose_entries_set(Compose *compose, const gchar *mailto, ComposeEntryType to_type)
2768 gchar *subject = NULL;
2772 gchar **attach = NULL;
2773 gchar *inreplyto = NULL;
2774 MailField mfield = NO_FIELD_PRESENT;
2776 /* get mailto parts but skip from */
2777 scan_mailto_url(mailto, NULL, &to, &cc, &bcc, &subject, &body, &attach, &inreplyto);
2780 compose_entry_append(compose, to, to_type, PREF_MAILTO);
2781 mfield = TO_FIELD_PRESENT;
2784 compose_entry_append(compose, cc, COMPOSE_CC, PREF_MAILTO);
2786 compose_entry_append(compose, bcc, COMPOSE_BCC, PREF_MAILTO);
2788 if (!g_utf8_validate (subject, -1, NULL)) {
2789 temp = g_locale_to_utf8 (subject, -1, NULL, &len, NULL);
2790 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), temp);
2793 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), subject);
2795 mfield = SUBJECT_FIELD_PRESENT;
2798 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2799 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2802 gboolean prev_autowrap = compose->autowrap;
2804 compose->autowrap = FALSE;
2806 mark = gtk_text_buffer_get_insert(buffer);
2807 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2809 if (!g_utf8_validate (body, -1, NULL)) {
2810 temp = g_locale_to_utf8 (body, -1, NULL, &len, NULL);
2811 gtk_text_buffer_insert(buffer, &iter, temp, -1);
2814 gtk_text_buffer_insert(buffer, &iter, body, -1);
2816 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
2818 compose->autowrap = prev_autowrap;
2819 if (compose->autowrap)
2820 compose_wrap_all(compose);
2821 mfield = BODY_FIELD_PRESENT;
2825 gint i = 0, att = 0;
2826 gchar *warn_files = NULL;
2827 while (attach[i] != NULL) {
2828 gchar *utf8_filename = conv_filename_to_utf8(attach[i]);
2829 if (utf8_filename) {
2830 if (compose_attach_append(compose, attach[i], utf8_filename, NULL, NULL)) {
2831 gchar *tmp = g_strdup_printf("%s%s\n",
2832 warn_files?warn_files:"",
2838 g_free(utf8_filename);
2840 alertpanel_error(_("Couldn't attach a file (charset conversion failed)."));
2845 alertpanel_notice(ngettext(
2846 "The following file has been attached: \n%s",
2847 "The following files have been attached: \n%s", att), warn_files);
2852 compose_entry_append(compose, inreplyto, COMPOSE_INREPLYTO, PREF_MAILTO);
2865 static gint compose_parse_header(Compose *compose, MsgInfo *msginfo)
2867 static HeaderEntry hentry[] = {
2868 {"Reply-To:", NULL, TRUE },
2869 {"Cc:", NULL, TRUE },
2870 {"References:", NULL, FALSE },
2871 {"Bcc:", NULL, TRUE },
2872 {"Newsgroups:", NULL, TRUE },
2873 {"Followup-To:", NULL, TRUE },
2874 {"List-Post:", NULL, FALSE },
2875 {"X-Priority:", NULL, FALSE },
2876 {NULL, NULL, FALSE }
2893 cm_return_val_if_fail(msginfo != NULL, -1);
2895 if ((fp = procmsg_open_message(msginfo)) == NULL) return -1;
2896 procheader_get_header_fields(fp, hentry);
2899 if (hentry[H_REPLY_TO].body != NULL) {
2900 if (hentry[H_REPLY_TO].body[0] != '\0') {
2902 conv_unmime_header(hentry[H_REPLY_TO].body,
2905 g_free(hentry[H_REPLY_TO].body);
2906 hentry[H_REPLY_TO].body = NULL;
2908 if (hentry[H_CC].body != NULL) {
2909 compose->cc = conv_unmime_header(hentry[H_CC].body, NULL, TRUE);
2910 g_free(hentry[H_CC].body);
2911 hentry[H_CC].body = NULL;
2913 if (hentry[H_REFERENCES].body != NULL) {
2914 if (compose->mode == COMPOSE_REEDIT)
2915 compose->references = hentry[H_REFERENCES].body;
2917 compose->references = compose_parse_references
2918 (hentry[H_REFERENCES].body, msginfo->msgid);
2919 g_free(hentry[H_REFERENCES].body);
2921 hentry[H_REFERENCES].body = NULL;
2923 if (hentry[H_BCC].body != NULL) {
2924 if (compose->mode == COMPOSE_REEDIT)
2926 conv_unmime_header(hentry[H_BCC].body, NULL, TRUE);
2927 g_free(hentry[H_BCC].body);
2928 hentry[H_BCC].body = NULL;
2930 if (hentry[H_NEWSGROUPS].body != NULL) {
2931 compose->newsgroups = hentry[H_NEWSGROUPS].body;
2932 hentry[H_NEWSGROUPS].body = NULL;
2934 if (hentry[H_FOLLOWUP_TO].body != NULL) {
2935 if (hentry[H_FOLLOWUP_TO].body[0] != '\0') {
2936 compose->followup_to =
2937 conv_unmime_header(hentry[H_FOLLOWUP_TO].body,
2940 g_free(hentry[H_FOLLOWUP_TO].body);
2941 hentry[H_FOLLOWUP_TO].body = NULL;
2943 if (hentry[H_LIST_POST].body != NULL) {
2944 gchar *to = NULL, *start = NULL;
2946 extract_address(hentry[H_LIST_POST].body);
2947 if (hentry[H_LIST_POST].body[0] != '\0') {
2948 start = strstr(hentry[H_LIST_POST].body, "mailto:");
2950 scan_mailto_url(start ? start : hentry[H_LIST_POST].body,
2951 NULL, &to, NULL, NULL, NULL, NULL, NULL, NULL);
2954 g_free(compose->ml_post);
2955 compose->ml_post = to;
2958 g_free(hentry[H_LIST_POST].body);
2959 hentry[H_LIST_POST].body = NULL;
2962 /* CLAWS - X-Priority */
2963 if (compose->mode == COMPOSE_REEDIT)
2964 if (hentry[H_X_PRIORITY].body != NULL) {
2967 priority = atoi(hentry[H_X_PRIORITY].body);
2968 g_free(hentry[H_X_PRIORITY].body);
2970 hentry[H_X_PRIORITY].body = NULL;
2972 if (priority < PRIORITY_HIGHEST ||
2973 priority > PRIORITY_LOWEST)
2974 priority = PRIORITY_NORMAL;
2976 compose->priority = priority;
2979 if (compose->mode == COMPOSE_REEDIT) {
2980 if (msginfo->inreplyto && *msginfo->inreplyto)
2981 compose->inreplyto = g_strdup(msginfo->inreplyto);
2983 if (msginfo->msgid && *msginfo->msgid &&
2984 compose->folder != NULL &&
2985 compose->folder->stype == F_DRAFT)
2986 compose->msgid = g_strdup(msginfo->msgid);
2988 if (msginfo->msgid && *msginfo->msgid)
2989 compose->inreplyto = g_strdup(msginfo->msgid);
2991 if (!compose->references) {
2992 if (msginfo->msgid && *msginfo->msgid) {
2993 if (msginfo->inreplyto && *msginfo->inreplyto)
2994 compose->references =
2995 g_strdup_printf("<%s>\n\t<%s>",
2999 compose->references =
3000 g_strconcat("<", msginfo->msgid, ">",
3002 } else if (msginfo->inreplyto && *msginfo->inreplyto) {
3003 compose->references =
3004 g_strconcat("<", msginfo->inreplyto, ">",
3013 static gint compose_parse_manual_headers(Compose *compose, MsgInfo *msginfo, HeaderEntry *entries)
3018 cm_return_val_if_fail(msginfo != NULL, -1);
3020 if ((fp = procmsg_open_message(msginfo)) == NULL) return -1;
3021 procheader_get_header_fields(fp, entries);
3025 while (he != NULL && he->name != NULL) {
3027 GtkListStore *model = NULL;
3029 debug_print("Adding manual header: %s with value %s\n", he->name, he->body);
3030 model = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(compose->header_last->combo)));
3031 COMBOBOX_ADD(model, he->name, COMPOSE_TO);
3032 gtk_combo_box_set_active_iter(GTK_COMBO_BOX(compose->header_last->combo), &iter);
3033 gtk_entry_set_text(GTK_ENTRY(compose->header_last->entry), he->body);
3040 static gchar *compose_parse_references(const gchar *ref, const gchar *msgid)
3042 GSList *ref_id_list, *cur;
3046 ref_id_list = references_list_append(NULL, ref);
3047 if (!ref_id_list) return NULL;
3048 if (msgid && *msgid)
3049 ref_id_list = g_slist_append(ref_id_list, g_strdup(msgid));
3054 for (cur = ref_id_list; cur != NULL; cur = cur->next)
3055 /* "<" + Message-ID + ">" + CR+LF+TAB */
3056 len += strlen((gchar *)cur->data) + 5;
3058 if (len > MAX_REFERENCES_LEN) {
3059 /* remove second message-ID */
3060 if (ref_id_list && ref_id_list->next &&
3061 ref_id_list->next->next) {
3062 g_free(ref_id_list->next->data);
3063 ref_id_list = g_slist_remove
3064 (ref_id_list, ref_id_list->next->data);
3066 slist_free_strings_full(ref_id_list);
3073 new_ref = g_string_new("");
3074 for (cur = ref_id_list; cur != NULL; cur = cur->next) {
3075 if (new_ref->len > 0)
3076 g_string_append(new_ref, "\n\t");
3077 g_string_append_printf(new_ref, "<%s>", (gchar *)cur->data);
3080 slist_free_strings_full(ref_id_list);
3082 new_ref_str = new_ref->str;
3083 g_string_free(new_ref, FALSE);
3088 static gchar *compose_quote_fmt(Compose *compose, MsgInfo *msginfo,
3089 const gchar *fmt, const gchar *qmark,
3090 const gchar *body, gboolean rewrap,
3091 gboolean need_unescape,
3092 const gchar *err_msg)
3094 MsgInfo* dummyinfo = NULL;
3095 gchar *quote_str = NULL;
3097 gboolean prev_autowrap;
3098 const gchar *trimmed_body = body;
3099 gint cursor_pos = -1;
3100 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3101 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3106 SIGNAL_BLOCK(buffer);
3109 dummyinfo = compose_msginfo_new_from_compose(compose);
3110 msginfo = dummyinfo;
3113 if (qmark != NULL) {
3115 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
3116 compose->gtkaspell);
3118 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
3120 quote_fmt_scan_string(qmark);
3123 buf = quote_fmt_get_buffer();
3126 alertpanel_error(_("The \"Quotation mark\" of the template is invalid."));
3128 Xstrdup_a(quote_str, buf, goto error)
3131 if (fmt && *fmt != '\0') {
3134 while (*trimmed_body == '\n')
3138 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE,
3139 compose->gtkaspell);
3141 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE);
3143 if (need_unescape) {
3146 /* decode \-escape sequences in the internal representation of the quote format */
3147 tmp = g_malloc(strlen(fmt)+1);
3148 pref_get_unescaped_pref(tmp, fmt);
3149 quote_fmt_scan_string(tmp);
3153 quote_fmt_scan_string(fmt);
3157 buf = quote_fmt_get_buffer();
3160 gint line = quote_fmt_get_line();
3161 alertpanel_error(err_msg, line);
3169 prev_autowrap = compose->autowrap;
3170 compose->autowrap = FALSE;
3172 mark = gtk_text_buffer_get_insert(buffer);
3173 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3174 if (g_utf8_validate(buf, -1, NULL)) {
3175 gtk_text_buffer_insert(buffer, &iter, buf, -1);
3177 gchar *tmpout = NULL;
3178 tmpout = conv_codeset_strdup
3179 (buf, conv_get_locale_charset_str_no_utf8(),
3181 if (!tmpout || !g_utf8_validate(tmpout, -1, NULL)) {
3183 tmpout = g_malloc(strlen(buf)*2+1);
3184 conv_localetodisp(tmpout, strlen(buf)*2+1, buf);
3186 gtk_text_buffer_insert(buffer, &iter, tmpout, -1);
3190 cursor_pos = quote_fmt_get_cursor_pos();
3191 if (cursor_pos == -1)
3192 cursor_pos = gtk_text_iter_get_offset(&iter);
3193 compose->set_cursor_pos = cursor_pos;
3195 gtk_text_buffer_get_start_iter(buffer, &iter);
3196 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
3197 gtk_text_buffer_place_cursor(buffer, &iter);
3199 compose->autowrap = prev_autowrap;
3200 if (compose->autowrap && rewrap)
3201 compose_wrap_all(compose);
3208 SIGNAL_UNBLOCK(buffer);
3210 procmsg_msginfo_free( &dummyinfo );
3215 /* if ml_post is of type addr@host and from is of type
3216 * addr-anything@host, return TRUE
3218 static gboolean is_subscription(const gchar *ml_post, const gchar *from)
3220 gchar *left_ml = NULL;
3221 gchar *right_ml = NULL;
3222 gchar *left_from = NULL;
3223 gchar *right_from = NULL;
3224 gboolean result = FALSE;
3226 if (!ml_post || !from)
3229 left_ml = g_strdup(ml_post);
3230 if (strstr(left_ml, "@")) {
3231 right_ml = strstr(left_ml, "@")+1;
3232 *(strstr(left_ml, "@")) = '\0';
3235 left_from = g_strdup(from);
3236 if (strstr(left_from, "@")) {
3237 right_from = strstr(left_from, "@")+1;
3238 *(strstr(left_from, "@")) = '\0';
3241 if (right_ml && right_from
3242 && !strncmp(left_from, left_ml, strlen(left_ml))
3243 && !strcmp(right_from, right_ml)) {
3252 static void compose_set_folder_prefs(Compose *compose, FolderItem *folder,
3253 gboolean respect_default_to)
3257 if (!folder || !folder->prefs)
3260 if (respect_default_to && folder->prefs->enable_default_to) {
3261 compose_entry_append(compose, folder->prefs->default_to,
3262 COMPOSE_TO, PREF_FOLDER);
3263 compose_entry_indicate(compose, folder->prefs->default_to);
3265 if (folder->prefs->enable_default_cc) {
3266 compose_entry_append(compose, folder->prefs->default_cc,
3267 COMPOSE_CC, PREF_FOLDER);
3268 compose_entry_indicate(compose, folder->prefs->default_cc);
3270 if (folder->prefs->enable_default_bcc) {
3271 compose_entry_append(compose, folder->prefs->default_bcc,
3272 COMPOSE_BCC, PREF_FOLDER);
3273 compose_entry_indicate(compose, folder->prefs->default_bcc);
3275 if (folder->prefs->enable_default_replyto) {
3276 compose_entry_append(compose, folder->prefs->default_replyto,
3277 COMPOSE_REPLYTO, PREF_FOLDER);
3278 compose_entry_indicate(compose, folder->prefs->default_replyto);
3282 static void compose_reply_set_subject(Compose *compose, MsgInfo *msginfo)
3287 if (!compose || !msginfo)
3290 if (msginfo->subject && *msginfo->subject) {
3291 buf = p = g_strdup(msginfo->subject);
3292 p += subject_get_prefix_length(p);
3293 memmove(buf, p, strlen(p) + 1);
3295 buf2 = g_strdup_printf("Re: %s", buf);
3296 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
3301 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), "Re: ");
3304 static void compose_reply_set_entry(Compose *compose, MsgInfo *msginfo,
3305 gboolean to_all, gboolean to_ml,
3307 gboolean followup_and_reply_to)
3309 GSList *cc_list = NULL;
3312 gchar *replyto = NULL;
3313 gchar *ac_email = NULL;
3315 gboolean reply_to_ml = FALSE;
3316 gboolean default_reply_to = FALSE;
3318 cm_return_if_fail(compose->account != NULL);
3319 cm_return_if_fail(msginfo != NULL);
3321 reply_to_ml = to_ml && compose->ml_post;
3323 default_reply_to = msginfo->folder &&
3324 msginfo->folder->prefs->enable_default_reply_to;
3326 if (compose->account->protocol != A_NNTP) {
3327 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
3329 if (reply_to_ml && !default_reply_to) {
3331 gboolean is_subscr = is_subscription(compose->ml_post,
3334 /* normal answer to ml post with a reply-to */
3335 compose_entry_append(compose,
3337 COMPOSE_TO, PREF_ML);
3338 if (compose->replyto)
3339 compose_entry_append(compose,
3341 COMPOSE_CC, PREF_ML);
3343 /* answer to subscription confirmation */
3344 if (compose->replyto)
3345 compose_entry_append(compose,
3347 COMPOSE_TO, PREF_ML);
3348 else if (msginfo->from)
3349 compose_entry_append(compose,
3351 COMPOSE_TO, PREF_ML);
3354 else if (!(to_all || to_sender) && default_reply_to) {
3355 compose_entry_append(compose,
3356 msginfo->folder->prefs->default_reply_to,
3357 COMPOSE_TO, PREF_FOLDER);
3358 compose_entry_indicate(compose,
3359 msginfo->folder->prefs->default_reply_to);
3365 compose_entry_append(compose, msginfo->from,
3366 COMPOSE_TO, PREF_NONE);
3368 Xstrdup_a(tmp1, msginfo->from, return);
3369 extract_address(tmp1);
3370 compose_entry_append(compose,
3371 (!account_find_from_address(tmp1, FALSE))
3374 COMPOSE_TO, PREF_NONE);
3375 if (compose->replyto)
3376 compose_entry_append(compose,
3378 COMPOSE_CC, PREF_NONE);
3380 if (!folder_has_parent_of_type(msginfo->folder, F_QUEUE) &&
3381 !folder_has_parent_of_type(msginfo->folder, F_OUTBOX) &&
3382 !folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
3383 if (compose->replyto) {
3384 compose_entry_append(compose,
3386 COMPOSE_TO, PREF_NONE);
3388 compose_entry_append(compose,
3389 msginfo->from ? msginfo->from : "",
3390 COMPOSE_TO, PREF_NONE);
3393 /* replying to own mail, use original recp */
3394 compose_entry_append(compose,
3395 msginfo->to ? msginfo->to : "",
3396 COMPOSE_TO, PREF_NONE);
3397 compose_entry_append(compose,
3398 msginfo->cc ? msginfo->cc : "",
3399 COMPOSE_CC, PREF_NONE);
3404 if (to_sender || (compose->followup_to &&
3405 !strncmp(compose->followup_to, "poster", 6)))
3406 compose_entry_append
3408 (compose->replyto ? compose->replyto :
3409 msginfo->from ? msginfo->from : ""),
3410 COMPOSE_TO, PREF_NONE);
3412 else if (followup_and_reply_to || to_all) {
3413 compose_entry_append
3415 (compose->replyto ? compose->replyto :
3416 msginfo->from ? msginfo->from : ""),
3417 COMPOSE_TO, PREF_NONE);
3419 compose_entry_append
3421 compose->followup_to ? compose->followup_to :
3422 compose->newsgroups ? compose->newsgroups : "",
3423 COMPOSE_NEWSGROUPS, PREF_NONE);
3425 compose_entry_append
3427 msginfo->cc ? msginfo->cc : "",
3428 COMPOSE_CC, PREF_NONE);
3431 compose_entry_append
3433 compose->followup_to ? compose->followup_to :
3434 compose->newsgroups ? compose->newsgroups : "",
3435 COMPOSE_NEWSGROUPS, PREF_NONE);
3437 compose_reply_set_subject(compose, msginfo);
3439 if (to_ml && compose->ml_post) return;
3440 if (!to_all || compose->account->protocol == A_NNTP) return;
3442 if (compose->replyto) {
3443 Xstrdup_a(replyto, compose->replyto, return);
3444 extract_address(replyto);
3446 if (msginfo->from) {
3447 Xstrdup_a(from, msginfo->from, return);
3448 extract_address(from);
3451 if (replyto && from)
3452 cc_list = address_list_append_with_comments(cc_list, from);
3453 if (to_all && msginfo->folder &&
3454 msginfo->folder->prefs->enable_default_reply_to)
3455 cc_list = address_list_append_with_comments(cc_list,
3456 msginfo->folder->prefs->default_reply_to);
3457 cc_list = address_list_append_with_comments(cc_list, msginfo->to);
3458 cc_list = address_list_append_with_comments(cc_list, compose->cc);
3460 ac_email = g_utf8_strdown(compose->account->address, -1);
3463 for (cur = cc_list; cur != NULL; cur = cur->next) {
3464 gchar *addr = g_utf8_strdown(cur->data, -1);
3465 extract_address(addr);
3467 if (strcmp(ac_email, addr))
3468 compose_entry_append(compose, (gchar *)cur->data,
3469 COMPOSE_CC, PREF_NONE);
3471 debug_print("Cc address same as compose account's, ignoring\n");
3476 slist_free_strings_full(cc_list);
3482 #define SET_ENTRY(entry, str) \
3485 gtk_entry_set_text(GTK_ENTRY(compose->entry), str); \
3488 #define SET_ADDRESS(type, str) \
3491 compose_entry_append(compose, str, type, PREF_NONE); \
3494 static void compose_reedit_set_entry(Compose *compose, MsgInfo *msginfo)
3496 cm_return_if_fail(msginfo != NULL);
3498 SET_ENTRY(subject_entry, msginfo->subject);
3499 SET_ENTRY(from_name, msginfo->from);
3500 SET_ADDRESS(COMPOSE_TO, msginfo->to);
3501 SET_ADDRESS(COMPOSE_CC, compose->cc);
3502 SET_ADDRESS(COMPOSE_BCC, compose->bcc);
3503 SET_ADDRESS(COMPOSE_REPLYTO, compose->replyto);
3504 SET_ADDRESS(COMPOSE_NEWSGROUPS, compose->newsgroups);
3505 SET_ADDRESS(COMPOSE_FOLLOWUPTO, compose->followup_to);
3507 compose_update_priority_menu_item(compose);
3508 compose_update_privacy_system_menu_item(compose, FALSE);
3509 compose_show_first_last_header(compose, TRUE);
3515 static void compose_insert_sig(Compose *compose, gboolean replace)
3517 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3518 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3520 GtkTextIter iter, iter_end;
3521 gint cur_pos, ins_pos;
3522 gboolean prev_autowrap;
3523 gboolean found = FALSE;
3524 gboolean exists = FALSE;
3526 cm_return_if_fail(compose->account != NULL);
3530 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3531 G_CALLBACK(compose_changed_cb),
3534 mark = gtk_text_buffer_get_insert(buffer);
3535 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3536 cur_pos = gtk_text_iter_get_offset (&iter);
3539 gtk_text_buffer_get_end_iter(buffer, &iter);
3541 exists = (compose->sig_str != NULL);
3544 GtkTextIter first_iter, start_iter, end_iter;
3546 gtk_text_buffer_get_start_iter(buffer, &first_iter);
3548 if (!exists || compose->sig_str[0] == '\0')
3551 found = gtk_text_iter_forward_to_tag_toggle(&first_iter,
3552 compose->signature_tag);
3555 /* include previous \n\n */
3556 gtk_text_iter_backward_chars(&first_iter, 1);
3557 start_iter = first_iter;
3558 end_iter = first_iter;
3560 found = gtk_text_iter_forward_to_tag_toggle(&end_iter,
3561 compose->signature_tag);
3562 found &= gtk_text_iter_forward_to_tag_toggle(&end_iter,
3563 compose->signature_tag);
3565 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
3571 g_free(compose->sig_str);
3572 compose->sig_str = account_get_signature_str(compose->account);
3574 cur_pos = gtk_text_iter_get_offset(&iter);
3576 if (!compose->sig_str || (replace && !compose->account->auto_sig)) {
3577 g_free(compose->sig_str);
3578 compose->sig_str = NULL;
3580 if (compose->sig_inserted == FALSE)
3581 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
3582 compose->sig_inserted = TRUE;
3584 cur_pos = gtk_text_iter_get_offset(&iter);
3585 gtk_text_buffer_insert(buffer, &iter, compose->sig_str, -1);
3587 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cur_pos);
3588 gtk_text_iter_forward_chars(&iter, 1);
3589 gtk_text_buffer_get_end_iter(buffer, &iter_end);
3590 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &iter_end);
3592 if (cur_pos > gtk_text_buffer_get_char_count (buffer))
3593 cur_pos = gtk_text_buffer_get_char_count (buffer);
3596 /* put the cursor where it should be
3597 * either where the quote_fmt says, either where it was */
3598 if (compose->set_cursor_pos < 0)
3599 gtk_text_buffer_get_iter_at_offset(buffer, &iter, ins_pos);
3601 gtk_text_buffer_get_iter_at_offset(buffer, &iter,
3602 compose->set_cursor_pos);
3604 compose->set_cursor_pos = -1;
3605 gtk_text_buffer_place_cursor(buffer, &iter);
3606 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3607 G_CALLBACK(compose_changed_cb),
3613 static ComposeInsertResult compose_insert_file(Compose *compose, const gchar *file)
3616 GtkTextBuffer *buffer;
3619 const gchar *cur_encoding;
3620 gchar buf[BUFFSIZE];
3623 gboolean prev_autowrap;
3627 GError *error = NULL;
3633 GString *file_contents = NULL;
3634 ComposeInsertResult result = COMPOSE_INSERT_SUCCESS;
3636 cm_return_val_if_fail(file != NULL, COMPOSE_INSERT_NO_FILE);
3638 /* get the size of the file we are about to insert */
3640 f = g_file_new_for_path(file);
3641 fi = g_file_query_info(f, "standard::size",
3642 G_FILE_QUERY_INFO_NONE, NULL, &error);
3644 if (error != NULL) {
3645 g_warning(error->message);
3647 g_error_free(error);
3651 ret = g_stat(file, &file_stat);
3654 gchar *shortfile = g_path_get_basename(file);
3655 alertpanel_error(_("Could not get size of file '%s'."), shortfile);
3657 return COMPOSE_INSERT_NO_FILE;
3658 } else if (prefs_common.warn_large_insert == TRUE) {
3660 size = g_file_info_get_size(fi);
3664 size = file_stat.st_size;
3667 /* ask user for confirmation if the file is large */
3668 if (prefs_common.warn_large_insert_size < 0 ||
3669 size > ((goffset) prefs_common.warn_large_insert_size * 1024)) {
3673 msg = g_strdup_printf(_("You are about to insert a file of %s "
3674 "in the message body. Are you sure you want to do that?"),
3675 to_human_readable(size));
3676 aval = alertpanel_full(_("Are you sure?"), msg, GTK_STOCK_CANCEL,
3677 _("_Insert"), NULL, ALERTFOCUS_SECOND, TRUE,
3678 NULL, ALERT_QUESTION);
3681 /* do we ask for confirmation next time? */
3682 if (aval & G_ALERTDISABLE) {
3683 /* no confirmation next time, disable feature in preferences */
3684 aval &= ~G_ALERTDISABLE;
3685 prefs_common.warn_large_insert = FALSE;
3688 /* abort file insertion if user canceled action */
3689 if (aval != G_ALERTALTERNATE) {
3690 return COMPOSE_INSERT_NO_FILE;
3696 if ((fp = claws_fopen(file, "rb")) == NULL) {
3697 FILE_OP_ERROR(file, "claws_fopen");
3698 return COMPOSE_INSERT_READ_ERROR;
3701 prev_autowrap = compose->autowrap;
3702 compose->autowrap = FALSE;
3704 text = GTK_TEXT_VIEW(compose->text);
3705 buffer = gtk_text_view_get_buffer(text);
3706 mark = gtk_text_buffer_get_insert(buffer);
3707 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3709 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3710 G_CALLBACK(text_inserted),
3713 cur_encoding = conv_get_locale_charset_str_no_utf8();
3715 file_contents = g_string_new("");
3716 while (claws_fgets(buf, sizeof(buf), fp) != NULL) {
3719 if (g_utf8_validate(buf, -1, NULL) == TRUE)
3720 str = g_strdup(buf);
3722 codeconv_set_strict(TRUE);
3723 str = conv_codeset_strdup
3724 (buf, cur_encoding, CS_INTERNAL);
3725 codeconv_set_strict(FALSE);
3728 result = COMPOSE_INSERT_INVALID_CHARACTER;
3734 /* strip <CR> if DOS/Windows file,
3735 replace <CR> with <LF> if Macintosh file. */
3738 if (len > 0 && str[len - 1] != '\n') {
3740 if (str[len] == '\r') str[len] = '\n';
3743 file_contents = g_string_append(file_contents, str);
3747 if (result == COMPOSE_INSERT_SUCCESS) {
3748 gtk_text_buffer_insert(buffer, &iter, file_contents->str, -1);
3750 compose_changed_cb(NULL, compose);
3751 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3752 G_CALLBACK(text_inserted),
3754 compose->autowrap = prev_autowrap;
3755 if (compose->autowrap)
3756 compose_wrap_all(compose);
3759 g_string_free(file_contents, TRUE);
3765 static gboolean compose_attach_append(Compose *compose, const gchar *file,
3766 const gchar *filename,
3767 const gchar *content_type,
3768 const gchar *charset)
3776 GtkListStore *store;
3778 gboolean has_binary = FALSE;
3780 if (!is_file_exist(file)) {
3781 gchar *file_from_uri = g_filename_from_uri(file, NULL, NULL);
3782 gboolean result = FALSE;
3783 if (file_from_uri && is_file_exist(file_from_uri)) {
3784 result = compose_attach_append(
3785 compose, file_from_uri,
3786 filename, content_type,
3789 g_free(file_from_uri);
3792 alertpanel_error("File %s doesn't exist or permission denied\n", filename);
3795 if ((size = get_file_size(file)) < 0) {
3796 alertpanel_error("Can't get file size of %s\n", filename);
3800 /* In batch mode, we allow 0-length files to be attached no questions asked */
3801 if (size == 0 && !compose->batch) {
3802 gchar * msg = g_strdup_printf(_("File %s is empty."), filename);
3803 AlertValue aval = alertpanel_full(_("Empty file"), msg,
3804 GTK_STOCK_CANCEL, _("_Attach anyway"), NULL,
3805 ALERTFOCUS_SECOND, FALSE, NULL, ALERT_WARNING);
3808 if (aval != G_ALERTALTERNATE) {
3812 if ((fp = claws_fopen(file, "rb")) == NULL) {
3813 alertpanel_error(_("Can't read %s."), filename);
3818 ainfo = g_new0(AttachInfo, 1);
3819 auto_ainfo = g_auto_pointer_new_with_free
3820 (ainfo, (GFreeFunc) compose_attach_info_free);
3821 ainfo->file = g_strdup(file);
3824 ainfo->content_type = g_strdup(content_type);
3825 if (!g_ascii_strcasecmp(content_type, "message/rfc822")) {
3827 MsgFlags flags = {0, 0};
3829 if (procmime_get_encoding_for_text_file(file, &has_binary) == ENC_7BIT)
3830 ainfo->encoding = ENC_7BIT;
3832 ainfo->encoding = ENC_8BIT;
3834 msginfo = procheader_parse_file(file, flags, FALSE, FALSE);
3835 if (msginfo && msginfo->subject)
3836 name = g_strdup(msginfo->subject);
3838 name = g_path_get_basename(filename ? filename : file);
3840 ainfo->name = g_strdup_printf(_("Message: %s"), name);
3842 procmsg_msginfo_free(&msginfo);
3844 if (!g_ascii_strncasecmp(content_type, "text/", 5)) {
3845 ainfo->charset = g_strdup(charset);
3846 ainfo->encoding = procmime_get_encoding_for_text_file(file, &has_binary);
3848 ainfo->encoding = ENC_BASE64;
3850 name = g_path_get_basename(filename ? filename : file);
3851 ainfo->name = g_strdup(name);
3855 ainfo->content_type = procmime_get_mime_type(file);
3856 if (!ainfo->content_type) {
3857 ainfo->content_type =
3858 g_strdup("application/octet-stream");
3859 ainfo->encoding = ENC_BASE64;
3860 } else if (!g_ascii_strncasecmp(ainfo->content_type, "text/", 5))
3862 procmime_get_encoding_for_text_file(file, &has_binary);
3864 ainfo->encoding = ENC_BASE64;
3865 name = g_path_get_basename(filename ? filename : file);
3866 ainfo->name = g_strdup(name);
3870 if (ainfo->name != NULL
3871 && !strcmp(ainfo->name, ".")) {
3872 g_free(ainfo->name);
3876 if (!strcmp(ainfo->content_type, "unknown") || has_binary) {
3877 g_free(ainfo->content_type);
3878 ainfo->content_type = g_strdup("application/octet-stream");
3879 g_free(ainfo->charset);
3880 ainfo->charset = NULL;
3883 ainfo->size = (goffset)size;
3884 size_text = to_human_readable((goffset)size);
3886 store = GTK_LIST_STORE(gtk_tree_view_get_model
3887 (GTK_TREE_VIEW(compose->attach_clist)));
3889 gtk_list_store_append(store, &iter);
3890 gtk_list_store_set(store, &iter,
3891 COL_MIMETYPE, ainfo->content_type,
3892 COL_SIZE, size_text,
3893 COL_NAME, ainfo->name,
3894 COL_CHARSET, ainfo->charset,
3896 COL_AUTODATA, auto_ainfo,
3899 g_auto_pointer_free(auto_ainfo);
3900 compose_attach_update_label(compose);
3904 void compose_use_signing(Compose *compose, gboolean use_signing)
3906 compose->use_signing = use_signing;
3907 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", use_signing);
3910 void compose_use_encryption(Compose *compose, gboolean use_encryption)
3912 compose->use_encryption = use_encryption;
3913 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", use_encryption);
3916 #define NEXT_PART_NOT_CHILD(info) \
3918 node = info->node; \
3919 while (node->children) \
3920 node = g_node_last_child(node); \
3921 info = procmime_mimeinfo_next((MimeInfo *)node->data); \
3924 static void compose_attach_parts(Compose *compose, MsgInfo *msginfo)
3928 MimeInfo *firsttext = NULL;
3929 MimeInfo *encrypted = NULL;
3932 const gchar *partname = NULL;
3934 mimeinfo = procmime_scan_message(msginfo);
3935 if (!mimeinfo) return;
3937 if (mimeinfo->node->children == NULL) {
3938 procmime_mimeinfo_free_all(&mimeinfo);
3942 /* find first content part */
3943 child = (MimeInfo *) mimeinfo->node->children->data;
3944 while (child && child->node->children && (child->type == MIMETYPE_MULTIPART))
3945 child = (MimeInfo *)child->node->children->data;
3948 if (child->type == MIMETYPE_TEXT) {
3950 debug_print("First text part found\n");
3951 } else if (compose->mode == COMPOSE_REEDIT &&
3952 child->type == MIMETYPE_APPLICATION &&
3953 !g_ascii_strcasecmp(child->subtype, "pgp-encrypted")) {
3954 encrypted = (MimeInfo *)child->node->parent->data;
3957 child = (MimeInfo *) mimeinfo->node->children->data;
3958 while (child != NULL) {
3961 if (child == encrypted) {
3962 /* skip this part of tree */
3963 NEXT_PART_NOT_CHILD(child);
3967 if (child->type == MIMETYPE_MULTIPART) {
3968 /* get the actual content */
3969 child = procmime_mimeinfo_next(child);
3973 if (child == firsttext) {
3974 child = procmime_mimeinfo_next(child);
3978 outfile = procmime_get_tmp_file_name(child);
3979 if ((err = procmime_get_part(outfile, child)) < 0)
3980 g_warning("Can't get the part of multipart message. (%s)", g_strerror(-err));
3982 gchar *content_type;
3984 content_type = procmime_get_content_type_str(child->type, child->subtype);
3986 /* if we meet a pgp signature, we don't attach it, but
3987 * we force signing. */
3988 if ((strcmp(content_type, "application/pgp-signature") &&
3989 strcmp(content_type, "application/pkcs7-signature") &&
3990 strcmp(content_type, "application/x-pkcs7-signature"))
3991 || compose->mode == COMPOSE_REDIRECT) {
3992 partname = procmime_mimeinfo_get_parameter(child, "filename");
3993 if (partname == NULL)
3994 partname = procmime_mimeinfo_get_parameter(child, "name");
3995 if (partname == NULL)
3997 compose_attach_append(compose, outfile,
3998 partname, content_type,
3999 procmime_mimeinfo_get_parameter(child, "charset"));
4001 compose_force_signing(compose, compose->account, NULL);
4003 g_free(content_type);
4006 NEXT_PART_NOT_CHILD(child);
4008 procmime_mimeinfo_free_all(&mimeinfo);
4011 #undef NEXT_PART_NOT_CHILD
4016 WAIT_FOR_INDENT_CHAR,
4017 WAIT_FOR_INDENT_CHAR_OR_SPACE,
4020 /* return indent length, we allow:
4021 indent characters followed by indent characters or spaces/tabs,
4022 alphabets and numbers immediately followed by indent characters,
4023 and the repeating sequences of the above
4024 If quote ends with multiple spaces, only the first one is included. */
4025 static gchar *compose_get_quote_str(GtkTextBuffer *buffer,
4026 const GtkTextIter *start, gint *len)
4028 GtkTextIter iter = *start;
4032 IndentState state = WAIT_FOR_INDENT_CHAR;
4035 gint alnum_count = 0;
4036 gint space_count = 0;
4039 if (prefs_common.quote_chars == NULL) {
4043 while (!gtk_text_iter_ends_line(&iter)) {
4044 wc = gtk_text_iter_get_char(&iter);
4045 if (g_unichar_iswide(wc))
4047 clen = g_unichar_to_utf8(wc, ch);
4051 is_indent = strchr(prefs_common.quote_chars, ch[0]) ? TRUE : FALSE;
4052 is_space = g_unichar_isspace(wc);
4054 if (state == WAIT_FOR_INDENT_CHAR) {
4055 if (!is_indent && !g_unichar_isalnum(wc))
4058 quote_len += alnum_count + space_count + 1;
4059 alnum_count = space_count = 0;
4060 state = WAIT_FOR_INDENT_CHAR_OR_SPACE;
4063 } else if (state == WAIT_FOR_INDENT_CHAR_OR_SPACE) {
4064 if (!is_indent && !is_space && !g_unichar_isalnum(wc))
4068 else if (is_indent) {
4069 quote_len += alnum_count + space_count + 1;
4070 alnum_count = space_count = 0;
4073 state = WAIT_FOR_INDENT_CHAR;
4077 gtk_text_iter_forward_char(&iter);
4080 if (quote_len > 0 && space_count > 0)
4086 if (quote_len > 0) {
4088 gtk_text_iter_forward_chars(&iter, quote_len);
4089 return gtk_text_buffer_get_text(buffer, start, &iter, FALSE);
4095 /* return >0 if the line is itemized */
4096 static int compose_itemized_length(GtkTextBuffer *buffer,
4097 const GtkTextIter *start)
4099 GtkTextIter iter = *start;
4104 if (gtk_text_iter_ends_line(&iter))
4109 wc = gtk_text_iter_get_char(&iter);
4110 if (!g_unichar_isspace(wc))
4112 gtk_text_iter_forward_char(&iter);
4113 if (gtk_text_iter_ends_line(&iter))
4117 clen = g_unichar_to_utf8(wc, ch);
4118 if (!((clen == 1 && strchr("*-+", ch[0])) ||
4120 wc == 0x2022 || /* BULLET */
4121 wc == 0x2023 || /* TRIANGULAR BULLET */
4122 wc == 0x2043 || /* HYPHEN BULLET */
4123 wc == 0x204c || /* BLACK LEFTWARDS BULLET */
4124 wc == 0x204d || /* BLACK RIGHTWARDS BULLET */
4125 wc == 0x2219 || /* BULLET OPERATOR */
4126 wc == 0x25d8 || /* INVERSE BULLET */
4127 wc == 0x25e6 || /* WHITE BULLET */
4128 wc == 0x2619 || /* REVERSED ROTATED FLORAL HEART BULLET */
4129 wc == 0x2765 || /* ROTATED HEAVY BLACK HEART BULLET */
4130 wc == 0x2767 || /* ROTATED FLORAL HEART BULLET */
4131 wc == 0x29be || /* CIRCLED WHITE BULLET */
4132 wc == 0x29bf /* CIRCLED BULLET */
4136 gtk_text_iter_forward_char(&iter);
4137 if (gtk_text_iter_ends_line(&iter))
4139 wc = gtk_text_iter_get_char(&iter);
4140 if (g_unichar_isspace(wc)) {
4146 /* return the string at the start of the itemization */
4147 static gchar * compose_get_itemized_chars(GtkTextBuffer *buffer,
4148 const GtkTextIter *start)
4150 GtkTextIter iter = *start;
4153 GString *item_chars = g_string_new("");
4156 if (gtk_text_iter_ends_line(&iter))
4161 wc = gtk_text_iter_get_char(&iter);
4162 if (!g_unichar_isspace(wc))
4164 gtk_text_iter_forward_char(&iter);
4165 if (gtk_text_iter_ends_line(&iter))
4167 g_string_append_unichar(item_chars, wc);
4170 str = item_chars->str;
4171 g_string_free(item_chars, FALSE);
4175 /* return the number of spaces at a line's start */
4176 static int compose_left_offset_length(GtkTextBuffer *buffer,
4177 const GtkTextIter *start)
4179 GtkTextIter iter = *start;
4182 if (gtk_text_iter_ends_line(&iter))
4186 wc = gtk_text_iter_get_char(&iter);
4187 if (!g_unichar_isspace(wc))
4190 gtk_text_iter_forward_char(&iter);
4191 if (gtk_text_iter_ends_line(&iter))
4195 gtk_text_iter_forward_char(&iter);
4196 if (gtk_text_iter_ends_line(&iter))
4201 static gboolean compose_get_line_break_pos(GtkTextBuffer *buffer,
4202 const GtkTextIter *start,
4203 GtkTextIter *break_pos,
4207 GtkTextIter iter = *start, line_end = *start;
4208 PangoLogAttr *attrs;
4215 gboolean can_break = FALSE;
4216 gboolean do_break = FALSE;
4217 gboolean was_white = FALSE;
4218 gboolean prev_dont_break = FALSE;
4220 gtk_text_iter_forward_to_line_end(&line_end);
4221 str = gtk_text_buffer_get_text(buffer, &iter, &line_end, FALSE);
4222 len = g_utf8_strlen(str, -1);
4226 g_warning("compose_get_line_break_pos: len = 0!");
4230 /* g_print("breaking line: %d: %s (len = %d)\n",
4231 gtk_text_iter_get_line(&iter), str, len); */
4233 attrs = g_new(PangoLogAttr, len + 1);
4235 pango_default_break(str, -1, NULL, attrs, len + 1);
4239 /* skip quote and leading spaces */
4240 for (i = 0; *p != '\0' && i < len; i++) {
4243 wc = g_utf8_get_char(p);
4244 if (i >= quote_len && !g_unichar_isspace(wc))
4246 if (g_unichar_iswide(wc))
4248 else if (*p == '\t')
4252 p = g_utf8_next_char(p);
4255 for (; *p != '\0' && i < len; i++) {
4256 PangoLogAttr *attr = attrs + i;
4260 if (attr->is_line_break && can_break && was_white && !prev_dont_break)
4263 was_white = attr->is_white;
4265 /* don't wrap URI */
4266 if ((uri_len = get_uri_len(p)) > 0) {
4268 if (pos > 0 && col > max_col) {
4278 wc = g_utf8_get_char(p);
4279 if (g_unichar_iswide(wc)) {
4281 if (prev_dont_break && can_break && attr->is_line_break)
4283 } else if (*p == '\t')
4287 if (pos > 0 && col > max_col) {
4292 if (*p == '-' || *p == '/')
4293 prev_dont_break = TRUE;
4295 prev_dont_break = FALSE;
4297 p = g_utf8_next_char(p);
4301 /* debug_print("compose_get_line_break_pos(): do_break = %d, pos = %d, col = %d\n", do_break, pos, col); */
4306 *break_pos = *start;
4307 gtk_text_iter_set_line_offset(break_pos, pos);
4312 static gboolean compose_join_next_line(Compose *compose,
4313 GtkTextBuffer *buffer,
4315 const gchar *quote_str)
4317 GtkTextIter iter_ = *iter, cur, prev, next, end;
4318 PangoLogAttr attrs[3];
4320 gchar *next_quote_str;
4323 gboolean keep_cursor = FALSE;
4325 if (!gtk_text_iter_forward_line(&iter_) ||
4326 gtk_text_iter_ends_line(&iter_)) {
4329 next_quote_str = compose_get_quote_str(buffer, &iter_, "e_len);
4331 if ((quote_str || next_quote_str) &&
4332 strcmp2(quote_str, next_quote_str) != 0) {
4333 g_free(next_quote_str);
4336 g_free(next_quote_str);
4339 if (quote_len > 0) {
4340 gtk_text_iter_forward_chars(&end, quote_len);
4341 if (gtk_text_iter_ends_line(&end)) {
4346 /* don't join itemized lines */
4347 if (compose_itemized_length(buffer, &end) > 0) {
4351 /* don't join signature separator */
4352 if (compose_is_sig_separator(compose, buffer, &iter_)) {
4355 /* delete quote str */
4357 gtk_text_buffer_delete(buffer, &iter_, &end);
4359 /* don't join line breaks put by the user */
4361 gtk_text_iter_backward_char(&cur);
4362 if (gtk_text_iter_has_tag(&cur, compose->no_join_tag)) {
4363 gtk_text_iter_forward_char(&cur);
4367 gtk_text_iter_forward_char(&cur);
4368 /* delete linebreak and extra spaces */
4369 while (gtk_text_iter_backward_char(&cur)) {
4370 wc1 = gtk_text_iter_get_char(&cur);
4371 if (!g_unichar_isspace(wc1))
4376 while (!gtk_text_iter_ends_line(&cur)) {
4377 wc1 = gtk_text_iter_get_char(&cur);
4378 if (!g_unichar_isspace(wc1))
4380 gtk_text_iter_forward_char(&cur);
4383 if (!gtk_text_iter_equal(&prev, &next)) {
4386 mark = gtk_text_buffer_get_insert(buffer);
4387 gtk_text_buffer_get_iter_at_mark(buffer, &cur, mark);
4388 if (gtk_text_iter_equal(&prev, &cur))
4390 gtk_text_buffer_delete(buffer, &prev, &next);
4394 /* insert space if required */
4395 gtk_text_iter_backward_char(&prev);
4396 wc1 = gtk_text_iter_get_char(&prev);
4397 wc2 = gtk_text_iter_get_char(&next);
4398 gtk_text_iter_forward_char(&next);
4399 str = gtk_text_buffer_get_text(buffer, &prev, &next, FALSE);
4400 pango_default_break(str, -1, NULL, attrs, 3);
4401 if (!attrs[1].is_line_break ||
4402 (!g_unichar_iswide(wc1) || !g_unichar_iswide(wc2))) {
4403 gtk_text_buffer_insert(buffer, &iter_, " ", 1);
4405 gtk_text_iter_backward_char(&iter_);
4406 gtk_text_buffer_place_cursor(buffer, &iter_);
4415 #define ADD_TXT_POS(bp_, ep_, pti_) \
4416 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
4417 last = last->next; \
4418 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
4419 last->next = NULL; \
4421 g_warning("alloc error scanning URIs"); \
4424 static gboolean compose_beautify_paragraph(Compose *compose, GtkTextIter *par_iter, gboolean force)
4426 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4427 GtkTextBuffer *buffer;
4428 GtkTextIter iter, break_pos, end_of_line;
4429 gchar *quote_str = NULL;
4431 gboolean wrap_quote = force || prefs_common.linewrap_quote;
4432 gboolean prev_autowrap = compose->autowrap;
4433 gint startq_offset = -1, noq_offset = -1;
4434 gint uri_start = -1, uri_stop = -1;
4435 gint nouri_start = -1, nouri_stop = -1;
4436 gint num_blocks = 0;
4437 gint quotelevel = -1;
4438 gboolean modified = force;
4439 gboolean removed = FALSE;
4440 gboolean modified_before_remove = FALSE;
4442 gboolean start = TRUE;
4443 gint itemized_len = 0, rem_item_len = 0;
4444 gchar *itemized_chars = NULL;
4445 gboolean item_continuation = FALSE;
4450 if (compose->draft_timeout_tag == COMPOSE_DRAFT_TIMEOUT_FORBIDDEN) {
4454 compose->autowrap = FALSE;
4456 buffer = gtk_text_view_get_buffer(text);
4457 undo_wrapping(compose->undostruct, TRUE);
4462 mark = gtk_text_buffer_get_insert(buffer);
4463 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
4467 if (compose->draft_timeout_tag == COMPOSE_DRAFT_TIMEOUT_FORBIDDEN) {
4468 if (gtk_text_iter_ends_line(&iter)) {
4469 while (gtk_text_iter_ends_line(&iter) &&
4470 gtk_text_iter_forward_line(&iter))
4473 while (gtk_text_iter_backward_line(&iter)) {
4474 if (gtk_text_iter_ends_line(&iter)) {
4475 gtk_text_iter_forward_line(&iter);
4481 /* move to line start */
4482 gtk_text_iter_set_line_offset(&iter, 0);
4485 itemized_len = compose_itemized_length(buffer, &iter);
4487 if (!itemized_len) {
4488 itemized_len = compose_left_offset_length(buffer, &iter);
4489 item_continuation = TRUE;
4493 itemized_chars = compose_get_itemized_chars(buffer, &iter);
4495 /* go until paragraph end (empty line) */
4496 while (start || !gtk_text_iter_ends_line(&iter)) {
4497 gchar *scanpos = NULL;
4498 /* parse table - in order of priority */
4500 const gchar *needle; /* token */
4502 /* token search function */
4503 gchar *(*search) (const gchar *haystack,
4504 const gchar *needle);
4505 /* part parsing function */
4506 gboolean (*parse) (const gchar *start,
4507 const gchar *scanpos,
4511 /* part to URI function */
4512 gchar *(*build_uri) (const gchar *bp,
4516 static struct table parser[] = {
4517 {"http://", strcasestr, get_uri_part, make_uri_string},
4518 {"https://", strcasestr, get_uri_part, make_uri_string},
4519 {"ftp://", strcasestr, get_uri_part, make_uri_string},
4520 {"sftp://", strcasestr, get_uri_part, make_uri_string},
4521 {"gopher://",strcasestr, get_uri_part, make_uri_string},
4522 {"www.", strcasestr, get_uri_part, make_http_string},
4523 {"mailto:", strcasestr, get_uri_part, make_uri_string},
4524 {"@", strcasestr, get_email_part, make_email_string}
4526 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
4527 gint last_index = PARSE_ELEMS;
4529 gchar *o_walk = NULL, *walk = NULL, *bp = NULL, *ep = NULL;
4533 if (!prev_autowrap && num_blocks == 0) {
4535 g_signal_handlers_block_by_func(G_OBJECT(buffer),
4536 G_CALLBACK(text_inserted),
4539 if (gtk_text_iter_has_tag(&iter, compose->no_wrap_tag) && !force)
4542 uri_start = uri_stop = -1;
4544 quote_str = compose_get_quote_str(buffer, &iter, "e_len);
4547 /* debug_print("compose_beautify_paragraph(): quote_str = '%s'\n", quote_str); */
4548 if (startq_offset == -1)
4549 startq_offset = gtk_text_iter_get_offset(&iter);
4550 quotelevel = get_quote_level(quote_str, prefs_common.quote_chars);
4551 if (quotelevel > 2) {
4552 /* recycle colors */
4553 if (prefs_common.recycle_quote_colors)
4562 if (startq_offset == -1)
4563 noq_offset = gtk_text_iter_get_offset(&iter);
4567 if (prev_autowrap == FALSE && !force && !wrap_quote) {
4570 if (gtk_text_iter_ends_line(&iter)) {
4572 } else if (compose_get_line_break_pos(buffer, &iter, &break_pos,
4573 prefs_common.linewrap_len,
4575 GtkTextIter prev, next, cur;
4576 if (prev_autowrap != FALSE || force) {
4577 compose->automatic_break = TRUE;
4579 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4580 compose->automatic_break = FALSE;
4581 if (itemized_len && compose->autoindent) {
4582 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4583 if (!item_continuation)
4584 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4586 } else if (quote_str && wrap_quote) {
4587 compose->automatic_break = TRUE;
4589 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4590 compose->automatic_break = FALSE;
4591 if (itemized_len && compose->autoindent) {
4592 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4593 if (!item_continuation)
4594 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4598 /* remove trailing spaces */
4600 rem_item_len = itemized_len;
4601 while (compose->autoindent && rem_item_len-- > 0)
4602 gtk_text_iter_backward_char(&cur);
4603 gtk_text_iter_backward_char(&cur);
4606 while (!gtk_text_iter_starts_line(&cur)) {
4609 gtk_text_iter_backward_char(&cur);
4610 wc = gtk_text_iter_get_char(&cur);
4611 if (!g_unichar_isspace(wc))
4615 if (!gtk_text_iter_equal(&prev, &next)) {
4616 gtk_text_buffer_delete(buffer, &prev, &next);
4618 gtk_text_iter_forward_char(&break_pos);
4622 gtk_text_buffer_insert(buffer, &break_pos,
4626 modified |= compose_join_next_line(compose, buffer, &iter, quote_str);
4628 /* move iter to current line start */
4629 gtk_text_iter_set_line_offset(&iter, 0);
4636 /* move iter to next line start */
4642 if (!prev_autowrap && num_blocks > 0) {
4644 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
4645 G_CALLBACK(text_inserted),
4649 while (!gtk_text_iter_ends_line(&end_of_line)) {
4650 gtk_text_iter_forward_char(&end_of_line);
4652 o_walk = walk = gtk_text_buffer_get_text(buffer, &iter, &end_of_line, FALSE);
4654 nouri_start = gtk_text_iter_get_offset(&iter);
4655 nouri_stop = gtk_text_iter_get_offset(&end_of_line);
4657 walk_pos = gtk_text_iter_get_offset(&iter);
4658 /* FIXME: this looks phony. scanning for anything in the parse table */
4659 for (n = 0; n < PARSE_ELEMS; n++) {
4662 tmp = parser[n].search(walk, parser[n].needle);
4664 if (scanpos == NULL || tmp < scanpos) {
4673 /* check if URI can be parsed */
4674 if (parser[last_index].parse(walk, scanpos, (const gchar **)&bp,
4675 (const gchar **)&ep, FALSE)
4676 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
4680 strlen(parser[last_index].needle);
4683 uri_start = walk_pos + (bp - o_walk);
4684 uri_stop = walk_pos + (ep - o_walk);
4688 gtk_text_iter_forward_line(&iter);
4691 if (startq_offset != -1) {
4692 GtkTextIter startquote, endquote;
4693 gtk_text_buffer_get_iter_at_offset(
4694 buffer, &startquote, startq_offset);
4697 switch (quotelevel) {
4699 if (!gtk_text_iter_has_tag(&startquote, compose->quote0_tag) ||
4700 !gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) {
4701 gtk_text_buffer_apply_tag_by_name(
4702 buffer, "quote0", &startquote, &endquote);
4703 gtk_text_buffer_remove_tag_by_name(
4704 buffer, "quote1", &startquote, &endquote);
4705 gtk_text_buffer_remove_tag_by_name(
4706 buffer, "quote2", &startquote, &endquote);
4711 if (!gtk_text_iter_has_tag(&startquote, compose->quote1_tag) ||
4712 !gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) {
4713 gtk_text_buffer_apply_tag_by_name(
4714 buffer, "quote1", &startquote, &endquote);
4715 gtk_text_buffer_remove_tag_by_name(
4716 buffer, "quote0", &startquote, &endquote);
4717 gtk_text_buffer_remove_tag_by_name(
4718 buffer, "quote2", &startquote, &endquote);
4723 if (!gtk_text_iter_has_tag(&startquote, compose->quote2_tag) ||
4724 !gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag)) {
4725 gtk_text_buffer_apply_tag_by_name(
4726 buffer, "quote2", &startquote, &endquote);
4727 gtk_text_buffer_remove_tag_by_name(
4728 buffer, "quote0", &startquote, &endquote);
4729 gtk_text_buffer_remove_tag_by_name(
4730 buffer, "quote1", &startquote, &endquote);
4736 } else if (noq_offset != -1) {
4737 GtkTextIter startnoquote, endnoquote;
4738 gtk_text_buffer_get_iter_at_offset(
4739 buffer, &startnoquote, noq_offset);
4742 if ((gtk_text_iter_has_tag(&startnoquote, compose->quote0_tag)
4743 && gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) ||
4744 (gtk_text_iter_has_tag(&startnoquote, compose->quote1_tag)
4745 && gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) ||
4746 (gtk_text_iter_has_tag(&startnoquote, compose->quote2_tag)
4747 && gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag))) {
4748 gtk_text_buffer_remove_tag_by_name(
4749 buffer, "quote0", &startnoquote, &endnoquote);
4750 gtk_text_buffer_remove_tag_by_name(
4751 buffer, "quote1", &startnoquote, &endnoquote);
4752 gtk_text_buffer_remove_tag_by_name(
4753 buffer, "quote2", &startnoquote, &endnoquote);
4759 if (uri_start != nouri_start && uri_stop != nouri_stop) {
4760 GtkTextIter nouri_start_iter, nouri_end_iter;
4761 gtk_text_buffer_get_iter_at_offset(
4762 buffer, &nouri_start_iter, nouri_start);
4763 gtk_text_buffer_get_iter_at_offset(
4764 buffer, &nouri_end_iter, nouri_stop);
4765 if (gtk_text_iter_has_tag(&nouri_start_iter, compose->uri_tag) &&
4766 gtk_text_iter_has_tag(&nouri_end_iter, compose->uri_tag)) {
4767 gtk_text_buffer_remove_tag_by_name(
4768 buffer, "link", &nouri_start_iter, &nouri_end_iter);
4769 modified_before_remove = modified;
4774 if (uri_start >= 0 && uri_stop > 0) {
4775 GtkTextIter uri_start_iter, uri_end_iter, back;
4776 gtk_text_buffer_get_iter_at_offset(
4777 buffer, &uri_start_iter, uri_start);
4778 gtk_text_buffer_get_iter_at_offset(
4779 buffer, &uri_end_iter, uri_stop);
4780 back = uri_end_iter;
4781 gtk_text_iter_backward_char(&back);
4782 if (!gtk_text_iter_has_tag(&uri_start_iter, compose->uri_tag) ||
4783 !gtk_text_iter_has_tag(&back, compose->uri_tag)) {
4784 gtk_text_buffer_apply_tag_by_name(
4785 buffer, "link", &uri_start_iter, &uri_end_iter);
4787 if (removed && !modified_before_remove) {
4793 /* debug_print("not modified, out after %d lines\n", lines); */
4797 /* debug_print("modified, out after %d lines\n", lines); */
4799 g_free(itemized_chars);
4802 undo_wrapping(compose->undostruct, FALSE);
4803 compose->autowrap = prev_autowrap;
4808 void compose_action_cb(void *data)
4810 Compose *compose = (Compose *)data;
4811 compose_wrap_all(compose);
4814 static void compose_wrap_all(Compose *compose)
4816 compose_wrap_all_full(compose, FALSE);
4819 static void compose_wrap_all_full(Compose *compose, gboolean force)
4821 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4822 GtkTextBuffer *buffer;
4824 gboolean modified = TRUE;
4826 buffer = gtk_text_view_get_buffer(text);
4828 gtk_text_buffer_get_start_iter(buffer, &iter);
4830 undo_wrapping(compose->undostruct, TRUE);
4832 while (!gtk_text_iter_is_end(&iter) && modified)
4833 modified = compose_beautify_paragraph(compose, &iter, force);
4835 undo_wrapping(compose->undostruct, FALSE);
4839 static void compose_set_title(Compose *compose)
4845 edited = compose->modified ? _(" [Edited]") : "";
4847 subject = gtk_editable_get_chars(
4848 GTK_EDITABLE(compose->subject_entry), 0, -1);
4850 #ifndef GENERIC_UMPC
4851 if (subject && strlen(subject))
4852 str = g_strdup_printf(_("%s - Compose message%s"),
4855 str = g_strdup_printf(_("[no subject] - Compose message%s"), edited);
4857 str = g_strdup(_("Compose message"));
4860 gtk_window_set_title(GTK_WINDOW(compose->window), str);
4866 * compose_current_mail_account:
4868 * Find a current mail account (the currently selected account, or the
4869 * default account, if a news account is currently selected). If a
4870 * mail account cannot be found, display an error message.
4872 * Return value: Mail account, or NULL if not found.
4874 static PrefsAccount *
4875 compose_current_mail_account(void)
4879 if (cur_account && cur_account->protocol != A_NNTP)
4882 ac = account_get_default();
4883 if (!ac || ac->protocol == A_NNTP) {
4884 alertpanel_error(_("Account for sending mail is not specified.\n"
4885 "Please select a mail account before sending."));
4892 #define QUOTE_IF_REQUIRED(out, str) \
4894 if (*str != '"' && strpbrk(str, ",.:;[]<>()@\\\"")) { \
4898 len = strlen(str) + 3; \
4899 if ((__tmp = alloca(len)) == NULL) { \
4900 g_warning("can't allocate memory"); \
4901 g_string_free(header, TRUE); \
4904 g_snprintf(__tmp, len, "\"%s\"", str); \
4909 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4910 g_warning("can't allocate memory"); \
4911 g_string_free(header, TRUE); \
4914 strcpy(__tmp, str); \
4920 #define QUOTE_IF_REQUIRED_NORMAL(out, str, errret) \
4922 if (*str != '"' && strpbrk(str, ",.:;[]<>()@\\\"")) { \
4926 len = strlen(str) + 3; \
4927 if ((__tmp = alloca(len)) == NULL) { \
4928 g_warning("can't allocate memory"); \
4931 g_snprintf(__tmp, len, "\"%s\"", str); \
4936 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4937 g_warning("can't allocate memory"); \
4940 strcpy(__tmp, str); \
4946 static void compose_select_account(Compose *compose, PrefsAccount *account,
4949 gchar *from = NULL, *header = NULL;
4950 ComposeHeaderEntry *header_entry;
4953 cm_return_if_fail(account != NULL);
4955 compose->account = account;
4956 if (account->name && *account->name) {
4958 QUOTE_IF_REQUIRED_NORMAL(buf, account->name, return);
4959 qbuf = escape_internal_quotes(buf, '"');
4960 from = g_strdup_printf("%s <%s>",
4961 qbuf, account->address);
4964 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4966 from = g_strdup_printf("<%s>",
4968 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4973 compose_set_title(compose);
4975 compose_activate_privacy_system(compose, account, FALSE);
4977 if (account->default_sign && privacy_system_can_sign(compose->privacy_system) &&
4978 compose->mode != COMPOSE_REDIRECT)
4979 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", TRUE);
4981 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", FALSE);
4982 if (account->default_encrypt && privacy_system_can_encrypt(compose->privacy_system) &&
4983 compose->mode != COMPOSE_REDIRECT)
4984 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", TRUE);
4986 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", FALSE);
4988 if (!init && compose->mode != COMPOSE_REDIRECT) {
4989 undo_block(compose->undostruct);
4990 compose_insert_sig(compose, TRUE);
4991 undo_unblock(compose->undostruct);
4994 header_entry = (ComposeHeaderEntry *) compose->header_list->data;
4995 if (gtk_combo_box_get_active_iter(GTK_COMBO_BOX(header_entry->combo), &iter))
4996 gtk_tree_model_get(gtk_combo_box_get_model(GTK_COMBO_BOX(
4997 header_entry->combo)), &iter, COMBOBOX_TEXT, &header, -1);
4999 if (header && !strlen(gtk_entry_get_text(GTK_ENTRY(header_entry->entry)))) {
5000 if (account->protocol == A_NNTP) {
5001 if (!strcmp(header, _("To:")))
5002 combobox_select_by_text(
5003 GTK_COMBO_BOX(header_entry->combo),
5006 if (!strcmp(header, _("Newsgroups:")))
5007 combobox_select_by_text(
5008 GTK_COMBO_BOX(header_entry->combo),
5016 /* use account's dict info if set */
5017 if (compose->gtkaspell) {
5018 if (account->enable_default_dictionary)
5019 gtkaspell_change_dict(compose->gtkaspell,
5020 account->default_dictionary, FALSE);
5021 if (account->enable_default_alt_dictionary)
5022 gtkaspell_change_alt_dict(compose->gtkaspell,
5023 account->default_alt_dictionary);
5024 if (account->enable_default_dictionary
5025 || account->enable_default_alt_dictionary)
5026 compose_spell_menu_changed(compose);
5031 gboolean compose_check_for_valid_recipient(Compose *compose) {
5032 gchar *recipient_headers_mail[] = {"To:", "Cc:", "Bcc:", NULL};
5033 gchar *recipient_headers_news[] = {"Newsgroups:", NULL};
5034 gboolean recipient_found = FALSE;
5038 /* free to and newsgroup list */
5039 slist_free_strings_full(compose->to_list);
5040 compose->to_list = NULL;
5042 slist_free_strings_full(compose->newsgroup_list);
5043 compose->newsgroup_list = NULL;
5045 /* search header entries for to and newsgroup entries */
5046 for (list = compose->header_list; list; list = list->next) {
5049 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5050 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5053 if (entry[0] != '\0') {
5054 for (strptr = recipient_headers_mail; *strptr != NULL; strptr++) {
5055 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
5056 compose->to_list = address_list_append(compose->to_list, entry);
5057 recipient_found = TRUE;
5060 for (strptr = recipient_headers_news; *strptr != NULL; strptr++) {
5061 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
5062 compose->newsgroup_list = newsgroup_list_append(compose->newsgroup_list, entry);
5063 recipient_found = TRUE;
5070 return recipient_found;
5073 static gboolean compose_check_for_set_recipients(Compose *compose)
5075 if (compose->account->set_autocc && compose->account->auto_cc) {
5076 gboolean found_other = FALSE;
5078 /* search header entries for to and newsgroup entries */
5079 for (list = compose->header_list; list; list = list->next) {
5082 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5083 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5086 if (strcmp(entry, compose->account->auto_cc)
5087 || strcmp(header, prefs_common_translated_header_name("Cc:"))) {
5098 if (compose->batch) {
5099 gtk_widget_show_all(compose->window);
5101 text = g_strdup_printf(_("The only recipient is the default '%s' address. Send anyway?"),
5102 prefs_common_translated_header_name("Cc"));
5103 aval = alertpanel(_("Send"),
5105 GTK_STOCK_CANCEL, _("_Send"), NULL, ALERTFOCUS_SECOND);
5107 if (aval != G_ALERTALTERNATE)
5111 if (compose->account->set_autobcc && compose->account->auto_bcc) {
5112 gboolean found_other = FALSE;
5114 /* search header entries for to and newsgroup entries */
5115 for (list = compose->header_list; list; list = list->next) {
5118 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5119 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5122 if (strcmp(entry, compose->account->auto_bcc)
5123 || strcmp(header, prefs_common_translated_header_name("Bcc:"))) {
5135 if (compose->batch) {
5136 gtk_widget_show_all(compose->window);
5138 text = g_strdup_printf(_("The only recipient is the default '%s' address. Send anyway?"),
5139 prefs_common_translated_header_name("Bcc"));
5140 aval = alertpanel(_("Send"),
5142 GTK_STOCK_CANCEL, _("_Send"), NULL, ALERTFOCUS_SECOND);
5144 if (aval != G_ALERTALTERNATE)
5151 static gboolean compose_check_entries(Compose *compose, gboolean check_everything)
5155 if (compose_check_for_valid_recipient(compose) == FALSE) {
5156 if (compose->batch) {
5157 gtk_widget_show_all(compose->window);
5159 alertpanel_error(_("Recipient is not specified."));
5163 if (compose_check_for_set_recipients(compose) == FALSE) {
5167 if (!compose->batch && prefs_common.warn_empty_subj == TRUE) {
5168 str = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5169 if (*str == '\0' && check_everything == TRUE &&
5170 compose->mode != COMPOSE_REDIRECT) {
5174 message = g_strdup_printf(_("Subject is empty. %s"),
5175 compose->sending?_("Send it anyway?"):
5176 _("Queue it anyway?"));
5178 aval = alertpanel_full(compose->sending?_("Send"):_("Send later"), message,
5179 GTK_STOCK_CANCEL, compose->sending?_("_Send"):_("_Queue"), NULL,
5180 ALERTFOCUS_FIRST, TRUE, NULL, ALERT_QUESTION);
5182 if (aval & G_ALERTDISABLE) {
5183 aval &= ~G_ALERTDISABLE;
5184 prefs_common.warn_empty_subj = FALSE;
5186 if (aval != G_ALERTALTERNATE)
5191 if (!compose->batch && prefs_common.warn_sending_many_recipients_num > 0
5192 && check_everything == TRUE) {
5196 /* count To and Cc recipients */
5197 for (list = compose->header_list; list; list = list->next) {
5201 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5202 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5205 if ((entry[0] != '\0') &&
5206 (!strcmp(header, prefs_common_translated_header_name("To:")) ||
5207 !strcmp(header, prefs_common_translated_header_name("Cc:")))) {
5213 if (cnt > prefs_common.warn_sending_many_recipients_num) {
5217 message = g_strdup_printf(_("Sending to %d recipients. %s"), cnt,
5218 compose->sending?_("Send it anyway?"):
5219 _("Queue it anyway?"));
5221 aval = alertpanel_full(compose->sending?_("Send"):_("Send later"), message,
5222 GTK_STOCK_CANCEL, compose->sending?_("_Send"):_("_Queue"), NULL,
5223 ALERTFOCUS_FIRST, TRUE, NULL, ALERT_QUESTION);
5225 if (aval & G_ALERTDISABLE) {
5226 aval &= ~G_ALERTDISABLE;
5227 prefs_common.warn_sending_many_recipients_num = 0;
5229 if (aval != G_ALERTALTERNATE)
5234 if (check_everything && hooks_invoke(COMPOSE_CHECK_BEFORE_SEND_HOOKLIST, compose))
5240 static void _display_queue_error(ComposeQueueResult val)
5243 case COMPOSE_QUEUE_SUCCESS:
5245 case COMPOSE_QUEUE_ERROR_NO_MSG:
5246 alertpanel_error(_("Could not queue message."));
5248 case COMPOSE_QUEUE_ERROR_WITH_ERRNO:
5249 alertpanel_error(_("Could not queue message:\n\n%s."),
5252 case COMPOSE_QUEUE_ERROR_SIGNING_FAILED:
5253 alertpanel_error(_("Could not queue message for sending:\n\n"
5254 "Signature failed: %s"),
5255 privacy_peek_error() ? privacy_get_error() : _("Unknown error"));
5257 case COMPOSE_QUEUE_ERROR_ENCRYPT_FAILED:
5258 alertpanel_error(_("Could not queue message for sending:\n\n"
5259 "Encryption failed: %s"),
5260 privacy_peek_error() ? privacy_get_error() : _("Unknown error"));
5262 case COMPOSE_QUEUE_ERROR_CHAR_CONVERSION:
5263 alertpanel_error(_("Could not queue message for sending:\n\n"
5264 "Charset conversion failed."));
5266 case COMPOSE_QUEUE_ERROR_NO_ENCRYPTION_KEY:
5267 alertpanel_error(_("Could not queue message for sending:\n\n"
5268 "Couldn't get recipient encryption key."));
5271 /* unhandled error */
5272 debug_print("oops, unhandled compose_queue() return value %d\n",
5278 gint compose_send(Compose *compose)
5281 FolderItem *folder = NULL;
5282 ComposeQueueResult val = COMPOSE_QUEUE_ERROR_NO_MSG;
5283 gchar *msgpath = NULL;
5284 gboolean discard_window = FALSE;
5285 gchar *errstr = NULL;
5286 gchar *tmsgid = NULL;
5287 MainWindow *mainwin = mainwindow_get_mainwindow();
5288 gboolean queued_removed = FALSE;
5290 if (prefs_common.send_dialog_invisible
5291 || compose->batch == TRUE)
5292 discard_window = TRUE;
5294 compose_allow_user_actions (compose, FALSE);
5295 compose->sending = TRUE;
5297 if (compose_check_entries(compose, TRUE) == FALSE) {
5298 if (compose->batch) {
5299 gtk_widget_show_all(compose->window);
5305 val = compose_queue(compose, &msgnum, &folder, &msgpath, TRUE);
5307 if (val != COMPOSE_QUEUE_SUCCESS) {
5308 if (compose->batch) {
5309 gtk_widget_show_all(compose->window);
5312 _display_queue_error(val);
5317 tmsgid = compose->msgid ? g_strdup(compose->msgid) : NULL;
5318 if (discard_window) {
5319 compose->sending = FALSE;
5320 compose_close(compose);
5321 /* No more compose access in the normal codepath
5322 * after this point! */
5327 alertpanel_error(_("The message was queued but could not be "
5328 "sent.\nUse \"Send queued messages\" from "
5329 "the main window to retry."));
5330 if (!discard_window) {
5337 if (msgpath == NULL) {
5338 msgpath = folder_item_fetch_msg(folder, msgnum);
5339 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5342 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5343 claws_unlink(msgpath);
5346 if (!discard_window) {
5348 if (!queued_removed)
5349 folder_item_remove_msg(folder, msgnum);
5350 folder_item_scan(folder);
5352 /* make sure we delete that */
5353 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5355 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5356 folder_item_remove_msg(folder, tmp->msgnum);
5357 procmsg_msginfo_free(&tmp);
5364 if (!queued_removed)
5365 folder_item_remove_msg(folder, msgnum);
5366 folder_item_scan(folder);
5368 /* make sure we delete that */
5369 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5371 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5372 folder_item_remove_msg(folder, tmp->msgnum);
5373 procmsg_msginfo_free(&tmp);
5376 if (!discard_window) {
5377 compose->sending = FALSE;
5378 compose_allow_user_actions (compose, TRUE);
5379 compose_close(compose);
5383 alertpanel_error_log(_("%s\nUse \"Send queued messages\" from "
5384 "the main window to retry."), errstr);
5387 alertpanel_error_log(_("The message was queued but could not be "
5388 "sent.\nUse \"Send queued messages\" from "
5389 "the main window to retry."));
5391 if (!discard_window) {
5400 toolbar_main_set_sensitive(mainwin);
5401 main_window_set_menu_sensitive(mainwin);
5407 compose_allow_user_actions (compose, TRUE);
5408 compose->sending = FALSE;
5409 compose->modified = TRUE;
5410 toolbar_main_set_sensitive(mainwin);
5411 main_window_set_menu_sensitive(mainwin);
5416 static gboolean compose_use_attach(Compose *compose)
5418 GtkTreeModel *model = gtk_tree_view_get_model
5419 (GTK_TREE_VIEW(compose->attach_clist));
5420 return gtk_tree_model_iter_n_children(model, NULL) > 0;
5423 static gint compose_redirect_write_headers_from_headerlist(Compose *compose,
5426 gchar buf[BUFFSIZE];
5428 gboolean first_to_address;
5429 gboolean first_cc_address;
5431 ComposeHeaderEntry *headerentry;
5432 const gchar *headerentryname;
5433 const gchar *cc_hdr;
5434 const gchar *to_hdr;
5435 gboolean err = FALSE;
5437 debug_print("Writing redirect header\n");
5439 cc_hdr = prefs_common_translated_header_name("Cc:");
5440 to_hdr = prefs_common_translated_header_name("To:");
5442 first_to_address = TRUE;
5443 for (list = compose->header_list; list; list = list->next) {
5444 headerentry = ((ComposeHeaderEntry *)list->data);
5445 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5447 if (g_utf8_collate(headerentryname, to_hdr) == 0) {
5448 const gchar *entstr = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5449 Xstrdup_a(str, entstr, return -1);
5451 if (str[0] != '\0') {
5452 compose_convert_header
5453 (compose, buf, sizeof(buf), str,
5454 strlen("Resent-To") + 2, TRUE);
5456 if (first_to_address) {
5457 err |= (fprintf(fp, "Resent-To: ") < 0);
5458 first_to_address = FALSE;
5460 err |= (fprintf(fp, ",") < 0);
5462 err |= (fprintf(fp, "%s", buf) < 0);
5466 if (!first_to_address) {
5467 err |= (fprintf(fp, "\n") < 0);
5470 first_cc_address = TRUE;
5471 for (list = compose->header_list; list; list = list->next) {
5472 headerentry = ((ComposeHeaderEntry *)list->data);
5473 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5475 if (g_utf8_collate(headerentryname, cc_hdr) == 0) {
5476 const gchar *strg = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5477 Xstrdup_a(str, strg, return -1);
5479 if (str[0] != '\0') {
5480 compose_convert_header
5481 (compose, buf, sizeof(buf), str,
5482 strlen("Resent-Cc") + 2, TRUE);
5484 if (first_cc_address) {
5485 err |= (fprintf(fp, "Resent-Cc: ") < 0);
5486 first_cc_address = FALSE;
5488 err |= (fprintf(fp, ",") < 0);
5490 err |= (fprintf(fp, "%s", buf) < 0);
5494 if (!first_cc_address) {
5495 err |= (fprintf(fp, "\n") < 0);
5498 return (err ? -1:0);
5501 static gint compose_redirect_write_headers(Compose *compose, FILE *fp)
5503 gchar date[RFC822_DATE_BUFFSIZE];
5504 gchar buf[BUFFSIZE];
5506 const gchar *entstr;
5507 /* struct utsname utsbuf; */
5508 gboolean err = FALSE;
5510 cm_return_val_if_fail(fp != NULL, -1);
5511 cm_return_val_if_fail(compose->account != NULL, -1);
5512 cm_return_val_if_fail(compose->account->address != NULL, -1);
5515 if (prefs_common.hide_timezone)
5516 get_rfc822_date_hide_tz(date, sizeof(date));
5518 get_rfc822_date(date, sizeof(date));
5519 err |= (fprintf(fp, "Resent-Date: %s\n", date) < 0);
5522 if (compose->account->name && *compose->account->name) {
5523 compose_convert_header
5524 (compose, buf, sizeof(buf), compose->account->name,
5525 strlen("From: "), TRUE);
5526 err |= (fprintf(fp, "Resent-From: %s <%s>\n",
5527 buf, compose->account->address) < 0);
5529 err |= (fprintf(fp, "Resent-From: %s\n", compose->account->address) < 0);
5532 entstr = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5533 if (*entstr != '\0') {
5534 Xstrdup_a(str, entstr, return -1);
5537 compose_convert_header(compose, buf, sizeof(buf), str,
5538 strlen("Subject: "), FALSE);
5539 err |= (fprintf(fp, "Subject: %s\n", buf) < 0);
5543 /* Resent-Message-ID */
5544 if (compose->account->gen_msgid) {
5545 gchar *addr = prefs_account_generate_msgid(compose->account);
5546 err |= (fprintf(fp, "Resent-Message-ID: <%s>\n", addr) < 0);
5548 g_free(compose->msgid);
5549 compose->msgid = addr;
5551 compose->msgid = NULL;
5554 if (compose_redirect_write_headers_from_headerlist(compose, fp))
5557 /* separator between header and body */
5558 err |= (claws_fputs("\n", fp) == EOF);
5560 return (err ? -1:0);
5563 static gint compose_redirect_write_to_file(Compose *compose, FILE *fdest)
5568 gchar rewrite_buf[BUFFSIZE];
5570 gboolean skip = FALSE;
5571 gboolean err = FALSE;
5572 gchar *not_included[]={
5573 "Return-Path:", "Delivered-To:", "Received:",
5574 "Subject:", "X-UIDL:", "AF:",
5575 "NF:", "PS:", "SRH:",
5576 "SFN:", "DSR:", "MID:",
5577 "CFG:", "PT:", "S:",
5578 "RQ:", "SSV:", "NSV:",
5579 "SSH:", "R:", "MAID:",
5580 "NAID:", "RMID:", "FMID:",
5581 "SCF:", "RRCPT:", "NG:",
5582 "X-Claws-Privacy", "X-Claws-Sign:", "X-Claws-Encrypt",
5583 "X-Claws-End-Special-Headers:", "X-Claws-Account-Id:",
5584 "X-Sylpheed-Privacy", "X-Sylpheed-Sign:", "X-Sylpheed-Encrypt",
5585 "X-Sylpheed-End-Special-Headers:", "X-Sylpheed-Account-Id:",
5586 "X-Claws-Auto-Wrapping:", "X-Claws-Auto-Indent:",
5591 if ((fp = claws_fopen(compose->redirect_filename, "rb")) == NULL) {
5592 FILE_OP_ERROR(compose->redirect_filename, "claws_fopen");
5596 while ((ret = procheader_get_one_field_asis(&buf, fp)) != -1) {
5598 for (i = 0; not_included[i] != NULL; i++) {
5599 if (g_ascii_strncasecmp(buf, not_included[i],
5600 strlen(not_included[i])) == 0) {
5610 if (claws_fputs(buf, fdest) == -1) {
5616 if (!prefs_common.redirect_keep_from) {
5617 if (g_ascii_strncasecmp(buf, "From:",
5618 strlen("From:")) == 0) {
5619 err |= (claws_fputs(" (by way of ", fdest) == EOF);
5620 if (compose->account->name
5621 && *compose->account->name) {
5622 gchar buffer[BUFFSIZE];
5624 compose_convert_header
5625 (compose, buffer, sizeof(buffer),
5626 compose->account->name,
5629 err |= (fprintf(fdest, "%s <%s>",
5631 compose->account->address) < 0);
5633 err |= (fprintf(fdest, "%s",
5634 compose->account->address) < 0);
5635 err |= (claws_fputs(")", fdest) == EOF);
5641 if (claws_fputs("\n", fdest) == -1)
5648 if (compose_redirect_write_headers(compose, fdest))
5651 while ((len = claws_fread(rewrite_buf, sizeof(gchar), sizeof(rewrite_buf), fp)) > 0) {
5652 if (claws_fwrite(rewrite_buf, sizeof(gchar), len, fdest) != len)
5666 static gint compose_write_to_file(Compose *compose, FILE *fp, gint action, gboolean attach_parts)
5668 GtkTextBuffer *buffer;
5669 GtkTextIter start, end, tmp;
5670 gchar *chars, *tmp_enc_file, *content;
5672 const gchar *out_codeset;
5673 EncodingType encoding = ENC_UNKNOWN;
5674 MimeInfo *mimemsg, *mimetext;
5676 const gchar *src_codeset = CS_INTERNAL;
5677 gchar *from_addr = NULL;
5678 gchar *from_name = NULL;
5681 if (action == COMPOSE_WRITE_FOR_SEND) {
5682 attach_parts = TRUE;
5684 /* We're sending the message, generate a Message-ID
5686 if (compose->msgid == NULL &&
5687 compose->account->gen_msgid) {
5688 compose->msgid = prefs_account_generate_msgid(compose->account);
5692 /* create message MimeInfo */
5693 mimemsg = procmime_mimeinfo_new();
5694 mimemsg->type = MIMETYPE_MESSAGE;
5695 mimemsg->subtype = g_strdup("rfc822");
5696 mimemsg->content = MIMECONTENT_MEM;
5697 mimemsg->tmp = TRUE; /* must free content later */
5698 mimemsg->data.mem = compose_get_header(compose);
5700 /* Create text part MimeInfo */
5701 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5702 gtk_text_buffer_get_end_iter(buffer, &end);
5705 /* We make sure that there is a newline at the end. */
5706 if (action == COMPOSE_WRITE_FOR_SEND && gtk_text_iter_backward_char(&tmp)) {
5707 chars = gtk_text_buffer_get_text(buffer, &tmp, &end, FALSE);
5708 if (*chars != '\n') {
5709 gtk_text_buffer_insert(buffer, &end, "\n", 1);
5714 /* get all composed text */
5715 gtk_text_buffer_get_start_iter(buffer, &start);
5716 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5718 out_codeset = conv_get_charset_str(compose->out_encoding);
5720 if (!out_codeset && is_ascii_str(chars)) {
5721 out_codeset = CS_US_ASCII;
5722 } else if (prefs_common.outgoing_fallback_to_ascii &&
5723 is_ascii_str(chars)) {
5724 out_codeset = CS_US_ASCII;
5725 encoding = ENC_7BIT;
5729 gchar *test_conv_global_out = NULL;
5730 gchar *test_conv_reply = NULL;
5732 /* automatic mode. be automatic. */
5733 codeconv_set_strict(TRUE);
5735 out_codeset = conv_get_outgoing_charset_str();
5737 debug_print("trying to convert to %s\n", out_codeset);
5738 test_conv_global_out = conv_codeset_strdup(chars, src_codeset, out_codeset);
5741 if (!test_conv_global_out && compose->orig_charset
5742 && strcmp(compose->orig_charset, CS_US_ASCII)) {
5743 out_codeset = compose->orig_charset;
5744 debug_print("failure; trying to convert to %s\n", out_codeset);
5745 test_conv_reply = conv_codeset_strdup(chars, src_codeset, out_codeset);
5748 if (!test_conv_global_out && !test_conv_reply) {
5750 out_codeset = CS_INTERNAL;
5751 debug_print("failure; finally using %s\n", out_codeset);
5753 g_free(test_conv_global_out);
5754 g_free(test_conv_reply);
5755 codeconv_set_strict(FALSE);
5758 if (encoding == ENC_UNKNOWN) {
5759 if (prefs_common.encoding_method == CTE_BASE64)
5760 encoding = ENC_BASE64;
5761 else if (prefs_common.encoding_method == CTE_QUOTED_PRINTABLE)
5762 encoding = ENC_QUOTED_PRINTABLE;
5763 else if (prefs_common.encoding_method == CTE_8BIT)
5764 encoding = ENC_8BIT;
5766 encoding = procmime_get_encoding_for_charset(out_codeset);
5769 debug_print("src encoding = %s, out encoding = %s, transfer encoding = %s\n",
5770 src_codeset, out_codeset, procmime_get_encoding_str(encoding));
5772 if (action == COMPOSE_WRITE_FOR_SEND) {
5773 codeconv_set_strict(TRUE);
5774 buf = conv_codeset_strdup(chars, src_codeset, out_codeset);
5775 codeconv_set_strict(FALSE);
5780 msg = g_strdup_printf(_("Can't convert the character encoding of the message \n"
5781 "to the specified %s charset.\n"
5782 "Send it as %s?"), out_codeset, src_codeset);
5783 aval = alertpanel_full(_("Error"), msg, GTK_STOCK_CANCEL,
5784 _("_Send"), NULL, ALERTFOCUS_SECOND, FALSE,
5788 if (aval != G_ALERTALTERNATE) {
5790 return COMPOSE_QUEUE_ERROR_CHAR_CONVERSION;
5793 out_codeset = src_codeset;
5799 out_codeset = src_codeset;
5804 if (prefs_common.rewrite_first_from && (encoding == ENC_8BIT || encoding == ENC_7BIT)) {
5805 if (!strncmp(buf, "From ", sizeof("From ")-1) ||
5806 strstr(buf, "\nFrom ") != NULL) {
5807 encoding = ENC_QUOTED_PRINTABLE;
5811 mimetext = procmime_mimeinfo_new();
5812 mimetext->content = MIMECONTENT_MEM;
5813 mimetext->tmp = TRUE; /* must free content later */
5814 /* dup'ed because procmime_encode_content can turn it into a tmpfile
5815 * and free the data, which we need later. */
5816 mimetext->data.mem = g_strdup(buf);
5817 mimetext->type = MIMETYPE_TEXT;
5818 mimetext->subtype = g_strdup("plain");
5819 g_hash_table_insert(mimetext->typeparameters, g_strdup("charset"),
5820 g_strdup(out_codeset));
5822 /* protect trailing spaces when signing message */
5823 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5824 privacy_system_can_sign(compose->privacy_system)) {
5825 encoding = ENC_QUOTED_PRINTABLE;
5829 debug_print("main text: %Id bytes encoded as %s in %d\n",
5831 debug_print("main text: %zd bytes encoded as %s in %d\n",
5833 strlen(buf), out_codeset, encoding);
5835 /* check for line length limit */
5836 if (action == COMPOSE_WRITE_FOR_SEND &&
5837 encoding != ENC_QUOTED_PRINTABLE && encoding != ENC_BASE64 &&
5838 check_line_length(buf, 1000, &line) < 0) {
5841 msg = g_strdup_printf
5842 (_("Line %d exceeds the line length limit (998 bytes).\n"
5843 "The contents of the message might be broken on the way to the delivery.\n"
5845 "Send it anyway?"), line + 1);
5846 aval = alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, GTK_STOCK_OK, NULL,
5849 if (aval != G_ALERTALTERNATE) {
5851 return COMPOSE_QUEUE_ERROR_NO_MSG;
5855 if (encoding != ENC_UNKNOWN)
5856 procmime_encode_content(mimetext, encoding);
5858 /* append attachment parts */
5859 if (compose_use_attach(compose) && attach_parts) {
5860 MimeInfo *mimempart;
5861 gchar *boundary = NULL;
5862 mimempart = procmime_mimeinfo_new();
5863 mimempart->content = MIMECONTENT_EMPTY;
5864 mimempart->type = MIMETYPE_MULTIPART;
5865 mimempart->subtype = g_strdup("mixed");
5869 boundary = generate_mime_boundary(NULL);
5870 } while (strstr(buf, boundary) != NULL);
5872 g_hash_table_insert(mimempart->typeparameters, g_strdup("boundary"),
5875 mimetext->disposition = DISPOSITIONTYPE_INLINE;
5877 g_node_append(mimempart->node, mimetext->node);
5878 g_node_append(mimemsg->node, mimempart->node);
5880 if (compose_add_attachments(compose, mimempart) < 0)
5881 return COMPOSE_QUEUE_ERROR_NO_MSG;
5883 g_node_append(mimemsg->node, mimetext->node);
5887 if (strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) != 0) {
5888 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
5889 /* extract name and address */
5890 if (strstr(spec, " <") && strstr(spec, ">")) {
5891 from_addr = g_strdup(strrchr(spec, '<')+1);
5892 *(strrchr(from_addr, '>')) = '\0';
5893 from_name = g_strdup(spec);
5894 *(strrchr(from_name, '<')) = '\0';
5901 /* sign message if sending */
5902 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5903 privacy_system_can_sign(compose->privacy_system))
5904 if (!privacy_sign(compose->privacy_system, mimemsg,
5905 compose->account, from_addr)) {
5908 return COMPOSE_QUEUE_ERROR_SIGNING_FAILED;
5913 if (compose->use_encryption) {
5914 if (compose->encdata != NULL &&
5915 strcmp(compose->encdata, "_DONT_ENCRYPT_")) {
5917 /* First, write an unencrypted copy and save it to outbox, if
5918 * user wants that. */
5919 if (compose->account->save_encrypted_as_clear_text) {
5920 debug_print("saving sent message unencrypted...\n");
5921 FILE *tmpfp = get_tmpfile_in_dir(get_mime_tmp_dir(), &tmp_enc_file);
5923 claws_fclose(tmpfp);
5925 /* fp now points to a file with headers written,
5926 * let's make a copy. */
5928 content = file_read_stream_to_str(fp);
5930 str_write_to_file(content, tmp_enc_file, TRUE);
5933 /* Now write the unencrypted body. */
5934 if ((tmpfp = claws_fopen(tmp_enc_file, "a")) != NULL) {
5935 procmime_write_mimeinfo(mimemsg, tmpfp);
5936 claws_fclose(tmpfp);
5938 outbox = folder_find_item_from_identifier(compose_get_save_to(compose));
5940 outbox = folder_get_default_outbox();
5942 procmsg_save_to_outbox(outbox, tmp_enc_file, TRUE);
5943 claws_unlink(tmp_enc_file);
5945 g_warning("Can't open file '%s'", tmp_enc_file);
5948 g_warning("couldn't get tempfile");
5951 if (!privacy_encrypt(compose->privacy_system, mimemsg, compose->encdata)) {
5952 debug_print("Couldn't encrypt mime structure: %s.\n",
5953 privacy_get_error());
5954 return COMPOSE_QUEUE_ERROR_ENCRYPT_FAILED;
5959 procmime_write_mimeinfo(mimemsg, fp);
5961 procmime_mimeinfo_free_all(&mimemsg);
5966 static gint compose_write_body_to_file(Compose *compose, const gchar *file)
5968 GtkTextBuffer *buffer;
5969 GtkTextIter start, end;
5974 if ((fp = claws_fopen(file, "wb")) == NULL) {
5975 FILE_OP_ERROR(file, "claws_fopen");
5979 /* chmod for security */
5980 if (change_file_mode_rw(fp, file) < 0) {
5981 FILE_OP_ERROR(file, "chmod");
5982 g_warning("can't change file mode");
5985 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5986 gtk_text_buffer_get_start_iter(buffer, &start);
5987 gtk_text_buffer_get_end_iter(buffer, &end);
5988 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5990 chars = conv_codeset_strdup
5991 (tmp, CS_INTERNAL, conv_get_locale_charset_str());
6000 len = strlen(chars);
6001 if (claws_fwrite(chars, sizeof(gchar), len, fp) != len) {
6002 FILE_OP_ERROR(file, "claws_fwrite");
6011 if (claws_safe_fclose(fp) == EOF) {
6012 FILE_OP_ERROR(file, "claws_fclose");
6019 static gint compose_remove_reedit_target(Compose *compose, gboolean force)
6022 MsgInfo *msginfo = compose->targetinfo;
6024 cm_return_val_if_fail(compose->mode == COMPOSE_REEDIT, -1);
6025 if (!msginfo) return -1;
6027 if (!force && MSG_IS_LOCKED(msginfo->flags))
6030 item = msginfo->folder;
6031 cm_return_val_if_fail(item != NULL, -1);
6033 if (procmsg_msg_exist(msginfo) &&
6034 (folder_has_parent_of_type(item, F_QUEUE) ||
6035 folder_has_parent_of_type(item, F_DRAFT)
6036 || msginfo == compose->autosaved_draft)) {
6037 if (folder_item_remove_msg(item, msginfo->msgnum) < 0) {
6038 g_warning("can't remove the old message");
6041 debug_print("removed reedit target %d\n", msginfo->msgnum);
6048 static void compose_remove_draft(Compose *compose)
6051 MsgInfo *msginfo = compose->targetinfo;
6052 drafts = account_get_special_folder(compose->account, F_DRAFT);
6054 if (procmsg_msg_exist(msginfo)) {
6055 folder_item_remove_msg(drafts, msginfo->msgnum);
6060 ComposeQueueResult compose_queue(Compose *compose, gint *msgnum, FolderItem **item, gchar **msgpath,
6061 gboolean remove_reedit_target)
6063 return compose_queue_sub (compose, msgnum, item, msgpath, FALSE, remove_reedit_target);
6066 static gboolean compose_warn_encryption(Compose *compose)
6068 const gchar *warning = privacy_get_encrypt_warning(compose->privacy_system);
6069 AlertValue val = G_ALERTALTERNATE;
6071 if (warning == NULL)
6074 val = alertpanel_full(_("Encryption warning"), warning,
6075 GTK_STOCK_CANCEL, _("C_ontinue"), NULL, ALERTFOCUS_SECOND,
6076 TRUE, NULL, ALERT_WARNING);
6077 if (val & G_ALERTDISABLE) {
6078 val &= ~G_ALERTDISABLE;
6079 if (val == G_ALERTALTERNATE)
6080 privacy_inhibit_encrypt_warning(compose->privacy_system,
6084 if (val == G_ALERTALTERNATE) {
6091 static ComposeQueueResult compose_queue_sub(Compose *compose, gint *msgnum, FolderItem **item,
6092 gchar **msgpath, gboolean perform_checks,
6093 gboolean remove_reedit_target)
6100 PrefsAccount *mailac = NULL, *newsac = NULL;
6101 gboolean err = FALSE;
6103 debug_print("queueing message...\n");
6104 cm_return_val_if_fail(compose->account != NULL, -1);
6106 if (compose_check_entries(compose, perform_checks) == FALSE) {
6107 if (compose->batch) {
6108 gtk_widget_show_all(compose->window);
6110 return COMPOSE_QUEUE_ERROR_NO_MSG;
6113 if (!compose->to_list && !compose->newsgroup_list) {
6114 g_warning("can't get recipient list.");
6115 return COMPOSE_QUEUE_ERROR_NO_MSG;
6118 if (compose->to_list) {
6119 if (compose->account->protocol != A_NNTP)
6120 mailac = compose->account;
6121 else if (cur_account && cur_account->protocol != A_NNTP)
6122 mailac = cur_account;
6123 else if (!(mailac = compose_current_mail_account())) {
6124 alertpanel_error(_("No account for sending mails available!"));
6125 return COMPOSE_QUEUE_ERROR_NO_MSG;
6129 if (compose->newsgroup_list) {
6130 if (compose->account->protocol == A_NNTP)
6131 newsac = compose->account;
6133 alertpanel_error(_("Selected account isn't NNTP: Posting is impossible."));
6134 return COMPOSE_QUEUE_ERROR_NO_MSG;
6138 /* write queue header */
6139 tmp = g_strdup_printf("%s%cqueue.%p%08x", get_tmp_dir(),
6140 G_DIR_SEPARATOR, compose, (guint) rand());
6141 debug_print("queuing to %s\n", tmp);
6142 if ((fp = claws_fopen(tmp, "w+b")) == NULL) {
6143 FILE_OP_ERROR(tmp, "claws_fopen");
6145 return COMPOSE_QUEUE_ERROR_WITH_ERRNO;
6148 if (change_file_mode_rw(fp, tmp) < 0) {
6149 FILE_OP_ERROR(tmp, "chmod");
6150 g_warning("can't change file mode");
6153 /* queueing variables */
6154 err |= (fprintf(fp, "AF:\n") < 0);
6155 err |= (fprintf(fp, "NF:0\n") < 0);
6156 err |= (fprintf(fp, "PS:10\n") < 0);
6157 err |= (fprintf(fp, "SRH:1\n") < 0);
6158 err |= (fprintf(fp, "SFN:\n") < 0);
6159 err |= (fprintf(fp, "DSR:\n") < 0);
6161 err |= (fprintf(fp, "MID:<%s>\n", compose->msgid) < 0);
6163 err |= (fprintf(fp, "MID:\n") < 0);
6164 err |= (fprintf(fp, "CFG:\n") < 0);
6165 err |= (fprintf(fp, "PT:0\n") < 0);
6166 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
6167 err |= (fprintf(fp, "RQ:\n") < 0);
6169 err |= (fprintf(fp, "SSV:%s\n", mailac->smtp_server) < 0);
6171 err |= (fprintf(fp, "SSV:\n") < 0);
6173 err |= (fprintf(fp, "NSV:%s\n", newsac->nntp_server) < 0);
6175 err |= (fprintf(fp, "NSV:\n") < 0);
6176 err |= (fprintf(fp, "SSH:\n") < 0);
6177 /* write recipient list */
6178 if (compose->to_list) {
6179 err |= (fprintf(fp, "R:<%s>", (gchar *)compose->to_list->data) < 0);
6180 for (cur = compose->to_list->next; cur != NULL;
6182 err |= (fprintf(fp, ",<%s>", (gchar *)cur->data) < 0);
6183 err |= (fprintf(fp, "\n") < 0);
6185 /* write newsgroup list */
6186 if (compose->newsgroup_list) {
6187 err |= (fprintf(fp, "NG:") < 0);
6188 err |= (fprintf(fp, "%s", (gchar *)compose->newsgroup_list->data) < 0);
6189 for (cur = compose->newsgroup_list->next; cur != NULL; cur = cur->next)
6190 err |= (fprintf(fp, ",%s", (gchar *)cur->data) < 0);
6191 err |= (fprintf(fp, "\n") < 0);
6195 err |= (fprintf(fp, "MAID:%d\n", mailac->account_id) < 0);
6197 err |= (fprintf(fp, "NAID:%d\n", newsac->account_id) < 0);
6200 if (compose->privacy_system != NULL) {
6201 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
6202 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
6203 if (compose->use_encryption) {
6204 if (!compose_warn_encryption(compose)) {
6208 return COMPOSE_QUEUE_ERROR_NO_MSG;
6210 if (mailac && mailac->encrypt_to_self) {
6211 GSList *tmp_list = g_slist_copy(compose->to_list);
6212 tmp_list = g_slist_append(tmp_list, compose->account->address);
6213 compose->encdata = privacy_get_encrypt_data(compose->privacy_system, tmp_list);
6214 g_slist_free(tmp_list);
6216 compose->encdata = privacy_get_encrypt_data(compose->privacy_system, compose->to_list);
6218 if (compose->encdata != NULL) {
6219 if (strcmp(compose->encdata, "_DONT_ENCRYPT_")) {
6220 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
6221 err |= (fprintf(fp, "X-Claws-Encrypt-Data:%s\n",
6222 compose->encdata) < 0);
6223 } /* else we finally dont want to encrypt */
6225 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
6226 /* and if encdata was null, it means there's been a problem in
6229 g_warning("failed to write queue message");
6233 return COMPOSE_QUEUE_ERROR_NO_ENCRYPTION_KEY;
6238 /* Save copy folder */
6239 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
6240 gchar *savefolderid;
6242 savefolderid = compose_get_save_to(compose);
6243 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
6244 g_free(savefolderid);
6246 /* Save copy folder */
6247 if (compose->return_receipt) {
6248 err |= (fprintf(fp, "RRCPT:1\n") < 0);
6250 /* Message-ID of message replying to */
6251 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
6252 gchar *folderid = NULL;
6254 if (compose->replyinfo->folder)
6255 folderid = folder_item_get_identifier(compose->replyinfo->folder);
6256 if (folderid == NULL)
6257 folderid = g_strdup("NULL");
6259 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
6262 /* Message-ID of message forwarding to */
6263 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
6264 gchar *folderid = NULL;
6266 if (compose->fwdinfo->folder)
6267 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
6268 if (folderid == NULL)
6269 folderid = g_strdup("NULL");
6271 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
6275 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
6276 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
6278 /* end of headers */
6279 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
6281 if (compose->redirect_filename != NULL) {
6282 if (compose_redirect_write_to_file(compose, fp) < 0) {
6286 return COMPOSE_QUEUE_ERROR_WITH_ERRNO;
6290 if ((result = compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_SEND, TRUE)) < 0) {
6298 g_warning("failed to write queue message");
6302 return COMPOSE_QUEUE_ERROR_WITH_ERRNO;
6304 if (claws_safe_fclose(fp) == EOF) {
6305 FILE_OP_ERROR(tmp, "claws_fclose");
6308 return COMPOSE_QUEUE_ERROR_WITH_ERRNO;
6311 if (item && *item) {
6314 queue = account_get_special_folder(compose->account, F_QUEUE);
6317 g_warning("can't find queue folder");
6320 return COMPOSE_QUEUE_ERROR_NO_MSG;
6322 folder_item_scan(queue);
6323 if ((num = folder_item_add_msg(queue, tmp, NULL, FALSE)) < 0) {
6324 g_warning("can't queue the message");
6327 return COMPOSE_QUEUE_ERROR_NO_MSG;
6330 if (msgpath == NULL) {
6336 if (compose->mode == COMPOSE_REEDIT && compose->targetinfo) {
6337 MsgInfo *mi = folder_item_get_msginfo(queue, num);
6339 procmsg_msginfo_change_flags(mi,
6340 compose->targetinfo->flags.perm_flags,
6341 compose->targetinfo->flags.tmp_flags & ~(MSG_COPY | MSG_MOVE | MSG_MOVE_DONE),
6344 g_slist_free(mi->tags);
6345 mi->tags = g_slist_copy(compose->targetinfo->tags);
6346 procmsg_msginfo_free(&mi);
6350 if (compose->mode == COMPOSE_REEDIT && remove_reedit_target) {
6351 compose_remove_reedit_target(compose, FALSE);
6354 if ((msgnum != NULL) && (item != NULL)) {
6359 return COMPOSE_QUEUE_SUCCESS;
6362 static int compose_add_attachments(Compose *compose, MimeInfo *parent)
6365 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
6370 GError *error = NULL;
6375 gchar *type, *subtype;
6376 GtkTreeModel *model;
6379 model = gtk_tree_view_get_model(tree_view);
6381 if (!gtk_tree_model_get_iter_first(model, &iter))
6384 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
6386 if (!is_file_exist(ainfo->file)) {
6387 gchar *msg = g_strdup_printf(_("Attachment %s doesn't exist anymore. Ignore?"), ainfo->file);
6388 AlertValue val = alertpanel_full(_("Warning"), msg,
6389 _("Cancel sending"), _("Ignore attachment"), NULL,
6390 ALERTFOCUS_FIRST, FALSE, NULL, ALERT_WARNING);
6392 if (val == G_ALERTDEFAULT) {
6398 f = g_file_new_for_path(ainfo->file);
6399 fi = g_file_query_info(f, "standard::size",
6400 G_FILE_QUERY_INFO_NONE, NULL, &error);
6401 if (error != NULL) {
6402 g_warning(error->message);
6403 g_error_free(error);
6407 size = g_file_info_get_size(fi);
6411 if (g_stat(ainfo->file, &statbuf) < 0)
6413 size = statbuf.st_size;
6416 mimepart = procmime_mimeinfo_new();
6417 mimepart->content = MIMECONTENT_FILE;
6418 mimepart->data.filename = g_strdup(ainfo->file);
6419 mimepart->tmp = FALSE; /* or we destroy our attachment */
6420 mimepart->offset = 0;
6421 mimepart->length = size;
6423 type = g_strdup(ainfo->content_type);
6425 if (!strchr(type, '/')) {
6427 type = g_strdup("application/octet-stream");
6430 subtype = strchr(type, '/') + 1;
6431 *(subtype - 1) = '\0';
6432 mimepart->type = procmime_get_media_type(type);
6433 mimepart->subtype = g_strdup(subtype);
6436 if (mimepart->type == MIMETYPE_MESSAGE &&
6437 !g_ascii_strcasecmp(mimepart->subtype, "rfc822")) {
6438 mimepart->disposition = DISPOSITIONTYPE_INLINE;
6439 } else if (mimepart->type == MIMETYPE_TEXT) {
6440 if (!ainfo->name && g_ascii_strcasecmp(mimepart->subtype, "plain")) {
6441 /* Text parts with no name come from multipart/alternative
6442 * forwards. Make sure the recipient won't look at the
6443 * original HTML part by mistake. */
6444 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6445 ainfo->name = g_strdup_printf(_("Original %s part"),
6449 g_hash_table_insert(mimepart->typeparameters,
6450 g_strdup("charset"), g_strdup(ainfo->charset));
6452 if (ainfo->name && mimepart->type != MIMETYPE_MESSAGE) {
6453 if (mimepart->type == MIMETYPE_APPLICATION &&
6454 !strcmp2(mimepart->subtype, "octet-stream"))
6455 g_hash_table_insert(mimepart->typeparameters,
6456 g_strdup("name"), g_strdup(ainfo->name));
6457 g_hash_table_insert(mimepart->dispositionparameters,
6458 g_strdup("filename"), g_strdup(ainfo->name));
6459 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6462 if (mimepart->type == MIMETYPE_MESSAGE
6463 || mimepart->type == MIMETYPE_MULTIPART)
6464 ainfo->encoding = ENC_BINARY;
6465 else if (compose->use_signing) {
6466 if (ainfo->encoding == ENC_7BIT)
6467 ainfo->encoding = ENC_QUOTED_PRINTABLE;
6468 else if (ainfo->encoding == ENC_8BIT)
6469 ainfo->encoding = ENC_BASE64;
6472 procmime_encode_content(mimepart, ainfo->encoding);
6474 g_node_append(parent->node, mimepart->node);
6475 } while (gtk_tree_model_iter_next(model, &iter));
6480 static gchar *compose_quote_list_of_addresses(gchar *str)
6482 GSList *list = NULL, *item = NULL;
6483 gchar *qname = NULL, *faddr = NULL, *result = NULL;
6485 list = address_list_append_with_comments(list, str);
6486 for (item = list; item != NULL; item = item->next) {
6487 gchar *spec = item->data;
6488 gchar *endofname = strstr(spec, " <");
6489 if (endofname != NULL) {
6492 QUOTE_IF_REQUIRED_NORMAL(qname, spec, return NULL);
6493 qqname = escape_internal_quotes(qname, '"');
6495 if (*qname != *spec || qqname != qname) { /* has been quoted, compute new */
6496 gchar *addr = g_strdup(endofname);
6497 gchar *name = (qqname != qname)? qqname: g_strdup(qname);
6498 faddr = g_strconcat(name, addr, NULL);
6501 debug_print("new auto-quoted address: '%s'\n", faddr);
6505 result = g_strdup((faddr != NULL)? faddr: spec);
6507 result = g_strconcat(result,
6509 (faddr != NULL)? faddr: spec,
6512 if (faddr != NULL) {
6517 slist_free_strings_full(list);
6522 #define IS_IN_CUSTOM_HEADER(header) \
6523 (compose->account->add_customhdr && \
6524 custom_header_find(compose->account->customhdr_list, header) != NULL)
6526 static const gchar *compose_untranslated_header_name(gchar *header_name)
6528 /* return the untranslated header name, if header_name is a known
6529 header name, in either its translated or untranslated form, with
6530 or without trailing colon. otherwise, returns header_name. */
6531 gchar *translated_header_name;
6532 gchar *translated_header_name_wcolon;
6533 const gchar *untranslated_header_name;
6534 const gchar *untranslated_header_name_wcolon;
6537 cm_return_val_if_fail(header_name != NULL, NULL);
6539 for (i = 0; HEADERS[i].header_name != NULL; i++) {
6540 untranslated_header_name = HEADERS[i].header_name;
6541 untranslated_header_name_wcolon = HEADERS[i].header_name_w_colon;
6543 translated_header_name = gettext(untranslated_header_name);
6544 translated_header_name_wcolon = gettext(untranslated_header_name_wcolon);
6546 if (!strcmp(header_name, untranslated_header_name) ||
6547 !strcmp(header_name, translated_header_name)) {
6548 return untranslated_header_name;
6550 if (!strcmp(header_name, untranslated_header_name_wcolon) ||
6551 !strcmp(header_name, translated_header_name_wcolon)) {
6552 return untranslated_header_name_wcolon;
6556 debug_print("compose_untranslated_header_name: unknown header '%s'\n", header_name);
6560 static void compose_add_headerfield_from_headerlist(Compose *compose,
6562 const gchar *fieldname,
6563 const gchar *seperator)
6565 gchar *str, *fieldname_w_colon;
6566 gboolean add_field = FALSE;
6568 ComposeHeaderEntry *headerentry;
6569 const gchar *headerentryname;
6570 const gchar *trans_fieldname;
6573 if (IS_IN_CUSTOM_HEADER(fieldname))
6576 debug_print("Adding %s-fields\n", fieldname);
6578 fieldstr = g_string_sized_new(64);
6580 fieldname_w_colon = g_strconcat(fieldname, ":", NULL);
6581 trans_fieldname = prefs_common_translated_header_name(fieldname_w_colon);
6583 for (list = compose->header_list; list; list = list->next) {
6584 headerentry = ((ComposeHeaderEntry *)list->data);
6585 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
6587 if (!g_utf8_collate(trans_fieldname, headerentryname)) {
6588 gchar * ustr = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
6590 str = compose_quote_list_of_addresses(ustr);
6592 if (str != NULL && str[0] != '\0') {
6594 g_string_append(fieldstr, seperator);
6595 g_string_append(fieldstr, str);
6604 buf = g_new0(gchar, fieldstr->len * 4 + 256);
6605 compose_convert_header
6606 (compose, buf, fieldstr->len * 4 + 256, fieldstr->str,
6607 strlen(fieldname) + 2, TRUE);
6608 g_string_append_printf(header, "%s: %s\n", fieldname, buf);
6612 g_free(fieldname_w_colon);
6613 g_string_free(fieldstr, TRUE);
6618 static gchar *compose_get_manual_headers_info(Compose *compose)
6620 GString *sh_header = g_string_new(" ");
6622 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6624 for (list = compose->header_list; list; list = list->next) {
6625 ComposeHeaderEntry *headerentry;
6628 gchar *headername_wcolon;
6629 const gchar *headername_trans;
6631 gboolean standard_header = FALSE;
6633 headerentry = ((ComposeHeaderEntry *)list->data);
6635 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6637 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6642 if (!strstr(tmp, ":")) {
6643 headername_wcolon = g_strconcat(tmp, ":", NULL);
6644 headername = g_strdup(tmp);
6646 headername_wcolon = g_strdup(tmp);
6647 headername = g_strdup(strtok(tmp, ":"));
6651 string = std_headers;
6652 while (*string != NULL) {
6653 headername_trans = prefs_common_translated_header_name(*string);
6654 if (!strcmp(headername_trans, headername_wcolon))
6655 standard_header = TRUE;
6658 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
6659 g_string_append_printf(sh_header, "%s ", headername);
6661 g_free(headername_wcolon);
6663 g_string_truncate(sh_header, strlen(sh_header->str) - 1); /* remove last space */
6664 return g_string_free(sh_header, FALSE);
6667 static gchar *compose_get_header(Compose *compose)
6669 gchar date[RFC822_DATE_BUFFSIZE];
6670 gchar buf[BUFFSIZE];
6671 const gchar *entry_str;
6675 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6677 gchar *from_name = NULL, *from_address = NULL;
6680 cm_return_val_if_fail(compose->account != NULL, NULL);
6681 cm_return_val_if_fail(compose->account->address != NULL, NULL);
6683 header = g_string_sized_new(64);
6686 if (prefs_common.hide_timezone)
6687 get_rfc822_date_hide_tz(date, sizeof(date));
6689 get_rfc822_date(date, sizeof(date));
6690 g_string_append_printf(header, "Date: %s\n", date);
6694 if (compose->account->name && *compose->account->name) {
6696 QUOTE_IF_REQUIRED(buf, compose->account->name);
6697 tmp = g_strdup_printf("%s <%s>",
6698 buf, compose->account->address);
6700 tmp = g_strdup_printf("%s",
6701 compose->account->address);
6703 if (!strcmp(gtk_entry_get_text(GTK_ENTRY(compose->from_name)), tmp)
6704 || strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) == 0) {
6706 from_name = compose->account->name ? g_strdup(compose->account->name):NULL;
6707 from_address = g_strdup(compose->account->address);
6709 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
6710 /* extract name and address */
6711 if (strstr(spec, " <") && strstr(spec, ">")) {
6712 from_address = g_strdup(strrchr(spec, '<')+1);
6713 *(strrchr(from_address, '>')) = '\0';
6714 from_name = g_strdup(spec);
6715 *(strrchr(from_name, '<')) = '\0';
6718 from_address = g_strdup(spec);
6725 if (from_name && *from_name) {
6727 compose_convert_header
6728 (compose, buf, sizeof(buf), from_name,
6729 strlen("From: "), TRUE);
6730 QUOTE_IF_REQUIRED(name, buf);
6731 qname = escape_internal_quotes(name, '"');
6733 g_string_append_printf(header, "From: %s <%s>\n",
6734 qname, from_address);
6735 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To") &&
6736 compose->return_receipt) {
6737 compose_convert_header(compose, buf, sizeof(buf), from_name,
6738 strlen("Disposition-Notification-To: "),
6740 g_string_append_printf(header, "Disposition-Notification-To: %s <%s>\n", buf, from_address);
6745 g_string_append_printf(header, "From: %s\n", from_address);
6746 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To") &&
6747 compose->return_receipt)
6748 g_string_append_printf(header, "Disposition-Notification-To: %s\n", from_address);
6752 g_free(from_address);
6755 compose_add_headerfield_from_headerlist(compose, header, "To", ", ");
6758 compose_add_headerfield_from_headerlist(compose, header, "Newsgroups", ",");
6761 compose_add_headerfield_from_headerlist(compose, header, "Cc", ", ");
6765 * If this account is a NNTP account remove Bcc header from
6766 * message body since it otherwise will be publicly shown
6768 if (compose->account->protocol != A_NNTP)
6769 compose_add_headerfield_from_headerlist(compose, header, "Bcc", ", ");
6772 str = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
6774 if (*str != '\0' && !IS_IN_CUSTOM_HEADER("Subject")) {
6777 compose_convert_header(compose, buf, sizeof(buf), str,
6778 strlen("Subject: "), FALSE);
6779 g_string_append_printf(header, "Subject: %s\n", buf);
6785 if (compose->msgid != NULL && strlen(compose->msgid) > 0) {
6786 g_string_append_printf(header, "Message-ID: <%s>\n",
6790 if (compose->remove_references == FALSE) {
6792 if (compose->inreplyto && compose->to_list)
6793 g_string_append_printf(header, "In-Reply-To: <%s>\n", compose->inreplyto);
6796 if (compose->references)
6797 g_string_append_printf(header, "References: %s\n", compose->references);
6801 compose_add_headerfield_from_headerlist(compose, header, "Followup-To", ",");
6804 compose_add_headerfield_from_headerlist(compose, header, "Reply-To", ", ");
6807 if (compose->account->organization &&
6808 strlen(compose->account->organization) &&
6809 !IS_IN_CUSTOM_HEADER("Organization")) {
6810 compose_convert_header(compose, buf, sizeof(buf),
6811 compose->account->organization,
6812 strlen("Organization: "), FALSE);
6813 g_string_append_printf(header, "Organization: %s\n", buf);
6816 /* Program version and system info */
6817 if (compose->account->gen_xmailer &&
6818 g_slist_length(compose->to_list) && !IS_IN_CUSTOM_HEADER("X-Mailer") &&
6819 !compose->newsgroup_list) {
6820 g_string_append_printf(header, "X-Mailer: %s (GTK+ %d.%d.%d; %s)\n",
6822 gtk_major_version, gtk_minor_version, gtk_micro_version,
6825 if (compose->account->gen_xmailer &&
6826 g_slist_length(compose->newsgroup_list) && !IS_IN_CUSTOM_HEADER("X-Newsreader")) {
6827 g_string_append_printf(header, "X-Newsreader: %s (GTK+ %d.%d.%d; %s)\n",
6829 gtk_major_version, gtk_minor_version, gtk_micro_version,
6833 /* custom headers */
6834 if (compose->account->add_customhdr) {
6837 for (cur = compose->account->customhdr_list; cur != NULL;
6839 CustomHeader *chdr = (CustomHeader *)cur->data;
6841 if (custom_header_is_allowed(chdr->name)
6842 && chdr->value != NULL
6843 && *(chdr->value) != '\0') {
6844 compose_convert_header
6845 (compose, buf, sizeof(buf),
6847 strlen(chdr->name) + 2, FALSE);
6848 g_string_append_printf(header, "%s: %s\n", chdr->name, buf);
6853 /* Automatic Faces and X-Faces */
6854 if (get_account_xface (buf, sizeof(buf), compose->account->account_name) == 0) {
6855 g_string_append_printf(header, "X-Face: %s\n", buf);
6857 else if (get_default_xface (buf, sizeof(buf)) == 0) {
6858 g_string_append_printf(header, "X-Face: %s\n", buf);
6860 if (get_account_face (buf, sizeof(buf), compose->account->account_name) == 0) {
6861 g_string_append_printf(header, "Face: %s\n", buf);
6863 else if (get_default_face (buf, sizeof(buf)) == 0) {
6864 g_string_append_printf(header, "Face: %s\n", buf);
6868 switch (compose->priority) {
6869 case PRIORITY_HIGHEST: g_string_append_printf(header, "Importance: high\n"
6870 "X-Priority: 1 (Highest)\n");
6872 case PRIORITY_HIGH: g_string_append_printf(header, "Importance: high\n"
6873 "X-Priority: 2 (High)\n");
6875 case PRIORITY_NORMAL: break;
6876 case PRIORITY_LOW: g_string_append_printf(header, "Importance: low\n"
6877 "X-Priority: 4 (Low)\n");
6879 case PRIORITY_LOWEST: g_string_append_printf(header, "Importance: low\n"
6880 "X-Priority: 5 (Lowest)\n");
6882 default: debug_print("compose: priority unknown : %d\n",
6886 /* get special headers */
6887 for (list = compose->header_list; list; list = list->next) {
6888 ComposeHeaderEntry *headerentry;
6891 gchar *headername_wcolon;
6892 const gchar *headername_trans;
6895 gboolean standard_header = FALSE;
6897 headerentry = ((ComposeHeaderEntry *)list->data);
6899 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6901 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6906 if (!strstr(tmp, ":")) {
6907 headername_wcolon = g_strconcat(tmp, ":", NULL);
6908 headername = g_strdup(tmp);
6910 headername_wcolon = g_strdup(tmp);
6911 headername = g_strdup(strtok(tmp, ":"));
6915 entry_str = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
6916 Xstrdup_a(headervalue, entry_str, return NULL);
6917 subst_char(headervalue, '\r', ' ');
6918 subst_char(headervalue, '\n', ' ');
6919 g_strstrip(headervalue);
6920 if (*headervalue != '\0') {
6921 string = std_headers;
6922 while (*string != NULL && !standard_header) {
6923 headername_trans = prefs_common_translated_header_name(*string);
6924 /* support mixed translated and untranslated headers */
6925 if (!strcmp(headername_trans, headername_wcolon) || !strcmp(*string, headername_wcolon))
6926 standard_header = TRUE;
6929 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername)) {
6930 /* store untranslated header name */
6931 g_string_append_printf(header, "%s %s\n",
6932 compose_untranslated_header_name(headername_wcolon), headervalue);
6936 g_free(headername_wcolon);
6940 g_string_free(header, FALSE);
6945 #undef IS_IN_CUSTOM_HEADER
6947 static void compose_convert_header(Compose *compose, gchar *dest, gint len, gchar *src,
6948 gint header_len, gboolean addr_field)
6950 gchar *tmpstr = NULL;
6951 const gchar *out_codeset = NULL;
6953 cm_return_if_fail(src != NULL);
6954 cm_return_if_fail(dest != NULL);
6956 if (len < 1) return;
6958 tmpstr = g_strdup(src);
6960 subst_char(tmpstr, '\n', ' ');
6961 subst_char(tmpstr, '\r', ' ');
6964 if (!g_utf8_validate(tmpstr, -1, NULL)) {
6965 gchar *mybuf = g_malloc(strlen(tmpstr)*2 +1);
6966 conv_localetodisp(mybuf, strlen(tmpstr)*2 +1, tmpstr);
6971 codeconv_set_strict(TRUE);
6972 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6973 conv_get_charset_str(compose->out_encoding));
6974 codeconv_set_strict(FALSE);
6976 if (!dest || *dest == '\0') {
6977 gchar *test_conv_global_out = NULL;
6978 gchar *test_conv_reply = NULL;
6980 /* automatic mode. be automatic. */
6981 codeconv_set_strict(TRUE);
6983 out_codeset = conv_get_outgoing_charset_str();
6985 debug_print("trying to convert to %s\n", out_codeset);
6986 test_conv_global_out = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6989 if (!test_conv_global_out && compose->orig_charset
6990 && strcmp(compose->orig_charset, CS_US_ASCII)) {
6991 out_codeset = compose->orig_charset;
6992 debug_print("failure; trying to convert to %s\n", out_codeset);
6993 test_conv_reply = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6996 if (!test_conv_global_out && !test_conv_reply) {
6998 out_codeset = CS_INTERNAL;
6999 debug_print("finally using %s\n", out_codeset);
7001 g_free(test_conv_global_out);
7002 g_free(test_conv_reply);
7003 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
7005 codeconv_set_strict(FALSE);
7010 static void compose_add_to_addressbook_cb(GtkMenuItem *menuitem, gpointer user_data)
7014 cm_return_if_fail(user_data != NULL);
7016 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(user_data)));
7017 g_strstrip(address);
7018 if (*address != '\0') {
7019 gchar *name = procheader_get_fromname(address);
7020 extract_address(address);
7021 #ifndef USE_ALT_ADDRBOOK
7022 addressbook_add_contact(name, address, NULL, NULL);
7024 debug_print("%s: %s\n", name, address);
7025 if (addressadd_selection(name, address, NULL, NULL)) {
7026 debug_print( "addressbook_add_contact - added\n" );
7033 static void compose_entry_popup_extend(GtkEntry *entry, GtkMenu *menu, gpointer user_data)
7035 GtkWidget *menuitem;
7038 cm_return_if_fail(menu != NULL);
7039 cm_return_if_fail(GTK_IS_MENU_SHELL(menu));
7041 menuitem = gtk_separator_menu_item_new();
7042 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
7043 gtk_widget_show(menuitem);
7045 menuitem = gtk_menu_item_new_with_mnemonic(_("Add to address _book"));
7046 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
7048 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
7049 g_strstrip(address);
7050 if (*address == '\0') {
7051 gtk_widget_set_sensitive(GTK_WIDGET(menuitem), FALSE);
7054 g_signal_connect(G_OBJECT(menuitem), "activate",
7055 G_CALLBACK(compose_add_to_addressbook_cb), entry);
7056 gtk_widget_show(menuitem);
7059 void compose_add_extra_header(gchar *header, GtkListStore *model)
7062 if (strcmp(header, "")) {
7063 COMBOBOX_ADD(model, header, COMPOSE_TO);
7067 void compose_add_extra_header_entries(GtkListStore *model)
7071 gchar buf[BUFFSIZE];
7074 if (extra_headers == NULL) {
7075 exhrc = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, "extraheaderrc", NULL);
7076 if ((exh = claws_fopen(exhrc, "rb")) == NULL) {
7077 debug_print("extra headers file not found\n");
7078 goto extra_headers_done;
7080 while (claws_fgets(buf, BUFFSIZE, exh) != NULL) {
7081 lastc = strlen(buf) - 1; /* remove trailing control chars */
7082 while (lastc >= 0 && buf[lastc] != ':')
7083 buf[lastc--] = '\0';
7084 if (lastc > 0 && buf[0] != '#' && buf[lastc] == ':') {
7085 buf[lastc] = '\0'; /* remove trailing : for comparison */
7086 if (custom_header_is_allowed(buf)) {
7088 extra_headers = g_slist_prepend(extra_headers, g_strdup(buf));
7091 g_message("disallowed extra header line: %s\n", buf);
7095 g_message("invalid extra header line: %s\n", buf);
7101 extra_headers = g_slist_prepend(extra_headers, g_strdup("")); /* end of list */
7102 extra_headers = g_slist_reverse(extra_headers);
7104 g_slist_foreach(extra_headers, (GFunc)compose_add_extra_header, (gpointer)model);
7108 static void _ldap_srv_func(gpointer data, gpointer user_data)
7110 LdapServer *server = (LdapServer *)data;
7111 gboolean *enable = (gboolean *)user_data;
7113 debug_print("%s server '%s'\n", (*enable == TRUE ? "enabling" : "disabling"), server->control->hostName);
7114 server->searchFlag = *enable;
7118 static void compose_create_header_entry(Compose *compose)
7120 gchar *headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
7127 const gchar *header = NULL;
7128 ComposeHeaderEntry *headerentry;
7129 gboolean standard_header = FALSE;
7130 GtkListStore *model;
7133 headerentry = g_new0(ComposeHeaderEntry, 1);
7135 /* Combo box model */
7136 model = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_BOOLEAN);
7137 COMBOBOX_ADD(model, prefs_common_translated_header_name("To:"),
7139 COMBOBOX_ADD(model, prefs_common_translated_header_name("Cc:"),
7141 COMBOBOX_ADD(model, prefs_common_translated_header_name("Bcc:"),
7143 COMBOBOX_ADD(model, prefs_common_translated_header_name("Newsgroups:"),
7144 COMPOSE_NEWSGROUPS);
7145 COMBOBOX_ADD(model, prefs_common_translated_header_name("Reply-To:"),
7147 COMBOBOX_ADD(model, prefs_common_translated_header_name("Followup-To:"),
7148 COMPOSE_FOLLOWUPTO);
7149 compose_add_extra_header_entries(model);
7152 combo = gtk_combo_box_new_with_model_and_entry(GTK_TREE_MODEL(model));
7153 GtkCellRenderer *cell = gtk_cell_renderer_text_new();
7154 gtk_cell_renderer_set_alignment(cell, 0.0, 0.5);
7155 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), cell, TRUE);
7156 gtk_combo_box_set_entry_text_column(GTK_COMBO_BOX(combo), 0);
7157 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
7158 g_signal_connect(G_OBJECT(gtk_bin_get_child(GTK_BIN(combo))), "grab_focus",
7159 G_CALLBACK(compose_grab_focus_cb), compose);
7160 gtk_widget_show(combo);
7162 /* Putting only the combobox child into focus chain of its parent causes
7163 * the parent to be skipped when changing focus via Tab or Shift+Tab.
7164 * This eliminates need to pres Tab twice in order to really get from the
7165 * combobox to next widget. */
7167 l = g_list_prepend(l, gtk_bin_get_child(GTK_BIN(combo)));
7168 gtk_container_set_focus_chain(GTK_CONTAINER(combo), l);
7171 gtk_table_attach(GTK_TABLE(compose->header_table), combo, 0, 1,
7172 compose->header_nextrow, compose->header_nextrow+1,
7173 GTK_SHRINK, GTK_FILL, 0, 0);
7174 if (compose->header_last && (compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN)) {
7175 const gchar *last_header_entry = gtk_entry_get_text(
7176 GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
7178 while (*string != NULL) {
7179 if (!strcmp(prefs_common_translated_header_name(*string), last_header_entry))
7180 standard_header = TRUE;
7183 if (standard_header)
7184 header = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
7186 if (!compose->header_last || !standard_header) {
7187 switch(compose->account->protocol) {
7189 header = prefs_common_translated_header_name("Newsgroups:");
7192 header = prefs_common_translated_header_name("To:");
7197 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((combo)))), header);
7199 gtk_editable_set_editable(
7200 GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((combo)))),
7201 prefs_common.type_any_header);
7203 g_signal_connect_after(G_OBJECT(gtk_bin_get_child(GTK_BIN((combo)))), "grab_focus",
7204 G_CALLBACK(compose_grab_focus_cb), compose);
7206 /* Entry field with cleanup button */
7207 button = gtk_button_new();
7208 gtk_button_set_image(GTK_BUTTON(button),
7209 gtk_image_new_from_stock(GTK_STOCK_CLEAR, GTK_ICON_SIZE_MENU));
7210 gtk_widget_show(button);
7211 CLAWS_SET_TIP(button,
7212 _("Delete entry contents"));
7213 entry = gtk_entry_new();
7214 gtk_widget_show(entry);
7215 CLAWS_SET_TIP(entry,
7216 _("Use <tab> to autocomplete from addressbook"));
7217 hbox = gtk_hbox_new (FALSE, 0);
7218 gtk_widget_show(hbox);
7219 gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, TRUE, 0);
7220 gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
7221 gtk_table_attach(GTK_TABLE(compose->header_table), hbox, 1, 2,
7222 compose->header_nextrow, compose->header_nextrow+1,
7223 GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
7225 g_signal_connect(G_OBJECT(entry), "key-press-event",
7226 G_CALLBACK(compose_headerentry_key_press_event_cb),
7228 g_signal_connect(G_OBJECT(entry), "changed",
7229 G_CALLBACK(compose_headerentry_changed_cb),
7231 g_signal_connect_after(G_OBJECT(entry), "grab_focus",
7232 G_CALLBACK(compose_grab_focus_cb), compose);
7234 g_signal_connect(G_OBJECT(button), "clicked",
7235 G_CALLBACK(compose_headerentry_button_clicked_cb),
7239 gtk_drag_dest_set(entry, GTK_DEST_DEFAULT_ALL, compose_mime_types,
7240 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7241 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7242 g_signal_connect(G_OBJECT(entry), "drag_data_received",
7243 G_CALLBACK(compose_header_drag_received_cb),
7245 g_signal_connect(G_OBJECT(entry), "drag-drop",
7246 G_CALLBACK(compose_drag_drop),
7248 g_signal_connect(G_OBJECT(entry), "populate-popup",
7249 G_CALLBACK(compose_entry_popup_extend),
7253 #ifndef PASSWORD_CRYPTO_OLD
7254 GSList *pwd_servers = addrindex_get_password_protected_ldap_servers();
7255 if (pwd_servers != NULL && master_passphrase() == NULL) {
7256 gboolean enable = FALSE;
7257 debug_print("Master passphrase not available, disabling password-protected LDAP servers for this compose window.\n");
7258 /* Temporarily disable password-protected LDAP servers,
7259 * because user did not provide a master passphrase.
7260 * We can safely enable searchFlag on all servers in this list
7261 * later, since addrindex_get_password_protected_ldap_servers()
7262 * includes servers which have it enabled initially. */
7263 g_slist_foreach(pwd_servers, _ldap_srv_func, &enable);
7264 compose->passworded_ldap_servers = pwd_servers;
7266 #endif /* PASSWORD_CRYPTO_OLD */
7267 #endif /* USE_LDAP */
7269 address_completion_register_entry(GTK_ENTRY(entry), TRUE);
7271 headerentry->compose = compose;
7272 headerentry->combo = combo;
7273 headerentry->entry = entry;
7274 headerentry->button = button;
7275 headerentry->hbox = hbox;
7276 headerentry->headernum = compose->header_nextrow;
7277 headerentry->type = PREF_NONE;
7279 compose->header_nextrow++;
7280 compose->header_last = headerentry;
7281 compose->header_list =
7282 g_slist_append(compose->header_list,
7286 static void compose_add_header_entry(Compose *compose, const gchar *header,
7287 gchar *text, ComposePrefType pref_type)
7289 ComposeHeaderEntry *last_header = compose->header_last;
7290 gchar *tmp = g_strdup(text), *email;
7291 gboolean replyto_hdr;
7293 replyto_hdr = (!strcasecmp(header,
7294 prefs_common_translated_header_name("Reply-To:")) ||
7296 prefs_common_translated_header_name("Followup-To:")) ||
7298 prefs_common_translated_header_name("In-Reply-To:")));
7300 extract_address(tmp);
7301 email = g_utf8_strdown(tmp, -1);
7303 if (replyto_hdr == FALSE &&
7304 g_hash_table_lookup(compose->email_hashtable, email) != NULL)
7306 debug_print("Ignoring duplicate address - %s %s, pref_type: %d\n",
7307 header, text, (gint) pref_type);
7313 if (!strcasecmp(header, prefs_common_translated_header_name("In-Reply-To:")))
7314 gtk_entry_set_text(GTK_ENTRY(
7315 gtk_bin_get_child(GTK_BIN(last_header->combo))), header);
7317 combobox_select_by_text(GTK_COMBO_BOX(last_header->combo), header);
7318 gtk_entry_set_text(GTK_ENTRY(last_header->entry), text);
7319 last_header->type = pref_type;
7321 if (replyto_hdr == FALSE)
7322 g_hash_table_insert(compose->email_hashtable, email,
7323 GUINT_TO_POINTER(1));
7330 static void compose_destroy_headerentry(Compose *compose,
7331 ComposeHeaderEntry *headerentry)
7333 gchar *text = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
7336 extract_address(text);
7337 email = g_utf8_strdown(text, -1);
7338 g_hash_table_remove(compose->email_hashtable, email);
7342 gtk_widget_destroy(headerentry->combo);
7343 gtk_widget_destroy(headerentry->entry);
7344 gtk_widget_destroy(headerentry->button);
7345 gtk_widget_destroy(headerentry->hbox);
7346 g_free(headerentry);
7349 static void compose_remove_header_entries(Compose *compose)
7352 for (list = compose->header_list; list; list = list->next)
7353 compose_destroy_headerentry(compose, (ComposeHeaderEntry *)list->data);
7355 compose->header_last = NULL;
7356 g_slist_free(compose->header_list);
7357 compose->header_list = NULL;
7358 compose->header_nextrow = 1;
7359 compose_create_header_entry(compose);
7362 static GtkWidget *compose_create_header(Compose *compose)
7364 GtkWidget *from_optmenu_hbox;
7365 GtkWidget *header_table_main;
7366 GtkWidget *header_scrolledwin;
7367 GtkWidget *header_table;
7369 /* parent with account selection and from header */
7370 header_table_main = gtk_table_new(2, 2, FALSE);
7371 gtk_widget_show(header_table_main);
7372 gtk_container_set_border_width(GTK_CONTAINER(header_table_main), BORDER_WIDTH);
7374 from_optmenu_hbox = compose_account_option_menu_create(compose);
7375 gtk_table_attach(GTK_TABLE(header_table_main), from_optmenu_hbox,
7376 0, 2, 0, 1, GTK_EXPAND | GTK_FILL, GTK_SHRINK, 0, 0);
7378 /* child with header labels and entries */
7379 header_scrolledwin = gtk_scrolled_window_new(NULL, NULL);
7380 gtk_widget_show(header_scrolledwin);
7381 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
7383 header_table = gtk_table_new(2, 2, FALSE);
7384 gtk_widget_show(header_table);
7385 gtk_container_set_border_width(GTK_CONTAINER(header_table), 0);
7386 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin), header_table);
7387 gtk_container_set_focus_vadjustment(GTK_CONTAINER(header_table),
7388 gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(header_scrolledwin)));
7389 gtk_viewport_set_shadow_type(GTK_VIEWPORT(gtk_bin_get_child(GTK_BIN(header_scrolledwin))), GTK_SHADOW_NONE);
7391 gtk_table_attach(GTK_TABLE(header_table_main), header_scrolledwin,
7392 0, 2, 1, 2, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 2);
7394 compose->header_table = header_table;
7395 compose->header_list = NULL;
7396 compose->header_nextrow = 0;
7398 compose_create_header_entry(compose);
7400 compose->table = NULL;
7402 return header_table_main;
7405 static gboolean popup_attach_button_pressed(GtkWidget *widget, gpointer data)
7407 Compose *compose = (Compose *)data;
7408 GdkEventButton event;
7411 event.time = gtk_get_current_event_time();
7413 return attach_button_pressed(compose->attach_clist, &event, compose);
7416 static GtkWidget *compose_create_attach(Compose *compose)
7418 GtkWidget *attach_scrwin;
7419 GtkWidget *attach_clist;
7421 GtkListStore *store;
7422 GtkCellRenderer *renderer;
7423 GtkTreeViewColumn *column;
7424 GtkTreeSelection *selection;
7426 /* attachment list */
7427 attach_scrwin = gtk_scrolled_window_new(NULL, NULL);
7428 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(attach_scrwin),
7429 GTK_POLICY_AUTOMATIC,
7430 GTK_POLICY_AUTOMATIC);
7431 gtk_widget_set_size_request(attach_scrwin, -1, 80);
7433 store = gtk_list_store_new(N_ATTACH_COLS,
7439 G_TYPE_AUTO_POINTER,
7441 attach_clist = GTK_WIDGET(gtk_tree_view_new_with_model
7442 (GTK_TREE_MODEL(store)));
7443 gtk_container_add(GTK_CONTAINER(attach_scrwin), attach_clist);
7444 g_object_unref(store);
7446 renderer = gtk_cell_renderer_text_new();
7447 column = gtk_tree_view_column_new_with_attributes
7448 (_("Mime type"), renderer, "text",
7449 COL_MIMETYPE, NULL);
7450 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7452 renderer = gtk_cell_renderer_text_new();
7453 column = gtk_tree_view_column_new_with_attributes
7454 (_("Size"), renderer, "text",
7456 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7458 renderer = gtk_cell_renderer_text_new();
7459 column = gtk_tree_view_column_new_with_attributes
7460 (_("Name"), renderer, "text",
7462 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7464 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(attach_clist),
7465 prefs_common.use_stripes_everywhere);
7466 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(attach_clist));
7467 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
7469 g_signal_connect(G_OBJECT(attach_clist), "row_activated",
7470 G_CALLBACK(attach_selected), compose);
7471 g_signal_connect(G_OBJECT(attach_clist), "button_press_event",
7472 G_CALLBACK(attach_button_pressed), compose);
7473 g_signal_connect(G_OBJECT(attach_clist), "popup-menu",
7474 G_CALLBACK(popup_attach_button_pressed), compose);
7475 g_signal_connect(G_OBJECT(attach_clist), "key_press_event",
7476 G_CALLBACK(attach_key_pressed), compose);
7479 gtk_drag_dest_set(attach_clist,
7480 GTK_DEST_DEFAULT_ALL, compose_mime_types,
7481 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7482 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7483 g_signal_connect(G_OBJECT(attach_clist), "drag_data_received",
7484 G_CALLBACK(compose_attach_drag_received_cb),
7486 g_signal_connect(G_OBJECT(attach_clist), "drag-drop",
7487 G_CALLBACK(compose_drag_drop),
7490 compose->attach_scrwin = attach_scrwin;
7491 compose->attach_clist = attach_clist;
7493 return attach_scrwin;
7496 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose);
7498 static GtkWidget *compose_create_others(Compose *compose)
7501 GtkWidget *savemsg_checkbtn;
7502 GtkWidget *savemsg_combo;
7503 GtkWidget *savemsg_select;
7506 gchar *folderidentifier;
7508 /* Table for settings */
7509 table = gtk_table_new(3, 1, FALSE);
7510 gtk_container_set_border_width(GTK_CONTAINER(table), BORDER_WIDTH);
7511 gtk_widget_show(table);
7512 gtk_table_set_row_spacings(GTK_TABLE(table), VSPACING_NARROW);
7515 /* Save Message to folder */
7516 savemsg_checkbtn = gtk_check_button_new_with_label(_("Save Message to "));
7517 gtk_widget_show(savemsg_checkbtn);
7518 gtk_table_attach(GTK_TABLE(table), savemsg_checkbtn, 0, 1, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7519 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7520 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), prefs_common.savemsg);
7523 savemsg_combo = gtk_combo_box_text_new_with_entry();
7524 compose->savemsg_checkbtn = savemsg_checkbtn;
7525 compose->savemsg_combo = savemsg_combo;
7526 gtk_widget_show(savemsg_combo);
7528 if (prefs_common.compose_save_to_history)
7529 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(savemsg_combo),
7530 prefs_common.compose_save_to_history);
7531 gtk_table_attach(GTK_TABLE(table), savemsg_combo, 1, 2, rowcount, rowcount + 1, GTK_FILL|GTK_EXPAND, GTK_SHRINK, 0, 0);
7532 gtk_widget_set_sensitive(GTK_WIDGET(savemsg_combo), prefs_common.savemsg);
7533 g_signal_connect_after(G_OBJECT(savemsg_combo), "grab_focus",
7534 G_CALLBACK(compose_grab_focus_cb), compose);
7535 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7536 if (compose->account->set_sent_folder)
7537 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), TRUE);
7539 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), FALSE);
7540 gtk_widget_set_sensitive(GTK_WIDGET(savemsg_combo), TRUE);
7541 folderidentifier = folder_item_get_identifier(account_get_special_folder
7542 (compose->account, F_OUTBOX));
7543 compose_set_save_to(compose, folderidentifier);
7544 g_free(folderidentifier);
7547 savemsg_select = gtkut_get_browse_file_btn(_("_Browse"));
7548 gtk_widget_show(savemsg_select);
7549 gtk_table_attach(GTK_TABLE(table), savemsg_select, 2, 3, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7550 g_signal_connect(G_OBJECT(savemsg_select), "clicked",
7551 G_CALLBACK(compose_savemsg_select_cb),
7557 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose)
7562 dest = foldersel_folder_sel(NULL, FOLDER_SEL_COPY, NULL, FALSE,
7563 _("Select folder to save message to"));
7566 path = folder_item_get_identifier(dest);
7568 compose_set_save_to(compose, path);
7572 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry, gboolean wrap,
7573 GdkAtom clip, GtkTextIter *insert_place);
7576 static gboolean text_clicked(GtkWidget *text, GdkEventButton *event,
7580 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
7582 if (event->button == 3) {
7584 GtkTextIter sel_start, sel_end;
7585 gboolean stuff_selected;
7587 /* move the cursor to allow GtkAspell to check the word
7588 * under the mouse */
7589 if (event->x && event->y) {
7590 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7591 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7593 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7596 GtkTextMark *mark = gtk_text_buffer_get_insert(buffer);
7597 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7600 stuff_selected = gtk_text_buffer_get_selection_bounds(
7602 &sel_start, &sel_end);
7604 gtk_text_buffer_place_cursor (buffer, &iter);
7605 /* reselect stuff */
7607 && gtk_text_iter_in_range(&iter, &sel_start, &sel_end)) {
7608 gtk_text_buffer_select_range(buffer,
7609 &sel_start, &sel_end);
7611 return FALSE; /* pass the event so that the right-click goes through */
7614 if (event->button == 2) {
7619 /* get the middle-click position to paste at the correct place */
7620 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7621 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7623 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7626 entry_paste_clipboard(compose, text,
7627 prefs_common.linewrap_pastes,
7628 GDK_SELECTION_PRIMARY, &iter);
7636 static void compose_spell_menu_changed(void *data)
7638 Compose *compose = (Compose *)data;
7640 GtkWidget *menuitem;
7641 GtkWidget *parent_item;
7642 GtkMenu *menu = GTK_MENU(gtk_menu_new());
7645 if (compose->gtkaspell == NULL)
7648 parent_item = gtk_ui_manager_get_widget(compose->ui_manager,
7649 "/Menu/Spelling/Options");
7651 /* setting the submenu removes /Spelling/Options from the factory
7652 * so we need to save it */
7654 if (parent_item == NULL) {
7655 parent_item = compose->aspell_options_menu;
7656 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), NULL);
7658 compose->aspell_options_menu = parent_item;
7660 spell_menu = gtkaspell_make_config_menu(compose->gtkaspell);
7662 spell_menu = g_slist_reverse(spell_menu);
7663 for (items = spell_menu;
7664 items; items = items->next) {
7665 menuitem = GTK_WIDGET(GTK_MENU_ITEM(items->data));
7666 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), GTK_WIDGET(menuitem));
7667 gtk_widget_show(GTK_WIDGET(menuitem));
7669 g_slist_free(spell_menu);
7671 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), GTK_WIDGET(menu));
7672 gtk_widget_show(parent_item);
7675 static void compose_dict_changed(void *data)
7677 Compose *compose = (Compose *) data;
7679 if(!compose->gtkaspell)
7681 if(compose->gtkaspell->recheck_when_changing_dict == FALSE)
7684 gtkaspell_highlight_all(compose->gtkaspell);
7685 claws_spell_entry_recheck_all(CLAWS_SPELL_ENTRY(compose->subject_entry));
7689 static gboolean compose_popup_menu(GtkWidget *widget, gpointer data)
7691 Compose *compose = (Compose *)data;
7692 GdkEventButton event;
7695 event.time = gtk_get_current_event_time();
7699 return text_clicked(compose->text, &event, compose);
7702 static gboolean compose_force_window_origin = TRUE;
7703 static Compose *compose_create(PrefsAccount *account,
7712 GtkWidget *handlebox;
7714 GtkWidget *notebook;
7716 GtkWidget *attach_hbox;
7717 GtkWidget *attach_lab1;
7718 GtkWidget *attach_lab2;
7723 GtkWidget *subject_hbox;
7724 GtkWidget *subject_frame;
7725 GtkWidget *subject_entry;
7729 GtkWidget *edit_vbox;
7730 GtkWidget *ruler_hbox;
7732 GtkWidget *scrolledwin;
7734 GtkTextBuffer *buffer;
7735 GtkClipboard *clipboard;
7737 UndoMain *undostruct;
7739 GtkWidget *popupmenu;
7740 GtkWidget *tmpl_menu;
7741 GtkActionGroup *action_group = NULL;
7744 GtkAspell * gtkaspell = NULL;
7747 static GdkGeometry geometry;
7749 cm_return_val_if_fail(account != NULL, NULL);
7751 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_DEFAULT_HEADER_BG],
7752 &default_header_bgcolor);
7753 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_DEFAULT_HEADER],
7754 &default_header_color);
7756 debug_print("Creating compose window...\n");
7757 compose = g_new0(Compose, 1);
7759 compose->batch = batch;
7760 compose->account = account;
7761 compose->folder = folder;
7763 compose->mutex = cm_mutex_new();
7764 compose->set_cursor_pos = -1;
7766 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose");
7768 gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
7769 gtk_widget_set_size_request(window, prefs_common.compose_width,
7770 prefs_common.compose_height);
7772 if (!geometry.max_width) {
7773 geometry.max_width = gdk_screen_width();
7774 geometry.max_height = gdk_screen_height();
7777 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7778 &geometry, GDK_HINT_MAX_SIZE);
7779 if (!geometry.min_width) {
7780 geometry.min_width = 600;
7781 geometry.min_height = 440;
7783 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7784 &geometry, GDK_HINT_MIN_SIZE);
7786 #ifndef GENERIC_UMPC
7787 if (compose_force_window_origin)
7788 gtk_window_move(GTK_WINDOW(window), prefs_common.compose_x,
7789 prefs_common.compose_y);
7791 g_signal_connect(G_OBJECT(window), "delete_event",
7792 G_CALLBACK(compose_delete_cb), compose);
7793 MANAGE_WINDOW_SIGNALS_CONNECT(window);
7794 gtk_widget_realize(window);
7796 gtkut_widget_set_composer_icon(window);
7798 vbox = gtk_vbox_new(FALSE, 0);
7799 gtk_container_add(GTK_CONTAINER(window), vbox);
7801 compose->ui_manager = gtk_ui_manager_new();
7802 action_group = cm_menu_create_action_group_full(compose->ui_manager,"Menu", compose_entries,
7803 G_N_ELEMENTS(compose_entries), (gpointer)compose);
7804 gtk_action_group_add_toggle_actions(action_group, compose_toggle_entries,
7805 G_N_ELEMENTS(compose_toggle_entries), (gpointer)compose);
7806 gtk_action_group_add_radio_actions(action_group, compose_radio_rm_entries,
7807 G_N_ELEMENTS(compose_radio_rm_entries), COMPOSE_REPLY, G_CALLBACK(compose_reply_change_mode_cb), (gpointer)compose);
7808 gtk_action_group_add_radio_actions(action_group, compose_radio_prio_entries,
7809 G_N_ELEMENTS(compose_radio_prio_entries), PRIORITY_NORMAL, G_CALLBACK(compose_set_priority_cb), (gpointer)compose);
7810 gtk_action_group_add_radio_actions(action_group, compose_radio_enc_entries,
7811 G_N_ELEMENTS(compose_radio_enc_entries), C_AUTO, G_CALLBACK(compose_set_encoding_cb), (gpointer)compose);
7813 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Menu", NULL, GTK_UI_MANAGER_MENUBAR)
7815 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Message", "Message", GTK_UI_MANAGER_MENU)
7816 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Edit", "Edit", GTK_UI_MANAGER_MENU)
7818 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Spelling", "Spelling", GTK_UI_MANAGER_MENU)
7820 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Options", "Options", GTK_UI_MANAGER_MENU)
7821 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Tools", "Tools", GTK_UI_MANAGER_MENU)
7822 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Help", "Help", GTK_UI_MANAGER_MENU)
7825 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Send", "Message/Send", GTK_UI_MANAGER_MENUITEM)
7826 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "SendLater", "Message/SendLater", GTK_UI_MANAGER_MENUITEM)
7827 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator1", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7828 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "AttachFile", "Message/AttachFile", GTK_UI_MANAGER_MENUITEM)
7829 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertFile", "Message/InsertFile", GTK_UI_MANAGER_MENUITEM)
7830 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertSig", "Message/InsertSig", GTK_UI_MANAGER_MENUITEM)
7831 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "ReplaceSig", "Message/ReplaceSig", GTK_UI_MANAGER_MENUITEM)
7832 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator2", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7833 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Save", "Message/Save", GTK_UI_MANAGER_MENUITEM)
7834 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator3", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7835 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Print", "Message/Print", GTK_UI_MANAGER_MENUITEM)
7836 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator4", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7837 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Close", "Message/Close", GTK_UI_MANAGER_MENUITEM)
7840 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Undo", "Edit/Undo", GTK_UI_MANAGER_MENUITEM)
7841 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Redo", "Edit/Redo", GTK_UI_MANAGER_MENUITEM)
7842 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator1", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7844 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Cut", "Edit/Cut", GTK_UI_MANAGER_MENUITEM)
7845 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Copy", "Edit/Copy", GTK_UI_MANAGER_MENUITEM)
7846 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Paste", "Edit/Paste", GTK_UI_MANAGER_MENUITEM)
7848 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SpecialPaste", "Edit/SpecialPaste", GTK_UI_MANAGER_MENU)
7849 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "AsQuotation", "Edit/SpecialPaste/AsQuotation", GTK_UI_MANAGER_MENUITEM)
7850 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Wrapped", "Edit/SpecialPaste/Wrapped", GTK_UI_MANAGER_MENUITEM)
7851 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Unwrapped", "Edit/SpecialPaste/Unwrapped", GTK_UI_MANAGER_MENUITEM)
7853 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SelectAll", "Edit/SelectAll", GTK_UI_MANAGER_MENUITEM)
7855 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Advanced", "Edit/Advanced", GTK_UI_MANAGER_MENU)
7856 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackChar", "Edit/Advanced/BackChar", GTK_UI_MANAGER_MENUITEM)
7857 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwChar", "Edit/Advanced/ForwChar", GTK_UI_MANAGER_MENUITEM)
7858 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackWord", "Edit/Advanced/BackWord", GTK_UI_MANAGER_MENUITEM)
7859 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwWord", "Edit/Advanced/ForwWord", GTK_UI_MANAGER_MENUITEM)
7860 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BegLine", "Edit/Advanced/BegLine", GTK_UI_MANAGER_MENUITEM)
7861 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "EndLine", "Edit/Advanced/EndLine", GTK_UI_MANAGER_MENUITEM)
7862 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "PrevLine", "Edit/Advanced/PrevLine", GTK_UI_MANAGER_MENUITEM)
7863 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "NextLine", "Edit/Advanced/NextLine", GTK_UI_MANAGER_MENUITEM)
7864 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackChar", "Edit/Advanced/DelBackChar", GTK_UI_MANAGER_MENUITEM)
7865 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwChar", "Edit/Advanced/DelForwChar", GTK_UI_MANAGER_MENUITEM)
7866 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackWord", "Edit/Advanced/DelBackWord", GTK_UI_MANAGER_MENUITEM)
7867 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwWord", "Edit/Advanced/DelForwWord", GTK_UI_MANAGER_MENUITEM)
7868 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelLine", "Edit/Advanced/DelLine", GTK_UI_MANAGER_MENUITEM)
7869 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelEndLine", "Edit/Advanced/DelEndLine", GTK_UI_MANAGER_MENUITEM)
7871 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator2", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7873 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Find", "Edit/Find", GTK_UI_MANAGER_MENUITEM)
7874 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapPara", "Edit/WrapPara", GTK_UI_MANAGER_MENUITEM)
7875 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapAllLines", "Edit/WrapAllLines", GTK_UI_MANAGER_MENUITEM)
7876 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoWrap", "Edit/AutoWrap", GTK_UI_MANAGER_MENUITEM)
7877 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoIndent", "Edit/AutoIndent", GTK_UI_MANAGER_MENUITEM)
7879 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator3", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7881 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "ExtEditor", "Edit/ExtEditor", GTK_UI_MANAGER_MENUITEM)
7885 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckAllSel", "Spelling/CheckAllSel", GTK_UI_MANAGER_MENUITEM)
7886 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "HighlightAll", "Spelling/HighlightAll", GTK_UI_MANAGER_MENUITEM)
7887 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckBackwards", "Spelling/CheckBackwards", GTK_UI_MANAGER_MENUITEM)
7888 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "ForwardNext", "Spelling/ForwardNext", GTK_UI_MANAGER_MENUITEM)
7889 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Separator1", "Spelling/---", GTK_UI_MANAGER_SEPARATOR)
7890 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Options", "Spelling/Options", GTK_UI_MANAGER_MENU)
7894 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "ReplyMode", "Options/ReplyMode", GTK_UI_MANAGER_MENU)
7895 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Normal", "Options/ReplyMode/Normal", GTK_UI_MANAGER_MENUITEM)
7896 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "All", "Options/ReplyMode/All", GTK_UI_MANAGER_MENUITEM)
7897 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Sender", "Options/ReplyMode/Sender", GTK_UI_MANAGER_MENUITEM)
7898 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "List", "Options/ReplyMode/List", GTK_UI_MANAGER_MENUITEM)
7900 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator1", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7901 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "PrivacySystem", "Options/PrivacySystem", GTK_UI_MANAGER_MENU)
7902 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/PrivacySystem", "PlaceHolder", "Options/PrivacySystem/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7903 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Sign", "Options/Sign", GTK_UI_MANAGER_MENUITEM)
7904 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encrypt", "Options/Encrypt", GTK_UI_MANAGER_MENUITEM)
7907 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator2", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7908 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Priority", "Options/Priority", GTK_UI_MANAGER_MENU)
7909 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Highest", "Options/Priority/Highest", GTK_UI_MANAGER_MENUITEM)
7910 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "High", "Options/Priority/High", GTK_UI_MANAGER_MENUITEM)
7911 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Normal", "Options/Priority/Normal", GTK_UI_MANAGER_MENUITEM)
7912 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Low", "Options/Priority/Low", GTK_UI_MANAGER_MENUITEM)
7913 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Lowest", "Options/Priority/Lowest", GTK_UI_MANAGER_MENUITEM)
7915 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator3", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7916 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RequestRetRcpt", "Options/RequestRetRcpt", GTK_UI_MANAGER_MENUITEM)
7917 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator4", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7918 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RemoveReferences", "Options/RemoveReferences", GTK_UI_MANAGER_MENUITEM)
7919 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator5", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7921 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encoding", "Options/Encoding", GTK_UI_MANAGER_MENU)
7923 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_AUTO, "Options/Encoding/"CS_AUTO, GTK_UI_MANAGER_MENUITEM)
7924 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator1", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7925 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_US_ASCII, "Options/Encoding/"CS_US_ASCII, GTK_UI_MANAGER_MENUITEM)
7926 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_UTF_8, "Options/Encoding/"CS_UTF_8, GTK_UI_MANAGER_MENUITEM)
7927 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator2", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7929 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Western", "Options/Encoding/Western", GTK_UI_MANAGER_MENU)
7930 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)
7931 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)
7932 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Western", CS_WINDOWS_1252, "Options/Encoding/Western/"CS_WINDOWS_1252, GTK_UI_MANAGER_MENUITEM)
7934 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_2, "Options/Encoding/"CS_ISO_8859_2, GTK_UI_MANAGER_MENUITEM)
7936 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Baltic", "Options/Encoding/Baltic", GTK_UI_MANAGER_MENU)
7937 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)
7938 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)
7940 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_7, "Options/Encoding/"CS_ISO_8859_7, GTK_UI_MANAGER_MENUITEM)
7942 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Hebrew", "Options/Encoding/Hebrew", GTK_UI_MANAGER_MENU)
7943 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)
7944 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Hebrew", CS_WINDOWS_1255, "Options/Encoding/Hebrew/"CS_WINDOWS_1255, GTK_UI_MANAGER_MENUITEM)
7946 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Arabic", "Options/Encoding/Arabic", GTK_UI_MANAGER_MENU)
7947 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)
7948 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Arabic", CS_WINDOWS_1256, "Options/Encoding/Arabic/"CS_WINDOWS_1256, GTK_UI_MANAGER_MENUITEM)
7950 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_9, "Options/Encoding/"CS_ISO_8859_9, GTK_UI_MANAGER_MENUITEM)
7952 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Cyrillic", "Options/Encoding/Cyrillic", GTK_UI_MANAGER_MENU)
7953 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)
7954 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_R, "Options/Encoding/Cyrillic/"CS_KOI8_R, GTK_UI_MANAGER_MENUITEM)
7955 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_MACCYR, "Options/Encoding/Cyrillic/"CS_MACCYR, GTK_UI_MANAGER_MENUITEM)
7956 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_U, "Options/Encoding/Cyrillic/"CS_KOI8_U, GTK_UI_MANAGER_MENUITEM)
7957 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_WINDOWS_1251, "Options/Encoding/Cyrillic/"CS_WINDOWS_1251, GTK_UI_MANAGER_MENUITEM)
7959 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Japanese", "Options/Encoding/Japanese", GTK_UI_MANAGER_MENU)
7960 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)
7961 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)
7962 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_EUC_JP, "Options/Encoding/Japanese/"CS_EUC_JP, GTK_UI_MANAGER_MENUITEM)
7963 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_SHIFT_JIS, "Options/Encoding/Japanese/"CS_SHIFT_JIS, GTK_UI_MANAGER_MENUITEM)
7965 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Chinese", "Options/Encoding/Chinese", GTK_UI_MANAGER_MENU)
7966 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB18030, "Options/Encoding/Chinese/"CS_GB18030, GTK_UI_MANAGER_MENUITEM)
7967 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB2312, "Options/Encoding/Chinese/"CS_GB2312, GTK_UI_MANAGER_MENUITEM)
7968 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GBK, "Options/Encoding/Chinese/"CS_GBK, GTK_UI_MANAGER_MENUITEM)
7969 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_BIG5, "Options/Encoding/Chinese/"CS_BIG5, GTK_UI_MANAGER_MENUITEM)
7970 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_EUC_TW, "Options/Encoding/Chinese/"CS_EUC_TW, GTK_UI_MANAGER_MENUITEM)
7972 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Korean", "Options/Encoding/Korean", GTK_UI_MANAGER_MENU)
7973 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Korean", CS_EUC_KR, "Options/Encoding/Korean/"CS_EUC_KR, GTK_UI_MANAGER_MENUITEM)
7974 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)
7976 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Thai", "Options/Encoding/Thai", GTK_UI_MANAGER_MENU)
7977 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_TIS_620, "Options/Encoding/Thai/"CS_TIS_620, GTK_UI_MANAGER_MENUITEM)
7978 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_WINDOWS_874, "Options/Encoding/Thai/"CS_WINDOWS_874, GTK_UI_MANAGER_MENUITEM)
7982 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "ShowRuler", "Tools/ShowRuler", GTK_UI_MANAGER_MENUITEM)
7983 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "AddressBook", "Tools/AddressBook", GTK_UI_MANAGER_MENUITEM)
7984 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Template", "Tools/Template", GTK_UI_MANAGER_MENU)
7985 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Template", "PlaceHolder", "Tools/Template/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7986 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Actions", "Tools/Actions", GTK_UI_MANAGER_MENU)
7987 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Actions", "PlaceHolder", "Tools/Actions/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7990 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Help", "About", "Help/About", GTK_UI_MANAGER_MENUITEM)
7992 menubar = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu");
7993 gtk_widget_show_all(menubar);
7995 gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(compose->ui_manager));
7996 gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, TRUE, 0);
7998 if (prefs_common.toolbar_detachable) {
7999 handlebox = gtk_handle_box_new();
8001 handlebox = gtk_hbox_new(FALSE, 0);
8003 gtk_box_pack_start(GTK_BOX(vbox), handlebox, FALSE, FALSE, 0);
8005 gtk_widget_realize(handlebox);
8006 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, handlebox,
8009 vbox2 = gtk_vbox_new(FALSE, 2);
8010 gtk_box_pack_start(GTK_BOX(vbox), vbox2, TRUE, TRUE, 0);
8011 gtk_container_set_border_width(GTK_CONTAINER(vbox2), 0);
8014 notebook = gtk_notebook_new();
8015 gtk_widget_show(notebook);
8017 /* header labels and entries */
8018 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
8019 compose_create_header(compose),
8020 gtk_label_new_with_mnemonic(_("Hea_der")));
8021 /* attachment list */
8022 attach_hbox = gtk_hbox_new(FALSE, 0);
8023 gtk_widget_show(attach_hbox);
8025 attach_lab1 = gtk_label_new_with_mnemonic(_("_Attachments"));
8026 gtk_widget_show(attach_lab1);
8027 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab1, TRUE, TRUE, 0);
8029 attach_lab2 = gtk_label_new("");
8030 gtk_widget_show(attach_lab2);
8031 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab2, FALSE, FALSE, 0);
8033 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
8034 compose_create_attach(compose),
8037 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
8038 compose_create_others(compose),
8039 gtk_label_new_with_mnemonic(_("Othe_rs")));
8042 subject_hbox = gtk_hbox_new(FALSE, 0);
8043 gtk_widget_show(subject_hbox);
8045 subject_frame = gtk_frame_new(NULL);
8046 gtk_frame_set_shadow_type(GTK_FRAME(subject_frame), GTK_SHADOW_NONE);
8047 gtk_box_pack_start(GTK_BOX(subject_hbox), subject_frame, TRUE, TRUE, 0);
8048 gtk_widget_show(subject_frame);
8050 subject = gtk_hbox_new(FALSE, HSPACING_NARROW);
8051 gtk_container_set_border_width(GTK_CONTAINER(subject), 0);
8052 gtk_widget_show(subject);
8054 label = gtk_label_new_with_mnemonic(_("S_ubject:"));
8055 gtk_box_pack_start(GTK_BOX(subject), label, FALSE, FALSE, 0);
8056 gtk_widget_show(label);
8059 subject_entry = claws_spell_entry_new();
8061 subject_entry = gtk_entry_new();
8063 gtk_box_pack_start(GTK_BOX(subject), subject_entry, TRUE, TRUE, 0);
8064 g_signal_connect_after(G_OBJECT(subject_entry), "grab_focus",
8065 G_CALLBACK(compose_grab_focus_cb), compose);
8066 gtk_label_set_mnemonic_widget(GTK_LABEL(label), subject_entry);
8067 gtk_widget_show(subject_entry);
8068 compose->subject_entry = subject_entry;
8069 gtk_container_add(GTK_CONTAINER(subject_frame), subject);
8071 edit_vbox = gtk_vbox_new(FALSE, 0);
8073 gtk_box_pack_start(GTK_BOX(edit_vbox), subject_hbox, FALSE, FALSE, 0);
8076 ruler_hbox = gtk_hbox_new(FALSE, 0);
8077 gtk_box_pack_start(GTK_BOX(edit_vbox), ruler_hbox, FALSE, FALSE, 0);
8079 ruler = gtk_shruler_new(GTK_ORIENTATION_HORIZONTAL);
8080 gtk_shruler_set_range(GTK_SHRULER(ruler), 0.0, 100.0, 1.0);
8081 gtk_box_pack_start(GTK_BOX(ruler_hbox), ruler, TRUE, TRUE,
8085 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
8086 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
8087 GTK_POLICY_AUTOMATIC,
8088 GTK_POLICY_AUTOMATIC);
8089 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
8091 gtk_box_pack_start(GTK_BOX(edit_vbox), scrolledwin, TRUE, TRUE, 0);
8093 text = gtk_text_view_new();
8094 if (prefs_common.show_compose_margin) {
8095 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text), 6);
8096 gtk_text_view_set_right_margin(GTK_TEXT_VIEW(text), 6);
8098 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
8099 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
8100 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), TRUE);
8101 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
8102 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
8104 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
8105 g_signal_connect_after(G_OBJECT(text), "size_allocate",
8106 G_CALLBACK(compose_edit_size_alloc),
8108 g_signal_connect(G_OBJECT(buffer), "changed",
8109 G_CALLBACK(compose_changed_cb), compose);
8110 g_signal_connect(G_OBJECT(text), "grab_focus",
8111 G_CALLBACK(compose_grab_focus_cb), compose);
8112 g_signal_connect(G_OBJECT(buffer), "insert_text",
8113 G_CALLBACK(text_inserted), compose);
8114 g_signal_connect(G_OBJECT(text), "button_press_event",
8115 G_CALLBACK(text_clicked), compose);
8116 g_signal_connect(G_OBJECT(text), "popup-menu",
8117 G_CALLBACK(compose_popup_menu), compose);
8118 g_signal_connect(G_OBJECT(subject_entry), "changed",
8119 G_CALLBACK(compose_changed_cb), compose);
8120 g_signal_connect(G_OBJECT(subject_entry), "activate",
8121 G_CALLBACK(compose_subject_entry_activated), compose);
8124 gtk_drag_dest_set(text, GTK_DEST_DEFAULT_ALL, compose_mime_types,
8125 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
8126 GDK_ACTION_COPY | GDK_ACTION_MOVE);
8127 g_signal_connect(G_OBJECT(text), "drag_data_received",
8128 G_CALLBACK(compose_insert_drag_received_cb),
8130 g_signal_connect(G_OBJECT(text), "drag-drop",
8131 G_CALLBACK(compose_drag_drop),
8133 g_signal_connect(G_OBJECT(text), "key-press-event",
8134 G_CALLBACK(completion_set_focus_to_subject),
8136 gtk_widget_show_all(vbox);
8138 /* pane between attach clist and text */
8139 paned = gtk_vpaned_new();
8140 gtk_container_add(GTK_CONTAINER(vbox2), paned);
8141 gtk_paned_pack1(GTK_PANED(paned), notebook, FALSE, FALSE);
8142 gtk_paned_pack2(GTK_PANED(paned), edit_vbox, TRUE, FALSE);
8143 gtk_paned_set_position(GTK_PANED(paned), prefs_common.compose_notebook_height);
8144 g_signal_connect(G_OBJECT(notebook), "size_allocate",
8145 G_CALLBACK(compose_notebook_size_alloc), paned);
8147 gtk_widget_show_all(paned);
8150 if (prefs_common.textfont) {
8151 PangoFontDescription *font_desc;
8153 font_desc = pango_font_description_from_string
8154 (prefs_common.textfont);
8156 gtk_widget_modify_font(text, font_desc);
8157 pango_font_description_free(font_desc);
8161 gtk_action_group_add_actions(action_group, compose_popup_entries,
8162 G_N_ELEMENTS(compose_popup_entries), (gpointer)compose);
8163 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Popup", NULL, GTK_UI_MANAGER_MENUBAR)
8164 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup", "Compose", "Compose", GTK_UI_MANAGER_MENU)
8165 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Add", "Compose/Add", GTK_UI_MANAGER_MENUITEM)
8166 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Remove", "Compose/Remove", GTK_UI_MANAGER_MENUITEM)
8167 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Separator1", "Compose/---", GTK_UI_MANAGER_SEPARATOR)
8168 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Properties", "Compose/Properties", GTK_UI_MANAGER_MENUITEM)
8170 popupmenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(gtk_ui_manager_get_widget(compose->ui_manager, "/Popup/Compose")));
8172 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
8173 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
8174 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
8176 tmpl_menu = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu/Tools/Template");
8178 undostruct = undo_init(text);
8179 undo_set_change_state_func(undostruct, &compose_undo_state_changed,
8182 address_completion_start(window);
8184 compose->window = window;
8185 compose->vbox = vbox;
8186 compose->menubar = menubar;
8187 compose->handlebox = handlebox;
8189 compose->vbox2 = vbox2;
8191 compose->paned = paned;
8193 compose->attach_label = attach_lab2;
8195 compose->notebook = notebook;
8196 compose->edit_vbox = edit_vbox;
8197 compose->ruler_hbox = ruler_hbox;
8198 compose->ruler = ruler;
8199 compose->scrolledwin = scrolledwin;
8200 compose->text = text;
8202 compose->focused_editable = NULL;
8204 compose->popupmenu = popupmenu;
8206 compose->tmpl_menu = tmpl_menu;
8208 compose->mode = mode;
8209 compose->rmode = mode;
8211 compose->targetinfo = NULL;
8212 compose->replyinfo = NULL;
8213 compose->fwdinfo = NULL;
8215 compose->email_hashtable = g_hash_table_new_full(g_str_hash,
8216 g_str_equal, (GDestroyNotify) g_free, NULL);
8218 compose->replyto = NULL;
8220 compose->bcc = NULL;
8221 compose->followup_to = NULL;
8223 compose->ml_post = NULL;
8225 compose->inreplyto = NULL;
8226 compose->references = NULL;
8227 compose->msgid = NULL;
8228 compose->boundary = NULL;
8230 compose->autowrap = prefs_common.autowrap;
8231 compose->autoindent = prefs_common.auto_indent;
8232 compose->use_signing = FALSE;
8233 compose->use_encryption = FALSE;
8234 compose->privacy_system = NULL;
8235 compose->encdata = NULL;
8237 compose->modified = FALSE;
8239 compose->return_receipt = FALSE;
8241 compose->to_list = NULL;
8242 compose->newsgroup_list = NULL;
8244 compose->undostruct = undostruct;
8246 compose->sig_str = NULL;
8248 compose->exteditor_file = NULL;
8249 compose->exteditor_pid = -1;
8250 compose->exteditor_tag = -1;
8251 compose->exteditor_socket = NULL;
8252 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN; /* inhibit auto-drafting while loading */
8254 compose->folder_update_callback_id =
8255 hooks_register_hook(FOLDER_UPDATE_HOOKLIST,
8256 compose_update_folder_hook,
8257 (gpointer) compose);
8260 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
8261 if (mode != COMPOSE_REDIRECT) {
8262 if (prefs_common.enable_aspell && prefs_common.dictionary &&
8263 strcmp(prefs_common.dictionary, "")) {
8264 gtkaspell = gtkaspell_new(prefs_common.dictionary,
8265 prefs_common.alt_dictionary,
8266 conv_get_locale_charset_str(),
8267 prefs_common.color[COL_MISSPELLED],
8268 prefs_common.check_while_typing,
8269 prefs_common.recheck_when_changing_dict,
8270 prefs_common.use_alternate,
8271 prefs_common.use_both_dicts,
8272 GTK_TEXT_VIEW(text),
8273 GTK_WINDOW(compose->window),
8274 compose_dict_changed,
8275 compose_spell_menu_changed,
8278 alertpanel_error(_("Spell checker could not "
8280 gtkaspell_checkers_strerror());
8281 gtkaspell_checkers_reset_error();
8283 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", TRUE);
8287 compose->gtkaspell = gtkaspell;
8288 compose_spell_menu_changed(compose);
8289 claws_spell_entry_set_gtkaspell(CLAWS_SPELL_ENTRY(subject_entry), gtkaspell);
8292 compose_select_account(compose, account, TRUE);
8294 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", prefs_common.autowrap);
8295 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", prefs_common.auto_indent);
8297 if (account->set_autocc && account->auto_cc && mode != COMPOSE_REEDIT)
8298 compose_entry_append(compose, account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8300 if (account->set_autobcc && account->auto_bcc && mode != COMPOSE_REEDIT)
8301 compose_entry_append(compose, account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8303 if (account->set_autoreplyto && account->auto_replyto && mode != COMPOSE_REEDIT)
8304 compose_entry_append(compose, account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8306 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", compose->mode == COMPOSE_REPLY);
8307 if (account->protocol != A_NNTP)
8308 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
8309 prefs_common_translated_header_name("To:"));
8311 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
8312 prefs_common_translated_header_name("Newsgroups:"));
8314 #ifndef USE_ALT_ADDRBOOK
8315 addressbook_set_target_compose(compose);
8317 if (mode != COMPOSE_REDIRECT)
8318 compose_set_template_menu(compose);
8320 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Template", FALSE);
8323 compose_list = g_list_append(compose_list, compose);
8325 if (!prefs_common.show_ruler)
8326 gtk_widget_hide(ruler_hbox);
8328 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Tools/ShowRuler", prefs_common.show_ruler);
8331 compose->priority = PRIORITY_NORMAL;
8332 compose_update_priority_menu_item(compose);
8334 compose_set_out_encoding(compose);
8337 compose_update_actions_menu(compose);
8339 /* Privacy Systems menu */
8340 compose_update_privacy_systems_menu(compose);
8341 compose_activate_privacy_system(compose, account, TRUE);
8343 toolbar_set_style(compose->toolbar->toolbar, compose->handlebox, prefs_common.toolbar_style);
8345 gtk_widget_realize(window);
8347 gtk_widget_show(window);
8353 static GtkWidget *compose_account_option_menu_create(Compose *compose)
8358 GtkWidget *optmenubox;
8359 GtkWidget *fromlabel;
8362 GtkWidget *from_name = NULL;
8364 gint num = 0, def_menu = 0;
8366 accounts = account_get_list();
8367 cm_return_val_if_fail(accounts != NULL, NULL);
8369 optmenubox = gtk_event_box_new();
8370 optmenu = gtkut_sc_combobox_create(optmenubox, FALSE);
8371 menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
8373 hbox = gtk_hbox_new(FALSE, 4);
8374 from_name = gtk_entry_new();
8376 g_signal_connect_after(G_OBJECT(from_name), "grab_focus",
8377 G_CALLBACK(compose_grab_focus_cb), compose);
8378 g_signal_connect_after(G_OBJECT(from_name), "activate",
8379 G_CALLBACK(from_name_activate_cb), optmenu);
8381 for (; accounts != NULL; accounts = accounts->next, num++) {
8382 PrefsAccount *ac = (PrefsAccount *)accounts->data;
8383 gchar *name, *from = NULL;
8385 if (ac == compose->account) def_menu = num;
8387 name = g_markup_printf_escaped("<i>%s</i>",
8390 if (ac == compose->account) {
8391 if (ac->name && *ac->name) {
8393 QUOTE_IF_REQUIRED_NORMAL(buf, ac->name, return NULL);
8394 from = g_strdup_printf("%s <%s>",
8396 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8398 from = g_strdup_printf("%s",
8400 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8402 if (cur_account != compose->account) {
8403 gtk_widget_modify_base(
8404 GTK_WIDGET(from_name),
8405 GTK_STATE_NORMAL, &default_header_bgcolor);
8406 gtk_widget_modify_text(
8407 GTK_WIDGET(from_name),
8408 GTK_STATE_NORMAL, &default_header_color);
8411 COMBOBOX_ADD(menu, name, ac->account_id);
8416 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), def_menu);
8418 g_signal_connect(G_OBJECT(optmenu), "changed",
8419 G_CALLBACK(account_activated),
8421 g_signal_connect(G_OBJECT(from_name), "populate-popup",
8422 G_CALLBACK(compose_entry_popup_extend),
8425 fromlabel = gtk_label_new_with_mnemonic(_("_From:"));
8426 gtk_label_set_mnemonic_widget(GTK_LABEL(fromlabel), from_name);
8428 gtk_box_pack_start(GTK_BOX(hbox), fromlabel, FALSE, FALSE, 4);
8429 gtk_box_pack_start(GTK_BOX(hbox), optmenubox, FALSE, FALSE, 0);
8430 gtk_box_pack_start(GTK_BOX(hbox), from_name, TRUE, TRUE, 0);
8432 /* Putting only the GtkEntry into focus chain of parent hbox causes
8433 * the account selector combobox next to it to be unreachable when
8434 * navigating widgets in GtkTable with up/down arrow keys.
8435 * Note: gtk_widget_set_can_focus() was not enough. */
8437 l = g_list_prepend(l, from_name);
8438 gtk_container_set_focus_chain(GTK_CONTAINER(hbox), l);
8441 CLAWS_SET_TIP(optmenubox,
8442 _("Account to use for this email"));
8443 CLAWS_SET_TIP(from_name,
8444 _("Sender address to be used"));
8446 compose->account_combo = optmenu;
8447 compose->from_name = from_name;
8452 static void compose_set_priority_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8454 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8455 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8456 Compose *compose = (Compose *) data;
8458 compose->priority = value;
8462 static void compose_reply_change_mode(Compose *compose,
8465 gboolean was_modified = compose->modified;
8467 gboolean all = FALSE, ml = FALSE, sender = FALSE, followup = FALSE;
8469 cm_return_if_fail(compose->replyinfo != NULL);
8471 if (action == COMPOSE_REPLY && prefs_common.default_reply_list)
8473 if (action == COMPOSE_REPLY && compose->rmode == COMPOSE_FOLLOWUP_AND_REPLY_TO)
8475 if (action == COMPOSE_REPLY_TO_ALL)
8477 if (action == COMPOSE_REPLY_TO_SENDER)
8479 if (action == COMPOSE_REPLY_TO_LIST)
8482 compose_remove_header_entries(compose);
8483 compose_reply_set_entry(compose, compose->replyinfo, all, ml, sender, followup);
8484 if (compose->account->set_autocc && compose->account->auto_cc)
8485 compose_entry_append(compose, compose->account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8487 if (compose->account->set_autobcc && compose->account->auto_bcc)
8488 compose_entry_append(compose, compose->account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8490 if (compose->account->set_autoreplyto && compose->account->auto_replyto)
8491 compose_entry_append(compose, compose->account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8492 compose_show_first_last_header(compose, TRUE);
8493 compose->modified = was_modified;
8494 compose_set_title(compose);
8497 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8499 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8500 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8501 Compose *compose = (Compose *) data;
8504 compose_reply_change_mode(compose, value);
8507 static void compose_update_priority_menu_item(Compose * compose)
8509 GtkWidget *menuitem = NULL;
8510 switch (compose->priority) {
8511 case PRIORITY_HIGHEST:
8512 menuitem = gtk_ui_manager_get_widget
8513 (compose->ui_manager, "/Menu/Options/Priority/Highest");
8516 menuitem = gtk_ui_manager_get_widget
8517 (compose->ui_manager, "/Menu/Options/Priority/High");
8519 case PRIORITY_NORMAL:
8520 menuitem = gtk_ui_manager_get_widget
8521 (compose->ui_manager, "/Menu/Options/Priority/Normal");
8524 menuitem = gtk_ui_manager_get_widget
8525 (compose->ui_manager, "/Menu/Options/Priority/Low");
8527 case PRIORITY_LOWEST:
8528 menuitem = gtk_ui_manager_get_widget
8529 (compose->ui_manager, "/Menu/Options/Priority/Lowest");
8532 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8535 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data)
8537 Compose *compose = (Compose *) data;
8539 gboolean can_sign = FALSE, can_encrypt = FALSE;
8541 cm_return_if_fail(GTK_IS_CHECK_MENU_ITEM(widget));
8543 if (!gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget)))
8546 systemid = g_object_get_data(G_OBJECT(widget), "privacy_system");
8547 g_free(compose->privacy_system);
8548 compose->privacy_system = NULL;
8549 g_free(compose->encdata);
8550 compose->encdata = NULL;
8551 if (systemid != NULL) {
8552 compose->privacy_system = g_strdup(systemid);
8554 can_sign = privacy_system_can_sign(systemid);
8555 can_encrypt = privacy_system_can_encrypt(systemid);
8558 debug_print("activated privacy system: %s\n", systemid != NULL ? systemid : "None");
8560 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8561 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8562 if (compose->toolbar->privacy_sign_btn != NULL) {
8563 gtk_widget_set_sensitive(
8564 GTK_WIDGET(compose->toolbar->privacy_sign_btn),
8566 gtk_toggle_tool_button_set_active(
8567 GTK_TOGGLE_TOOL_BUTTON(compose->toolbar->privacy_sign_btn),
8568 can_sign ? compose->use_signing : FALSE);
8570 if (compose->toolbar->privacy_encrypt_btn != NULL) {
8571 gtk_widget_set_sensitive(
8572 GTK_WIDGET(compose->toolbar->privacy_encrypt_btn),
8574 gtk_toggle_tool_button_set_active(
8575 GTK_TOGGLE_TOOL_BUTTON(compose->toolbar->privacy_encrypt_btn),
8576 can_encrypt ? compose->use_encryption : FALSE);
8580 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn)
8582 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8583 GtkWidget *menuitem = NULL;
8584 GList *children, *amenu;
8585 gboolean can_sign = FALSE, can_encrypt = FALSE;
8586 gboolean found = FALSE;
8588 if (compose->privacy_system != NULL) {
8590 menuitem = gtk_menu_item_get_submenu(GTK_MENU_ITEM(
8591 gtk_ui_manager_get_widget(compose->ui_manager, branch_path)));
8592 cm_return_if_fail(menuitem != NULL);
8594 children = gtk_container_get_children(GTK_CONTAINER(GTK_MENU_SHELL(menuitem)));
8597 while (amenu != NULL) {
8598 systemid = g_object_get_data(G_OBJECT(amenu->data), "privacy_system");
8599 if (systemid != NULL) {
8600 if (strcmp(systemid, compose->privacy_system) == 0 &&
8601 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8602 menuitem = GTK_WIDGET(amenu->data);
8604 can_sign = privacy_system_can_sign(systemid);
8605 can_encrypt = privacy_system_can_encrypt(systemid);
8609 } else if (strlen(compose->privacy_system) == 0 &&
8610 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8611 menuitem = GTK_WIDGET(amenu->data);
8614 can_encrypt = FALSE;
8619 amenu = amenu->next;
8621 g_list_free(children);
8622 if (menuitem != NULL)
8623 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8625 if (warn && !found && strlen(compose->privacy_system)) {
8626 alertpanel_warning(_("The privacy system '%s' cannot be loaded. You "
8627 "will not be able to sign or encrypt this message."),
8628 compose->privacy_system);
8632 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8633 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8634 if (compose->toolbar->privacy_sign_btn != NULL) {
8635 gtk_widget_set_sensitive(
8636 GTK_WIDGET(compose->toolbar->privacy_sign_btn),
8639 if (compose->toolbar->privacy_encrypt_btn != NULL) {
8640 gtk_widget_set_sensitive(
8641 GTK_WIDGET(compose->toolbar->privacy_encrypt_btn),
8646 static void compose_set_out_encoding(Compose *compose)
8648 CharSet out_encoding;
8649 const gchar *branch = NULL;
8650 out_encoding = conv_get_charset_from_str(prefs_common.outgoing_charset);
8652 switch(out_encoding) {
8653 case C_AUTO: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8654 case C_US_ASCII: branch = "Menu/Options/Encoding/" CS_US_ASCII; break;
8655 case C_UTF_8: branch = "Menu/Options/Encoding/" CS_UTF_8; break;
8656 case C_ISO_8859_2: branch = "Menu/Options/Encoding/" CS_ISO_8859_2; break;
8657 case C_ISO_8859_7: branch = "Menu/Options/Encoding/" CS_ISO_8859_7; break;
8658 case C_ISO_8859_9: branch = "Menu/Options/Encoding/" CS_ISO_8859_9; break;
8659 case C_ISO_8859_1: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_1; break;
8660 case C_ISO_8859_15: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_15; break;
8661 case C_WINDOWS_1252: branch = "Menu/Options/Encoding/Western/" CS_WINDOWS_1252; break;
8662 case C_ISO_8859_13: branch = "Menu/Options/Encoding/Baltic/" CS_ISO_8859_13; break;
8663 case C_ISO_8859_4: branch = "Menu/Options/Encoding/Baltic" CS_ISO_8859_4; break;
8664 case C_ISO_8859_8: branch = "Menu/Options/Encoding/Hebrew/" CS_ISO_8859_8; break;
8665 case C_WINDOWS_1255: branch = "Menu/Options/Encoding/Hebrew/" CS_WINDOWS_1255; break;
8666 case C_ISO_8859_6: branch = "Menu/Options/Encoding/Arabic/" CS_ISO_8859_6; break;
8667 case C_WINDOWS_1256: branch = "Menu/Options/Encoding/Arabic/" CS_WINDOWS_1256; break;
8668 case C_ISO_8859_5: branch = "Menu/Options/Encoding/Cyrillic/" CS_ISO_8859_5; break;
8669 case C_KOI8_R: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_R; break;
8670 case C_MACCYR: branch = "Menu/Options/Encoding/Cyrillic/" CS_MACCYR; break;
8671 case C_KOI8_U: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_U; break;
8672 case C_WINDOWS_1251: branch = "Menu/Options/Encoding/Cyrillic/" CS_WINDOWS_1251; break;
8673 case C_ISO_2022_JP: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP; break;
8674 case C_ISO_2022_JP_2: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP_2; break;
8675 case C_EUC_JP: branch = "Menu/Options/Encoding/Japanese/" CS_EUC_JP; break;
8676 case C_SHIFT_JIS: branch = "Menu/Options/Encoding/Japanese/" CS_SHIFT_JIS; break;
8677 case C_GB18030: branch = "Menu/Options/Encoding/Chinese/" CS_GB18030; break;
8678 case C_GB2312: branch = "Menu/Options/Encoding/Chinese/" CS_GB2312; break;
8679 case C_GBK: branch = "Menu/Options/Encoding/Chinese/" CS_GBK; break;
8680 case C_BIG5: branch = "Menu/Options/Encoding/Chinese/" CS_BIG5; break;
8681 case C_EUC_TW: branch = "Menu/Options/Encoding/Chinese/" CS_EUC_TW; break;
8682 case C_EUC_KR: branch = "Menu/Options/Encoding/Korean/" CS_EUC_KR; break;
8683 case C_ISO_2022_KR: branch = "Menu/Options/Encoding/Korean/" CS_ISO_2022_KR; break;
8684 case C_TIS_620: branch = "Menu/Options/Encoding/Thai/" CS_TIS_620; break;
8685 case C_WINDOWS_874: branch = "Menu/Options/Encoding/Thai/" CS_WINDOWS_874; break;
8686 default: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8688 cm_toggle_menu_set_active_full(compose->ui_manager, (gchar *)branch, TRUE);
8691 static void compose_set_template_menu(Compose *compose)
8693 GSList *tmpl_list, *cur;
8697 tmpl_list = template_get_config();
8699 menu = gtk_menu_new();
8701 gtk_menu_set_accel_group (GTK_MENU (menu),
8702 gtk_ui_manager_get_accel_group(compose->ui_manager));
8703 for (cur = tmpl_list; cur != NULL; cur = cur->next) {
8704 Template *tmpl = (Template *)cur->data;
8705 gchar *accel_path = NULL;
8706 item = gtk_menu_item_new_with_label(tmpl->name);
8707 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
8708 g_signal_connect(G_OBJECT(item), "activate",
8709 G_CALLBACK(compose_template_activate_cb),
8711 g_object_set_data(G_OBJECT(item), "template", tmpl);
8712 gtk_widget_show(item);
8713 accel_path = g_strconcat("<ComposeTemplates>" , "/", tmpl->name, NULL);
8714 gtk_menu_item_set_accel_path(GTK_MENU_ITEM(item), accel_path);
8718 gtk_widget_show(menu);
8719 gtk_menu_item_set_submenu(GTK_MENU_ITEM(compose->tmpl_menu), menu);
8722 void compose_update_actions_menu(Compose *compose)
8724 action_update_compose_menu(compose->ui_manager, "/Menu/Tools/Actions", compose);
8727 static void compose_update_privacy_systems_menu(Compose *compose)
8729 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8730 GSList *systems, *cur;
8732 GtkWidget *system_none;
8734 GtkWidget *privacy_menuitem = gtk_ui_manager_get_widget(compose->ui_manager, branch_path);
8735 GtkWidget *privacy_menu = gtk_menu_new();
8737 system_none = gtk_radio_menu_item_new_with_mnemonic(NULL, _("_None"));
8738 g_object_set_data_full(G_OBJECT(system_none), "privacy_system", NULL, NULL);
8740 g_signal_connect(G_OBJECT(system_none), "activate",
8741 G_CALLBACK(compose_set_privacy_system_cb), compose);
8743 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), system_none);
8744 gtk_widget_show(system_none);
8746 systems = privacy_get_system_ids();
8747 for (cur = systems; cur != NULL; cur = g_slist_next(cur)) {
8748 gchar *systemid = cur->data;
8750 group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(system_none));
8751 widget = gtk_radio_menu_item_new_with_label(group,
8752 privacy_system_get_name(systemid));
8753 g_object_set_data_full(G_OBJECT(widget), "privacy_system",
8754 g_strdup(systemid), g_free);
8755 g_signal_connect(G_OBJECT(widget), "activate",
8756 G_CALLBACK(compose_set_privacy_system_cb), compose);
8758 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), widget);
8759 gtk_widget_show(widget);
8762 g_slist_free(systems);
8763 gtk_menu_item_set_submenu(GTK_MENU_ITEM(privacy_menuitem), privacy_menu);
8764 gtk_widget_show_all(privacy_menu);
8765 gtk_widget_show_all(privacy_menuitem);
8768 void compose_reflect_prefs_all(void)
8773 for (cur = compose_list; cur != NULL; cur = cur->next) {
8774 compose = (Compose *)cur->data;
8775 compose_set_template_menu(compose);
8779 void compose_reflect_prefs_pixmap_theme(void)
8784 for (cur = compose_list; cur != NULL; cur = cur->next) {
8785 compose = (Compose *)cur->data;
8786 toolbar_update(TOOLBAR_COMPOSE, compose);
8790 static const gchar *compose_quote_char_from_context(Compose *compose)
8792 const gchar *qmark = NULL;
8794 cm_return_val_if_fail(compose != NULL, NULL);
8796 switch (compose->mode) {
8797 /* use forward-specific quote char */
8798 case COMPOSE_FORWARD:
8799 case COMPOSE_FORWARD_AS_ATTACH:
8800 case COMPOSE_FORWARD_INLINE:
8801 if (compose->folder && compose->folder->prefs &&
8802 compose->folder->prefs->forward_with_format)
8803 qmark = compose->folder->prefs->forward_quotemark;
8804 else if (compose->account->forward_with_format)
8805 qmark = compose->account->forward_quotemark;
8807 qmark = prefs_common.fw_quotemark;
8810 /* use reply-specific quote char in all other modes */
8812 if (compose->folder && compose->folder->prefs &&
8813 compose->folder->prefs->reply_with_format)
8814 qmark = compose->folder->prefs->reply_quotemark;
8815 else if (compose->account->reply_with_format)
8816 qmark = compose->account->reply_quotemark;
8818 qmark = prefs_common.quotemark;
8822 if (qmark == NULL || *qmark == '\0')
8828 static void compose_template_apply(Compose *compose, Template *tmpl,
8832 GtkTextBuffer *buffer;
8836 gchar *parsed_str = NULL;
8837 gint cursor_pos = 0;
8838 const gchar *err_msg = _("The body of the template has an error at line %d.");
8841 /* process the body */
8843 text = GTK_TEXT_VIEW(compose->text);
8844 buffer = gtk_text_view_get_buffer(text);
8847 qmark = compose_quote_char_from_context(compose);
8849 if (compose->replyinfo != NULL) {
8852 gtk_text_buffer_set_text(buffer, "", -1);
8853 mark = gtk_text_buffer_get_insert(buffer);
8854 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8856 parsed_str = compose_quote_fmt(compose, compose->replyinfo,
8857 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8859 } else if (compose->fwdinfo != NULL) {
8862 gtk_text_buffer_set_text(buffer, "", -1);
8863 mark = gtk_text_buffer_get_insert(buffer);
8864 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8866 parsed_str = compose_quote_fmt(compose, compose->fwdinfo,
8867 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8870 MsgInfo* dummyinfo = compose_msginfo_new_from_compose(compose);
8872 GtkTextIter start, end;
8875 gtk_text_buffer_get_start_iter(buffer, &start);
8876 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
8877 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
8879 /* clear the buffer now */
8881 gtk_text_buffer_set_text(buffer, "", -1);
8883 parsed_str = compose_quote_fmt(compose, dummyinfo,
8884 tmpl->value, qmark, tmp, FALSE, FALSE, err_msg);
8885 procmsg_msginfo_free( &dummyinfo );
8891 gtk_text_buffer_set_text(buffer, "", -1);
8892 mark = gtk_text_buffer_get_insert(buffer);
8893 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8896 if (replace && parsed_str && compose->account->auto_sig)
8897 compose_insert_sig(compose, FALSE);
8899 if (replace && parsed_str) {
8900 gtk_text_buffer_get_start_iter(buffer, &iter);
8901 gtk_text_buffer_place_cursor(buffer, &iter);
8905 cursor_pos = quote_fmt_get_cursor_pos();
8906 compose->set_cursor_pos = cursor_pos;
8907 if (cursor_pos == -1)
8909 gtk_text_buffer_get_start_iter(buffer, &iter);
8910 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
8911 gtk_text_buffer_place_cursor(buffer, &iter);
8914 /* process the other fields */
8916 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
8917 compose_template_apply_fields(compose, tmpl);
8918 quote_fmt_reset_vartable();
8919 quote_fmtlex_destroy();
8921 compose_changed_cb(NULL, compose);
8924 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
8925 gtkaspell_highlight_all(compose->gtkaspell);
8929 static void compose_template_apply_fields_error(const gchar *header)
8934 tr = g_strdup(C_("'%s' stands for a header name",
8935 "Template '%s' format error."));
8936 text = g_strdup_printf(tr, prefs_common_translated_header_name(header));
8937 alertpanel_error("%s", text);
8943 static void compose_template_apply_fields(Compose *compose, Template *tmpl)
8945 MsgInfo* dummyinfo = NULL;
8946 MsgInfo *msginfo = NULL;
8949 if (compose->replyinfo != NULL)
8950 msginfo = compose->replyinfo;
8951 else if (compose->fwdinfo != NULL)
8952 msginfo = compose->fwdinfo;
8954 dummyinfo = compose_msginfo_new_from_compose(compose);
8955 msginfo = dummyinfo;
8958 if (tmpl->from && *tmpl->from != '\0') {
8960 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8961 compose->gtkaspell);
8963 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8965 quote_fmt_scan_string(tmpl->from);
8968 buf = quote_fmt_get_buffer();
8970 compose_template_apply_fields_error("From");
8972 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
8975 quote_fmt_reset_vartable();
8976 quote_fmtlex_destroy();
8979 if (tmpl->to && *tmpl->to != '\0') {
8981 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8982 compose->gtkaspell);
8984 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8986 quote_fmt_scan_string(tmpl->to);
8989 buf = quote_fmt_get_buffer();
8991 compose_template_apply_fields_error("To");
8993 compose_entry_append(compose, buf, COMPOSE_TO, PREF_TEMPLATE);
8996 quote_fmt_reset_vartable();
8997 quote_fmtlex_destroy();
9000 if (tmpl->cc && *tmpl->cc != '\0') {
9002 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
9003 compose->gtkaspell);
9005 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
9007 quote_fmt_scan_string(tmpl->cc);
9010 buf = quote_fmt_get_buffer();
9012 compose_template_apply_fields_error("Cc");
9014 compose_entry_append(compose, buf, COMPOSE_CC, PREF_TEMPLATE);
9017 quote_fmt_reset_vartable();
9018 quote_fmtlex_destroy();
9021 if (tmpl->bcc && *tmpl->bcc != '\0') {
9023 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
9024 compose->gtkaspell);
9026 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
9028 quote_fmt_scan_string(tmpl->bcc);
9031 buf = quote_fmt_get_buffer();
9033 compose_template_apply_fields_error("Bcc");
9035 compose_entry_append(compose, buf, COMPOSE_BCC, PREF_TEMPLATE);
9038 quote_fmt_reset_vartable();
9039 quote_fmtlex_destroy();
9042 if (tmpl->replyto && *tmpl->replyto != '\0') {
9044 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
9045 compose->gtkaspell);
9047 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
9049 quote_fmt_scan_string(tmpl->replyto);
9052 buf = quote_fmt_get_buffer();
9054 compose_template_apply_fields_error("Reply-To");
9056 compose_entry_append(compose, buf, COMPOSE_REPLYTO, PREF_TEMPLATE);
9059 quote_fmt_reset_vartable();
9060 quote_fmtlex_destroy();
9063 /* process the subject */
9064 if (tmpl->subject && *tmpl->subject != '\0') {
9066 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
9067 compose->gtkaspell);
9069 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
9071 quote_fmt_scan_string(tmpl->subject);
9074 buf = quote_fmt_get_buffer();
9076 compose_template_apply_fields_error("Subject");
9078 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
9081 quote_fmt_reset_vartable();
9082 quote_fmtlex_destroy();
9085 procmsg_msginfo_free( &dummyinfo );
9088 static void compose_destroy(Compose *compose)
9090 GtkAllocation allocation;
9091 GtkTextBuffer *buffer;
9092 GtkClipboard *clipboard;
9094 compose_list = g_list_remove(compose_list, compose);
9097 gboolean enable = TRUE;
9098 g_slist_foreach(compose->passworded_ldap_servers,
9099 _ldap_srv_func, &enable);
9100 g_slist_free(compose->passworded_ldap_servers);
9103 if (compose->updating) {
9104 debug_print("danger, not destroying anything now\n");
9105 compose->deferred_destroy = TRUE;
9109 /* NOTE: address_completion_end() does nothing with the window
9110 * however this may change. */
9111 address_completion_end(compose->window);
9113 slist_free_strings_full(compose->to_list);
9114 slist_free_strings_full(compose->newsgroup_list);
9115 slist_free_strings_full(compose->header_list);
9117 slist_free_strings_full(extra_headers);
9118 extra_headers = NULL;
9120 compose->header_list = compose->newsgroup_list = compose->to_list = NULL;
9122 g_hash_table_destroy(compose->email_hashtable);
9124 hooks_unregister_hook(FOLDER_UPDATE_HOOKLIST,
9125 compose->folder_update_callback_id);
9127 procmsg_msginfo_free(&(compose->targetinfo));
9128 procmsg_msginfo_free(&(compose->replyinfo));
9129 procmsg_msginfo_free(&(compose->fwdinfo));
9131 g_free(compose->replyto);
9132 g_free(compose->cc);
9133 g_free(compose->bcc);
9134 g_free(compose->newsgroups);
9135 g_free(compose->followup_to);
9137 g_free(compose->ml_post);
9139 g_free(compose->inreplyto);
9140 g_free(compose->references);
9141 g_free(compose->msgid);
9142 g_free(compose->boundary);
9144 g_free(compose->redirect_filename);
9145 if (compose->undostruct)
9146 undo_destroy(compose->undostruct);
9148 g_free(compose->sig_str);
9150 g_free(compose->exteditor_file);
9152 g_free(compose->orig_charset);
9154 g_free(compose->privacy_system);
9155 g_free(compose->encdata);
9157 #ifndef USE_ALT_ADDRBOOK
9158 if (addressbook_get_target_compose() == compose)
9159 addressbook_set_target_compose(NULL);
9162 if (compose->gtkaspell) {
9163 gtkaspell_delete(compose->gtkaspell);
9164 compose->gtkaspell = NULL;
9168 if (!compose->batch) {
9169 gtk_widget_get_allocation(compose->window, &allocation);
9170 prefs_common.compose_width = allocation.width;
9171 prefs_common.compose_height = allocation.height;
9174 if (!gtk_widget_get_parent(compose->paned))
9175 gtk_widget_destroy(compose->paned);
9176 gtk_widget_destroy(compose->popupmenu);
9178 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
9179 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
9180 gtk_text_buffer_remove_selection_clipboard(buffer, clipboard);
9182 message_search_close(compose);
9183 gtk_widget_destroy(compose->window);
9184 toolbar_destroy(compose->toolbar);
9185 g_free(compose->toolbar);
9186 cm_mutex_free(compose->mutex);
9190 static void compose_attach_info_free(AttachInfo *ainfo)
9192 g_free(ainfo->file);
9193 g_free(ainfo->content_type);
9194 g_free(ainfo->name);
9195 g_free(ainfo->charset);
9199 static void compose_attach_update_label(Compose *compose)
9204 GtkTreeModel *model;
9208 if (compose == NULL)
9211 model = gtk_tree_view_get_model(GTK_TREE_VIEW(compose->attach_clist));
9212 if (!gtk_tree_model_get_iter_first(model, &iter)) {
9213 gtk_label_set_text(GTK_LABEL(compose->attach_label), "");
9217 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
9218 total_size = ainfo->size;
9219 while(gtk_tree_model_iter_next(model, &iter)) {
9220 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
9221 total_size += ainfo->size;
9224 text = g_strdup_printf(" (%d/%s)", i, to_human_readable(total_size));
9225 gtk_label_set_text(GTK_LABEL(compose->attach_label), text);
9229 static void compose_attach_remove_selected(GtkAction *action, gpointer data)
9231 Compose *compose = (Compose *)data;
9232 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
9233 GtkTreeSelection *selection;
9235 GtkTreeModel *model;
9237 selection = gtk_tree_view_get_selection(tree_view);
9238 sel = gtk_tree_selection_get_selected_rows(selection, &model);
9239 cm_return_if_fail(sel);
9241 for (cur = sel; cur != NULL; cur = cur->next) {
9242 GtkTreePath *path = cur->data;
9243 GtkTreeRowReference *ref = gtk_tree_row_reference_new
9246 gtk_tree_path_free(path);
9249 for (cur = sel; cur != NULL; cur = cur->next) {
9250 GtkTreeRowReference *ref = cur->data;
9251 GtkTreePath *path = gtk_tree_row_reference_get_path(ref);
9254 if (gtk_tree_model_get_iter(model, &iter, path))
9255 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
9257 gtk_tree_path_free(path);
9258 gtk_tree_row_reference_free(ref);
9262 compose_attach_update_label(compose);
9265 static struct _AttachProperty
9268 GtkWidget *mimetype_entry;
9269 GtkWidget *encoding_optmenu;
9270 GtkWidget *path_entry;
9271 GtkWidget *filename_entry;
9273 GtkWidget *cancel_btn;
9276 static void gtk_tree_path_free_(gpointer ptr, gpointer data)
9278 gtk_tree_path_free((GtkTreePath *)ptr);
9281 static void compose_attach_property(GtkAction *action, gpointer data)
9283 Compose *compose = (Compose *)data;
9284 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
9286 GtkComboBox *optmenu;
9287 GtkTreeSelection *selection;
9289 GtkTreeModel *model;
9292 static gboolean cancelled;
9294 /* only if one selected */
9295 selection = gtk_tree_view_get_selection(tree_view);
9296 if (gtk_tree_selection_count_selected_rows(selection) != 1)
9299 sel = gtk_tree_selection_get_selected_rows(selection, &model);
9300 cm_return_if_fail(sel);
9302 path = (GtkTreePath *) sel->data;
9303 gtk_tree_model_get_iter(model, &iter, path);
9304 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
9307 g_list_foreach(sel, gtk_tree_path_free_, NULL);
9313 if (!attach_prop.window)
9314 compose_attach_property_create(&cancelled);
9315 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), TRUE);
9316 gtk_widget_grab_focus(attach_prop.ok_btn);
9317 gtk_widget_show(attach_prop.window);
9318 gtk_window_set_transient_for(GTK_WINDOW(attach_prop.window),
9319 GTK_WINDOW(compose->window));
9321 optmenu = GTK_COMBO_BOX(attach_prop.encoding_optmenu);
9322 if (ainfo->encoding == ENC_UNKNOWN)
9323 combobox_select_by_data(optmenu, ENC_BASE64);
9325 combobox_select_by_data(optmenu, ainfo->encoding);
9327 gtk_entry_set_text(GTK_ENTRY(attach_prop.mimetype_entry),
9328 ainfo->content_type ? ainfo->content_type : "");
9329 gtk_entry_set_text(GTK_ENTRY(attach_prop.path_entry),
9330 ainfo->file ? ainfo->file : "");
9331 gtk_entry_set_text(GTK_ENTRY(attach_prop.filename_entry),
9332 ainfo->name ? ainfo->name : "");
9335 const gchar *entry_text;
9337 gchar *cnttype = NULL;
9344 gtk_widget_hide(attach_prop.window);
9345 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), FALSE);
9350 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.mimetype_entry));
9351 if (*entry_text != '\0') {
9354 text = g_strstrip(g_strdup(entry_text));
9355 if ((p = strchr(text, '/')) && !strchr(p + 1, '/')) {
9356 cnttype = g_strdup(text);
9359 alertpanel_error(_("Invalid MIME type."));
9365 ainfo->encoding = combobox_get_active_data(optmenu);
9367 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.path_entry));
9368 if (*entry_text != '\0') {
9369 if (is_file_exist(entry_text) &&
9370 (size = get_file_size(entry_text)) > 0)
9371 file = g_strdup(entry_text);
9374 (_("File doesn't exist or is empty."));
9380 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.filename_entry));
9381 if (*entry_text != '\0') {
9382 g_free(ainfo->name);
9383 ainfo->name = g_strdup(entry_text);
9387 g_free(ainfo->content_type);
9388 ainfo->content_type = cnttype;
9391 g_free(ainfo->file);
9395 ainfo->size = (goffset)size;
9397 /* update tree store */
9398 text = to_human_readable(ainfo->size);
9399 gtk_tree_model_get_iter(model, &iter, path);
9400 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
9401 COL_MIMETYPE, ainfo->content_type,
9403 COL_NAME, ainfo->name,
9404 COL_CHARSET, ainfo->charset,
9410 gtk_tree_path_free(path);
9413 #define SET_LABEL_AND_ENTRY(str, entry, top) \
9415 label = gtk_label_new(str); \
9416 gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), \
9417 GTK_FILL, 0, 0, 0); \
9418 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); \
9420 entry = gtk_entry_new(); \
9421 gtk_table_attach(GTK_TABLE(table), entry, 1, 2, top, (top + 1), \
9422 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); \
9425 static void compose_attach_property_create(gboolean *cancelled)
9431 GtkWidget *mimetype_entry;
9434 GtkListStore *optmenu_menu;
9435 GtkWidget *path_entry;
9436 GtkWidget *filename_entry;
9439 GtkWidget *cancel_btn;
9440 GList *mime_type_list, *strlist;
9443 debug_print("Creating attach_property window...\n");
9445 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose_attach_property");
9446 gtk_widget_set_size_request(window, 480, -1);
9447 gtk_container_set_border_width(GTK_CONTAINER(window), 8);
9448 gtk_window_set_title(GTK_WINDOW(window), _("Properties"));
9449 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
9450 g_signal_connect(G_OBJECT(window), "delete_event",
9451 G_CALLBACK(attach_property_delete_event),
9453 g_signal_connect(G_OBJECT(window), "key_press_event",
9454 G_CALLBACK(attach_property_key_pressed),
9457 vbox = gtk_vbox_new(FALSE, 8);
9458 gtk_container_add(GTK_CONTAINER(window), vbox);
9460 table = gtk_table_new(4, 2, FALSE);
9461 gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
9462 gtk_table_set_row_spacings(GTK_TABLE(table), 8);
9463 gtk_table_set_col_spacings(GTK_TABLE(table), 8);
9465 label = gtk_label_new(_("MIME type"));
9466 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, (0 + 1),
9468 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
9469 mimetype_entry = gtk_combo_box_text_new_with_entry();
9470 gtk_table_attach(GTK_TABLE(table), mimetype_entry, 1, 2, 0, (0 + 1),
9471 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
9473 /* stuff with list */
9474 mime_type_list = procmime_get_mime_type_list();
9476 for (; mime_type_list != NULL; mime_type_list = mime_type_list->next) {
9477 MimeType *type = (MimeType *) mime_type_list->data;
9480 tmp = g_strdup_printf("%s/%s", type->type, type->sub_type);
9482 if (g_list_find_custom(strlist, tmp, (GCompareFunc)strcmp2))
9485 strlist = g_list_insert_sorted(strlist, (gpointer)tmp,
9486 (GCompareFunc)strcmp2);
9489 for (mime_type_list = strlist; mime_type_list != NULL;
9490 mime_type_list = mime_type_list->next) {
9491 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(mimetype_entry), mime_type_list->data);
9492 g_free(mime_type_list->data);
9494 g_list_free(strlist);
9495 gtk_combo_box_set_active(GTK_COMBO_BOX(mimetype_entry), 0);
9496 mimetype_entry = gtk_bin_get_child(GTK_BIN((mimetype_entry)));
9498 label = gtk_label_new(_("Encoding"));
9499 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2,
9501 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
9503 hbox = gtk_hbox_new(FALSE, 0);
9504 gtk_table_attach(GTK_TABLE(table), hbox, 1, 2, 1, 2,
9505 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
9507 optmenu = gtkut_sc_combobox_create(NULL, TRUE);
9508 optmenu_menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
9510 COMBOBOX_ADD(optmenu_menu, "7bit", ENC_7BIT);
9511 COMBOBOX_ADD(optmenu_menu, "8bit", ENC_8BIT);
9512 COMBOBOX_ADD(optmenu_menu, "quoted-printable", ENC_QUOTED_PRINTABLE);
9513 COMBOBOX_ADD(optmenu_menu, "base64", ENC_BASE64);
9514 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), 0);
9516 gtk_box_pack_start(GTK_BOX(hbox), optmenu, TRUE, TRUE, 0);
9518 SET_LABEL_AND_ENTRY(_("Path"), path_entry, 2);
9519 SET_LABEL_AND_ENTRY(_("File name"), filename_entry, 3);
9521 gtkut_stock_button_set_create(&hbbox, &cancel_btn, GTK_STOCK_CANCEL,
9522 &ok_btn, GTK_STOCK_OK,
9524 gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
9525 gtk_widget_grab_default(ok_btn);
9527 g_signal_connect(G_OBJECT(ok_btn), "clicked",
9528 G_CALLBACK(attach_property_ok),
9530 g_signal_connect(G_OBJECT(cancel_btn), "clicked",
9531 G_CALLBACK(attach_property_cancel),
9534 gtk_widget_show_all(vbox);
9536 attach_prop.window = window;
9537 attach_prop.mimetype_entry = mimetype_entry;
9538 attach_prop.encoding_optmenu = optmenu;
9539 attach_prop.path_entry = path_entry;
9540 attach_prop.filename_entry = filename_entry;
9541 attach_prop.ok_btn = ok_btn;
9542 attach_prop.cancel_btn = cancel_btn;
9545 #undef SET_LABEL_AND_ENTRY
9547 static void attach_property_ok(GtkWidget *widget, gboolean *cancelled)
9553 static void attach_property_cancel(GtkWidget *widget, gboolean *cancelled)
9559 static gint attach_property_delete_event(GtkWidget *widget, GdkEventAny *event,
9560 gboolean *cancelled)
9568 static gboolean attach_property_key_pressed(GtkWidget *widget,
9570 gboolean *cancelled)
9572 if (event && event->keyval == GDK_KEY_Escape) {
9576 if (event && event->keyval == GDK_KEY_Return) {
9584 static void compose_exec_ext_editor(Compose *compose)
9589 GdkNativeWindow socket_wid = 0;
9593 tmp = g_strdup_printf("%s%ctmpmsg.%p", get_tmp_dir(),
9594 G_DIR_SEPARATOR, compose);
9596 if (compose_get_ext_editor_uses_socket()) {
9597 /* Only allow one socket */
9598 if (compose->exteditor_socket != NULL) {
9599 if (gtk_widget_is_focus(compose->exteditor_socket)) {
9600 /* Move the focus off of the socket */
9601 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9606 /* Create the receiving GtkSocket */
9607 socket = gtk_socket_new ();
9608 g_signal_connect (GTK_OBJECT(socket), "plug-removed",
9609 G_CALLBACK(compose_ext_editor_plug_removed_cb),
9611 gtk_box_pack_start(GTK_BOX(compose->edit_vbox), socket, TRUE, TRUE, 0);
9612 gtk_widget_set_size_request(socket, prefs_common.compose_width, -1);
9613 /* Realize the socket so that we can use its ID */
9614 gtk_widget_realize(socket);
9615 socket_wid = gtk_socket_get_id(GTK_SOCKET (socket));
9616 compose->exteditor_socket = socket;
9619 if (pipe(pipe_fds) < 0) {
9625 if ((pid = fork()) < 0) {
9632 /* close the write side of the pipe */
9635 compose->exteditor_file = g_strdup(tmp);
9636 compose->exteditor_pid = pid;
9638 compose_set_ext_editor_sensitive(compose, FALSE);
9641 compose->exteditor_ch = g_io_channel_unix_new(pipe_fds[0]);
9643 compose->exteditor_ch = g_io_channel_win32_new_fd(pipe_fds[0]);
9645 compose->exteditor_tag = g_io_add_watch(compose->exteditor_ch,
9649 } else { /* process-monitoring process */
9655 /* close the read side of the pipe */
9658 if (compose_write_body_to_file(compose, tmp) < 0) {
9659 fd_write_all(pipe_fds[1], "2\n", 2);
9663 pid_ed = compose_exec_ext_editor_real(tmp, socket_wid);
9665 fd_write_all(pipe_fds[1], "1\n", 2);
9669 /* wait until editor is terminated */
9670 waitpid(pid_ed, NULL, 0);
9672 fd_write_all(pipe_fds[1], "0\n", 2);
9679 #endif /* G_OS_UNIX */
9682 static gboolean compose_can_autosave(Compose *compose)
9684 if (compose->privacy_system && compose->use_encryption)
9685 return prefs_common.autosave && prefs_common.autosave_encrypted;
9687 return prefs_common.autosave;
9691 static gboolean compose_get_ext_editor_cmd_valid()
9693 gboolean has_s = FALSE;
9694 gboolean has_w = FALSE;
9695 const gchar *p = prefs_common_get_ext_editor_cmd();
9698 while ((p = strchr(p, '%'))) {
9704 } else if (*p == 'w') {
9715 static gint compose_exec_ext_editor_real(const gchar *file, GdkNativeWindow socket_wid)
9722 cm_return_val_if_fail(file != NULL, -1);
9724 if ((pid = fork()) < 0) {
9729 if (pid != 0) return pid;
9731 /* grandchild process */
9733 if (setpgid(0, getppid()))
9736 if (compose_get_ext_editor_cmd_valid()) {
9737 if (compose_get_ext_editor_uses_socket()) {
9738 p = g_strdup(prefs_common_get_ext_editor_cmd());
9739 s = strstr(p, "%w");
9741 if (strstr(p, "%s") < s)
9742 buf = g_strdup_printf(p, file, socket_wid);
9744 buf = g_strdup_printf(p, socket_wid, file);
9747 buf = g_strdup_printf(prefs_common_get_ext_editor_cmd(), file);
9750 if (prefs_common_get_ext_editor_cmd())
9751 g_warning("External editor command-line is invalid: '%s'",
9752 prefs_common_get_ext_editor_cmd());
9753 buf = g_strdup_printf(DEFAULT_EDITOR_CMD, file);
9756 cmdline = strsplit_with_quote(buf, " ", 0);
9758 execvp(cmdline[0], cmdline);
9761 g_strfreev(cmdline);
9766 static gboolean compose_ext_editor_kill(Compose *compose)
9768 pid_t pgid = compose->exteditor_pid * -1;
9771 ret = kill(pgid, 0);
9773 if (ret == 0 || (ret == -1 && EPERM == errno)) {
9777 msg = g_strdup_printf
9778 (_("The external editor is still working.\n"
9779 "Force terminating the process?\n"
9780 "process group id: %d"), -pgid);
9781 val = alertpanel_full(_("Notice"), msg, GTK_STOCK_NO, GTK_STOCK_YES,
9782 NULL, ALERTFOCUS_FIRST, FALSE, NULL,
9787 if (val == G_ALERTALTERNATE) {
9788 g_source_remove(compose->exteditor_tag);
9789 g_io_channel_shutdown(compose->exteditor_ch,
9791 g_io_channel_unref(compose->exteditor_ch);
9793 if (kill(pgid, SIGTERM) < 0) perror("kill");
9794 waitpid(compose->exteditor_pid, NULL, 0);
9796 g_warning("Terminated process group id: %d. "
9797 "Temporary file: %s", -pgid, compose->exteditor_file);
9799 compose_set_ext_editor_sensitive(compose, TRUE);
9801 g_free(compose->exteditor_file);
9802 compose->exteditor_file = NULL;
9803 compose->exteditor_pid = -1;
9804 compose->exteditor_ch = NULL;
9805 compose->exteditor_tag = -1;
9813 static gboolean compose_input_cb(GIOChannel *source, GIOCondition condition,
9817 Compose *compose = (Compose *)data;
9820 debug_print("Compose: input from monitoring process\n");
9822 if (g_io_channel_read_chars(source, buf, sizeof(buf), &bytes_read, NULL) != G_IO_STATUS_NORMAL) {
9827 g_io_channel_shutdown(source, FALSE, NULL);
9828 g_io_channel_unref(source);
9830 waitpid(compose->exteditor_pid, NULL, 0);
9832 if (buf[0] == '0') { /* success */
9833 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9834 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
9835 GtkTextIter start, end;
9838 gtk_text_buffer_set_text(buffer, "", -1);
9839 compose_insert_file(compose, compose->exteditor_file);
9840 compose_changed_cb(NULL, compose);
9842 /* Check if we should save the draft or not */
9843 if (compose_can_autosave(compose))
9844 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
9846 if (claws_unlink(compose->exteditor_file) < 0)
9847 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9849 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
9850 gtk_text_buffer_get_start_iter(buffer, &start);
9851 gtk_text_buffer_get_end_iter(buffer, &end);
9852 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
9853 if (chars && strlen(chars) > 0)
9854 compose->modified = TRUE;
9856 } else if (buf[0] == '1') { /* failed */
9857 g_warning("Couldn't exec external editor");
9858 if (claws_unlink(compose->exteditor_file) < 0)
9859 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9860 } else if (buf[0] == '2') {
9861 g_warning("Couldn't write to file");
9862 } else if (buf[0] == '3') {
9863 g_warning("Pipe read failed");
9866 compose_set_ext_editor_sensitive(compose, TRUE);
9868 g_free(compose->exteditor_file);
9869 compose->exteditor_file = NULL;
9870 compose->exteditor_pid = -1;
9871 compose->exteditor_ch = NULL;
9872 compose->exteditor_tag = -1;
9873 if (compose->exteditor_socket) {
9874 gtk_widget_destroy(compose->exteditor_socket);
9875 compose->exteditor_socket = NULL;
9882 static char *ext_editor_menu_entries[] = {
9883 "Menu/Message/Send",
9884 "Menu/Message/SendLater",
9885 "Menu/Message/InsertFile",
9886 "Menu/Message/InsertSig",
9887 "Menu/Message/ReplaceSig",
9888 "Menu/Message/Save",
9889 "Menu/Message/Print",
9894 "Menu/Tools/ShowRuler",
9895 "Menu/Tools/Actions",
9900 static void compose_set_ext_editor_sensitive(Compose *compose,
9905 for (i = 0; ext_editor_menu_entries[i]; ++i) {
9906 cm_menu_set_sensitive_full(compose->ui_manager,
9907 ext_editor_menu_entries[i], sensitive);
9910 if (compose_get_ext_editor_uses_socket()) {
9912 if (compose->exteditor_socket)
9913 gtk_widget_hide(compose->exteditor_socket);
9914 gtk_widget_show(compose->scrolledwin);
9915 if (prefs_common.show_ruler)
9916 gtk_widget_show(compose->ruler_hbox);
9917 /* Fix the focus, as it doesn't go anywhere when the
9918 * socket is hidden or destroyed */
9919 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9921 g_assert (compose->exteditor_socket != NULL);
9922 /* Fix the focus, as it doesn't go anywhere when the
9923 * edit box is hidden */
9924 if (gtk_widget_is_focus(compose->text))
9925 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9926 gtk_widget_hide(compose->scrolledwin);
9927 gtk_widget_hide(compose->ruler_hbox);
9928 gtk_widget_show(compose->exteditor_socket);
9931 gtk_widget_set_sensitive(compose->text, sensitive);
9933 if (compose->toolbar->send_btn)
9934 gtk_widget_set_sensitive(compose->toolbar->send_btn, sensitive);
9935 if (compose->toolbar->sendl_btn)
9936 gtk_widget_set_sensitive(compose->toolbar->sendl_btn, sensitive);
9937 if (compose->toolbar->draft_btn)
9938 gtk_widget_set_sensitive(compose->toolbar->draft_btn, sensitive);
9939 if (compose->toolbar->insert_btn)
9940 gtk_widget_set_sensitive(compose->toolbar->insert_btn, sensitive);
9941 if (compose->toolbar->sig_btn)
9942 gtk_widget_set_sensitive(compose->toolbar->sig_btn, sensitive);
9943 if (compose->toolbar->exteditor_btn)
9944 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, sensitive);
9945 if (compose->toolbar->linewrap_current_btn)
9946 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, sensitive);
9947 if (compose->toolbar->linewrap_all_btn)
9948 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, sensitive);
9951 static gboolean compose_get_ext_editor_uses_socket()
9953 return (prefs_common_get_ext_editor_cmd() &&
9954 strstr(prefs_common_get_ext_editor_cmd(), "%w"));
9957 static gboolean compose_ext_editor_plug_removed_cb(GtkSocket *socket, Compose *compose)
9959 compose->exteditor_socket = NULL;
9960 /* returning FALSE allows destruction of the socket */
9963 #endif /* G_OS_UNIX */
9966 * compose_undo_state_changed:
9968 * Change the sensivity of the menuentries undo and redo
9970 static void compose_undo_state_changed(UndoMain *undostruct, gint undo_state,
9971 gint redo_state, gpointer data)
9973 Compose *compose = (Compose *)data;
9975 switch (undo_state) {
9976 case UNDO_STATE_TRUE:
9977 if (!undostruct->undo_state) {
9978 undostruct->undo_state = TRUE;
9979 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", TRUE);
9982 case UNDO_STATE_FALSE:
9983 if (undostruct->undo_state) {
9984 undostruct->undo_state = FALSE;
9985 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
9988 case UNDO_STATE_UNCHANGED:
9990 case UNDO_STATE_REFRESH:
9991 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", undostruct->undo_state);
9994 g_warning("Undo state not recognized");
9998 switch (redo_state) {
9999 case UNDO_STATE_TRUE:
10000 if (!undostruct->redo_state) {
10001 undostruct->redo_state = TRUE;
10002 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", TRUE);
10005 case UNDO_STATE_FALSE:
10006 if (undostruct->redo_state) {
10007 undostruct->redo_state = FALSE;
10008 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
10011 case UNDO_STATE_UNCHANGED:
10013 case UNDO_STATE_REFRESH:
10014 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", undostruct->redo_state);
10017 g_warning("Redo state not recognized");
10022 /* callback functions */
10024 static void compose_notebook_size_alloc(GtkNotebook *notebook,
10025 GtkAllocation *allocation,
10028 prefs_common.compose_notebook_height = gtk_paned_get_position(paned);
10031 /* compose_edit_size_alloc() - called when resized. don't know whether Gtk
10032 * includes "non-client" (windows-izm) in calculation, so this calculation
10033 * may not be accurate.
10035 static gboolean compose_edit_size_alloc(GtkEditable *widget,
10036 GtkAllocation *allocation,
10037 GtkSHRuler *shruler)
10039 if (prefs_common.show_ruler) {
10040 gint char_width = 0, char_height = 0;
10041 gint line_width_in_chars;
10043 gtkut_get_font_size(GTK_WIDGET(widget),
10044 &char_width, &char_height);
10045 line_width_in_chars =
10046 (allocation->width - allocation->x) / char_width;
10048 /* got the maximum */
10049 gtk_shruler_set_range(GTK_SHRULER(shruler),
10050 0.0, line_width_in_chars, 0);
10059 ComposePrefType type;
10060 gboolean entry_marked;
10061 } HeaderEntryState;
10063 static void account_activated(GtkComboBox *optmenu, gpointer data)
10065 Compose *compose = (Compose *)data;
10068 gchar *folderidentifier;
10069 gint account_id = 0;
10070 GtkTreeModel *menu;
10072 GSList *list, *saved_list = NULL;
10073 HeaderEntryState *state;
10076 /* Get ID of active account in the combo box */
10077 menu = gtk_combo_box_get_model(optmenu);
10078 cm_return_if_fail(gtk_combo_box_get_active_iter(optmenu, &iter));
10079 gtk_tree_model_get(menu, &iter, 1, &account_id, -1);
10081 ac = account_find_from_id(account_id);
10082 cm_return_if_fail(ac != NULL);
10084 if (ac != compose->account) {
10085 compose_select_account(compose, ac, FALSE);
10087 for (list = compose->header_list; list; list = list->next) {
10088 ComposeHeaderEntry *hentry=(ComposeHeaderEntry *)list->data;
10090 if (hentry->type == PREF_ACCOUNT || !list->next) {
10091 compose_destroy_headerentry(compose, hentry);
10094 state = g_malloc0(sizeof(HeaderEntryState));
10095 state->header = gtk_editable_get_chars(GTK_EDITABLE(
10096 gtk_bin_get_child(GTK_BIN(hentry->combo))), 0, -1);
10097 state->entry = gtk_editable_get_chars(
10098 GTK_EDITABLE(hentry->entry), 0, -1);
10099 state->type = hentry->type;
10101 saved_list = g_slist_append(saved_list, state);
10102 compose_destroy_headerentry(compose, hentry);
10105 compose->header_last = NULL;
10106 g_slist_free(compose->header_list);
10107 compose->header_list = NULL;
10108 compose->header_nextrow = 1;
10109 compose_create_header_entry(compose);
10111 if (ac->set_autocc && ac->auto_cc)
10112 compose_entry_append(compose, ac->auto_cc,
10113 COMPOSE_CC, PREF_ACCOUNT);
10114 if (ac->set_autobcc && ac->auto_bcc)
10115 compose_entry_append(compose, ac->auto_bcc,
10116 COMPOSE_BCC, PREF_ACCOUNT);
10117 if (ac->set_autoreplyto && ac->auto_replyto)
10118 compose_entry_append(compose, ac->auto_replyto,
10119 COMPOSE_REPLYTO, PREF_ACCOUNT);
10121 for (list = saved_list; list; list = list->next) {
10122 state = (HeaderEntryState *) list->data;
10124 compose_add_header_entry(compose, state->header,
10125 state->entry, state->type);
10127 g_free(state->header);
10128 g_free(state->entry);
10131 g_slist_free(saved_list);
10133 combobox_select_by_data(GTK_COMBO_BOX(compose->header_last->combo),
10134 (ac->protocol == A_NNTP) ?
10135 COMPOSE_NEWSGROUPS : COMPOSE_TO);
10138 /* Set message save folder */
10139 compose_set_save_to(compose, NULL);
10140 if (compose->folder && compose->folder->prefs && compose->folder->prefs->save_copy_to_folder) {
10141 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo), TRUE);
10142 folderidentifier = folder_item_get_identifier(compose->folder);
10143 compose_set_save_to(compose, folderidentifier);
10144 g_free(folderidentifier);
10145 } else if ((item = account_get_special_folder(compose->account, F_OUTBOX)) != NULL) {
10146 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo), TRUE);
10147 folderidentifier = folder_item_get_identifier(item);
10148 compose_set_save_to(compose, folderidentifier);
10149 g_free(folderidentifier);
10153 static void attach_selected(GtkTreeView *tree_view, GtkTreePath *tree_path,
10154 GtkTreeViewColumn *column, Compose *compose)
10156 compose_attach_property(NULL, compose);
10159 static gboolean attach_button_pressed(GtkWidget *widget, GdkEventButton *event,
10162 Compose *compose = (Compose *)data;
10163 GtkTreeSelection *attach_selection;
10164 gint attach_nr_selected;
10167 if (!event) return FALSE;
10169 if (event->button == 3) {
10170 attach_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
10171 attach_nr_selected = gtk_tree_selection_count_selected_rows(attach_selection);
10173 /* If no rows, or just one row is selected, right-click should
10174 * open menu relevant to the row being right-clicked on. We
10175 * achieve that by selecting the clicked row first. If more
10176 * than one row is selected, we shouldn't modify the selection,
10177 * as user may want to remove selected rows (attachments). */
10178 if (attach_nr_selected < 2) {
10179 gtk_tree_selection_unselect_all(attach_selection);
10180 attach_nr_selected = 0;
10181 gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget),
10182 event->x, event->y, &path, NULL, NULL, NULL);
10183 if (path != NULL) {
10184 gtk_tree_selection_select_path(attach_selection, path);
10185 gtk_tree_path_free(path);
10186 attach_nr_selected++;
10190 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", (attach_nr_selected > 0));
10191 /* Properties menu item makes no sense with more than one row
10192 * selected, the properties dialog can only edit one attachment. */
10193 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", (attach_nr_selected == 1));
10195 gtk_menu_popup(GTK_MENU(compose->popupmenu), NULL, NULL,
10196 NULL, NULL, event->button, event->time);
10203 static gboolean attach_key_pressed(GtkWidget *widget, GdkEventKey *event,
10206 Compose *compose = (Compose *)data;
10208 if (!event) return FALSE;
10210 switch (event->keyval) {
10211 case GDK_KEY_Delete:
10212 compose_attach_remove_selected(NULL, compose);
10218 static void compose_allow_user_actions (Compose *compose, gboolean allow)
10220 toolbar_comp_set_sensitive(compose, allow);
10221 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message", allow);
10222 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", allow);
10224 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", allow);
10226 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", allow);
10227 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools", allow);
10228 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Help", allow);
10230 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), allow);
10234 static void compose_send_cb(GtkAction *action, gpointer data)
10236 Compose *compose = (Compose *)data;
10239 if (compose->exteditor_tag != -1) {
10240 debug_print("ignoring send: external editor still open\n");
10244 if (prefs_common.work_offline &&
10245 !inc_offline_should_override(TRUE,
10246 _("Claws Mail needs network access in order "
10247 "to send this email.")))
10250 if (compose->draft_timeout_tag >= 0) { /* CLAWS: disable draft timeout */
10251 g_source_remove(compose->draft_timeout_tag);
10252 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
10255 compose_send(compose);
10258 static void compose_send_later_cb(GtkAction *action, gpointer data)
10260 Compose *compose = (Compose *)data;
10261 ComposeQueueResult val;
10264 compose_allow_user_actions(compose, FALSE);
10265 val = compose_queue_sub(compose, NULL, NULL, NULL, TRUE, TRUE);
10266 compose_allow_user_actions(compose, TRUE);
10269 if (val == COMPOSE_QUEUE_SUCCESS) {
10270 compose_close(compose);
10272 _display_queue_error(val);
10275 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
10278 #define DRAFTED_AT_EXIT "drafted_at_exit"
10279 static void compose_register_draft(MsgInfo *info)
10281 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10282 DRAFTED_AT_EXIT, NULL);
10283 FILE *fp = claws_fopen(filepath, "ab");
10286 fprintf(fp, "%s\t%d\n", folder_item_get_identifier(info->folder),
10294 gboolean compose_draft (gpointer data, guint action)
10296 Compose *compose = (Compose *)data;
10301 MsgFlags flag = {0, 0};
10302 static gboolean lock = FALSE;
10303 MsgInfo *newmsginfo;
10305 gboolean target_locked = FALSE;
10306 gboolean err = FALSE;
10308 if (lock) return FALSE;
10310 if (compose->sending)
10313 draft = account_get_special_folder(compose->account, F_DRAFT);
10314 cm_return_val_if_fail(draft != NULL, FALSE);
10316 if (!g_mutex_trylock(compose->mutex)) {
10317 /* we don't want to lock the mutex once it's available,
10318 * because as the only other part of compose.c locking
10319 * it is compose_close - which means once unlocked,
10320 * the compose struct will be freed */
10321 debug_print("couldn't lock mutex, probably sending\n");
10327 tmp = g_strdup_printf("%s%cdraft.%p", get_tmp_dir(),
10328 G_DIR_SEPARATOR, compose);
10329 if ((fp = claws_fopen(tmp, "wb")) == NULL) {
10330 FILE_OP_ERROR(tmp, "claws_fopen");
10334 /* chmod for security */
10335 if (change_file_mode_rw(fp, tmp) < 0) {
10336 FILE_OP_ERROR(tmp, "chmod");
10337 g_warning("can't change file mode");
10340 /* Save draft infos */
10341 err |= (fprintf(fp, "X-Claws-Account-Id:%d\n", compose->account->account_id) < 0);
10342 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
10344 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
10345 gchar *savefolderid;
10347 savefolderid = compose_get_save_to(compose);
10348 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
10349 g_free(savefolderid);
10351 if (compose->return_receipt) {
10352 err |= (fprintf(fp, "RRCPT:1\n") < 0);
10354 if (compose->privacy_system) {
10355 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
10356 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
10357 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
10360 /* Message-ID of message replying to */
10361 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
10362 gchar *folderid = NULL;
10364 if (compose->replyinfo->folder)
10365 folderid = folder_item_get_identifier(compose->replyinfo->folder);
10366 if (folderid == NULL)
10367 folderid = g_strdup("NULL");
10369 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
10372 /* Message-ID of message forwarding to */
10373 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
10374 gchar *folderid = NULL;
10376 if (compose->fwdinfo->folder)
10377 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
10378 if (folderid == NULL)
10379 folderid = g_strdup("NULL");
10381 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
10385 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
10386 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
10388 sheaders = compose_get_manual_headers_info(compose);
10389 err |= (fprintf(fp, "X-Claws-Manual-Headers:%s\n", sheaders) < 0);
10392 /* end of headers */
10393 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
10400 if (compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_STORE, action != COMPOSE_AUTO_SAVE) < 0) {
10404 if (claws_safe_fclose(fp) == EOF) {
10408 flag.perm_flags = MSG_NEW|MSG_UNREAD;
10409 if (compose->targetinfo) {
10410 target_locked = MSG_IS_LOCKED(compose->targetinfo->flags);
10412 flag.perm_flags |= MSG_LOCKED;
10414 flag.tmp_flags = MSG_DRAFT;
10416 folder_item_scan(draft);
10417 if ((msgnum = folder_item_add_msg(draft, tmp, &flag, TRUE)) < 0) {
10418 MsgInfo *tmpinfo = NULL;
10419 debug_print("didn't get msgnum after adding draft [%s]\n", compose->msgid?compose->msgid:"no msgid");
10420 if (compose->msgid) {
10421 tmpinfo = folder_item_get_msginfo_by_msgid(draft, compose->msgid);
10424 msgnum = tmpinfo->msgnum;
10425 procmsg_msginfo_free(&tmpinfo);
10426 debug_print("got draft msgnum %d from scanning\n", msgnum);
10428 debug_print("didn't get draft msgnum after scanning\n");
10431 debug_print("got draft msgnum %d from adding\n", msgnum);
10437 if (action != COMPOSE_AUTO_SAVE) {
10438 if (action != COMPOSE_DRAFT_FOR_EXIT)
10439 alertpanel_error(_("Could not save draft."));
10442 gtkut_window_popup(compose->window);
10443 val = alertpanel_full(_("Could not save draft"),
10444 _("Could not save draft.\n"
10445 "Do you want to cancel exit or discard this email?"),
10446 _("_Cancel exit"), _("_Discard email"), NULL, ALERTFOCUS_FIRST,
10447 FALSE, NULL, ALERT_QUESTION);
10448 if (val == G_ALERTALTERNATE) {
10450 g_mutex_unlock(compose->mutex); /* must be done before closing */
10451 compose_close(compose);
10455 g_mutex_unlock(compose->mutex); /* must be done before closing */
10464 if (compose->mode == COMPOSE_REEDIT) {
10465 compose_remove_reedit_target(compose, TRUE);
10468 newmsginfo = folder_item_get_msginfo(draft, msgnum);
10471 procmsg_msginfo_unset_flags(newmsginfo, ~0, ~0);
10473 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD|MSG_LOCKED, MSG_DRAFT);
10475 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD, MSG_DRAFT);
10476 if (compose_use_attach(compose) && action != COMPOSE_AUTO_SAVE)
10477 procmsg_msginfo_set_flags(newmsginfo, 0,
10478 MSG_HAS_ATTACHMENT);
10480 if (action == COMPOSE_DRAFT_FOR_EXIT) {
10481 compose_register_draft(newmsginfo);
10483 procmsg_msginfo_free(&newmsginfo);
10486 folder_item_scan(draft);
10488 if (action == COMPOSE_QUIT_EDITING || action == COMPOSE_DRAFT_FOR_EXIT) {
10490 g_mutex_unlock(compose->mutex); /* must be done before closing */
10491 compose_close(compose);
10498 GError *error = NULL;
10503 goffset size, mtime;
10505 path = folder_item_fetch_msg(draft, msgnum);
10506 if (path == NULL) {
10507 debug_print("can't fetch %s:%d\n", draft->path, msgnum);
10511 f = g_file_new_for_path(path);
10512 fi = g_file_query_info(f, "standard::size,time::modified",
10513 G_FILE_QUERY_INFO_NONE, NULL, &error);
10514 if (error != NULL) {
10515 debug_print("couldn't query file info for '%s': %s\n",
10516 path, error->message);
10517 g_error_free(error);
10522 size = g_file_info_get_size(fi);
10523 g_file_info_get_modification_time(fi, &tv);
10525 g_object_unref(fi);
10528 if (g_stat(path, &s) < 0) {
10529 FILE_OP_ERROR(path, "stat");
10534 mtime = s.st_mtime;
10538 procmsg_msginfo_free(&(compose->targetinfo));
10539 compose->targetinfo = procmsg_msginfo_new();
10540 compose->targetinfo->msgnum = msgnum;
10541 compose->targetinfo->size = size;
10542 compose->targetinfo->mtime = mtime;
10543 compose->targetinfo->folder = draft;
10545 procmsg_msginfo_set_flags(compose->targetinfo, MSG_LOCKED, 0);
10546 compose->mode = COMPOSE_REEDIT;
10548 if (action == COMPOSE_AUTO_SAVE) {
10549 compose->autosaved_draft = compose->targetinfo;
10551 compose->modified = FALSE;
10552 compose_set_title(compose);
10556 g_mutex_unlock(compose->mutex);
10560 void compose_clear_exit_drafts(void)
10562 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10563 DRAFTED_AT_EXIT, NULL);
10564 if (is_file_exist(filepath))
10565 claws_unlink(filepath);
10570 void compose_reopen_exit_drafts(void)
10572 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10573 DRAFTED_AT_EXIT, NULL);
10574 FILE *fp = claws_fopen(filepath, "rb");
10578 while (claws_fgets(buf, sizeof(buf), fp)) {
10579 gchar **parts = g_strsplit(buf, "\t", 2);
10580 const gchar *folder = parts[0];
10581 int msgnum = parts[1] ? atoi(parts[1]):-1;
10583 if (folder && *folder && msgnum > -1) {
10584 FolderItem *item = folder_find_item_from_identifier(folder);
10585 MsgInfo *info = folder_item_get_msginfo(item, msgnum);
10587 compose_reedit(info, FALSE);
10594 compose_clear_exit_drafts();
10597 static void compose_save_cb(GtkAction *action, gpointer data)
10599 Compose *compose = (Compose *)data;
10600 compose_draft(compose, COMPOSE_KEEP_EDITING);
10601 compose->rmode = COMPOSE_REEDIT;
10604 void compose_attach_from_list(Compose *compose, GList *file_list, gboolean free_data)
10606 if (compose && file_list) {
10609 for ( tmp = file_list; tmp; tmp = tmp->next) {
10610 gchar *file = (gchar *) tmp->data;
10611 gchar *utf8_filename = conv_filename_to_utf8(file);
10612 compose_attach_append(compose, file, utf8_filename, NULL, NULL);
10613 compose_changed_cb(NULL, compose);
10618 g_free(utf8_filename);
10623 static void compose_attach_cb(GtkAction *action, gpointer data)
10625 Compose *compose = (Compose *)data;
10628 if (compose->redirect_filename != NULL)
10631 /* Set focus_window properly, in case we were called via popup menu,
10632 * which unsets it (via focus_out_event callback on compose window). */
10633 manage_window_focus_in(compose->window, NULL, NULL);
10635 file_list = filesel_select_multiple_files_open(_("Select file"), NULL);
10638 compose_attach_from_list(compose, file_list, TRUE);
10639 g_list_free(file_list);
10643 static void compose_insert_file_cb(GtkAction *action, gpointer data)
10645 Compose *compose = (Compose *)data;
10647 gint files_inserted = 0;
10649 file_list = filesel_select_multiple_files_open(_("Select file"), NULL);
10654 for ( tmp = file_list; tmp; tmp = tmp->next) {
10655 gchar *file = (gchar *) tmp->data;
10656 gchar *filedup = g_strdup(file);
10657 gchar *shortfile = g_path_get_basename(filedup);
10658 ComposeInsertResult res;
10659 /* insert the file if the file is short or if the user confirmed that
10660 he/she wants to insert the large file */
10661 res = compose_insert_file(compose, file);
10662 if (res == COMPOSE_INSERT_READ_ERROR) {
10663 alertpanel_error(_("File '%s' could not be read."), shortfile);
10664 } else if (res == COMPOSE_INSERT_INVALID_CHARACTER) {
10665 alertpanel_error(_("File '%s' contained invalid characters\n"
10666 "for the current encoding, insertion may be incorrect."),
10668 } else if (res == COMPOSE_INSERT_SUCCESS)
10675 g_list_free(file_list);
10679 if (files_inserted > 0 && compose->gtkaspell &&
10680 compose->gtkaspell->check_while_typing)
10681 gtkaspell_highlight_all(compose->gtkaspell);
10685 static void compose_insert_sig_cb(GtkAction *action, gpointer data)
10687 Compose *compose = (Compose *)data;
10689 compose_insert_sig(compose, FALSE);
10692 static void compose_replace_sig_cb(GtkAction *action, gpointer data)
10694 Compose *compose = (Compose *)data;
10696 compose_insert_sig(compose, TRUE);
10699 static gint compose_delete_cb(GtkWidget *widget, GdkEventAny *event,
10703 Compose *compose = (Compose *)data;
10705 gtkut_widget_get_uposition(widget, &x, &y);
10706 if (!compose->batch) {
10707 prefs_common.compose_x = x;
10708 prefs_common.compose_y = y;
10710 if (compose->sending || compose->updating)
10712 compose_close_cb(NULL, compose);
10716 void compose_close_toolbar(Compose *compose)
10718 compose_close_cb(NULL, compose);
10721 static void compose_close_cb(GtkAction *action, gpointer data)
10723 Compose *compose = (Compose *)data;
10727 if (compose->exteditor_tag != -1) {
10728 if (!compose_ext_editor_kill(compose))
10733 if (compose->modified) {
10734 gboolean reedit = (compose->rmode == COMPOSE_REEDIT);
10735 if (!g_mutex_trylock(compose->mutex)) {
10736 /* we don't want to lock the mutex once it's available,
10737 * because as the only other part of compose.c locking
10738 * it is compose_close - which means once unlocked,
10739 * the compose struct will be freed */
10740 debug_print("couldn't lock mutex, probably sending\n");
10743 if (!reedit || compose->folder->stype == F_DRAFT) {
10744 val = alertpanel(_("Discard message"),
10745 _("This message has been modified. Discard it?"),
10746 _("_Discard"), _("_Save to Drafts"), GTK_STOCK_CANCEL,
10749 val = alertpanel(_("Save changes"),
10750 _("This message has been modified. Save the latest changes?"),
10751 _("_Don't save"), _("_Save to Drafts"), GTK_STOCK_CANCEL,
10752 ALERTFOCUS_SECOND);
10754 g_mutex_unlock(compose->mutex);
10756 case G_ALERTDEFAULT:
10757 if (compose_can_autosave(compose) && !reedit)
10758 compose_remove_draft(compose);
10760 case G_ALERTALTERNATE:
10761 compose_draft(data, COMPOSE_QUIT_EDITING);
10768 compose_close(compose);
10771 static void compose_print_cb(GtkAction *action, gpointer data)
10773 Compose *compose = (Compose *) data;
10775 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
10776 if (compose->targetinfo)
10777 messageview_print(compose->targetinfo, FALSE, -1, -1, 0);
10780 static void compose_set_encoding_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
10782 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
10783 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
10784 Compose *compose = (Compose *) data;
10787 compose->out_encoding = (CharSet)value;
10790 static void compose_address_cb(GtkAction *action, gpointer data)
10792 Compose *compose = (Compose *)data;
10794 #ifndef USE_ALT_ADDRBOOK
10795 addressbook_open(compose);
10797 GError* error = NULL;
10798 addressbook_connect_signals(compose);
10799 addressbook_dbus_open(TRUE, &error);
10801 g_warning("%s", error->message);
10802 g_error_free(error);
10807 static void about_show_cb(GtkAction *action, gpointer data)
10812 static void compose_template_activate_cb(GtkWidget *widget, gpointer data)
10814 Compose *compose = (Compose *)data;
10819 tmpl = g_object_get_data(G_OBJECT(widget), "template");
10820 cm_return_if_fail(tmpl != NULL);
10822 msg = g_strdup_printf(_("Do you want to apply the template '%s'?"),
10824 val = alertpanel(_("Apply template"), msg,
10825 _("_Replace"), _("_Insert"), GTK_STOCK_CANCEL, ALERTFOCUS_FIRST);
10828 if (val == G_ALERTDEFAULT)
10829 compose_template_apply(compose, tmpl, TRUE);
10830 else if (val == G_ALERTALTERNATE)
10831 compose_template_apply(compose, tmpl, FALSE);
10834 static void compose_ext_editor_cb(GtkAction *action, gpointer data)
10836 Compose *compose = (Compose *)data;
10839 if (compose->exteditor_tag != -1) {
10840 debug_print("ignoring open external editor: external editor still open\n");
10844 compose_exec_ext_editor(compose);
10847 static void compose_undo_cb(GtkAction *action, gpointer data)
10849 Compose *compose = (Compose *)data;
10850 gboolean prev_autowrap = compose->autowrap;
10852 compose->autowrap = FALSE;
10853 undo_undo(compose->undostruct);
10854 compose->autowrap = prev_autowrap;
10857 static void compose_redo_cb(GtkAction *action, gpointer data)
10859 Compose *compose = (Compose *)data;
10860 gboolean prev_autowrap = compose->autowrap;
10862 compose->autowrap = FALSE;
10863 undo_redo(compose->undostruct);
10864 compose->autowrap = prev_autowrap;
10867 static void entry_cut_clipboard(GtkWidget *entry)
10869 if (GTK_IS_EDITABLE(entry))
10870 gtk_editable_cut_clipboard (GTK_EDITABLE(entry));
10871 else if (GTK_IS_TEXT_VIEW(entry))
10872 gtk_text_buffer_cut_clipboard(
10873 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10874 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
10878 static void entry_copy_clipboard(GtkWidget *entry)
10880 if (GTK_IS_EDITABLE(entry))
10881 gtk_editable_copy_clipboard (GTK_EDITABLE(entry));
10882 else if (GTK_IS_TEXT_VIEW(entry))
10883 gtk_text_buffer_copy_clipboard(
10884 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10885 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
10888 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry,
10889 gboolean wrap, GdkAtom clip, GtkTextIter *insert_place)
10891 if (GTK_IS_TEXT_VIEW(entry)) {
10892 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10893 GtkTextMark *mark_start = gtk_text_buffer_get_insert(buffer);
10894 GtkTextIter start_iter, end_iter;
10896 gchar *contents = gtk_clipboard_wait_for_text(gtk_clipboard_get(clip));
10898 if (contents == NULL)
10901 /* we shouldn't delete the selection when middle-click-pasting, or we
10902 * can't mid-click-paste our own selection */
10903 if (clip != GDK_SELECTION_PRIMARY) {
10904 undo_paste_clipboard(GTK_TEXT_VIEW(compose->text), compose->undostruct);
10905 gtk_text_buffer_delete_selection(buffer, FALSE, TRUE);
10908 if (insert_place == NULL) {
10909 /* if insert_place isn't specified, insert at the cursor.
10910 * used for Ctrl-V pasting */
10911 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10912 start = gtk_text_iter_get_offset(&start_iter);
10913 gtk_text_buffer_insert(buffer, &start_iter, contents, strlen(contents));
10915 /* if insert_place is specified, paste here.
10916 * used for mid-click-pasting */
10917 start = gtk_text_iter_get_offset(insert_place);
10918 gtk_text_buffer_insert(buffer, insert_place, contents, strlen(contents));
10919 if (prefs_common.primary_paste_unselects)
10920 gtk_text_buffer_select_range(buffer, insert_place, insert_place);
10924 /* paste unwrapped: mark the paste so it's not wrapped later */
10925 end = start + strlen(contents);
10926 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, start);
10927 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, end);
10928 gtk_text_buffer_apply_tag_by_name(buffer, "no_wrap", &start_iter, &end_iter);
10929 } else if (wrap && clip == GDK_SELECTION_PRIMARY) {
10930 /* rewrap paragraph now (after a mid-click-paste) */
10931 mark_start = gtk_text_buffer_get_insert(buffer);
10932 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10933 gtk_text_iter_backward_char(&start_iter);
10934 compose_beautify_paragraph(compose, &start_iter, TRUE);
10936 } else if (GTK_IS_EDITABLE(entry))
10937 gtk_editable_paste_clipboard (GTK_EDITABLE(entry));
10939 compose->modified = TRUE;
10942 static void entry_allsel(GtkWidget *entry)
10944 if (GTK_IS_EDITABLE(entry))
10945 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
10946 else if (GTK_IS_TEXT_VIEW(entry)) {
10947 GtkTextIter startiter, enditer;
10948 GtkTextBuffer *textbuf;
10950 textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10951 gtk_text_buffer_get_start_iter(textbuf, &startiter);
10952 gtk_text_buffer_get_end_iter(textbuf, &enditer);
10954 gtk_text_buffer_move_mark_by_name(textbuf,
10955 "selection_bound", &startiter);
10956 gtk_text_buffer_move_mark_by_name(textbuf,
10957 "insert", &enditer);
10961 static void compose_cut_cb(GtkAction *action, gpointer data)
10963 Compose *compose = (Compose *)data;
10964 if (compose->focused_editable
10965 #ifndef GENERIC_UMPC
10966 && gtk_widget_has_focus(compose->focused_editable)
10969 entry_cut_clipboard(compose->focused_editable);
10972 static void compose_copy_cb(GtkAction *action, gpointer data)
10974 Compose *compose = (Compose *)data;
10975 if (compose->focused_editable
10976 #ifndef GENERIC_UMPC
10977 && gtk_widget_has_focus(compose->focused_editable)
10980 entry_copy_clipboard(compose->focused_editable);
10983 static void compose_paste_cb(GtkAction *action, gpointer data)
10985 Compose *compose = (Compose *)data;
10986 gint prev_autowrap;
10987 GtkTextBuffer *buffer;
10989 if (compose->focused_editable
10990 #ifndef GENERIC_UMPC
10991 && gtk_widget_has_focus(compose->focused_editable)
10994 entry_paste_clipboard(compose, compose->focused_editable,
10995 prefs_common.linewrap_pastes,
10996 GDK_SELECTION_CLIPBOARD, NULL);
11001 #ifndef GENERIC_UMPC
11002 gtk_widget_has_focus(compose->text) &&
11004 compose->gtkaspell &&
11005 compose->gtkaspell->check_while_typing)
11006 gtkaspell_highlight_all(compose->gtkaspell);
11010 static void compose_paste_as_quote_cb(GtkAction *action, gpointer data)
11012 Compose *compose = (Compose *)data;
11013 gint wrap_quote = prefs_common.linewrap_quote;
11014 if (compose->focused_editable
11015 #ifndef GENERIC_UMPC
11016 && gtk_widget_has_focus(compose->focused_editable)
11019 /* let text_insert() (called directly or at a later time
11020 * after the gtk_editable_paste_clipboard) know that
11021 * text is to be inserted as a quotation. implemented
11022 * by using a simple refcount... */
11023 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data(
11024 G_OBJECT(compose->focused_editable),
11025 "paste_as_quotation"));
11026 g_object_set_data(G_OBJECT(compose->focused_editable),
11027 "paste_as_quotation",
11028 GINT_TO_POINTER(paste_as_quotation + 1));
11029 prefs_common.linewrap_quote = prefs_common.linewrap_pastes;
11030 entry_paste_clipboard(compose, compose->focused_editable,
11031 prefs_common.linewrap_pastes,
11032 GDK_SELECTION_CLIPBOARD, NULL);
11033 prefs_common.linewrap_quote = wrap_quote;
11037 static void compose_paste_no_wrap_cb(GtkAction *action, gpointer data)
11039 Compose *compose = (Compose *)data;
11040 gint prev_autowrap;
11041 GtkTextBuffer *buffer;
11043 if (compose->focused_editable
11044 #ifndef GENERIC_UMPC
11045 && gtk_widget_has_focus(compose->focused_editable)
11048 entry_paste_clipboard(compose, compose->focused_editable, FALSE,
11049 GDK_SELECTION_CLIPBOARD, NULL);
11054 #ifndef GENERIC_UMPC
11055 gtk_widget_has_focus(compose->text) &&
11057 compose->gtkaspell &&
11058 compose->gtkaspell->check_while_typing)
11059 gtkaspell_highlight_all(compose->gtkaspell);
11063 static void compose_paste_wrap_cb(GtkAction *action, gpointer data)
11065 Compose *compose = (Compose *)data;
11066 gint prev_autowrap;
11067 GtkTextBuffer *buffer;
11069 if (compose->focused_editable
11070 #ifndef GENERIC_UMPC
11071 && gtk_widget_has_focus(compose->focused_editable)
11074 entry_paste_clipboard(compose, compose->focused_editable, TRUE,
11075 GDK_SELECTION_CLIPBOARD, NULL);
11080 #ifndef GENERIC_UMPC
11081 gtk_widget_has_focus(compose->text) &&
11083 compose->gtkaspell &&
11084 compose->gtkaspell->check_while_typing)
11085 gtkaspell_highlight_all(compose->gtkaspell);
11089 static void compose_allsel_cb(GtkAction *action, gpointer data)
11091 Compose *compose = (Compose *)data;
11092 if (compose->focused_editable
11093 #ifndef GENERIC_UMPC
11094 && gtk_widget_has_focus(compose->focused_editable)
11097 entry_allsel(compose->focused_editable);
11100 static void textview_move_beginning_of_line (GtkTextView *text)
11102 GtkTextBuffer *buffer;
11106 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11108 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11109 mark = gtk_text_buffer_get_insert(buffer);
11110 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11111 gtk_text_iter_set_line_offset(&ins, 0);
11112 gtk_text_buffer_place_cursor(buffer, &ins);
11115 static void textview_move_forward_character (GtkTextView *text)
11117 GtkTextBuffer *buffer;
11121 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11123 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11124 mark = gtk_text_buffer_get_insert(buffer);
11125 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11126 if (gtk_text_iter_forward_cursor_position(&ins))
11127 gtk_text_buffer_place_cursor(buffer, &ins);
11130 static void textview_move_backward_character (GtkTextView *text)
11132 GtkTextBuffer *buffer;
11136 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11138 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11139 mark = gtk_text_buffer_get_insert(buffer);
11140 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11141 if (gtk_text_iter_backward_cursor_position(&ins))
11142 gtk_text_buffer_place_cursor(buffer, &ins);
11145 static void textview_move_forward_word (GtkTextView *text)
11147 GtkTextBuffer *buffer;
11152 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11154 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11155 mark = gtk_text_buffer_get_insert(buffer);
11156 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11157 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
11158 if (gtk_text_iter_forward_word_ends(&ins, count)) {
11159 gtk_text_iter_backward_word_start(&ins);
11160 gtk_text_buffer_place_cursor(buffer, &ins);
11164 static void textview_move_backward_word (GtkTextView *text)
11166 GtkTextBuffer *buffer;
11170 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11172 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11173 mark = gtk_text_buffer_get_insert(buffer);
11174 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11175 if (gtk_text_iter_backward_word_starts(&ins, 1))
11176 gtk_text_buffer_place_cursor(buffer, &ins);
11179 static void textview_move_end_of_line (GtkTextView *text)
11181 GtkTextBuffer *buffer;
11185 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11187 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11188 mark = gtk_text_buffer_get_insert(buffer);
11189 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11190 if (gtk_text_iter_forward_to_line_end(&ins))
11191 gtk_text_buffer_place_cursor(buffer, &ins);
11194 static void textview_move_next_line (GtkTextView *text)
11196 GtkTextBuffer *buffer;
11201 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11203 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11204 mark = gtk_text_buffer_get_insert(buffer);
11205 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11206 offset = gtk_text_iter_get_line_offset(&ins);
11207 if (gtk_text_iter_forward_line(&ins)) {
11208 gtk_text_iter_set_line_offset(&ins, offset);
11209 gtk_text_buffer_place_cursor(buffer, &ins);
11213 static void textview_move_previous_line (GtkTextView *text)
11215 GtkTextBuffer *buffer;
11220 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11222 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11223 mark = gtk_text_buffer_get_insert(buffer);
11224 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11225 offset = gtk_text_iter_get_line_offset(&ins);
11226 if (gtk_text_iter_backward_line(&ins)) {
11227 gtk_text_iter_set_line_offset(&ins, offset);
11228 gtk_text_buffer_place_cursor(buffer, &ins);
11232 static void textview_delete_forward_character (GtkTextView *text)
11234 GtkTextBuffer *buffer;
11236 GtkTextIter ins, end_iter;
11238 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11240 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11241 mark = gtk_text_buffer_get_insert(buffer);
11242 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11244 if (gtk_text_iter_forward_char(&end_iter)) {
11245 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11249 static void textview_delete_backward_character (GtkTextView *text)
11251 GtkTextBuffer *buffer;
11253 GtkTextIter ins, end_iter;
11255 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11257 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11258 mark = gtk_text_buffer_get_insert(buffer);
11259 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11261 if (gtk_text_iter_backward_char(&end_iter)) {
11262 gtk_text_buffer_delete(buffer, &end_iter, &ins);
11266 static void textview_delete_forward_word (GtkTextView *text)
11268 GtkTextBuffer *buffer;
11270 GtkTextIter ins, end_iter;
11272 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11274 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11275 mark = gtk_text_buffer_get_insert(buffer);
11276 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11278 if (gtk_text_iter_forward_word_end(&end_iter)) {
11279 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11283 static void textview_delete_backward_word (GtkTextView *text)
11285 GtkTextBuffer *buffer;
11287 GtkTextIter ins, end_iter;
11289 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11291 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11292 mark = gtk_text_buffer_get_insert(buffer);
11293 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11295 if (gtk_text_iter_backward_word_start(&end_iter)) {
11296 gtk_text_buffer_delete(buffer, &end_iter, &ins);
11300 static void textview_delete_line (GtkTextView *text)
11302 GtkTextBuffer *buffer;
11304 GtkTextIter ins, start_iter, end_iter;
11306 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11308 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11309 mark = gtk_text_buffer_get_insert(buffer);
11310 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11313 gtk_text_iter_set_line_offset(&start_iter, 0);
11316 if (gtk_text_iter_ends_line(&end_iter)){
11317 if (!gtk_text_iter_forward_char(&end_iter))
11318 gtk_text_iter_backward_char(&start_iter);
11321 gtk_text_iter_forward_to_line_end(&end_iter);
11322 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
11325 static void textview_delete_to_line_end (GtkTextView *text)
11327 GtkTextBuffer *buffer;
11329 GtkTextIter ins, end_iter;
11331 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11333 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11334 mark = gtk_text_buffer_get_insert(buffer);
11335 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11337 if (gtk_text_iter_ends_line(&end_iter))
11338 gtk_text_iter_forward_char(&end_iter);
11340 gtk_text_iter_forward_to_line_end(&end_iter);
11341 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11344 #define DO_ACTION(name, act) { \
11345 if(!strcmp(name, a_name)) { \
11349 static ComposeCallAdvancedAction compose_call_advanced_action_from_path(GtkAction *action)
11351 const gchar *a_name = gtk_action_get_name(action);
11352 DO_ACTION("Edit/Advanced/BackChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER);
11353 DO_ACTION("Edit/Advanced/ForwChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER);
11354 DO_ACTION("Edit/Advanced/BackWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD);
11355 DO_ACTION("Edit/Advanced/ForwWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD);
11356 DO_ACTION("Edit/Advanced/BegLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE);
11357 DO_ACTION("Edit/Advanced/EndLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE);
11358 DO_ACTION("Edit/Advanced/PrevLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE);
11359 DO_ACTION("Edit/Advanced/NextLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE);
11360 DO_ACTION("Edit/Advanced/DelBackChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER);
11361 DO_ACTION("Edit/Advanced/DelForwChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER);
11362 DO_ACTION("Edit/Advanced/DelBackWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD);
11363 DO_ACTION("Edit/Advanced/DelForwWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD);
11364 DO_ACTION("Edit/Advanced/DelLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE);
11365 DO_ACTION("Edit/Advanced/DelEndLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END);
11366 return COMPOSE_CALL_ADVANCED_ACTION_UNDEFINED;
11369 static void compose_advanced_action_cb(GtkAction *gaction, gpointer data)
11371 Compose *compose = (Compose *)data;
11372 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11373 ComposeCallAdvancedAction action = COMPOSE_CALL_ADVANCED_ACTION_UNDEFINED;
11375 action = compose_call_advanced_action_from_path(gaction);
11378 void (*do_action) (GtkTextView *text);
11379 } action_table[] = {
11380 {textview_move_beginning_of_line},
11381 {textview_move_forward_character},
11382 {textview_move_backward_character},
11383 {textview_move_forward_word},
11384 {textview_move_backward_word},
11385 {textview_move_end_of_line},
11386 {textview_move_next_line},
11387 {textview_move_previous_line},
11388 {textview_delete_forward_character},
11389 {textview_delete_backward_character},
11390 {textview_delete_forward_word},
11391 {textview_delete_backward_word},
11392 {textview_delete_line},
11393 {textview_delete_to_line_end}
11396 if (!gtk_widget_has_focus(GTK_WIDGET(text))) return;
11398 if (action >= COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE &&
11399 action <= COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END) {
11400 if (action_table[action].do_action)
11401 action_table[action].do_action(text);
11403 g_warning("Not implemented yet.");
11407 static void compose_grab_focus_cb(GtkWidget *widget, Compose *compose)
11409 GtkAllocation allocation;
11413 if (GTK_IS_EDITABLE(widget)) {
11414 str = gtk_editable_get_chars(GTK_EDITABLE(widget), 0, -1);
11415 gtk_editable_set_position(GTK_EDITABLE(widget),
11418 if ((parent = gtk_widget_get_parent(widget))
11419 && (parent = gtk_widget_get_parent(parent))
11420 && (parent = gtk_widget_get_parent(parent))) {
11421 if (GTK_IS_SCROLLED_WINDOW(parent)) {
11422 gtk_widget_get_allocation(widget, &allocation);
11423 gint y = allocation.y;
11424 gint height = allocation.height;
11425 GtkAdjustment *shown = gtk_scrolled_window_get_vadjustment
11426 (GTK_SCROLLED_WINDOW(parent));
11428 gfloat value = gtk_adjustment_get_value(shown);
11429 gfloat upper = gtk_adjustment_get_upper(shown);
11430 gfloat page_size = gtk_adjustment_get_page_size(shown);
11431 if (y < (int)value) {
11432 gtk_adjustment_set_value(shown, y - 1);
11434 if ((y + height) > ((int)value + (int)page_size)) {
11435 if ((y - height - 1) < ((int)upper - (int)page_size)) {
11436 gtk_adjustment_set_value(shown,
11437 y + height - (int)page_size - 1);
11439 gtk_adjustment_set_value(shown,
11440 (int)upper - (int)page_size - 1);
11447 if (GTK_IS_EDITABLE(widget) || GTK_IS_TEXT_VIEW(widget))
11448 compose->focused_editable = widget;
11450 #ifdef GENERIC_UMPC
11451 if (GTK_IS_TEXT_VIEW(widget)
11452 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->edit_vbox) {
11453 g_object_ref(compose->notebook);
11454 g_object_ref(compose->edit_vbox);
11455 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
11456 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
11457 gtk_paned_add1(GTK_PANED(compose->paned), compose->edit_vbox);
11458 gtk_paned_add2(GTK_PANED(compose->paned), compose->notebook);
11459 g_object_unref(compose->notebook);
11460 g_object_unref(compose->edit_vbox);
11461 g_signal_handlers_block_by_func(G_OBJECT(widget),
11462 G_CALLBACK(compose_grab_focus_cb),
11464 gtk_widget_grab_focus(widget);
11465 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
11466 G_CALLBACK(compose_grab_focus_cb),
11468 } else if (!GTK_IS_TEXT_VIEW(widget)
11469 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->notebook) {
11470 g_object_ref(compose->notebook);
11471 g_object_ref(compose->edit_vbox);
11472 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
11473 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
11474 gtk_paned_add1(GTK_PANED(compose->paned), compose->notebook);
11475 gtk_paned_add2(GTK_PANED(compose->paned), compose->edit_vbox);
11476 g_object_unref(compose->notebook);
11477 g_object_unref(compose->edit_vbox);
11478 g_signal_handlers_block_by_func(G_OBJECT(widget),
11479 G_CALLBACK(compose_grab_focus_cb),
11481 gtk_widget_grab_focus(widget);
11482 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
11483 G_CALLBACK(compose_grab_focus_cb),
11489 static void compose_changed_cb(GtkTextBuffer *textbuf, Compose *compose)
11491 compose->modified = TRUE;
11492 /* compose_beautify_paragraph(compose, NULL, TRUE); */
11493 #ifndef GENERIC_UMPC
11494 compose_set_title(compose);
11498 static void compose_wrap_cb(GtkAction *action, gpointer data)
11500 Compose *compose = (Compose *)data;
11501 compose_beautify_paragraph(compose, NULL, TRUE);
11504 static void compose_wrap_all_cb(GtkAction *action, gpointer data)
11506 Compose *compose = (Compose *)data;
11507 compose_wrap_all_full(compose, TRUE);
11510 static void compose_find_cb(GtkAction *action, gpointer data)
11512 Compose *compose = (Compose *)data;
11514 message_search_compose(compose);
11517 static void compose_toggle_autowrap_cb(GtkToggleAction *action,
11520 Compose *compose = (Compose *)data;
11521 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11522 if (compose->autowrap)
11523 compose_wrap_all_full(compose, TRUE);
11524 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11527 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
11530 Compose *compose = (Compose *)data;
11531 compose->autoindent = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11534 static void compose_toggle_sign_cb(GtkToggleAction *action, gpointer data)
11536 Compose *compose = (Compose *)data;
11538 compose->use_signing = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11539 gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(compose->toolbar->privacy_sign_btn), compose->use_signing);
11542 static void compose_toggle_encrypt_cb(GtkToggleAction *action, gpointer data)
11544 Compose *compose = (Compose *)data;
11546 compose->use_encryption = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11547 gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(compose->toolbar->privacy_encrypt_btn), compose->use_encryption);
11550 static void compose_activate_privacy_system(Compose *compose, PrefsAccount *account, gboolean warn)
11552 g_free(compose->privacy_system);
11553 g_free(compose->encdata);
11555 compose->privacy_system = g_strdup(account->default_privacy_system);
11556 compose_update_privacy_system_menu_item(compose, warn);
11559 static void compose_apply_folder_privacy_settings(Compose *compose, FolderItem *folder_item)
11561 if (folder_item != NULL) {
11562 if (folder_item->prefs->always_sign != SIGN_OR_ENCRYPT_DEFAULT &&
11563 privacy_system_can_sign(compose->privacy_system)) {
11564 compose_use_signing(compose,
11565 (folder_item->prefs->always_sign == SIGN_OR_ENCRYPT_ALWAYS) ? TRUE : FALSE);
11567 if (folder_item->prefs->always_encrypt != SIGN_OR_ENCRYPT_DEFAULT &&
11568 privacy_system_can_encrypt(compose->privacy_system)) {
11569 compose_use_encryption(compose,
11570 (folder_item->prefs->always_encrypt == SIGN_OR_ENCRYPT_ALWAYS) ? TRUE : FALSE);
11575 static void compose_toggle_ruler_cb(GtkToggleAction *action, gpointer data)
11577 Compose *compose = (Compose *)data;
11579 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action))) {
11580 gtk_widget_show(compose->ruler_hbox);
11581 prefs_common.show_ruler = TRUE;
11583 gtk_widget_hide(compose->ruler_hbox);
11584 gtk_widget_queue_resize(compose->edit_vbox);
11585 prefs_common.show_ruler = FALSE;
11589 static void compose_attach_drag_received_cb (GtkWidget *widget,
11590 GdkDragContext *context,
11593 GtkSelectionData *data,
11596 gpointer user_data)
11598 Compose *compose = (Compose *)user_data;
11602 type = gtk_selection_data_get_data_type(data);
11603 if ((gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list"))
11604 && gtk_drag_get_source_widget(context) !=
11605 summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
11606 list = uri_list_extract_filenames(
11607 (const gchar *)gtk_selection_data_get_data(data));
11608 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11609 gchar *utf8_filename = conv_filename_to_utf8((const gchar *)tmp->data);
11610 compose_attach_append
11611 (compose, (const gchar *)tmp->data,
11612 utf8_filename, NULL, NULL);
11613 g_free(utf8_filename);
11616 compose_changed_cb(NULL, compose);
11617 list_free_strings_full(list);
11618 } else if (gtk_drag_get_source_widget(context)
11619 == summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
11620 /* comes from our summaryview */
11621 SummaryView * summaryview = NULL;
11622 GSList * list = NULL, *cur = NULL;
11624 if (mainwindow_get_mainwindow())
11625 summaryview = mainwindow_get_mainwindow()->summaryview;
11628 list = summary_get_selected_msg_list(summaryview);
11630 for (cur = list; cur; cur = cur->next) {
11631 MsgInfo *msginfo = (MsgInfo *)cur->data;
11632 gchar *file = NULL;
11634 file = procmsg_get_message_file_full(msginfo,
11637 compose_attach_append(compose, (const gchar *)file,
11638 (const gchar *)file, "message/rfc822", NULL);
11642 g_slist_free(list);
11646 static gboolean compose_drag_drop(GtkWidget *widget,
11647 GdkDragContext *drag_context,
11649 guint time, gpointer user_data)
11651 /* not handling this signal makes compose_insert_drag_received_cb
11656 static gboolean completion_set_focus_to_subject
11657 (GtkWidget *widget,
11658 GdkEventKey *event,
11661 cm_return_val_if_fail(compose != NULL, FALSE);
11663 /* make backtab move to subject field */
11664 if(event->keyval == GDK_KEY_ISO_Left_Tab) {
11665 gtk_widget_grab_focus(compose->subject_entry);
11671 static void compose_insert_drag_received_cb (GtkWidget *widget,
11672 GdkDragContext *drag_context,
11675 GtkSelectionData *data,
11678 gpointer user_data)
11680 Compose *compose = (Compose *)user_data;
11686 /* strangely, testing data->type == gdk_atom_intern("text/uri-list", TRUE)
11688 type = gtk_selection_data_get_data_type(data);
11689 if (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list")) {
11690 AlertValue val = G_ALERTDEFAULT;
11691 const gchar* ddata = (const gchar *)gtk_selection_data_get_data(data);
11693 list = uri_list_extract_filenames(ddata);
11694 num_files = g_list_length(list);
11695 if (list == NULL && strstr(ddata, "://")) {
11696 /* Assume a list of no files, and data has ://, is a remote link */
11697 gchar *tmpdata = g_strstrip(g_strdup(ddata));
11698 gchar *tmpfile = get_tmp_file();
11699 str_write_to_file(tmpdata, tmpfile, TRUE);
11701 compose_insert_file(compose, tmpfile);
11702 claws_unlink(tmpfile);
11704 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11705 compose_beautify_paragraph(compose, NULL, TRUE);
11708 switch (prefs_common.compose_dnd_mode) {
11709 case COMPOSE_DND_ASK:
11710 msg = g_strdup_printf(
11712 "Do you want to insert the contents of the file "
11713 "into the message body, or attach it to the email?",
11714 "Do you want to insert the contents of the %d files "
11715 "into the message body, or attach them to the email?",
11718 val = alertpanel_full(_("Insert or attach?"), msg,
11719 GTK_STOCK_CANCEL, _("_Insert"), _("_Attach"),
11721 TRUE, NULL, ALERT_QUESTION);
11724 case COMPOSE_DND_INSERT:
11725 val = G_ALERTALTERNATE;
11727 case COMPOSE_DND_ATTACH:
11728 val = G_ALERTOTHER;
11731 /* unexpected case */
11732 g_warning("error: unexpected compose_dnd_mode option value in compose_insert_drag_received_cb()");
11735 if (val & G_ALERTDISABLE) {
11736 val &= ~G_ALERTDISABLE;
11737 /* remember what action to perform by default, only if we don't click Cancel */
11738 if (val == G_ALERTALTERNATE)
11739 prefs_common.compose_dnd_mode = COMPOSE_DND_INSERT;
11740 else if (val == G_ALERTOTHER)
11741 prefs_common.compose_dnd_mode = COMPOSE_DND_ATTACH;
11744 if (val == G_ALERTDEFAULT || val == G_ALERTCANCEL) {
11745 gtk_drag_finish(drag_context, FALSE, FALSE, time);
11746 list_free_strings_full(list);
11748 } else if (val == G_ALERTOTHER) {
11749 compose_attach_drag_received_cb(widget, drag_context, x, y, data, info, time, user_data);
11750 list_free_strings_full(list);
11754 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11755 compose_insert_file(compose, (const gchar *)tmp->data);
11757 list_free_strings_full(list);
11758 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11763 static void compose_header_drag_received_cb (GtkWidget *widget,
11764 GdkDragContext *drag_context,
11767 GtkSelectionData *data,
11770 gpointer user_data)
11772 GtkEditable *entry = (GtkEditable *)user_data;
11773 const gchar *email = (const gchar *)gtk_selection_data_get_data(data);
11775 /* strangely, testing data->type == gdk_atom_intern("text/plain", TRUE)
11778 if (!strncmp(email, "mailto:", strlen("mailto:"))) {
11779 gchar *decoded=g_new(gchar, strlen(email));
11782 decode_uri(decoded, email + strlen("mailto:")); /* will fit */
11783 gtk_editable_delete_text(entry, 0, -1);
11784 gtk_editable_insert_text(entry, decoded, strlen(decoded), &start);
11785 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11789 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11792 static void compose_toggle_return_receipt_cb(GtkToggleAction *action, gpointer data)
11794 Compose *compose = (Compose *)data;
11796 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11797 compose->return_receipt = TRUE;
11799 compose->return_receipt = FALSE;
11802 static void compose_toggle_remove_refs_cb(GtkToggleAction *action, gpointer data)
11804 Compose *compose = (Compose *)data;
11806 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11807 compose->remove_references = TRUE;
11809 compose->remove_references = FALSE;
11812 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
11813 ComposeHeaderEntry *headerentry)
11815 gtk_entry_set_text(GTK_ENTRY(headerentry->entry), "");
11816 gtk_widget_modify_base(GTK_WIDGET(headerentry->entry), GTK_STATE_NORMAL, NULL);
11817 gtk_widget_modify_text(GTK_WIDGET(headerentry->entry), GTK_STATE_NORMAL, NULL);
11821 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
11822 GdkEventKey *event,
11823 ComposeHeaderEntry *headerentry)
11825 if ((g_slist_length(headerentry->compose->header_list) > 0) &&
11826 ((headerentry->headernum + 1) != headerentry->compose->header_nextrow) &&
11827 !(event->state & GDK_MODIFIER_MASK) &&
11828 (event->keyval == GDK_KEY_BackSpace) &&
11829 (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) == 0)) {
11830 gtk_container_remove
11831 (GTK_CONTAINER(headerentry->compose->header_table),
11832 headerentry->combo);
11833 gtk_container_remove
11834 (GTK_CONTAINER(headerentry->compose->header_table),
11835 headerentry->entry);
11836 headerentry->compose->header_list =
11837 g_slist_remove(headerentry->compose->header_list,
11839 g_free(headerentry);
11840 } else if (event->keyval == GDK_KEY_Tab) {
11841 if (headerentry->compose->header_last == headerentry) {
11842 /* Override default next focus, and give it to subject_entry
11843 * instead of notebook tabs
11845 g_signal_stop_emission_by_name(G_OBJECT(entry), "key-press-event");
11846 gtk_widget_grab_focus(headerentry->compose->subject_entry);
11853 static gboolean scroll_postpone(gpointer data)
11855 Compose *compose = (Compose *)data;
11857 if (compose->batch)
11860 GTK_EVENTS_FLUSH();
11861 compose_show_first_last_header(compose, FALSE);
11865 static void compose_headerentry_changed_cb(GtkWidget *entry,
11866 ComposeHeaderEntry *headerentry)
11868 if (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) != 0) {
11869 compose_create_header_entry(headerentry->compose);
11870 g_signal_handlers_disconnect_matched
11871 (G_OBJECT(entry), G_SIGNAL_MATCH_DATA,
11872 0, 0, NULL, NULL, headerentry);
11874 if (!headerentry->compose->batch)
11875 g_timeout_add(0, scroll_postpone, headerentry->compose);
11879 static gboolean compose_defer_auto_save_draft(Compose *compose)
11881 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
11882 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
11886 static void compose_show_first_last_header(Compose *compose, gboolean show_first)
11888 GtkAdjustment *vadj;
11890 cm_return_if_fail(compose);
11895 cm_return_if_fail(GTK_IS_WIDGET(compose->header_table));
11896 cm_return_if_fail(GTK_IS_VIEWPORT(gtk_widget_get_parent(compose->header_table)));
11897 vadj = gtk_viewport_get_vadjustment(GTK_VIEWPORT(
11898 gtk_widget_get_parent(compose->header_table)));
11899 gtk_adjustment_set_value(vadj, (show_first ?
11900 gtk_adjustment_get_lower(vadj) :
11901 (gtk_adjustment_get_upper(vadj) -
11902 gtk_adjustment_get_page_size(vadj))));
11903 gtk_adjustment_changed(vadj);
11906 static void text_inserted(GtkTextBuffer *buffer, GtkTextIter *iter,
11907 const gchar *text, gint len, Compose *compose)
11909 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data
11910 (G_OBJECT(compose->text), "paste_as_quotation"));
11913 cm_return_if_fail(text != NULL);
11915 g_signal_handlers_block_by_func(G_OBJECT(buffer),
11916 G_CALLBACK(text_inserted),
11918 if (paste_as_quotation) {
11920 const gchar *qmark;
11922 GtkTextIter start_iter;
11925 len = strlen(text);
11927 new_text = g_strndup(text, len);
11929 qmark = compose_quote_char_from_context(compose);
11931 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11932 gtk_text_buffer_place_cursor(buffer, iter);
11934 pos = gtk_text_iter_get_offset(iter);
11936 compose_quote_fmt(compose, NULL, "%Q", qmark, new_text, TRUE, FALSE,
11937 _("Quote format error at line %d."));
11938 quote_fmt_reset_vartable();
11940 g_object_set_data(G_OBJECT(compose->text), "paste_as_quotation",
11941 GINT_TO_POINTER(paste_as_quotation - 1));
11943 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11944 gtk_text_buffer_place_cursor(buffer, iter);
11945 gtk_text_buffer_delete_mark(buffer, mark);
11947 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, pos);
11948 mark = gtk_text_buffer_create_mark(buffer, NULL, &start_iter, FALSE);
11949 compose_beautify_paragraph(compose, &start_iter, FALSE);
11950 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark);
11951 gtk_text_buffer_delete_mark(buffer, mark);
11953 if (strcmp(text, "\n") || compose->automatic_break
11954 || gtk_text_iter_starts_line(iter)) {
11955 GtkTextIter before_ins;
11956 gtk_text_buffer_insert(buffer, iter, text, len);
11957 if (!strstr(text, "\n") && gtk_text_iter_has_tag(iter, compose->no_join_tag)) {
11958 before_ins = *iter;
11959 gtk_text_iter_backward_chars(&before_ins, len);
11960 gtk_text_buffer_remove_tag_by_name(buffer, "no_join", &before_ins, iter);
11963 /* check if the preceding is just whitespace or quote */
11964 GtkTextIter start_line;
11965 gchar *tmp = NULL, *quote = NULL;
11966 gint quote_len = 0, is_normal = 0;
11967 start_line = *iter;
11968 gtk_text_iter_set_line_offset(&start_line, 0);
11969 tmp = gtk_text_buffer_get_text(buffer, &start_line, iter, FALSE);
11972 if (*tmp == '\0') {
11975 quote = compose_get_quote_str(buffer, &start_line, "e_len);
11983 gtk_text_buffer_insert(buffer, iter, text, len);
11985 gtk_text_buffer_insert_with_tags_by_name(buffer,
11986 iter, text, len, "no_join", NULL);
11991 if (!paste_as_quotation) {
11992 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11993 compose_beautify_paragraph(compose, iter, FALSE);
11994 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11995 gtk_text_buffer_delete_mark(buffer, mark);
11998 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
11999 G_CALLBACK(text_inserted),
12001 g_signal_stop_emission_by_name(G_OBJECT(buffer), "insert-text");
12003 if (compose_can_autosave(compose) &&
12004 gtk_text_buffer_get_char_count(buffer) % prefs_common.autosave_length == 0 &&
12005 compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN /* disabled while loading */)
12006 compose->draft_timeout_tag = g_timeout_add
12007 (500, (GSourceFunc) compose_defer_auto_save_draft, compose);
12011 static void compose_check_all(GtkAction *action, gpointer data)
12013 Compose *compose = (Compose *)data;
12014 if (!compose->gtkaspell)
12017 if (gtk_widget_has_focus(compose->subject_entry))
12018 claws_spell_entry_check_all(
12019 CLAWS_SPELL_ENTRY(compose->subject_entry));
12021 gtkaspell_check_all(compose->gtkaspell);
12024 static void compose_highlight_all(GtkAction *action, gpointer data)
12026 Compose *compose = (Compose *)data;
12027 if (compose->gtkaspell) {
12028 claws_spell_entry_recheck_all(
12029 CLAWS_SPELL_ENTRY(compose->subject_entry));
12030 gtkaspell_highlight_all(compose->gtkaspell);
12034 static void compose_check_backwards(GtkAction *action, gpointer data)
12036 Compose *compose = (Compose *)data;
12037 if (!compose->gtkaspell) {
12038 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
12042 if (gtk_widget_has_focus(compose->subject_entry))
12043 claws_spell_entry_check_backwards(
12044 CLAWS_SPELL_ENTRY(compose->subject_entry));
12046 gtkaspell_check_backwards(compose->gtkaspell);
12049 static void compose_check_forwards_go(GtkAction *action, gpointer data)
12051 Compose *compose = (Compose *)data;
12052 if (!compose->gtkaspell) {
12053 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
12057 if (gtk_widget_has_focus(compose->subject_entry))
12058 claws_spell_entry_check_forwards_go(
12059 CLAWS_SPELL_ENTRY(compose->subject_entry));
12061 gtkaspell_check_forwards_go(compose->gtkaspell);
12066 *\brief Guess originating forward account from MsgInfo and several
12067 * "common preference" settings. Return NULL if no guess.
12069 static PrefsAccount *compose_find_account(MsgInfo *msginfo)
12071 PrefsAccount *account = NULL;
12073 cm_return_val_if_fail(msginfo, NULL);
12074 cm_return_val_if_fail(msginfo->folder, NULL);
12075 cm_return_val_if_fail(msginfo->folder->prefs, NULL);
12077 if (msginfo->folder->prefs->enable_default_account)
12078 account = account_find_from_id(msginfo->folder->prefs->default_account);
12080 if (!account && msginfo->to && prefs_common.forward_account_autosel) {
12082 Xstrdup_a(to, msginfo->to, return NULL);
12083 extract_address(to);
12084 account = account_find_from_address(to, FALSE);
12087 if (!account && prefs_common.forward_account_autosel) {
12089 if (!procheader_get_header_from_msginfo
12090 (msginfo, &cc, "Cc:")) {
12091 gchar *buf = cc + strlen("Cc:");
12092 extract_address(buf);
12093 account = account_find_from_address(buf, FALSE);
12098 if (!account && prefs_common.forward_account_autosel) {
12099 gchar *deliveredto = NULL;
12100 if (!procheader_get_header_from_msginfo
12101 (msginfo, &deliveredto, "Delivered-To:")) {
12102 gchar *buf = deliveredto + strlen("Delivered-To:");
12103 extract_address(buf);
12104 account = account_find_from_address(buf, FALSE);
12105 g_free(deliveredto);
12110 account = msginfo->folder->folder->account;
12115 gboolean compose_close(Compose *compose)
12119 cm_return_val_if_fail(compose, FALSE);
12121 if (!g_mutex_trylock(compose->mutex)) {
12122 /* we have to wait for the (possibly deferred by auto-save)
12123 * drafting to be done, before destroying the compose under
12125 debug_print("waiting for drafting to finish...\n");
12126 compose_allow_user_actions(compose, FALSE);
12127 if (compose->close_timeout_tag == 0) {
12128 compose->close_timeout_tag =
12129 g_timeout_add (500, (GSourceFunc) compose_close,
12135 if (compose->draft_timeout_tag >= 0) {
12136 g_source_remove(compose->draft_timeout_tag);
12137 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN;
12140 gtkut_widget_get_uposition(compose->window, &x, &y);
12141 if (!compose->batch) {
12142 prefs_common.compose_x = x;
12143 prefs_common.compose_y = y;
12145 g_mutex_unlock(compose->mutex);
12146 compose_destroy(compose);
12151 * Add entry field for each address in list.
12152 * \param compose E-Mail composition object.
12153 * \param listAddress List of (formatted) E-Mail addresses.
12155 static void compose_add_field_list( Compose *compose, GList *listAddress ) {
12158 node = listAddress;
12160 addr = ( gchar * ) node->data;
12161 compose_entry_append( compose, addr, COMPOSE_TO, PREF_NONE );
12162 node = g_list_next( node );
12166 static void compose_reply_from_messageview_real(MessageView *msgview, GSList *msginfo_list,
12167 guint action, gboolean opening_multiple)
12169 gchar *body = NULL;
12170 GSList *new_msglist = NULL;
12171 MsgInfo *tmp_msginfo = NULL;
12172 gboolean originally_enc = FALSE;
12173 gboolean originally_sig = FALSE;
12174 Compose *compose = NULL;
12175 gchar *s_system = NULL;
12177 cm_return_if_fail(msginfo_list != NULL);
12179 if (g_slist_length(msginfo_list) == 1 && !opening_multiple && msgview != NULL) {
12180 MimeInfo *mimeinfo = messageview_get_selected_mime_part(msgview);
12181 MsgInfo *orig_msginfo = (MsgInfo *)msginfo_list->data;
12183 if (mimeinfo != NULL && mimeinfo->type == MIMETYPE_MESSAGE &&
12184 !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
12185 tmp_msginfo = procmsg_msginfo_new_from_mimeinfo(
12186 orig_msginfo, mimeinfo);
12187 if (tmp_msginfo != NULL) {
12188 new_msglist = g_slist_append(NULL, tmp_msginfo);
12190 originally_enc = MSG_IS_ENCRYPTED(orig_msginfo->flags);
12191 privacy_msginfo_get_signed_state(orig_msginfo, &s_system);
12192 originally_sig = MSG_IS_SIGNED(orig_msginfo->flags);
12194 tmp_msginfo->folder = orig_msginfo->folder;
12195 tmp_msginfo->msgnum = orig_msginfo->msgnum;
12196 if (orig_msginfo->tags) {
12197 tmp_msginfo->tags = g_slist_copy(orig_msginfo->tags);
12198 tmp_msginfo->folder->tags_dirty = TRUE;
12204 if (!opening_multiple && msgview != NULL)
12205 body = messageview_get_selection(msgview);
12208 compose = compose_reply_mode((ComposeMode)action, new_msglist, body);
12209 procmsg_msginfo_free(&tmp_msginfo);
12210 g_slist_free(new_msglist);
12212 compose = compose_reply_mode((ComposeMode)action, msginfo_list, body);
12214 if (compose && originally_enc) {
12215 compose_force_encryption(compose, compose->account, FALSE, s_system);
12218 if (compose && originally_sig && compose->account->default_sign_reply) {
12219 compose_force_signing(compose, compose->account, s_system);
12223 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
12226 void compose_reply_from_messageview(MessageView *msgview, GSList *msginfo_list,
12229 if ((!prefs_common.forward_as_attachment || action != COMPOSE_FORWARD)
12230 && msginfo_list != NULL
12231 && action != COMPOSE_FORWARD_AS_ATTACH && g_slist_length(msginfo_list) > 1) {
12232 GSList *cur = msginfo_list;
12233 gchar *msg = g_strdup_printf(_("You are about to reply to %d "
12234 "messages. Opening the windows "
12235 "could take some time. Do you "
12236 "want to continue?"),
12237 g_slist_length(msginfo_list));
12238 if (g_slist_length(msginfo_list) > 9
12239 && alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, GTK_STOCK_YES, NULL,
12240 ALERTFOCUS_SECOND) != G_ALERTALTERNATE) {
12245 /* We'll open multiple compose windows */
12246 /* let the WM place the next windows */
12247 compose_force_window_origin = FALSE;
12248 for (; cur; cur = cur->next) {
12250 tmplist.data = cur->data;
12251 tmplist.next = NULL;
12252 compose_reply_from_messageview_real(msgview, &tmplist, action, TRUE);
12254 compose_force_window_origin = TRUE;
12256 /* forwarding multiple mails as attachments is done via a
12257 * single compose window */
12258 if (msginfo_list != NULL) {
12259 compose_reply_from_messageview_real(msgview, msginfo_list, action, FALSE);
12260 } else if (msgview != NULL) {
12262 tmplist.data = msgview->msginfo;
12263 tmplist.next = NULL;
12264 compose_reply_from_messageview_real(msgview, &tmplist, action, FALSE);
12266 debug_print("Nothing to reply to\n");
12271 void compose_check_for_email_account(Compose *compose)
12273 PrefsAccount *ac = NULL, *curr = NULL;
12279 if (compose->account && compose->account->protocol == A_NNTP) {
12280 ac = account_get_cur_account();
12281 if (ac->protocol == A_NNTP) {
12282 list = account_get_list();
12284 for( ; list != NULL ; list = g_list_next(list)) {
12285 curr = (PrefsAccount *) list->data;
12286 if (curr->protocol != A_NNTP) {
12292 combobox_select_by_data(GTK_COMBO_BOX(compose->account_combo),
12297 void compose_reply_to_address(MessageView *msgview, MsgInfo *msginfo,
12298 const gchar *address)
12300 GSList *msginfo_list = NULL;
12301 gchar *body = messageview_get_selection(msgview);
12304 msginfo_list = g_slist_prepend(msginfo_list, msginfo);
12306 compose = compose_reply_mode(COMPOSE_REPLY_TO_ADDRESS, msginfo_list, body);
12307 compose_check_for_email_account(compose);
12308 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
12309 compose_entry_append(compose, address, COMPOSE_TO, PREF_NONE);
12310 compose_reply_set_subject(compose, msginfo);
12313 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
12316 void compose_set_position(Compose *compose, gint pos)
12318 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
12320 gtkut_text_view_set_position(text, pos);
12323 gboolean compose_search_string(Compose *compose,
12324 const gchar *str, gboolean case_sens)
12326 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
12328 return gtkut_text_view_search_string(text, str, case_sens);
12331 gboolean compose_search_string_backward(Compose *compose,
12332 const gchar *str, gboolean case_sens)
12334 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
12336 return gtkut_text_view_search_string_backward(text, str, case_sens);
12339 /* allocate a msginfo structure and populate its data from a compose data structure */
12340 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose)
12342 MsgInfo *newmsginfo;
12344 gchar date[RFC822_DATE_BUFFSIZE];
12346 cm_return_val_if_fail( compose != NULL, NULL );
12348 newmsginfo = procmsg_msginfo_new();
12351 get_rfc822_date(date, sizeof(date));
12352 newmsginfo->date = g_strdup(date);
12355 if (compose->from_name) {
12356 newmsginfo->from = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
12357 newmsginfo->fromname = procheader_get_fromname(newmsginfo->from);
12361 if (compose->subject_entry)
12362 newmsginfo->subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
12364 /* to, cc, reply-to, newsgroups */
12365 for (list = compose->header_list; list; list = list->next) {
12366 gchar *header = gtk_editable_get_chars(
12368 gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
12369 gchar *entry = gtk_editable_get_chars(
12370 GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
12372 if ( strcasecmp(header, prefs_common_translated_header_name("To:")) == 0 ) {
12373 if ( newmsginfo->to == NULL ) {
12374 newmsginfo->to = g_strdup(entry);
12375 } else if (entry && *entry) {
12376 gchar *tmp = g_strconcat(newmsginfo->to, ", ", entry, NULL);
12377 g_free(newmsginfo->to);
12378 newmsginfo->to = tmp;
12381 if ( strcasecmp(header, prefs_common_translated_header_name("Cc:")) == 0 ) {
12382 if ( newmsginfo->cc == NULL ) {
12383 newmsginfo->cc = g_strdup(entry);
12384 } else if (entry && *entry) {
12385 gchar *tmp = g_strconcat(newmsginfo->cc, ", ", entry, NULL);
12386 g_free(newmsginfo->cc);
12387 newmsginfo->cc = tmp;
12390 if ( strcasecmp(header,
12391 prefs_common_translated_header_name("Newsgroups:")) == 0 ) {
12392 if ( newmsginfo->newsgroups == NULL ) {
12393 newmsginfo->newsgroups = g_strdup(entry);
12394 } else if (entry && *entry) {
12395 gchar *tmp = g_strconcat(newmsginfo->newsgroups, ", ", entry, NULL);
12396 g_free(newmsginfo->newsgroups);
12397 newmsginfo->newsgroups = tmp;
12405 /* other data is unset */
12411 /* update compose's dictionaries from folder dict settings */
12412 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
12413 FolderItem *folder_item)
12415 cm_return_if_fail(compose != NULL);
12417 if (compose->gtkaspell && folder_item && folder_item->prefs) {
12418 FolderItemPrefs *prefs = folder_item->prefs;
12420 if (prefs->enable_default_dictionary)
12421 gtkaspell_change_dict(compose->gtkaspell,
12422 prefs->default_dictionary, FALSE);
12423 if (folder_item->prefs->enable_default_alt_dictionary)
12424 gtkaspell_change_alt_dict(compose->gtkaspell,
12425 prefs->default_alt_dictionary);
12426 if (prefs->enable_default_dictionary
12427 || prefs->enable_default_alt_dictionary)
12428 compose_spell_menu_changed(compose);
12433 static void compose_subject_entry_activated(GtkWidget *widget, gpointer data)
12435 Compose *compose = (Compose *)data;
12437 cm_return_if_fail(compose != NULL);
12439 gtk_widget_grab_focus(compose->text);
12442 static void from_name_activate_cb(GtkWidget *widget, gpointer data)
12444 gtk_combo_box_popup(GTK_COMBO_BOX(data));