2 * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2019 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_entry_set_text(GTK_ENTRY(entry), folderidentifier);
946 gtk_entry_set_text(GTK_ENTRY(entry), "");
949 static gchar *compose_get_save_to(Compose *compose)
952 gchar *result = NULL;
953 entry = GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(compose->savemsg_combo)));
954 result = gtk_editable_get_chars(entry, 0, -1);
957 combobox_unset_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo));
958 prefs_common.compose_save_to_history = add_history(
959 prefs_common.compose_save_to_history, result);
960 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo),
961 prefs_common.compose_save_to_history);
966 Compose *compose_generic_new(PrefsAccount *account, const gchar *mailto, FolderItem *item,
967 GList *attach_files, GList *listAddress )
970 GtkTextView *textview;
971 GtkTextBuffer *textbuf;
973 const gchar *subject_format = NULL;
974 const gchar *body_format = NULL;
975 gchar *mailto_from = NULL;
976 PrefsAccount *mailto_account = NULL;
977 MsgInfo* dummyinfo = NULL;
978 gint cursor_pos = -1;
979 MailField mfield = NO_FIELD_PRESENT;
983 /* check if mailto defines a from */
984 if (mailto && *mailto != '\0') {
985 scan_mailto_url(mailto, &mailto_from, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
986 /* mailto defines a from, check if we can get account prefs from it,
987 if not, the account prefs will be guessed using other ways, but we'll keep
990 mailto_account = account_find_from_address(mailto_from, TRUE);
991 if (mailto_account == NULL) {
993 Xstrdup_a(tmp_from, mailto_from, return NULL);
994 extract_address(tmp_from);
995 mailto_account = account_find_from_address(tmp_from, TRUE);
999 account = mailto_account;
1002 /* if no account prefs set from mailto, set if from folder prefs (if any) */
1003 if (!mailto_account && item && item->prefs && item->prefs->enable_default_account)
1004 account = account_find_from_id(item->prefs->default_account);
1006 /* if no account prefs set, fallback to the current one */
1007 if (!account) account = cur_account;
1008 cm_return_val_if_fail(account != NULL, NULL);
1010 compose = compose_create(account, item, COMPOSE_NEW, FALSE);
1011 compose_apply_folder_privacy_settings(compose, item);
1013 if (privacy_system_can_sign(compose->privacy_system) == FALSE &&
1014 (account->default_encrypt || account->default_sign))
1015 alertpanel_error(_("You have opted to sign and/or encrypt this "
1016 "message but have not selected a privacy system.\n\n"
1017 "Signing and encrypting have been disabled for this "
1020 /* override from name if mailto asked for it */
1022 gtk_entry_set_text(GTK_ENTRY(compose->from_name), mailto_from);
1023 g_free(mailto_from);
1025 /* override from name according to folder properties */
1026 if (item && item->prefs &&
1027 item->prefs->compose_with_format &&
1028 item->prefs->compose_override_from_format &&
1029 *item->prefs->compose_override_from_format != '\0') {
1034 dummyinfo = compose_msginfo_new_from_compose(compose);
1036 /* decode \-escape sequences in the internal representation of the quote format */
1037 tmp = g_malloc(strlen(item->prefs->compose_override_from_format)+1);
1038 pref_get_unescaped_pref(tmp, item->prefs->compose_override_from_format);
1041 quote_fmt_init(dummyinfo, NULL, NULL, FALSE, compose->account, FALSE,
1042 compose->gtkaspell);
1044 quote_fmt_init(dummyinfo, NULL, NULL, FALSE, compose->account, FALSE);
1046 quote_fmt_scan_string(tmp);
1049 buf = quote_fmt_get_buffer();
1051 alertpanel_error(_("New message From format error."));
1053 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1054 quote_fmt_reset_vartable();
1055 quote_fmtlex_destroy();
1060 compose->replyinfo = NULL;
1061 compose->fwdinfo = NULL;
1063 textview = GTK_TEXT_VIEW(compose->text);
1064 textbuf = gtk_text_view_get_buffer(textview);
1065 compose_create_tags(textview, compose);
1067 undo_block(compose->undostruct);
1069 compose_set_dictionaries_from_folder_prefs(compose, item);
1072 if (account->auto_sig)
1073 compose_insert_sig(compose, FALSE);
1074 gtk_text_buffer_get_start_iter(textbuf, &iter);
1075 gtk_text_buffer_place_cursor(textbuf, &iter);
1077 if (account->protocol != A_NNTP) {
1078 if (mailto && *mailto != '\0') {
1079 mfield = compose_entries_set(compose, mailto, COMPOSE_TO);
1082 compose_set_folder_prefs(compose, item, TRUE);
1084 if (item && item->ret_rcpt) {
1085 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
1088 if (mailto && *mailto != '\0') {
1089 if (!strchr(mailto, '@'))
1090 mfield = compose_entries_set(compose, mailto, COMPOSE_NEWSGROUPS);
1092 mfield = compose_entries_set(compose, mailto, COMPOSE_TO);
1093 } else if (item && FOLDER_CLASS(item->folder) == news_get_class()) {
1094 compose_entry_append(compose, item->path, COMPOSE_NEWSGROUPS, PREF_FOLDER);
1095 mfield = TO_FIELD_PRESENT;
1098 * CLAWS: just don't allow return receipt request, even if the user
1099 * may want to send an email. simple but foolproof.
1101 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", FALSE);
1103 compose_add_field_list( compose, listAddress );
1105 if (item && item->prefs && item->prefs->compose_with_format) {
1106 subject_format = item->prefs->compose_subject_format;
1107 body_format = item->prefs->compose_body_format;
1108 } else if (account->compose_with_format) {
1109 subject_format = account->compose_subject_format;
1110 body_format = account->compose_body_format;
1111 } else if (prefs_common.compose_with_format) {
1112 subject_format = prefs_common.compose_subject_format;
1113 body_format = prefs_common.compose_body_format;
1116 if (subject_format || body_format) {
1119 && *subject_format != '\0' )
1121 gchar *subject = NULL;
1126 dummyinfo = compose_msginfo_new_from_compose(compose);
1128 /* decode \-escape sequences in the internal representation of the quote format */
1129 tmp = g_malloc(strlen(subject_format)+1);
1130 pref_get_unescaped_pref(tmp, subject_format);
1132 subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1134 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account, FALSE,
1135 compose->gtkaspell);
1137 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account, FALSE);
1139 quote_fmt_scan_string(tmp);
1142 buf = quote_fmt_get_buffer();
1144 alertpanel_error(_("New message subject format error."));
1146 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1147 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1148 quote_fmt_reset_vartable();
1149 quote_fmtlex_destroy();
1153 mfield = SUBJECT_FIELD_PRESENT;
1157 && *body_format != '\0' )
1160 GtkTextBuffer *buffer;
1161 GtkTextIter start, end;
1165 dummyinfo = compose_msginfo_new_from_compose(compose);
1167 text = GTK_TEXT_VIEW(compose->text);
1168 buffer = gtk_text_view_get_buffer(text);
1169 gtk_text_buffer_get_start_iter(buffer, &start);
1170 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
1171 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
1173 compose_quote_fmt(compose, dummyinfo,
1175 NULL, tmp, FALSE, TRUE,
1176 _("The body of the \"New message\" template has an error at line %d."));
1177 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1178 quote_fmt_reset_vartable();
1182 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1183 gtkaspell_highlight_all(compose->gtkaspell);
1185 mfield = BODY_FIELD_PRESENT;
1189 procmsg_msginfo_free( &dummyinfo );
1195 for (curr = attach_files ; curr != NULL ; curr = curr->next) {
1196 ainfo = (AttachInfo *) curr->data;
1198 compose_insert_file(compose, ainfo->file);
1200 compose_attach_append(compose, ainfo->file, ainfo->file,
1201 ainfo->content_type, ainfo->charset);
1205 compose_show_first_last_header(compose, TRUE);
1207 /* Set save folder */
1208 if (item && item->prefs && item->prefs->save_copy_to_folder) {
1209 gchar *folderidentifier;
1211 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1212 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo), TRUE);
1213 folderidentifier = folder_item_get_identifier(item);
1214 compose_set_save_to(compose, folderidentifier);
1215 g_free(folderidentifier);
1218 /* Place cursor according to provided input (mfield) */
1220 case NO_FIELD_PRESENT:
1221 if (compose->header_last)
1222 gtk_widget_grab_focus(compose->header_last->entry);
1224 case TO_FIELD_PRESENT:
1225 buf = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1227 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1230 gtk_widget_grab_focus(compose->subject_entry);
1232 case SUBJECT_FIELD_PRESENT:
1233 textview = GTK_TEXT_VIEW(compose->text);
1236 textbuf = gtk_text_view_get_buffer(textview);
1239 mark = gtk_text_buffer_get_insert(textbuf);
1240 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
1241 gtk_text_buffer_insert(textbuf, &iter, "", -1);
1243 * SUBJECT_FIELD_PRESENT and BODY_FIELD_PRESENT
1244 * only defers where it comes to the variable body
1245 * is not null. If no body is present compose->text
1246 * will be null in which case you cannot place the
1247 * cursor inside the component so. An empty component
1248 * is therefore created before placing the cursor
1250 case BODY_FIELD_PRESENT:
1251 cursor_pos = quote_fmt_get_cursor_pos();
1252 if (cursor_pos == -1)
1253 gtk_widget_grab_focus(compose->header_last->entry);
1255 gtk_widget_grab_focus(compose->text);
1259 undo_unblock(compose->undostruct);
1261 if (prefs_common.auto_exteditor)
1262 compose_exec_ext_editor(compose);
1264 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
1266 SCROLL_TO_CURSOR(compose);
1268 compose->modified = FALSE;
1269 compose_set_title(compose);
1271 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
1276 static void compose_force_encryption(Compose *compose, PrefsAccount *account,
1277 gboolean override_pref, const gchar *system)
1279 const gchar *privacy = NULL;
1281 cm_return_if_fail(compose != NULL);
1282 cm_return_if_fail(account != NULL);
1284 if (privacy_system_can_encrypt(compose->privacy_system) == FALSE ||
1285 (override_pref == FALSE && account->default_encrypt_reply == FALSE))
1288 if (account->default_privacy_system && strlen(account->default_privacy_system))
1289 privacy = account->default_privacy_system;
1293 GSList *privacy_avail = privacy_get_system_ids();
1294 if (privacy_avail && g_slist_length(privacy_avail)) {
1295 privacy = (gchar *)(privacy_avail->data);
1297 g_slist_free_full(privacy_avail, g_free);
1299 if (privacy != NULL) {
1301 g_free(compose->privacy_system);
1302 compose->privacy_system = NULL;
1303 g_free(compose->encdata);
1304 compose->encdata = NULL;
1306 if (compose->privacy_system == NULL)
1307 compose->privacy_system = g_strdup(privacy);
1308 else if (*(compose->privacy_system) == '\0') {
1309 g_free(compose->privacy_system);
1310 g_free(compose->encdata);
1311 compose->encdata = NULL;
1312 compose->privacy_system = g_strdup(privacy);
1314 compose_update_privacy_system_menu_item(compose, FALSE);
1315 compose_use_encryption(compose, TRUE);
1319 static void compose_force_signing(Compose *compose, PrefsAccount *account, const gchar *system)
1321 const gchar *privacy = NULL;
1322 if (privacy_system_can_sign(compose->privacy_system) == FALSE)
1325 if (account->default_privacy_system && strlen(account->default_privacy_system))
1326 privacy = account->default_privacy_system;
1330 GSList *privacy_avail = privacy_get_system_ids();
1331 if (privacy_avail && g_slist_length(privacy_avail)) {
1332 privacy = (gchar *)(privacy_avail->data);
1336 if (privacy != NULL) {
1338 g_free(compose->privacy_system);
1339 compose->privacy_system = NULL;
1340 g_free(compose->encdata);
1341 compose->encdata = NULL;
1343 if (compose->privacy_system == NULL)
1344 compose->privacy_system = g_strdup(privacy);
1345 compose_update_privacy_system_menu_item(compose, FALSE);
1346 compose_use_signing(compose, TRUE);
1350 static Compose *compose_reply_mode(ComposeMode mode, GSList *msginfo_list, gchar *body)
1354 Compose *compose = NULL;
1356 cm_return_val_if_fail(msginfo_list != NULL, NULL);
1358 msginfo = (MsgInfo*)g_slist_nth_data(msginfo_list, 0);
1359 cm_return_val_if_fail(msginfo != NULL, NULL);
1361 list_len = g_slist_length(msginfo_list);
1365 case COMPOSE_REPLY_TO_ADDRESS:
1366 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1367 FALSE, prefs_common.default_reply_list, FALSE, body);
1369 case COMPOSE_REPLY_WITH_QUOTE:
1370 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1371 FALSE, prefs_common.default_reply_list, FALSE, body);
1373 case COMPOSE_REPLY_WITHOUT_QUOTE:
1374 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1375 FALSE, prefs_common.default_reply_list, FALSE, NULL);
1377 case COMPOSE_REPLY_TO_SENDER:
1378 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1379 FALSE, FALSE, TRUE, body);
1381 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1382 compose = compose_followup_and_reply_to(msginfo,
1383 COMPOSE_QUOTE_CHECK,
1384 FALSE, FALSE, body);
1386 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1387 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1388 FALSE, FALSE, TRUE, body);
1390 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1391 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1392 FALSE, FALSE, TRUE, NULL);
1394 case COMPOSE_REPLY_TO_ALL:
1395 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1396 TRUE, FALSE, FALSE, body);
1398 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1399 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1400 TRUE, FALSE, FALSE, body);
1402 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1403 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1404 TRUE, FALSE, FALSE, NULL);
1406 case COMPOSE_REPLY_TO_LIST:
1407 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1408 FALSE, TRUE, FALSE, body);
1410 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1411 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1412 FALSE, TRUE, FALSE, body);
1414 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1415 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1416 FALSE, TRUE, FALSE, NULL);
1418 case COMPOSE_FORWARD:
1419 if (prefs_common.forward_as_attachment) {
1420 compose = compose_reply_mode(COMPOSE_FORWARD_AS_ATTACH, msginfo_list, body);
1423 compose = compose_reply_mode(COMPOSE_FORWARD_INLINE, msginfo_list, body);
1427 case COMPOSE_FORWARD_INLINE:
1428 /* check if we reply to more than one Message */
1429 if (list_len == 1) {
1430 compose = compose_forward(NULL, msginfo, FALSE, body, FALSE, FALSE);
1433 /* more messages FALL THROUGH */
1434 case COMPOSE_FORWARD_AS_ATTACH:
1435 compose = compose_forward_multiple(NULL, msginfo_list);
1437 case COMPOSE_REDIRECT:
1438 compose = compose_redirect(NULL, msginfo, FALSE);
1441 g_warning("compose_reply_mode(): invalid Compose Mode: %d", mode);
1444 if (compose == NULL) {
1445 alertpanel_error(_("Unable to reply. The original email probably doesn't exist."));
1449 compose->rmode = mode;
1450 switch (compose->rmode) {
1452 case COMPOSE_REPLY_WITH_QUOTE:
1453 case COMPOSE_REPLY_WITHOUT_QUOTE:
1454 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1455 debug_print("reply mode Normal\n");
1456 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/Normal", TRUE);
1457 compose_reply_change_mode(compose, COMPOSE_REPLY); /* force update */
1459 case COMPOSE_REPLY_TO_SENDER:
1460 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1461 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1462 debug_print("reply mode Sender\n");
1463 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/Sender", TRUE);
1465 case COMPOSE_REPLY_TO_ALL:
1466 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1467 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1468 debug_print("reply mode All\n");
1469 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/All", TRUE);
1471 case COMPOSE_REPLY_TO_LIST:
1472 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1473 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1474 debug_print("reply mode List\n");
1475 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/List", TRUE);
1477 case COMPOSE_REPLY_TO_ADDRESS:
1478 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", FALSE);
1486 static Compose *compose_reply(MsgInfo *msginfo,
1487 ComposeQuoteMode quote_mode,
1493 return compose_generic_reply(msginfo, quote_mode, to_all, to_ml,
1494 to_sender, FALSE, body);
1497 static Compose *compose_followup_and_reply_to(MsgInfo *msginfo,
1498 ComposeQuoteMode quote_mode,
1503 return compose_generic_reply(msginfo, quote_mode, to_all, FALSE,
1504 to_sender, TRUE, body);
1507 static void compose_extract_original_charset(Compose *compose)
1509 MsgInfo *info = NULL;
1510 if (compose->replyinfo) {
1511 info = compose->replyinfo;
1512 } else if (compose->fwdinfo) {
1513 info = compose->fwdinfo;
1514 } else if (compose->targetinfo) {
1515 info = compose->targetinfo;
1518 MimeInfo *mimeinfo = procmime_scan_message_short(info);
1519 MimeInfo *partinfo = mimeinfo;
1520 while (partinfo && partinfo->type != MIMETYPE_TEXT)
1521 partinfo = procmime_mimeinfo_next(partinfo);
1523 compose->orig_charset =
1524 g_strdup(procmime_mimeinfo_get_parameter(
1525 partinfo, "charset"));
1527 procmime_mimeinfo_free_all(&mimeinfo);
1531 #define SIGNAL_BLOCK(buffer) { \
1532 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1533 G_CALLBACK(compose_changed_cb), \
1535 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1536 G_CALLBACK(text_inserted), \
1540 #define SIGNAL_UNBLOCK(buffer) { \
1541 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1542 G_CALLBACK(compose_changed_cb), \
1544 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1545 G_CALLBACK(text_inserted), \
1549 static Compose *compose_generic_reply(MsgInfo *msginfo,
1550 ComposeQuoteMode quote_mode,
1551 gboolean to_all, gboolean to_ml,
1553 gboolean followup_and_reply_to,
1557 PrefsAccount *account = NULL;
1558 GtkTextView *textview;
1559 GtkTextBuffer *textbuf;
1560 gboolean quote = FALSE;
1561 const gchar *qmark = NULL;
1562 const gchar *body_fmt = NULL;
1563 gchar *s_system = NULL;
1565 cm_return_val_if_fail(msginfo != NULL, NULL);
1566 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
1568 account = account_get_reply_account(msginfo, prefs_common.reply_account_autosel);
1570 cm_return_val_if_fail(account != NULL, NULL);
1572 compose = compose_create(account, msginfo->folder, COMPOSE_REPLY, FALSE);
1573 compose_apply_folder_privacy_settings(compose, msginfo->folder);
1575 compose->updating = TRUE;
1577 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
1578 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", TRUE);
1580 compose->replyinfo = procmsg_msginfo_get_full_info(msginfo);
1581 if (!compose->replyinfo)
1582 compose->replyinfo = procmsg_msginfo_copy(msginfo);
1584 compose_extract_original_charset(compose);
1586 if (msginfo->folder && msginfo->folder->ret_rcpt)
1587 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
1589 /* Set save folder */
1590 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1591 gchar *folderidentifier;
1593 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1594 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo), TRUE);
1595 folderidentifier = folder_item_get_identifier(msginfo->folder);
1596 compose_set_save_to(compose, folderidentifier);
1597 g_free(folderidentifier);
1600 if (compose_parse_header(compose, msginfo) < 0) {
1601 compose->updating = FALSE;
1602 compose_destroy(compose);
1606 /* override from name according to folder properties */
1607 if (msginfo->folder && msginfo->folder->prefs &&
1608 msginfo->folder->prefs->reply_with_format &&
1609 msginfo->folder->prefs->reply_override_from_format &&
1610 *msginfo->folder->prefs->reply_override_from_format != '\0') {
1615 /* decode \-escape sequences in the internal representation of the quote format */
1616 tmp = g_malloc(strlen(msginfo->folder->prefs->reply_override_from_format)+1);
1617 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->reply_override_from_format);
1620 quote_fmt_init(compose->replyinfo, NULL, NULL, FALSE, compose->account, FALSE,
1621 compose->gtkaspell);
1623 quote_fmt_init(compose->replyinfo, NULL, NULL, FALSE, compose->account, FALSE);
1625 quote_fmt_scan_string(tmp);
1628 buf = quote_fmt_get_buffer();
1630 alertpanel_error(_("The \"From\" field of the \"Reply\" template contains an invalid email address."));
1632 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1633 quote_fmt_reset_vartable();
1634 quote_fmtlex_destroy();
1639 textview = (GTK_TEXT_VIEW(compose->text));
1640 textbuf = gtk_text_view_get_buffer(textview);
1641 compose_create_tags(textview, compose);
1643 undo_block(compose->undostruct);
1645 compose_set_dictionaries_from_folder_prefs(compose, msginfo->folder);
1646 gtkaspell_block_check(compose->gtkaspell);
1649 if (quote_mode == COMPOSE_QUOTE_FORCED ||
1650 (quote_mode == COMPOSE_QUOTE_CHECK && prefs_common.reply_with_quote)) {
1651 /* use the reply format of folder (if enabled), or the account's one
1652 (if enabled) or fallback to the global reply format, which is always
1653 enabled (even if empty), and use the relevant quotemark */
1655 if (msginfo->folder && msginfo->folder->prefs &&
1656 msginfo->folder->prefs->reply_with_format) {
1657 qmark = msginfo->folder->prefs->reply_quotemark;
1658 body_fmt = msginfo->folder->prefs->reply_body_format;
1660 } else if (account->reply_with_format) {
1661 qmark = account->reply_quotemark;
1662 body_fmt = account->reply_body_format;
1665 qmark = prefs_common.quotemark;
1666 if (prefs_common.quotefmt && *prefs_common.quotefmt)
1667 body_fmt = gettext(prefs_common.quotefmt);
1674 /* empty quotemark is not allowed */
1675 if (qmark == NULL || *qmark == '\0')
1677 compose_quote_fmt(compose, compose->replyinfo,
1678 body_fmt, qmark, body, FALSE, TRUE,
1679 _("The body of the \"Reply\" template has an error at line %d."));
1680 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1681 quote_fmt_reset_vartable();
1684 if (MSG_IS_ENCRYPTED(compose->replyinfo->flags)) {
1685 compose_force_encryption(compose, account, FALSE, s_system);
1688 privacy_msginfo_get_signed_state(compose->replyinfo, &s_system);
1689 if (MSG_IS_SIGNED(compose->replyinfo->flags) && account->default_sign_reply) {
1690 compose_force_signing(compose, account, s_system);
1694 if (privacy_system_can_sign(compose->privacy_system) == FALSE &&
1695 ((account->default_encrypt_reply && MSG_IS_ENCRYPTED(compose->replyinfo->flags)) ||
1696 (account->default_sign_reply && MSG_IS_SIGNED(compose->replyinfo->flags))))
1697 alertpanel_error(_("You have opted to sign and/or encrypt this "
1698 "message but have not selected a privacy system.\n\n"
1699 "Signing and encrypting have been disabled for this "
1702 SIGNAL_BLOCK(textbuf);
1704 if (account->auto_sig)
1705 compose_insert_sig(compose, FALSE);
1707 compose_wrap_all(compose);
1710 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1711 gtkaspell_highlight_all(compose->gtkaspell);
1712 gtkaspell_unblock_check(compose->gtkaspell);
1714 SIGNAL_UNBLOCK(textbuf);
1716 gtk_widget_grab_focus(compose->text);
1718 undo_unblock(compose->undostruct);
1720 if (prefs_common.auto_exteditor)
1721 compose_exec_ext_editor(compose);
1723 compose->modified = FALSE;
1724 compose_set_title(compose);
1726 compose->updating = FALSE;
1727 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
1728 SCROLL_TO_CURSOR(compose);
1730 if (compose->deferred_destroy) {
1731 compose_destroy(compose);
1739 #define INSERT_FW_HEADER(var, hdr) \
1740 if (msginfo->var && *msginfo->var) { \
1741 gtk_stext_insert(text, NULL, NULL, NULL, hdr, -1); \
1742 gtk_stext_insert(text, NULL, NULL, NULL, msginfo->var, -1); \
1743 gtk_stext_insert(text, NULL, NULL, NULL, "\n", 1); \
1746 Compose *compose_forward(PrefsAccount *account, MsgInfo *msginfo,
1747 gboolean as_attach, const gchar *body,
1748 gboolean no_extedit,
1752 GtkTextView *textview;
1753 GtkTextBuffer *textbuf;
1754 gint cursor_pos = -1;
1757 cm_return_val_if_fail(msginfo != NULL, NULL);
1758 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
1760 if (!account && !(account = compose_find_account(msginfo)))
1761 account = cur_account;
1763 if (!prefs_common.forward_as_attachment)
1764 mode = COMPOSE_FORWARD_INLINE;
1766 mode = COMPOSE_FORWARD;
1767 compose = compose_create(account, msginfo->folder, mode, batch);
1768 compose_apply_folder_privacy_settings(compose, msginfo->folder);
1770 compose->updating = TRUE;
1771 compose->fwdinfo = procmsg_msginfo_get_full_info(msginfo);
1772 if (!compose->fwdinfo)
1773 compose->fwdinfo = procmsg_msginfo_copy(msginfo);
1775 compose_extract_original_charset(compose);
1777 if (msginfo->subject && *msginfo->subject) {
1778 gchar *buf, *buf2, *p;
1780 buf = p = g_strdup(msginfo->subject);
1781 p += subject_get_prefix_length(p);
1782 memmove(buf, p, strlen(p) + 1);
1784 buf2 = g_strdup_printf("Fw: %s", buf);
1785 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
1791 /* override from name according to folder properties */
1792 if (msginfo->folder && msginfo->folder->prefs &&
1793 msginfo->folder->prefs->forward_with_format &&
1794 msginfo->folder->prefs->forward_override_from_format &&
1795 *msginfo->folder->prefs->forward_override_from_format != '\0') {
1799 MsgInfo *full_msginfo = NULL;
1802 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1804 full_msginfo = procmsg_msginfo_copy(msginfo);
1806 /* decode \-escape sequences in the internal representation of the quote format */
1807 tmp = g_malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
1808 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
1811 gtkaspell_block_check(compose->gtkaspell);
1812 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE,
1813 compose->gtkaspell);
1815 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE);
1817 quote_fmt_scan_string(tmp);
1820 buf = quote_fmt_get_buffer();
1822 alertpanel_error(_("The \"From\" field of the \"Forward\" template contains an invalid email address."));
1824 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1825 quote_fmt_reset_vartable();
1826 quote_fmtlex_destroy();
1829 procmsg_msginfo_free(&full_msginfo);
1832 textview = GTK_TEXT_VIEW(compose->text);
1833 textbuf = gtk_text_view_get_buffer(textview);
1834 compose_create_tags(textview, compose);
1836 undo_block(compose->undostruct);
1840 msgfile = procmsg_get_message_file(msginfo);
1841 if (!is_file_exist(msgfile))
1842 g_warning("%s: file does not exist", msgfile);
1844 compose_attach_append(compose, msgfile, msgfile,
1845 "message/rfc822", NULL);
1849 const gchar *qmark = NULL;
1850 const gchar *body_fmt = NULL;
1851 MsgInfo *full_msginfo;
1853 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1855 full_msginfo = procmsg_msginfo_copy(msginfo);
1857 /* use the forward format of folder (if enabled), or the account's one
1858 (if enabled) or fallback to the global forward format, which is always
1859 enabled (even if empty), and use the relevant quotemark */
1860 if (msginfo->folder && msginfo->folder->prefs &&
1861 msginfo->folder->prefs->forward_with_format) {
1862 qmark = msginfo->folder->prefs->forward_quotemark;
1863 body_fmt = msginfo->folder->prefs->forward_body_format;
1865 } else if (account->forward_with_format) {
1866 qmark = account->forward_quotemark;
1867 body_fmt = account->forward_body_format;
1870 qmark = prefs_common.fw_quotemark;
1871 if (prefs_common.fw_quotefmt && *prefs_common.fw_quotefmt)
1872 body_fmt = gettext(prefs_common.fw_quotefmt);
1877 /* empty quotemark is not allowed */
1878 if (qmark == NULL || *qmark == '\0')
1881 compose_quote_fmt(compose, full_msginfo,
1882 body_fmt, qmark, body, FALSE, TRUE,
1883 _("The body of the \"Forward\" template has an error at line %d."));
1884 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1885 quote_fmt_reset_vartable();
1886 compose_attach_parts(compose, msginfo);
1888 procmsg_msginfo_free(&full_msginfo);
1891 SIGNAL_BLOCK(textbuf);
1893 if (account->auto_sig)
1894 compose_insert_sig(compose, FALSE);
1896 compose_wrap_all(compose);
1899 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1900 gtkaspell_highlight_all(compose->gtkaspell);
1901 gtkaspell_unblock_check(compose->gtkaspell);
1903 SIGNAL_UNBLOCK(textbuf);
1905 cursor_pos = quote_fmt_get_cursor_pos();
1906 if (cursor_pos == -1)
1907 gtk_widget_grab_focus(compose->header_last->entry);
1909 gtk_widget_grab_focus(compose->text);
1911 if (!no_extedit && prefs_common.auto_exteditor)
1912 compose_exec_ext_editor(compose);
1915 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1916 gchar *folderidentifier;
1918 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
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_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
2410 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo), TRUE);
2411 compose_set_save_to(compose, &queueheader_buf[4]);
2412 g_free(queueheader_buf);
2414 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf, "RRCPT:")) {
2415 gint active = atoi(&queueheader_buf[strlen("RRCPT:")]);
2417 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
2419 g_free(queueheader_buf);
2423 if (compose_parse_header(compose, msginfo) < 0) {
2424 compose->updating = FALSE;
2425 compose_destroy(compose);
2428 compose_reedit_set_entry(compose, msginfo);
2430 textview = GTK_TEXT_VIEW(compose->text);
2431 textbuf = gtk_text_view_get_buffer(textview);
2432 compose_create_tags(textview, compose);
2434 mark = gtk_text_buffer_get_insert(textbuf);
2435 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
2437 g_signal_handlers_block_by_func(G_OBJECT(textbuf),
2438 G_CALLBACK(compose_changed_cb),
2441 if (MSG_IS_ENCRYPTED(msginfo->flags)) {
2442 fp = procmime_get_first_encrypted_text_content(msginfo);
2444 compose_force_encryption(compose, account, TRUE, NULL);
2447 fp = procmime_get_first_text_content(msginfo);
2450 g_warning("Can't get text part");
2454 gchar buf[BUFFSIZE];
2455 gboolean prev_autowrap;
2456 GtkTextBuffer *buffer;
2458 while (claws_fgets(buf, sizeof(buf), fp) != NULL) {
2460 gtk_text_buffer_insert(textbuf, &iter, buf, -1);
2466 compose_attach_parts(compose, msginfo);
2468 compose_colorize_signature(compose);
2470 g_signal_handlers_unblock_by_func(G_OBJECT(textbuf),
2471 G_CALLBACK(compose_changed_cb),
2474 if (manual_headers != NULL) {
2475 if (compose_parse_manual_headers(compose, msginfo, manual_headers) < 0) {
2476 procheader_entries_free(manual_headers);
2477 compose->updating = FALSE;
2478 compose_destroy(compose);
2481 procheader_entries_free(manual_headers);
2484 gtk_widget_grab_focus(compose->text);
2486 if (prefs_common.auto_exteditor) {
2487 compose_exec_ext_editor(compose);
2489 compose->modified = FALSE;
2490 compose_set_title(compose);
2492 compose->updating = FALSE;
2493 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2494 SCROLL_TO_CURSOR(compose);
2496 if (compose->deferred_destroy) {
2497 compose_destroy(compose);
2501 compose->sig_str = account_get_signature_str(compose->account);
2503 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2508 Compose *compose_redirect(PrefsAccount *account, MsgInfo *msginfo,
2515 cm_return_val_if_fail(msginfo != NULL, NULL);
2518 account = account_get_reply_account(msginfo,
2519 prefs_common.reply_account_autosel);
2520 cm_return_val_if_fail(account != NULL, NULL);
2522 compose = compose_create(account, msginfo->folder, COMPOSE_REDIRECT, batch);
2523 compose_apply_folder_privacy_settings(compose, msginfo->folder);
2525 compose->updating = TRUE;
2527 compose_create_tags(GTK_TEXT_VIEW(compose->text), compose);
2528 compose->replyinfo = NULL;
2529 compose->fwdinfo = NULL;
2531 compose_show_first_last_header(compose, TRUE);
2533 gtk_widget_grab_focus(compose->header_last->entry);
2535 filename = procmsg_get_message_file(msginfo);
2537 if (filename == NULL) {
2538 compose->updating = FALSE;
2539 compose_destroy(compose);
2544 compose->redirect_filename = filename;
2546 /* Set save folder */
2547 item = msginfo->folder;
2548 if (item && item->prefs && item->prefs->save_copy_to_folder) {
2549 gchar *folderidentifier;
2551 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
2552 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo), TRUE);
2553 folderidentifier = folder_item_get_identifier(item);
2554 compose_set_save_to(compose, folderidentifier);
2555 g_free(folderidentifier);
2558 compose_attach_parts(compose, msginfo);
2560 if (msginfo->subject)
2561 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2563 gtk_editable_set_editable(GTK_EDITABLE(compose->subject_entry), FALSE);
2565 compose_quote_fmt(compose, msginfo, "%M", NULL, NULL, FALSE, FALSE,
2566 _("The body of the \"Redirect\" template has an error at line %d."));
2567 quote_fmt_reset_vartable();
2568 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), FALSE);
2570 compose_colorize_signature(compose);
2573 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Add", FALSE);
2574 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", FALSE);
2575 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", FALSE);
2577 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/Save", FALSE);
2578 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertFile", FALSE);
2579 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/AttachFile", FALSE);
2580 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertSig", FALSE);
2581 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/ReplaceSig", FALSE);
2582 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", FALSE);
2583 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", FALSE);
2584 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/ShowRuler", FALSE);
2585 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Actions", FALSE);
2587 if (compose->toolbar->draft_btn)
2588 gtk_widget_set_sensitive(compose->toolbar->draft_btn, FALSE);
2589 if (compose->toolbar->insert_btn)
2590 gtk_widget_set_sensitive(compose->toolbar->insert_btn, FALSE);
2591 if (compose->toolbar->attach_btn)
2592 gtk_widget_set_sensitive(compose->toolbar->attach_btn, FALSE);
2593 if (compose->toolbar->sig_btn)
2594 gtk_widget_set_sensitive(compose->toolbar->sig_btn, FALSE);
2595 if (compose->toolbar->exteditor_btn)
2596 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, FALSE);
2597 if (compose->toolbar->linewrap_current_btn)
2598 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, FALSE);
2599 if (compose->toolbar->linewrap_all_btn)
2600 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, FALSE);
2602 compose->modified = FALSE;
2603 compose_set_title(compose);
2604 compose->updating = FALSE;
2605 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2606 SCROLL_TO_CURSOR(compose);
2608 if (compose->deferred_destroy) {
2609 compose_destroy(compose);
2613 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2618 const GList *compose_get_compose_list(void)
2620 return compose_list;
2623 void compose_entry_append(Compose *compose, const gchar *address,
2624 ComposeEntryType type, ComposePrefType pref_type)
2626 const gchar *header;
2628 gboolean in_quote = FALSE;
2629 if (!address || *address == '\0') return;
2636 header = N_("Bcc:");
2638 case COMPOSE_REPLYTO:
2639 header = N_("Reply-To:");
2641 case COMPOSE_NEWSGROUPS:
2642 header = N_("Newsgroups:");
2644 case COMPOSE_FOLLOWUPTO:
2645 header = N_( "Followup-To:");
2647 case COMPOSE_INREPLYTO:
2648 header = N_( "In-Reply-To:");
2655 header = prefs_common_translated_header_name(header);
2657 cur = begin = (gchar *)address;
2659 /* we separate the line by commas, but not if we're inside a quoted
2661 while (*cur != '\0') {
2663 in_quote = !in_quote;
2664 if (*cur == ',' && !in_quote) {
2665 gchar *tmp = g_strdup(begin);
2667 tmp[cur-begin]='\0';
2670 while (*tmp == ' ' || *tmp == '\t')
2672 compose_add_header_entry(compose, header, tmp, pref_type);
2673 compose_entry_indicate(compose, tmp);
2680 gchar *tmp = g_strdup(begin);
2682 tmp[cur-begin]='\0';
2683 while (*tmp == ' ' || *tmp == '\t')
2685 compose_add_header_entry(compose, header, tmp, pref_type);
2686 compose_entry_indicate(compose, tmp);
2691 static void compose_entry_indicate(Compose *compose, const gchar *mailto)
2696 for (h_list = compose->header_list; h_list != NULL; h_list = h_list->next) {
2697 entry = GTK_ENTRY(((ComposeHeaderEntry *)h_list->data)->entry);
2698 if (gtk_entry_get_text(entry) &&
2699 !g_utf8_collate(gtk_entry_get_text(entry), mailto)) {
2700 gtk_widget_modify_base(
2701 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2702 GTK_STATE_NORMAL, &default_header_bgcolor);
2703 gtk_widget_modify_text(
2704 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2705 GTK_STATE_NORMAL, &default_header_color);
2710 void compose_toolbar_cb(gint action, gpointer data)
2712 ToolbarItem *toolbar_item = (ToolbarItem*)data;
2713 Compose *compose = (Compose*)toolbar_item->parent;
2715 cm_return_if_fail(compose != NULL);
2719 compose_send_cb(NULL, compose);
2722 compose_send_later_cb(NULL, compose);
2725 compose_draft(compose, COMPOSE_QUIT_EDITING);
2728 compose_insert_file_cb(NULL, compose);
2731 compose_attach_cb(NULL, compose);
2734 compose_insert_sig(compose, FALSE);
2737 compose_insert_sig(compose, TRUE);
2740 compose_ext_editor_cb(NULL, compose);
2742 case A_LINEWRAP_CURRENT:
2743 compose_beautify_paragraph(compose, NULL, TRUE);
2745 case A_LINEWRAP_ALL:
2746 compose_wrap_all_full(compose, TRUE);
2749 compose_address_cb(NULL, compose);
2752 case A_CHECK_SPELLING:
2753 compose_check_all(NULL, compose);
2756 case A_PRIVACY_SIGN:
2758 case A_PRIVACY_ENCRYPT:
2765 static MailField compose_entries_set(Compose *compose, const gchar *mailto, ComposeEntryType to_type)
2770 gchar *subject = NULL;
2774 gchar **attach = NULL;
2775 gchar *inreplyto = NULL;
2776 MailField mfield = NO_FIELD_PRESENT;
2778 /* get mailto parts but skip from */
2779 scan_mailto_url(mailto, NULL, &to, &cc, &bcc, &subject, &body, &attach, &inreplyto);
2782 compose_entry_append(compose, to, to_type, PREF_MAILTO);
2783 mfield = TO_FIELD_PRESENT;
2786 compose_entry_append(compose, cc, COMPOSE_CC, PREF_MAILTO);
2788 compose_entry_append(compose, bcc, COMPOSE_BCC, PREF_MAILTO);
2790 if (!g_utf8_validate (subject, -1, NULL)) {
2791 temp = g_locale_to_utf8 (subject, -1, NULL, &len, NULL);
2792 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), temp);
2795 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), subject);
2797 mfield = SUBJECT_FIELD_PRESENT;
2800 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2801 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2804 gboolean prev_autowrap = compose->autowrap;
2806 compose->autowrap = FALSE;
2808 mark = gtk_text_buffer_get_insert(buffer);
2809 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2811 if (!g_utf8_validate (body, -1, NULL)) {
2812 temp = g_locale_to_utf8 (body, -1, NULL, &len, NULL);
2813 gtk_text_buffer_insert(buffer, &iter, temp, -1);
2816 gtk_text_buffer_insert(buffer, &iter, body, -1);
2818 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
2820 compose->autowrap = prev_autowrap;
2821 if (compose->autowrap)
2822 compose_wrap_all(compose);
2823 mfield = BODY_FIELD_PRESENT;
2827 gint i = 0, att = 0;
2828 gchar *warn_files = NULL;
2829 while (attach[i] != NULL) {
2830 gchar *utf8_filename = conv_filename_to_utf8(attach[i]);
2831 if (utf8_filename) {
2832 if (compose_attach_append(compose, attach[i], utf8_filename, NULL, NULL)) {
2833 gchar *tmp = g_strdup_printf("%s%s\n",
2834 warn_files?warn_files:"",
2840 g_free(utf8_filename);
2842 alertpanel_error(_("Couldn't attach a file (charset conversion failed)."));
2847 alertpanel_notice(ngettext(
2848 "The following file has been attached: \n%s",
2849 "The following files have been attached: \n%s", att), warn_files);
2854 compose_entry_append(compose, inreplyto, COMPOSE_INREPLYTO, PREF_MAILTO);
2867 static gint compose_parse_header(Compose *compose, MsgInfo *msginfo)
2869 static HeaderEntry hentry[] = {
2870 {"Reply-To:", NULL, TRUE },
2871 {"Cc:", NULL, TRUE },
2872 {"References:", NULL, FALSE },
2873 {"Bcc:", NULL, TRUE },
2874 {"Newsgroups:", NULL, TRUE },
2875 {"Followup-To:", NULL, TRUE },
2876 {"List-Post:", NULL, FALSE },
2877 {"X-Priority:", NULL, FALSE },
2878 {NULL, NULL, FALSE }
2895 cm_return_val_if_fail(msginfo != NULL, -1);
2897 if ((fp = procmsg_open_message(msginfo, FALSE)) == NULL) return -1;
2898 procheader_get_header_fields(fp, hentry);
2901 if (hentry[H_REPLY_TO].body != NULL) {
2902 if (hentry[H_REPLY_TO].body[0] != '\0') {
2904 conv_unmime_header(hentry[H_REPLY_TO].body,
2907 g_free(hentry[H_REPLY_TO].body);
2908 hentry[H_REPLY_TO].body = NULL;
2910 if (hentry[H_CC].body != NULL) {
2911 compose->cc = conv_unmime_header(hentry[H_CC].body, NULL, TRUE);
2912 g_free(hentry[H_CC].body);
2913 hentry[H_CC].body = NULL;
2915 if (hentry[H_REFERENCES].body != NULL) {
2916 if (compose->mode == COMPOSE_REEDIT)
2917 compose->references = hentry[H_REFERENCES].body;
2919 compose->references = compose_parse_references
2920 (hentry[H_REFERENCES].body, msginfo->msgid);
2921 g_free(hentry[H_REFERENCES].body);
2923 hentry[H_REFERENCES].body = NULL;
2925 if (hentry[H_BCC].body != NULL) {
2926 if (compose->mode == COMPOSE_REEDIT)
2928 conv_unmime_header(hentry[H_BCC].body, NULL, TRUE);
2929 g_free(hentry[H_BCC].body);
2930 hentry[H_BCC].body = NULL;
2932 if (hentry[H_NEWSGROUPS].body != NULL) {
2933 compose->newsgroups = hentry[H_NEWSGROUPS].body;
2934 hentry[H_NEWSGROUPS].body = NULL;
2936 if (hentry[H_FOLLOWUP_TO].body != NULL) {
2937 if (hentry[H_FOLLOWUP_TO].body[0] != '\0') {
2938 compose->followup_to =
2939 conv_unmime_header(hentry[H_FOLLOWUP_TO].body,
2942 g_free(hentry[H_FOLLOWUP_TO].body);
2943 hentry[H_FOLLOWUP_TO].body = NULL;
2945 if (hentry[H_LIST_POST].body != NULL) {
2946 gchar *to = NULL, *start = NULL;
2948 extract_address(hentry[H_LIST_POST].body);
2949 if (hentry[H_LIST_POST].body[0] != '\0') {
2950 start = strstr(hentry[H_LIST_POST].body, "mailto:");
2952 scan_mailto_url(start ? start : hentry[H_LIST_POST].body,
2953 NULL, &to, NULL, NULL, NULL, NULL, NULL, NULL);
2956 g_free(compose->ml_post);
2957 compose->ml_post = to;
2960 g_free(hentry[H_LIST_POST].body);
2961 hentry[H_LIST_POST].body = NULL;
2964 /* CLAWS - X-Priority */
2965 if (compose->mode == COMPOSE_REEDIT)
2966 if (hentry[H_X_PRIORITY].body != NULL) {
2969 priority = atoi(hentry[H_X_PRIORITY].body);
2970 g_free(hentry[H_X_PRIORITY].body);
2972 hentry[H_X_PRIORITY].body = NULL;
2974 if (priority < PRIORITY_HIGHEST ||
2975 priority > PRIORITY_LOWEST)
2976 priority = PRIORITY_NORMAL;
2978 compose->priority = priority;
2981 if (compose->mode == COMPOSE_REEDIT) {
2982 if (msginfo->inreplyto && *msginfo->inreplyto)
2983 compose->inreplyto = g_strdup(msginfo->inreplyto);
2985 if (msginfo->msgid && *msginfo->msgid &&
2986 compose->folder != NULL &&
2987 compose->folder->stype == F_DRAFT)
2988 compose->msgid = g_strdup(msginfo->msgid);
2990 if (msginfo->msgid && *msginfo->msgid)
2991 compose->inreplyto = g_strdup(msginfo->msgid);
2993 if (!compose->references) {
2994 if (msginfo->msgid && *msginfo->msgid) {
2995 if (msginfo->inreplyto && *msginfo->inreplyto)
2996 compose->references =
2997 g_strdup_printf("<%s>\n\t<%s>",
3001 compose->references =
3002 g_strconcat("<", msginfo->msgid, ">",
3004 } else if (msginfo->inreplyto && *msginfo->inreplyto) {
3005 compose->references =
3006 g_strconcat("<", msginfo->inreplyto, ">",
3015 static gint compose_parse_manual_headers(Compose *compose, MsgInfo *msginfo, HeaderEntry *entries)
3020 cm_return_val_if_fail(msginfo != NULL, -1);
3022 if ((fp = procmsg_open_message(msginfo, FALSE)) == NULL) return -1;
3023 procheader_get_header_fields(fp, entries);
3027 while (he != NULL && he->name != NULL) {
3029 GtkListStore *model = NULL;
3031 debug_print("Adding manual header: %s with value %s\n", he->name, he->body);
3032 model = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(compose->header_last->combo)));
3033 COMBOBOX_ADD(model, he->name, COMPOSE_TO);
3034 gtk_combo_box_set_active_iter(GTK_COMBO_BOX(compose->header_last->combo), &iter);
3035 gtk_entry_set_text(GTK_ENTRY(compose->header_last->entry), he->body);
3042 static gchar *compose_parse_references(const gchar *ref, const gchar *msgid)
3044 GSList *ref_id_list, *cur;
3048 ref_id_list = references_list_append(NULL, ref);
3049 if (!ref_id_list) return NULL;
3050 if (msgid && *msgid)
3051 ref_id_list = g_slist_append(ref_id_list, g_strdup(msgid));
3056 for (cur = ref_id_list; cur != NULL; cur = cur->next)
3057 /* "<" + Message-ID + ">" + CR+LF+TAB */
3058 len += strlen((gchar *)cur->data) + 5;
3060 if (len > MAX_REFERENCES_LEN) {
3061 /* remove second message-ID */
3062 if (ref_id_list && ref_id_list->next &&
3063 ref_id_list->next->next) {
3064 g_free(ref_id_list->next->data);
3065 ref_id_list = g_slist_remove
3066 (ref_id_list, ref_id_list->next->data);
3068 slist_free_strings_full(ref_id_list);
3075 new_ref = g_string_new("");
3076 for (cur = ref_id_list; cur != NULL; cur = cur->next) {
3077 if (new_ref->len > 0)
3078 g_string_append(new_ref, "\n\t");
3079 g_string_append_printf(new_ref, "<%s>", (gchar *)cur->data);
3082 slist_free_strings_full(ref_id_list);
3084 new_ref_str = new_ref->str;
3085 g_string_free(new_ref, FALSE);
3090 static gchar *compose_quote_fmt(Compose *compose, MsgInfo *msginfo,
3091 const gchar *fmt, const gchar *qmark,
3092 const gchar *body, gboolean rewrap,
3093 gboolean need_unescape,
3094 const gchar *err_msg)
3096 MsgInfo* dummyinfo = NULL;
3097 gchar *quote_str = NULL;
3099 gboolean prev_autowrap;
3100 const gchar *trimmed_body = body;
3101 gint cursor_pos = -1;
3102 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3103 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3108 SIGNAL_BLOCK(buffer);
3111 dummyinfo = compose_msginfo_new_from_compose(compose);
3112 msginfo = dummyinfo;
3115 if (qmark != NULL) {
3117 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
3118 compose->gtkaspell);
3120 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
3122 quote_fmt_scan_string(qmark);
3125 buf = quote_fmt_get_buffer();
3128 alertpanel_error(_("The \"Quotation mark\" of the template is invalid."));
3130 Xstrdup_a(quote_str, buf, goto error)
3133 if (fmt && *fmt != '\0') {
3136 while (*trimmed_body == '\n')
3140 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE,
3141 compose->gtkaspell);
3143 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE);
3145 if (need_unescape) {
3148 /* decode \-escape sequences in the internal representation of the quote format */
3149 tmp = g_malloc(strlen(fmt)+1);
3150 pref_get_unescaped_pref(tmp, fmt);
3151 quote_fmt_scan_string(tmp);
3155 quote_fmt_scan_string(fmt);
3159 buf = quote_fmt_get_buffer();
3162 gint line = quote_fmt_get_line();
3163 alertpanel_error(err_msg, line);
3171 prev_autowrap = compose->autowrap;
3172 compose->autowrap = FALSE;
3174 mark = gtk_text_buffer_get_insert(buffer);
3175 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3176 if (g_utf8_validate(buf, -1, NULL)) {
3177 gtk_text_buffer_insert(buffer, &iter, buf, -1);
3179 gchar *tmpout = NULL;
3180 tmpout = conv_codeset_strdup
3181 (buf, conv_get_locale_charset_str_no_utf8(),
3183 if (!tmpout || !g_utf8_validate(tmpout, -1, NULL)) {
3185 tmpout = g_malloc(strlen(buf)*2+1);
3186 conv_localetodisp(tmpout, strlen(buf)*2+1, buf);
3188 gtk_text_buffer_insert(buffer, &iter, tmpout, -1);
3192 cursor_pos = quote_fmt_get_cursor_pos();
3193 if (cursor_pos == -1)
3194 cursor_pos = gtk_text_iter_get_offset(&iter);
3195 compose->set_cursor_pos = cursor_pos;
3197 gtk_text_buffer_get_start_iter(buffer, &iter);
3198 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
3199 gtk_text_buffer_place_cursor(buffer, &iter);
3201 compose->autowrap = prev_autowrap;
3202 if (compose->autowrap && rewrap)
3203 compose_wrap_all(compose);
3210 SIGNAL_UNBLOCK(buffer);
3212 procmsg_msginfo_free( &dummyinfo );
3217 /* if ml_post is of type addr@host and from is of type
3218 * addr-anything@host, return TRUE
3220 static gboolean is_subscription(const gchar *ml_post, const gchar *from)
3222 gchar *left_ml = NULL;
3223 gchar *right_ml = NULL;
3224 gchar *left_from = NULL;
3225 gchar *right_from = NULL;
3226 gboolean result = FALSE;
3228 if (!ml_post || !from)
3231 left_ml = g_strdup(ml_post);
3232 if (strstr(left_ml, "@")) {
3233 right_ml = strstr(left_ml, "@")+1;
3234 *(strstr(left_ml, "@")) = '\0';
3237 left_from = g_strdup(from);
3238 if (strstr(left_from, "@")) {
3239 right_from = strstr(left_from, "@")+1;
3240 *(strstr(left_from, "@")) = '\0';
3243 if (right_ml && right_from
3244 && !strncmp(left_from, left_ml, strlen(left_ml))
3245 && !strcmp(right_from, right_ml)) {
3254 static void compose_set_folder_prefs(Compose *compose, FolderItem *folder,
3255 gboolean respect_default_to)
3259 if (!folder || !folder->prefs)
3262 if (respect_default_to && folder->prefs->enable_default_to) {
3263 compose_entry_append(compose, folder->prefs->default_to,
3264 COMPOSE_TO, PREF_FOLDER);
3265 compose_entry_indicate(compose, folder->prefs->default_to);
3267 if (folder->prefs->enable_default_cc) {
3268 compose_entry_append(compose, folder->prefs->default_cc,
3269 COMPOSE_CC, PREF_FOLDER);
3270 compose_entry_indicate(compose, folder->prefs->default_cc);
3272 if (folder->prefs->enable_default_bcc) {
3273 compose_entry_append(compose, folder->prefs->default_bcc,
3274 COMPOSE_BCC, PREF_FOLDER);
3275 compose_entry_indicate(compose, folder->prefs->default_bcc);
3277 if (folder->prefs->enable_default_replyto) {
3278 compose_entry_append(compose, folder->prefs->default_replyto,
3279 COMPOSE_REPLYTO, PREF_FOLDER);
3280 compose_entry_indicate(compose, folder->prefs->default_replyto);
3284 static void compose_reply_set_subject(Compose *compose, MsgInfo *msginfo)
3289 if (!compose || !msginfo)
3292 if (msginfo->subject && *msginfo->subject) {
3293 buf = p = g_strdup(msginfo->subject);
3294 p += subject_get_prefix_length(p);
3295 memmove(buf, p, strlen(p) + 1);
3297 buf2 = g_strdup_printf("Re: %s", buf);
3298 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
3303 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), "Re: ");
3306 static void compose_reply_set_entry(Compose *compose, MsgInfo *msginfo,
3307 gboolean to_all, gboolean to_ml,
3309 gboolean followup_and_reply_to)
3311 GSList *cc_list = NULL;
3314 gchar *replyto = NULL;
3315 gchar *ac_email = NULL;
3317 gboolean reply_to_ml = FALSE;
3318 gboolean default_reply_to = FALSE;
3320 cm_return_if_fail(compose->account != NULL);
3321 cm_return_if_fail(msginfo != NULL);
3323 reply_to_ml = to_ml && compose->ml_post;
3325 default_reply_to = msginfo->folder &&
3326 msginfo->folder->prefs->enable_default_reply_to;
3328 if (compose->account->protocol != A_NNTP) {
3329 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
3331 if (reply_to_ml && !default_reply_to) {
3333 gboolean is_subscr = is_subscription(compose->ml_post,
3336 /* normal answer to ml post with a reply-to */
3337 compose_entry_append(compose,
3339 COMPOSE_TO, PREF_ML);
3340 if (compose->replyto)
3341 compose_entry_append(compose,
3343 COMPOSE_CC, PREF_ML);
3345 /* answer to subscription confirmation */
3346 if (compose->replyto)
3347 compose_entry_append(compose,
3349 COMPOSE_TO, PREF_ML);
3350 else if (msginfo->from)
3351 compose_entry_append(compose,
3353 COMPOSE_TO, PREF_ML);
3356 else if (!(to_all || to_sender) && default_reply_to) {
3357 compose_entry_append(compose,
3358 msginfo->folder->prefs->default_reply_to,
3359 COMPOSE_TO, PREF_FOLDER);
3360 compose_entry_indicate(compose,
3361 msginfo->folder->prefs->default_reply_to);
3367 compose_entry_append(compose, msginfo->from,
3368 COMPOSE_TO, PREF_NONE);
3370 Xstrdup_a(tmp1, msginfo->from, return);
3371 extract_address(tmp1);
3372 compose_entry_append(compose,
3373 (!account_find_from_address(tmp1, FALSE))
3376 COMPOSE_TO, PREF_NONE);
3377 if (compose->replyto)
3378 compose_entry_append(compose,
3380 COMPOSE_CC, PREF_NONE);
3382 if (!folder_has_parent_of_type(msginfo->folder, F_QUEUE) &&
3383 !folder_has_parent_of_type(msginfo->folder, F_OUTBOX) &&
3384 !folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
3385 if (compose->replyto) {
3386 compose_entry_append(compose,
3388 COMPOSE_TO, PREF_NONE);
3390 compose_entry_append(compose,
3391 msginfo->from ? msginfo->from : "",
3392 COMPOSE_TO, PREF_NONE);
3395 /* replying to own mail, use original recp */
3396 compose_entry_append(compose,
3397 msginfo->to ? msginfo->to : "",
3398 COMPOSE_TO, PREF_NONE);
3399 compose_entry_append(compose,
3400 msginfo->cc ? msginfo->cc : "",
3401 COMPOSE_CC, PREF_NONE);
3406 if (to_sender || (compose->followup_to &&
3407 !strncmp(compose->followup_to, "poster", 6)))
3408 compose_entry_append
3410 (compose->replyto ? compose->replyto :
3411 msginfo->from ? msginfo->from : ""),
3412 COMPOSE_TO, PREF_NONE);
3414 else if (followup_and_reply_to || to_all) {
3415 compose_entry_append
3417 (compose->replyto ? compose->replyto :
3418 msginfo->from ? msginfo->from : ""),
3419 COMPOSE_TO, PREF_NONE);
3421 compose_entry_append
3423 compose->followup_to ? compose->followup_to :
3424 compose->newsgroups ? compose->newsgroups : "",
3425 COMPOSE_NEWSGROUPS, PREF_NONE);
3427 compose_entry_append
3429 msginfo->cc ? msginfo->cc : "",
3430 COMPOSE_CC, PREF_NONE);
3433 compose_entry_append
3435 compose->followup_to ? compose->followup_to :
3436 compose->newsgroups ? compose->newsgroups : "",
3437 COMPOSE_NEWSGROUPS, PREF_NONE);
3439 compose_reply_set_subject(compose, msginfo);
3441 if (to_ml && compose->ml_post) return;
3442 if (!to_all || compose->account->protocol == A_NNTP) return;
3444 if (compose->replyto) {
3445 Xstrdup_a(replyto, compose->replyto, return);
3446 extract_address(replyto);
3448 if (msginfo->from) {
3449 Xstrdup_a(from, msginfo->from, return);
3450 extract_address(from);
3453 if (replyto && from)
3454 cc_list = address_list_append_with_comments(cc_list, from);
3455 if (to_all && msginfo->folder &&
3456 msginfo->folder->prefs->enable_default_reply_to)
3457 cc_list = address_list_append_with_comments(cc_list,
3458 msginfo->folder->prefs->default_reply_to);
3459 cc_list = address_list_append_with_comments(cc_list, msginfo->to);
3460 cc_list = address_list_append_with_comments(cc_list, compose->cc);
3462 ac_email = g_utf8_strdown(compose->account->address, -1);
3465 for (cur = cc_list; cur != NULL; cur = cur->next) {
3466 gchar *addr = g_utf8_strdown(cur->data, -1);
3467 extract_address(addr);
3469 if (strcmp(ac_email, addr))
3470 compose_entry_append(compose, (gchar *)cur->data,
3471 COMPOSE_CC, PREF_NONE);
3473 debug_print("Cc address same as compose account's, ignoring\n");
3478 slist_free_strings_full(cc_list);
3484 #define SET_ENTRY(entry, str) \
3487 gtk_entry_set_text(GTK_ENTRY(compose->entry), str); \
3490 #define SET_ADDRESS(type, str) \
3493 compose_entry_append(compose, str, type, PREF_NONE); \
3496 static void compose_reedit_set_entry(Compose *compose, MsgInfo *msginfo)
3498 cm_return_if_fail(msginfo != NULL);
3500 SET_ENTRY(subject_entry, msginfo->subject);
3501 SET_ENTRY(from_name, msginfo->from);
3502 SET_ADDRESS(COMPOSE_TO, msginfo->to);
3503 SET_ADDRESS(COMPOSE_CC, compose->cc);
3504 SET_ADDRESS(COMPOSE_BCC, compose->bcc);
3505 SET_ADDRESS(COMPOSE_REPLYTO, compose->replyto);
3506 SET_ADDRESS(COMPOSE_NEWSGROUPS, compose->newsgroups);
3507 SET_ADDRESS(COMPOSE_FOLLOWUPTO, compose->followup_to);
3509 compose_update_priority_menu_item(compose);
3510 compose_update_privacy_system_menu_item(compose, FALSE);
3511 compose_show_first_last_header(compose, TRUE);
3517 static void compose_insert_sig(Compose *compose, gboolean replace)
3519 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3520 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3522 GtkTextIter iter, iter_end;
3523 gint cur_pos, ins_pos;
3524 gboolean prev_autowrap;
3525 gboolean found = FALSE;
3526 gboolean exists = FALSE;
3528 cm_return_if_fail(compose->account != NULL);
3532 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3533 G_CALLBACK(compose_changed_cb),
3536 mark = gtk_text_buffer_get_insert(buffer);
3537 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3538 cur_pos = gtk_text_iter_get_offset (&iter);
3541 gtk_text_buffer_get_end_iter(buffer, &iter);
3543 exists = (compose->sig_str != NULL);
3546 GtkTextIter first_iter, start_iter, end_iter;
3548 gtk_text_buffer_get_start_iter(buffer, &first_iter);
3550 if (!exists || compose->sig_str[0] == '\0')
3553 found = gtk_text_iter_forward_to_tag_toggle(&first_iter,
3554 compose->signature_tag);
3557 /* include previous \n\n */
3558 gtk_text_iter_backward_chars(&first_iter, 1);
3559 start_iter = first_iter;
3560 end_iter = first_iter;
3562 found = gtk_text_iter_forward_to_tag_toggle(&end_iter,
3563 compose->signature_tag);
3564 found &= gtk_text_iter_forward_to_tag_toggle(&end_iter,
3565 compose->signature_tag);
3567 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
3573 g_free(compose->sig_str);
3574 compose->sig_str = account_get_signature_str(compose->account);
3576 cur_pos = gtk_text_iter_get_offset(&iter);
3578 if (!compose->sig_str || (replace && !compose->account->auto_sig)) {
3579 g_free(compose->sig_str);
3580 compose->sig_str = NULL;
3582 if (compose->sig_inserted == FALSE)
3583 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
3584 compose->sig_inserted = TRUE;
3586 cur_pos = gtk_text_iter_get_offset(&iter);
3587 gtk_text_buffer_insert(buffer, &iter, compose->sig_str, -1);
3589 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cur_pos);
3590 gtk_text_iter_forward_chars(&iter, 1);
3591 gtk_text_buffer_get_end_iter(buffer, &iter_end);
3592 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &iter_end);
3594 if (cur_pos > gtk_text_buffer_get_char_count (buffer))
3595 cur_pos = gtk_text_buffer_get_char_count (buffer);
3598 /* put the cursor where it should be
3599 * either where the quote_fmt says, either where it was */
3600 if (compose->set_cursor_pos < 0)
3601 gtk_text_buffer_get_iter_at_offset(buffer, &iter, ins_pos);
3603 gtk_text_buffer_get_iter_at_offset(buffer, &iter,
3604 compose->set_cursor_pos);
3606 compose->set_cursor_pos = -1;
3607 gtk_text_buffer_place_cursor(buffer, &iter);
3608 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3609 G_CALLBACK(compose_changed_cb),
3615 static ComposeInsertResult compose_insert_file(Compose *compose, const gchar *file)
3618 GtkTextBuffer *buffer;
3621 const gchar *cur_encoding;
3622 gchar buf[BUFFSIZE];
3625 gboolean prev_autowrap;
3629 GError *error = NULL;
3635 GString *file_contents = NULL;
3636 ComposeInsertResult result = COMPOSE_INSERT_SUCCESS;
3638 cm_return_val_if_fail(file != NULL, COMPOSE_INSERT_NO_FILE);
3640 /* get the size of the file we are about to insert */
3642 f = g_file_new_for_path(file);
3643 fi = g_file_query_info(f, "standard::size",
3644 G_FILE_QUERY_INFO_NONE, NULL, &error);
3646 if (error != NULL) {
3647 g_warning(error->message);
3649 g_error_free(error);
3653 ret = g_stat(file, &file_stat);
3656 gchar *shortfile = g_path_get_basename(file);
3657 alertpanel_error(_("Could not get size of file '%s'."), shortfile);
3659 return COMPOSE_INSERT_NO_FILE;
3660 } else if (prefs_common.warn_large_insert == TRUE) {
3662 size = g_file_info_get_size(fi);
3666 size = file_stat.st_size;
3669 /* ask user for confirmation if the file is large */
3670 if (prefs_common.warn_large_insert_size < 0 ||
3671 size > ((goffset) prefs_common.warn_large_insert_size * 1024)) {
3675 msg = g_strdup_printf(_("You are about to insert a file of %s "
3676 "in the message body. Are you sure you want to do that?"),
3677 to_human_readable(size));
3678 aval = alertpanel_full(_("Are you sure?"), msg, GTK_STOCK_CANCEL,
3679 _("_Insert"), NULL, ALERTFOCUS_SECOND, TRUE,
3680 NULL, ALERT_QUESTION);
3683 /* do we ask for confirmation next time? */
3684 if (aval & G_ALERTDISABLE) {
3685 /* no confirmation next time, disable feature in preferences */
3686 aval &= ~G_ALERTDISABLE;
3687 prefs_common.warn_large_insert = FALSE;
3690 /* abort file insertion if user canceled action */
3691 if (aval != G_ALERTALTERNATE) {
3692 return COMPOSE_INSERT_NO_FILE;
3698 if ((fp = claws_fopen(file, "rb")) == NULL) {
3699 FILE_OP_ERROR(file, "claws_fopen");
3700 return COMPOSE_INSERT_READ_ERROR;
3703 prev_autowrap = compose->autowrap;
3704 compose->autowrap = FALSE;
3706 text = GTK_TEXT_VIEW(compose->text);
3707 buffer = gtk_text_view_get_buffer(text);
3708 mark = gtk_text_buffer_get_insert(buffer);
3709 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3711 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3712 G_CALLBACK(text_inserted),
3715 cur_encoding = conv_get_locale_charset_str_no_utf8();
3717 file_contents = g_string_new("");
3718 while (claws_fgets(buf, sizeof(buf), fp) != NULL) {
3721 if (g_utf8_validate(buf, -1, NULL) == TRUE)
3722 str = g_strdup(buf);
3724 codeconv_set_strict(TRUE);
3725 str = conv_codeset_strdup
3726 (buf, cur_encoding, CS_INTERNAL);
3727 codeconv_set_strict(FALSE);
3730 result = COMPOSE_INSERT_INVALID_CHARACTER;
3736 /* strip <CR> if DOS/Windows file,
3737 replace <CR> with <LF> if Macintosh file. */
3740 if (len > 0 && str[len - 1] != '\n') {
3742 if (str[len] == '\r') str[len] = '\n';
3745 file_contents = g_string_append(file_contents, str);
3749 if (result == COMPOSE_INSERT_SUCCESS) {
3750 gtk_text_buffer_insert(buffer, &iter, file_contents->str, -1);
3752 compose_changed_cb(NULL, compose);
3753 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3754 G_CALLBACK(text_inserted),
3756 compose->autowrap = prev_autowrap;
3757 if (compose->autowrap)
3758 compose_wrap_all(compose);
3761 g_string_free(file_contents, TRUE);
3767 static gboolean compose_attach_append(Compose *compose, const gchar *file,
3768 const gchar *filename,
3769 const gchar *content_type,
3770 const gchar *charset)
3778 GtkListStore *store;
3780 gboolean has_binary = FALSE;
3782 if (!is_file_exist(file)) {
3783 gchar *file_from_uri = g_filename_from_uri(file, NULL, NULL);
3784 gboolean result = FALSE;
3785 if (file_from_uri && is_file_exist(file_from_uri)) {
3786 result = compose_attach_append(
3787 compose, file_from_uri,
3788 filename, content_type,
3791 g_free(file_from_uri);
3794 alertpanel_error("File %s doesn't exist or permission denied\n", filename);
3797 if ((size = get_file_size(file)) < 0) {
3798 alertpanel_error("Can't get file size of %s\n", filename);
3802 /* In batch mode, we allow 0-length files to be attached no questions asked */
3803 if (size == 0 && !compose->batch) {
3804 gchar * msg = g_strdup_printf(_("File %s is empty."), filename);
3805 AlertValue aval = alertpanel_full(_("Empty file"), msg,
3806 GTK_STOCK_CANCEL, _("_Attach anyway"), NULL,
3807 ALERTFOCUS_SECOND, FALSE, NULL, ALERT_WARNING);
3810 if (aval != G_ALERTALTERNATE) {
3814 if ((fp = claws_fopen(file, "rb")) == NULL) {
3815 alertpanel_error(_("Can't read %s."), filename);
3820 ainfo = g_new0(AttachInfo, 1);
3821 auto_ainfo = g_auto_pointer_new_with_free
3822 (ainfo, (GFreeFunc) compose_attach_info_free);
3823 ainfo->file = g_strdup(file);
3826 ainfo->content_type = g_strdup(content_type);
3827 if (!g_ascii_strcasecmp(content_type, "message/rfc822")) {
3829 MsgFlags flags = {0, 0};
3831 if (procmime_get_encoding_for_text_file(file, &has_binary) == ENC_7BIT)
3832 ainfo->encoding = ENC_7BIT;
3834 ainfo->encoding = ENC_8BIT;
3836 msginfo = procheader_parse_file(file, flags, FALSE, FALSE);
3837 if (msginfo && msginfo->subject)
3838 name = g_strdup(msginfo->subject);
3840 name = g_path_get_basename(filename ? filename : file);
3842 ainfo->name = g_strdup_printf(_("Message: %s"), name);
3844 procmsg_msginfo_free(&msginfo);
3846 if (!g_ascii_strncasecmp(content_type, "text/", 5)) {
3847 ainfo->charset = g_strdup(charset);
3848 ainfo->encoding = procmime_get_encoding_for_text_file(file, &has_binary);
3850 ainfo->encoding = ENC_BASE64;
3852 name = g_path_get_basename(filename ? filename : file);
3853 ainfo->name = g_strdup(name);
3857 ainfo->content_type = procmime_get_mime_type(file);
3858 if (!ainfo->content_type) {
3859 ainfo->content_type =
3860 g_strdup("application/octet-stream");
3861 ainfo->encoding = ENC_BASE64;
3862 } else if (!g_ascii_strncasecmp(ainfo->content_type, "text/", 5))
3864 procmime_get_encoding_for_text_file(file, &has_binary);
3866 ainfo->encoding = ENC_BASE64;
3867 name = g_path_get_basename(filename ? filename : file);
3868 ainfo->name = g_strdup(name);
3872 if (ainfo->name != NULL
3873 && !strcmp(ainfo->name, ".")) {
3874 g_free(ainfo->name);
3878 if (!strcmp(ainfo->content_type, "unknown") || has_binary) {
3879 g_free(ainfo->content_type);
3880 ainfo->content_type = g_strdup("application/octet-stream");
3881 g_free(ainfo->charset);
3882 ainfo->charset = NULL;
3885 ainfo->size = (goffset)size;
3886 size_text = to_human_readable((goffset)size);
3888 store = GTK_LIST_STORE(gtk_tree_view_get_model
3889 (GTK_TREE_VIEW(compose->attach_clist)));
3891 gtk_list_store_append(store, &iter);
3892 gtk_list_store_set(store, &iter,
3893 COL_MIMETYPE, ainfo->content_type,
3894 COL_SIZE, size_text,
3895 COL_NAME, ainfo->name,
3896 COL_CHARSET, ainfo->charset,
3898 COL_AUTODATA, auto_ainfo,
3901 g_auto_pointer_free(auto_ainfo);
3902 compose_attach_update_label(compose);
3906 void compose_use_signing(Compose *compose, gboolean use_signing)
3908 compose->use_signing = use_signing;
3909 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", use_signing);
3912 void compose_use_encryption(Compose *compose, gboolean use_encryption)
3914 compose->use_encryption = use_encryption;
3915 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", use_encryption);
3918 #define NEXT_PART_NOT_CHILD(info) \
3920 node = info->node; \
3921 while (node->children) \
3922 node = g_node_last_child(node); \
3923 info = procmime_mimeinfo_next((MimeInfo *)node->data); \
3926 static void compose_attach_parts(Compose *compose, MsgInfo *msginfo)
3930 MimeInfo *firsttext = NULL;
3931 MimeInfo *encrypted = NULL;
3934 const gchar *partname = NULL;
3936 mimeinfo = procmime_scan_message(msginfo);
3937 if (!mimeinfo) return;
3939 if (mimeinfo->node->children == NULL) {
3940 procmime_mimeinfo_free_all(&mimeinfo);
3944 /* find first content part */
3945 child = (MimeInfo *) mimeinfo->node->children->data;
3946 while (child && child->node->children && (child->type == MIMETYPE_MULTIPART))
3947 child = (MimeInfo *)child->node->children->data;
3950 if (child->type == MIMETYPE_TEXT) {
3952 debug_print("First text part found\n");
3953 } else if (compose->mode == COMPOSE_REEDIT &&
3954 child->type == MIMETYPE_APPLICATION &&
3955 !g_ascii_strcasecmp(child->subtype, "pgp-encrypted")) {
3956 encrypted = (MimeInfo *)child->node->parent->data;
3959 child = (MimeInfo *) mimeinfo->node->children->data;
3960 while (child != NULL) {
3963 if (child == encrypted) {
3964 /* skip this part of tree */
3965 NEXT_PART_NOT_CHILD(child);
3969 if (child->type == MIMETYPE_MULTIPART) {
3970 /* get the actual content */
3971 child = procmime_mimeinfo_next(child);
3975 if (child == firsttext) {
3976 child = procmime_mimeinfo_next(child);
3980 outfile = procmime_get_tmp_file_name(child);
3981 if ((err = procmime_get_part(outfile, child)) < 0)
3982 g_warning("Can't get the part of multipart message. (%s)", g_strerror(-err));
3984 gchar *content_type;
3986 content_type = procmime_get_content_type_str(child->type, child->subtype);
3988 /* if we meet a pgp signature, we don't attach it, but
3989 * we force signing. */
3990 if ((strcmp(content_type, "application/pgp-signature") &&
3991 strcmp(content_type, "application/pkcs7-signature") &&
3992 strcmp(content_type, "application/x-pkcs7-signature"))
3993 || compose->mode == COMPOSE_REDIRECT) {
3994 partname = procmime_mimeinfo_get_parameter(child, "filename");
3995 if (partname == NULL)
3996 partname = procmime_mimeinfo_get_parameter(child, "name");
3997 if (partname == NULL)
3999 compose_attach_append(compose, outfile,
4000 partname, content_type,
4001 procmime_mimeinfo_get_parameter(child, "charset"));
4003 compose_force_signing(compose, compose->account, NULL);
4005 g_free(content_type);
4008 NEXT_PART_NOT_CHILD(child);
4010 procmime_mimeinfo_free_all(&mimeinfo);
4013 #undef NEXT_PART_NOT_CHILD
4018 WAIT_FOR_INDENT_CHAR,
4019 WAIT_FOR_INDENT_CHAR_OR_SPACE,
4022 /* return indent length, we allow:
4023 indent characters followed by indent characters or spaces/tabs,
4024 alphabets and numbers immediately followed by indent characters,
4025 and the repeating sequences of the above
4026 If quote ends with multiple spaces, only the first one is included. */
4027 static gchar *compose_get_quote_str(GtkTextBuffer *buffer,
4028 const GtkTextIter *start, gint *len)
4030 GtkTextIter iter = *start;
4034 IndentState state = WAIT_FOR_INDENT_CHAR;
4037 gint alnum_count = 0;
4038 gint space_count = 0;
4041 if (prefs_common.quote_chars == NULL) {
4045 while (!gtk_text_iter_ends_line(&iter)) {
4046 wc = gtk_text_iter_get_char(&iter);
4047 if (g_unichar_iswide(wc))
4049 clen = g_unichar_to_utf8(wc, ch);
4053 is_indent = strchr(prefs_common.quote_chars, ch[0]) ? TRUE : FALSE;
4054 is_space = g_unichar_isspace(wc);
4056 if (state == WAIT_FOR_INDENT_CHAR) {
4057 if (!is_indent && !g_unichar_isalnum(wc))
4060 quote_len += alnum_count + space_count + 1;
4061 alnum_count = space_count = 0;
4062 state = WAIT_FOR_INDENT_CHAR_OR_SPACE;
4065 } else if (state == WAIT_FOR_INDENT_CHAR_OR_SPACE) {
4066 if (!is_indent && !is_space && !g_unichar_isalnum(wc))
4070 else if (is_indent) {
4071 quote_len += alnum_count + space_count + 1;
4072 alnum_count = space_count = 0;
4075 state = WAIT_FOR_INDENT_CHAR;
4079 gtk_text_iter_forward_char(&iter);
4082 if (quote_len > 0 && space_count > 0)
4088 if (quote_len > 0) {
4090 gtk_text_iter_forward_chars(&iter, quote_len);
4091 return gtk_text_buffer_get_text(buffer, start, &iter, FALSE);
4097 /* return >0 if the line is itemized */
4098 static int compose_itemized_length(GtkTextBuffer *buffer,
4099 const GtkTextIter *start)
4101 GtkTextIter iter = *start;
4106 if (gtk_text_iter_ends_line(&iter))
4111 wc = gtk_text_iter_get_char(&iter);
4112 if (!g_unichar_isspace(wc))
4114 gtk_text_iter_forward_char(&iter);
4115 if (gtk_text_iter_ends_line(&iter))
4119 clen = g_unichar_to_utf8(wc, ch);
4120 if (!((clen == 1 && strchr("*-+", ch[0])) ||
4122 wc == 0x2022 || /* BULLET */
4123 wc == 0x2023 || /* TRIANGULAR BULLET */
4124 wc == 0x2043 || /* HYPHEN BULLET */
4125 wc == 0x204c || /* BLACK LEFTWARDS BULLET */
4126 wc == 0x204d || /* BLACK RIGHTWARDS BULLET */
4127 wc == 0x2219 || /* BULLET OPERATOR */
4128 wc == 0x25d8 || /* INVERSE BULLET */
4129 wc == 0x25e6 || /* WHITE BULLET */
4130 wc == 0x2619 || /* REVERSED ROTATED FLORAL HEART BULLET */
4131 wc == 0x2765 || /* ROTATED HEAVY BLACK HEART BULLET */
4132 wc == 0x2767 || /* ROTATED FLORAL HEART BULLET */
4133 wc == 0x29be || /* CIRCLED WHITE BULLET */
4134 wc == 0x29bf /* CIRCLED BULLET */
4138 gtk_text_iter_forward_char(&iter);
4139 if (gtk_text_iter_ends_line(&iter))
4141 wc = gtk_text_iter_get_char(&iter);
4142 if (g_unichar_isspace(wc)) {
4148 /* return the string at the start of the itemization */
4149 static gchar * compose_get_itemized_chars(GtkTextBuffer *buffer,
4150 const GtkTextIter *start)
4152 GtkTextIter iter = *start;
4155 GString *item_chars = g_string_new("");
4158 if (gtk_text_iter_ends_line(&iter))
4163 wc = gtk_text_iter_get_char(&iter);
4164 if (!g_unichar_isspace(wc))
4166 gtk_text_iter_forward_char(&iter);
4167 if (gtk_text_iter_ends_line(&iter))
4169 g_string_append_unichar(item_chars, wc);
4172 str = item_chars->str;
4173 g_string_free(item_chars, FALSE);
4177 /* return the number of spaces at a line's start */
4178 static int compose_left_offset_length(GtkTextBuffer *buffer,
4179 const GtkTextIter *start)
4181 GtkTextIter iter = *start;
4184 if (gtk_text_iter_ends_line(&iter))
4188 wc = gtk_text_iter_get_char(&iter);
4189 if (!g_unichar_isspace(wc))
4192 gtk_text_iter_forward_char(&iter);
4193 if (gtk_text_iter_ends_line(&iter))
4197 gtk_text_iter_forward_char(&iter);
4198 if (gtk_text_iter_ends_line(&iter))
4203 static gboolean compose_get_line_break_pos(GtkTextBuffer *buffer,
4204 const GtkTextIter *start,
4205 GtkTextIter *break_pos,
4209 GtkTextIter iter = *start, line_end = *start;
4210 PangoLogAttr *attrs;
4217 gboolean can_break = FALSE;
4218 gboolean do_break = FALSE;
4219 gboolean was_white = FALSE;
4220 gboolean prev_dont_break = FALSE;
4222 gtk_text_iter_forward_to_line_end(&line_end);
4223 str = gtk_text_buffer_get_text(buffer, &iter, &line_end, FALSE);
4224 len = g_utf8_strlen(str, -1);
4228 g_warning("compose_get_line_break_pos: len = 0!");
4232 /* g_print("breaking line: %d: %s (len = %d)\n",
4233 gtk_text_iter_get_line(&iter), str, len); */
4235 attrs = g_new(PangoLogAttr, len + 1);
4237 pango_default_break(str, -1, NULL, attrs, len + 1);
4241 /* skip quote and leading spaces */
4242 for (i = 0; *p != '\0' && i < len; i++) {
4245 wc = g_utf8_get_char(p);
4246 if (i >= quote_len && !g_unichar_isspace(wc))
4248 if (g_unichar_iswide(wc))
4250 else if (*p == '\t')
4254 p = g_utf8_next_char(p);
4257 for (; *p != '\0' && i < len; i++) {
4258 PangoLogAttr *attr = attrs + i;
4259 gunichar wc = g_utf8_get_char(p);
4262 /* attr->is_line_break will be false for some characters that
4263 * we want to break a line before, like '/' or ':', so we
4264 * also allow breaking on any non-wide character. The
4265 * mentioned pango attribute is still useful to decide on
4266 * line breaks when wide characters are involved. */
4267 if ((!g_unichar_iswide(wc) || attr->is_line_break)
4268 && can_break && was_white && !prev_dont_break)
4271 was_white = attr->is_white;
4273 /* don't wrap URI */
4274 if ((uri_len = get_uri_len(p)) > 0) {
4276 if (pos > 0 && col > max_col) {
4286 if (g_unichar_iswide(wc)) {
4288 if (prev_dont_break && can_break && attr->is_line_break)
4290 } else if (*p == '\t')
4294 if (pos > 0 && col > max_col) {
4299 if (*p == '-' || *p == '/')
4300 prev_dont_break = TRUE;
4302 prev_dont_break = FALSE;
4304 p = g_utf8_next_char(p);
4308 /* debug_print("compose_get_line_break_pos(): do_break = %d, pos = %d, col = %d\n", do_break, pos, col); */
4313 *break_pos = *start;
4314 gtk_text_iter_set_line_offset(break_pos, pos);
4319 static gboolean compose_join_next_line(Compose *compose,
4320 GtkTextBuffer *buffer,
4322 const gchar *quote_str)
4324 GtkTextIter iter_ = *iter, cur, prev, next, end;
4325 PangoLogAttr attrs[3];
4327 gchar *next_quote_str;
4330 gboolean keep_cursor = FALSE;
4332 if (!gtk_text_iter_forward_line(&iter_) ||
4333 gtk_text_iter_ends_line(&iter_)) {
4336 next_quote_str = compose_get_quote_str(buffer, &iter_, "e_len);
4338 if ((quote_str || next_quote_str) &&
4339 g_strcmp0(quote_str, next_quote_str) != 0) {
4340 g_free(next_quote_str);
4343 g_free(next_quote_str);
4346 if (quote_len > 0) {
4347 gtk_text_iter_forward_chars(&end, quote_len);
4348 if (gtk_text_iter_ends_line(&end)) {
4353 /* don't join itemized lines */
4354 if (compose_itemized_length(buffer, &end) > 0) {
4358 /* don't join signature separator */
4359 if (compose_is_sig_separator(compose, buffer, &iter_)) {
4362 /* delete quote str */
4364 gtk_text_buffer_delete(buffer, &iter_, &end);
4366 /* don't join line breaks put by the user */
4368 gtk_text_iter_backward_char(&cur);
4369 if (gtk_text_iter_has_tag(&cur, compose->no_join_tag)) {
4370 gtk_text_iter_forward_char(&cur);
4374 gtk_text_iter_forward_char(&cur);
4375 /* delete linebreak and extra spaces */
4376 while (gtk_text_iter_backward_char(&cur)) {
4377 wc1 = gtk_text_iter_get_char(&cur);
4378 if (!g_unichar_isspace(wc1))
4383 while (!gtk_text_iter_ends_line(&cur)) {
4384 wc1 = gtk_text_iter_get_char(&cur);
4385 if (!g_unichar_isspace(wc1))
4387 gtk_text_iter_forward_char(&cur);
4390 if (!gtk_text_iter_equal(&prev, &next)) {
4393 mark = gtk_text_buffer_get_insert(buffer);
4394 gtk_text_buffer_get_iter_at_mark(buffer, &cur, mark);
4395 if (gtk_text_iter_equal(&prev, &cur))
4397 gtk_text_buffer_delete(buffer, &prev, &next);
4401 /* insert space if required */
4402 gtk_text_iter_backward_char(&prev);
4403 wc1 = gtk_text_iter_get_char(&prev);
4404 wc2 = gtk_text_iter_get_char(&next);
4405 gtk_text_iter_forward_char(&next);
4406 str = gtk_text_buffer_get_text(buffer, &prev, &next, FALSE);
4407 pango_default_break(str, -1, NULL, attrs, 3);
4408 if (!attrs[1].is_line_break ||
4409 (!g_unichar_iswide(wc1) || !g_unichar_iswide(wc2))) {
4410 gtk_text_buffer_insert(buffer, &iter_, " ", 1);
4412 gtk_text_iter_backward_char(&iter_);
4413 gtk_text_buffer_place_cursor(buffer, &iter_);
4422 #define ADD_TXT_POS(bp_, ep_, pti_) \
4423 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
4424 last = last->next; \
4425 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
4426 last->next = NULL; \
4428 g_warning("alloc error scanning URIs"); \
4431 static gboolean compose_beautify_paragraph(Compose *compose, GtkTextIter *par_iter, gboolean force)
4433 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4434 GtkTextBuffer *buffer;
4435 GtkTextIter iter, break_pos, end_of_line;
4436 gchar *quote_str = NULL;
4438 gboolean wrap_quote = force || prefs_common.linewrap_quote;
4439 gboolean prev_autowrap = compose->autowrap;
4440 gint startq_offset = -1, noq_offset = -1;
4441 gint uri_start = -1, uri_stop = -1;
4442 gint nouri_start = -1, nouri_stop = -1;
4443 gint num_blocks = 0;
4444 gint quotelevel = -1;
4445 gboolean modified = force;
4446 gboolean removed = FALSE;
4447 gboolean modified_before_remove = FALSE;
4449 gboolean start = TRUE;
4450 gint itemized_len = 0, rem_item_len = 0;
4451 gchar *itemized_chars = NULL;
4452 gboolean item_continuation = FALSE;
4457 if (compose->draft_timeout_tag == COMPOSE_DRAFT_TIMEOUT_FORBIDDEN) {
4461 compose->autowrap = FALSE;
4463 buffer = gtk_text_view_get_buffer(text);
4464 undo_wrapping(compose->undostruct, TRUE);
4469 mark = gtk_text_buffer_get_insert(buffer);
4470 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
4474 if (compose->draft_timeout_tag == COMPOSE_DRAFT_TIMEOUT_FORBIDDEN) {
4475 if (gtk_text_iter_ends_line(&iter)) {
4476 while (gtk_text_iter_ends_line(&iter) &&
4477 gtk_text_iter_forward_line(&iter))
4480 while (gtk_text_iter_backward_line(&iter)) {
4481 if (gtk_text_iter_ends_line(&iter)) {
4482 gtk_text_iter_forward_line(&iter);
4488 /* move to line start */
4489 gtk_text_iter_set_line_offset(&iter, 0);
4492 itemized_len = compose_itemized_length(buffer, &iter);
4494 if (!itemized_len) {
4495 itemized_len = compose_left_offset_length(buffer, &iter);
4496 item_continuation = TRUE;
4500 itemized_chars = compose_get_itemized_chars(buffer, &iter);
4502 /* go until paragraph end (empty line) */
4503 while (start || !gtk_text_iter_ends_line(&iter)) {
4504 gchar *scanpos = NULL;
4505 /* parse table - in order of priority */
4507 const gchar *needle; /* token */
4509 /* token search function */
4510 gchar *(*search) (const gchar *haystack,
4511 const gchar *needle);
4512 /* part parsing function */
4513 gboolean (*parse) (const gchar *start,
4514 const gchar *scanpos,
4518 /* part to URI function */
4519 gchar *(*build_uri) (const gchar *bp,
4523 static struct table parser[] = {
4524 {"http://", strcasestr, get_uri_part, make_uri_string},
4525 {"https://", strcasestr, get_uri_part, make_uri_string},
4526 {"ftp://", strcasestr, get_uri_part, make_uri_string},
4527 {"sftp://", strcasestr, get_uri_part, make_uri_string},
4528 {"gopher://",strcasestr, get_uri_part, make_uri_string},
4529 {"www.", strcasestr, get_uri_part, make_http_string},
4530 {"mailto:", strcasestr, get_uri_part, make_uri_string},
4531 {"@", strcasestr, get_email_part, make_email_string}
4533 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
4534 gint last_index = PARSE_ELEMS;
4536 gchar *o_walk = NULL, *walk = NULL, *bp = NULL, *ep = NULL;
4540 if (!prev_autowrap && num_blocks == 0) {
4542 g_signal_handlers_block_by_func(G_OBJECT(buffer),
4543 G_CALLBACK(text_inserted),
4546 if (gtk_text_iter_has_tag(&iter, compose->no_wrap_tag) && !force)
4549 uri_start = uri_stop = -1;
4551 quote_str = compose_get_quote_str(buffer, &iter, "e_len);
4554 /* debug_print("compose_beautify_paragraph(): quote_str = '%s'\n", quote_str); */
4555 if (startq_offset == -1)
4556 startq_offset = gtk_text_iter_get_offset(&iter);
4557 quotelevel = get_quote_level(quote_str, prefs_common.quote_chars);
4558 if (quotelevel > 2) {
4559 /* recycle colors */
4560 if (prefs_common.recycle_quote_colors)
4569 if (startq_offset == -1)
4570 noq_offset = gtk_text_iter_get_offset(&iter);
4574 if (prev_autowrap == FALSE && !force && !wrap_quote) {
4577 if (gtk_text_iter_ends_line(&iter)) {
4579 } else if (compose_get_line_break_pos(buffer, &iter, &break_pos,
4580 prefs_common.linewrap_len,
4582 GtkTextIter prev, next, cur;
4583 if (prev_autowrap != FALSE || force) {
4584 compose->automatic_break = TRUE;
4586 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4587 compose->automatic_break = FALSE;
4588 if (itemized_len && compose->autoindent) {
4589 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4590 if (!item_continuation)
4591 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4593 } else if (quote_str && wrap_quote) {
4594 compose->automatic_break = TRUE;
4596 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4597 compose->automatic_break = FALSE;
4598 if (itemized_len && compose->autoindent) {
4599 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4600 if (!item_continuation)
4601 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4605 /* remove trailing spaces */
4607 rem_item_len = itemized_len;
4608 while (compose->autoindent && rem_item_len-- > 0)
4609 gtk_text_iter_backward_char(&cur);
4610 gtk_text_iter_backward_char(&cur);
4613 while (!gtk_text_iter_starts_line(&cur)) {
4616 gtk_text_iter_backward_char(&cur);
4617 wc = gtk_text_iter_get_char(&cur);
4618 if (!g_unichar_isspace(wc))
4622 if (!gtk_text_iter_equal(&prev, &next)) {
4623 gtk_text_buffer_delete(buffer, &prev, &next);
4625 gtk_text_iter_forward_char(&break_pos);
4629 gtk_text_buffer_insert(buffer, &break_pos,
4633 modified |= compose_join_next_line(compose, buffer, &iter, quote_str);
4635 /* move iter to current line start */
4636 gtk_text_iter_set_line_offset(&iter, 0);
4643 /* move iter to next line start */
4649 if (!prev_autowrap && num_blocks > 0) {
4651 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
4652 G_CALLBACK(text_inserted),
4656 while (!gtk_text_iter_ends_line(&end_of_line)) {
4657 gtk_text_iter_forward_char(&end_of_line);
4659 o_walk = walk = gtk_text_buffer_get_text(buffer, &iter, &end_of_line, FALSE);
4661 nouri_start = gtk_text_iter_get_offset(&iter);
4662 nouri_stop = gtk_text_iter_get_offset(&end_of_line);
4664 walk_pos = gtk_text_iter_get_offset(&iter);
4665 /* FIXME: this looks phony. scanning for anything in the parse table */
4666 for (n = 0; n < PARSE_ELEMS; n++) {
4669 tmp = parser[n].search(walk, parser[n].needle);
4671 if (scanpos == NULL || tmp < scanpos) {
4680 /* check if URI can be parsed */
4681 if (parser[last_index].parse(walk, scanpos, (const gchar **)&bp,
4682 (const gchar **)&ep, FALSE)
4683 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
4687 strlen(parser[last_index].needle);
4690 uri_start = walk_pos + (bp - o_walk);
4691 uri_stop = walk_pos + (ep - o_walk);
4695 gtk_text_iter_forward_line(&iter);
4698 if (startq_offset != -1) {
4699 GtkTextIter startquote, endquote;
4700 gtk_text_buffer_get_iter_at_offset(
4701 buffer, &startquote, startq_offset);
4704 switch (quotelevel) {
4706 if (!gtk_text_iter_has_tag(&startquote, compose->quote0_tag) ||
4707 !gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) {
4708 gtk_text_buffer_apply_tag_by_name(
4709 buffer, "quote0", &startquote, &endquote);
4710 gtk_text_buffer_remove_tag_by_name(
4711 buffer, "quote1", &startquote, &endquote);
4712 gtk_text_buffer_remove_tag_by_name(
4713 buffer, "quote2", &startquote, &endquote);
4718 if (!gtk_text_iter_has_tag(&startquote, compose->quote1_tag) ||
4719 !gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) {
4720 gtk_text_buffer_apply_tag_by_name(
4721 buffer, "quote1", &startquote, &endquote);
4722 gtk_text_buffer_remove_tag_by_name(
4723 buffer, "quote0", &startquote, &endquote);
4724 gtk_text_buffer_remove_tag_by_name(
4725 buffer, "quote2", &startquote, &endquote);
4730 if (!gtk_text_iter_has_tag(&startquote, compose->quote2_tag) ||
4731 !gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag)) {
4732 gtk_text_buffer_apply_tag_by_name(
4733 buffer, "quote2", &startquote, &endquote);
4734 gtk_text_buffer_remove_tag_by_name(
4735 buffer, "quote0", &startquote, &endquote);
4736 gtk_text_buffer_remove_tag_by_name(
4737 buffer, "quote1", &startquote, &endquote);
4743 } else if (noq_offset != -1) {
4744 GtkTextIter startnoquote, endnoquote;
4745 gtk_text_buffer_get_iter_at_offset(
4746 buffer, &startnoquote, noq_offset);
4749 if ((gtk_text_iter_has_tag(&startnoquote, compose->quote0_tag)
4750 && gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) ||
4751 (gtk_text_iter_has_tag(&startnoquote, compose->quote1_tag)
4752 && gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) ||
4753 (gtk_text_iter_has_tag(&startnoquote, compose->quote2_tag)
4754 && gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag))) {
4755 gtk_text_buffer_remove_tag_by_name(
4756 buffer, "quote0", &startnoquote, &endnoquote);
4757 gtk_text_buffer_remove_tag_by_name(
4758 buffer, "quote1", &startnoquote, &endnoquote);
4759 gtk_text_buffer_remove_tag_by_name(
4760 buffer, "quote2", &startnoquote, &endnoquote);
4766 if (uri_start != nouri_start && uri_stop != nouri_stop) {
4767 GtkTextIter nouri_start_iter, nouri_end_iter;
4768 gtk_text_buffer_get_iter_at_offset(
4769 buffer, &nouri_start_iter, nouri_start);
4770 gtk_text_buffer_get_iter_at_offset(
4771 buffer, &nouri_end_iter, nouri_stop);
4772 if (gtk_text_iter_has_tag(&nouri_start_iter, compose->uri_tag) &&
4773 gtk_text_iter_has_tag(&nouri_end_iter, compose->uri_tag)) {
4774 gtk_text_buffer_remove_tag_by_name(
4775 buffer, "link", &nouri_start_iter, &nouri_end_iter);
4776 modified_before_remove = modified;
4781 if (uri_start >= 0 && uri_stop > 0) {
4782 GtkTextIter uri_start_iter, uri_end_iter, back;
4783 gtk_text_buffer_get_iter_at_offset(
4784 buffer, &uri_start_iter, uri_start);
4785 gtk_text_buffer_get_iter_at_offset(
4786 buffer, &uri_end_iter, uri_stop);
4787 back = uri_end_iter;
4788 gtk_text_iter_backward_char(&back);
4789 if (!gtk_text_iter_has_tag(&uri_start_iter, compose->uri_tag) ||
4790 !gtk_text_iter_has_tag(&back, compose->uri_tag)) {
4791 gtk_text_buffer_apply_tag_by_name(
4792 buffer, "link", &uri_start_iter, &uri_end_iter);
4794 if (removed && !modified_before_remove) {
4800 /* debug_print("not modified, out after %d lines\n", lines); */
4804 /* debug_print("modified, out after %d lines\n", lines); */
4806 g_free(itemized_chars);
4809 undo_wrapping(compose->undostruct, FALSE);
4810 compose->autowrap = prev_autowrap;
4815 void compose_action_cb(void *data)
4817 Compose *compose = (Compose *)data;
4818 compose_wrap_all(compose);
4821 static void compose_wrap_all(Compose *compose)
4823 compose_wrap_all_full(compose, FALSE);
4826 static void compose_wrap_all_full(Compose *compose, gboolean force)
4828 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4829 GtkTextBuffer *buffer;
4831 gboolean modified = TRUE;
4833 buffer = gtk_text_view_get_buffer(text);
4835 gtk_text_buffer_get_start_iter(buffer, &iter);
4837 undo_wrapping(compose->undostruct, TRUE);
4839 while (!gtk_text_iter_is_end(&iter) && modified)
4840 modified = compose_beautify_paragraph(compose, &iter, force);
4842 undo_wrapping(compose->undostruct, FALSE);
4846 static void compose_set_title(Compose *compose)
4852 edited = compose->modified ? _(" [Edited]") : "";
4854 subject = gtk_editable_get_chars(
4855 GTK_EDITABLE(compose->subject_entry), 0, -1);
4857 #ifndef GENERIC_UMPC
4858 if (subject && strlen(subject))
4859 str = g_strdup_printf(_("%s - Compose message%s"),
4862 str = g_strdup_printf(_("[no subject] - Compose message%s"), edited);
4864 str = g_strdup(_("Compose message"));
4867 gtk_window_set_title(GTK_WINDOW(compose->window), str);
4873 * compose_current_mail_account:
4875 * Find a current mail account (the currently selected account, or the
4876 * default account, if a news account is currently selected). If a
4877 * mail account cannot be found, display an error message.
4879 * Return value: Mail account, or NULL if not found.
4881 static PrefsAccount *
4882 compose_current_mail_account(void)
4886 if (cur_account && cur_account->protocol != A_NNTP)
4889 ac = account_get_default();
4890 if (!ac || ac->protocol == A_NNTP) {
4891 alertpanel_error(_("Account for sending mail is not specified.\n"
4892 "Please select a mail account before sending."));
4899 #define QUOTE_IF_REQUIRED(out, str) \
4901 if (*str != '"' && strpbrk(str, ",.:;[]<>()@\\\"")) { \
4905 len = strlen(str) + 3; \
4906 if ((__tmp = alloca(len)) == NULL) { \
4907 g_warning("can't allocate memory"); \
4908 g_string_free(header, TRUE); \
4911 g_snprintf(__tmp, len, "\"%s\"", str); \
4916 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4917 g_warning("can't allocate memory"); \
4918 g_string_free(header, TRUE); \
4921 strcpy(__tmp, str); \
4927 #define QUOTE_IF_REQUIRED_NORMAL(out, str, errret) \
4929 if (*str != '"' && strpbrk(str, ",.:;[]<>()@\\\"")) { \
4933 len = strlen(str) + 3; \
4934 if ((__tmp = alloca(len)) == NULL) { \
4935 g_warning("can't allocate memory"); \
4938 g_snprintf(__tmp, len, "\"%s\"", str); \
4943 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4944 g_warning("can't allocate memory"); \
4947 strcpy(__tmp, str); \
4953 static void compose_select_account(Compose *compose, PrefsAccount *account,
4956 gchar *from = NULL, *header = NULL;
4957 ComposeHeaderEntry *header_entry;
4960 cm_return_if_fail(account != NULL);
4962 compose->account = account;
4963 if (account->name && *account->name) {
4965 QUOTE_IF_REQUIRED_NORMAL(buf, account->name, return);
4966 qbuf = escape_internal_quotes(buf, '"');
4967 from = g_strdup_printf("%s <%s>",
4968 qbuf, account->address);
4971 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4973 from = g_strdup_printf("<%s>",
4975 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4980 compose_set_title(compose);
4982 compose_activate_privacy_system(compose, account, FALSE);
4984 if (account->default_sign && privacy_system_can_sign(compose->privacy_system) &&
4985 compose->mode != COMPOSE_REDIRECT)
4986 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", TRUE);
4988 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", FALSE);
4989 if (account->default_encrypt && privacy_system_can_encrypt(compose->privacy_system) &&
4990 compose->mode != COMPOSE_REDIRECT)
4991 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", TRUE);
4993 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", FALSE);
4995 if (!init && compose->mode != COMPOSE_REDIRECT) {
4996 undo_block(compose->undostruct);
4997 compose_insert_sig(compose, TRUE);
4998 undo_unblock(compose->undostruct);
5001 header_entry = (ComposeHeaderEntry *) compose->header_list->data;
5002 if (gtk_combo_box_get_active_iter(GTK_COMBO_BOX(header_entry->combo), &iter))
5003 gtk_tree_model_get(gtk_combo_box_get_model(GTK_COMBO_BOX(
5004 header_entry->combo)), &iter, COMBOBOX_TEXT, &header, -1);
5006 if (header && !strlen(gtk_entry_get_text(GTK_ENTRY(header_entry->entry)))) {
5007 if (account->protocol == A_NNTP) {
5008 if (!strcmp(header, _("To:")))
5009 combobox_select_by_text(
5010 GTK_COMBO_BOX(header_entry->combo),
5013 if (!strcmp(header, _("Newsgroups:")))
5014 combobox_select_by_text(
5015 GTK_COMBO_BOX(header_entry->combo),
5023 /* use account's dict info if set */
5024 if (compose->gtkaspell) {
5025 if (account->enable_default_dictionary)
5026 gtkaspell_change_dict(compose->gtkaspell,
5027 account->default_dictionary, FALSE);
5028 if (account->enable_default_alt_dictionary)
5029 gtkaspell_change_alt_dict(compose->gtkaspell,
5030 account->default_alt_dictionary);
5031 if (account->enable_default_dictionary
5032 || account->enable_default_alt_dictionary)
5033 compose_spell_menu_changed(compose);
5038 gboolean compose_check_for_valid_recipient(Compose *compose) {
5039 gchar *recipient_headers_mail[] = {"To:", "Cc:", "Bcc:", NULL};
5040 gchar *recipient_headers_news[] = {"Newsgroups:", NULL};
5041 gboolean recipient_found = FALSE;
5045 /* free to and newsgroup list */
5046 slist_free_strings_full(compose->to_list);
5047 compose->to_list = NULL;
5049 slist_free_strings_full(compose->newsgroup_list);
5050 compose->newsgroup_list = NULL;
5052 /* search header entries for to and newsgroup entries */
5053 for (list = compose->header_list; list; list = list->next) {
5056 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5057 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5060 if (entry[0] != '\0') {
5061 for (strptr = recipient_headers_mail; *strptr != NULL; strptr++) {
5062 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
5063 compose->to_list = address_list_append(compose->to_list, entry);
5064 recipient_found = TRUE;
5067 for (strptr = recipient_headers_news; *strptr != NULL; strptr++) {
5068 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
5069 compose->newsgroup_list = newsgroup_list_append(compose->newsgroup_list, entry);
5070 recipient_found = TRUE;
5077 return recipient_found;
5080 static gboolean compose_check_for_set_recipients(Compose *compose)
5082 if (compose->account->set_autocc && compose->account->auto_cc) {
5083 gboolean found_other = FALSE;
5085 /* search header entries for to and newsgroup entries */
5086 for (list = compose->header_list; list; list = list->next) {
5089 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5090 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5093 if (strcmp(entry, compose->account->auto_cc)
5094 || strcmp(header, prefs_common_translated_header_name("Cc:"))) {
5105 if (compose->batch) {
5106 gtk_widget_show_all(compose->window);
5108 text = g_strdup_printf(_("The only recipient is the default '%s' address. Send anyway?"),
5109 prefs_common_translated_header_name("Cc"));
5110 aval = alertpanel(_("Send"),
5112 GTK_STOCK_CANCEL, _("_Send"), NULL, ALERTFOCUS_SECOND);
5114 if (aval != G_ALERTALTERNATE)
5118 if (compose->account->set_autobcc && compose->account->auto_bcc) {
5119 gboolean found_other = FALSE;
5121 /* search header entries for to and newsgroup entries */
5122 for (list = compose->header_list; list; list = list->next) {
5125 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5126 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5129 if (strcmp(entry, compose->account->auto_bcc)
5130 || strcmp(header, prefs_common_translated_header_name("Bcc:"))) {
5142 if (compose->batch) {
5143 gtk_widget_show_all(compose->window);
5145 text = g_strdup_printf(_("The only recipient is the default '%s' address. Send anyway?"),
5146 prefs_common_translated_header_name("Bcc"));
5147 aval = alertpanel(_("Send"),
5149 GTK_STOCK_CANCEL, _("_Send"), NULL, ALERTFOCUS_SECOND);
5151 if (aval != G_ALERTALTERNATE)
5158 static gboolean compose_check_entries(Compose *compose, gboolean check_everything)
5162 if (compose_check_for_valid_recipient(compose) == FALSE) {
5163 if (compose->batch) {
5164 gtk_widget_show_all(compose->window);
5166 alertpanel_error(_("Recipient is not specified."));
5170 if (compose_check_for_set_recipients(compose) == FALSE) {
5174 if (!compose->batch && prefs_common.warn_empty_subj == TRUE) {
5175 str = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5176 if (*str == '\0' && check_everything == TRUE &&
5177 compose->mode != COMPOSE_REDIRECT) {
5181 message = g_strdup_printf(_("Subject is empty. %s"),
5182 compose->sending?_("Send it anyway?"):
5183 _("Queue it anyway?"));
5185 aval = alertpanel_full(compose->sending?_("Send"):_("Send later"), message,
5186 GTK_STOCK_CANCEL, compose->sending?_("_Send"):_("_Queue"), NULL,
5187 ALERTFOCUS_FIRST, TRUE, NULL, ALERT_QUESTION);
5189 if (aval & G_ALERTDISABLE) {
5190 aval &= ~G_ALERTDISABLE;
5191 prefs_common.warn_empty_subj = FALSE;
5193 if (aval != G_ALERTALTERNATE)
5198 if (!compose->batch && prefs_common.warn_sending_many_recipients_num > 0
5199 && check_everything == TRUE) {
5203 /* count To and Cc recipients */
5204 for (list = compose->header_list; list; list = list->next) {
5208 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5209 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5212 if ((entry[0] != '\0') &&
5213 (!strcmp(header, prefs_common_translated_header_name("To:")) ||
5214 !strcmp(header, prefs_common_translated_header_name("Cc:")))) {
5220 if (cnt > prefs_common.warn_sending_many_recipients_num) {
5224 message = g_strdup_printf(_("Sending to %d recipients. %s"), cnt,
5225 compose->sending?_("Send it anyway?"):
5226 _("Queue it anyway?"));
5228 aval = alertpanel_full(compose->sending?_("Send"):_("Send later"), message,
5229 GTK_STOCK_CANCEL, compose->sending?_("_Send"):_("_Queue"), NULL,
5230 ALERTFOCUS_FIRST, TRUE, NULL, ALERT_QUESTION);
5232 if (aval & G_ALERTDISABLE) {
5233 aval &= ~G_ALERTDISABLE;
5234 prefs_common.warn_sending_many_recipients_num = 0;
5236 if (aval != G_ALERTALTERNATE)
5241 if (check_everything && hooks_invoke(COMPOSE_CHECK_BEFORE_SEND_HOOKLIST, compose))
5247 static void _display_queue_error(ComposeQueueResult val)
5250 case COMPOSE_QUEUE_SUCCESS:
5252 case COMPOSE_QUEUE_ERROR_NO_MSG:
5253 alertpanel_error(_("Could not queue message."));
5255 case COMPOSE_QUEUE_ERROR_WITH_ERRNO:
5256 alertpanel_error(_("Could not queue message:\n\n%s."),
5259 case COMPOSE_QUEUE_ERROR_SIGNING_FAILED:
5260 alertpanel_error(_("Could not queue message for sending:\n\n"
5261 "Signature failed: %s"),
5262 privacy_peek_error() ? privacy_get_error() : _("Unknown error"));
5264 case COMPOSE_QUEUE_ERROR_ENCRYPT_FAILED:
5265 alertpanel_error(_("Could not queue message for sending:\n\n"
5266 "Encryption failed: %s"),
5267 privacy_peek_error() ? privacy_get_error() : _("Unknown error"));
5269 case COMPOSE_QUEUE_ERROR_CHAR_CONVERSION:
5270 alertpanel_error(_("Could not queue message for sending:\n\n"
5271 "Charset conversion failed."));
5273 case COMPOSE_QUEUE_ERROR_NO_ENCRYPTION_KEY:
5274 alertpanel_error(_("Could not queue message for sending:\n\n"
5275 "Couldn't get recipient encryption key."));
5278 /* unhandled error */
5279 debug_print("oops, unhandled compose_queue() return value %d\n",
5285 gint compose_send(Compose *compose)
5288 FolderItem *folder = NULL;
5289 ComposeQueueResult val = COMPOSE_QUEUE_ERROR_NO_MSG;
5290 gchar *msgpath = NULL;
5291 gboolean discard_window = FALSE;
5292 gchar *errstr = NULL;
5293 gchar *tmsgid = NULL;
5294 MainWindow *mainwin = mainwindow_get_mainwindow();
5295 gboolean queued_removed = FALSE;
5297 if (prefs_common.send_dialog_invisible
5298 || compose->batch == TRUE)
5299 discard_window = TRUE;
5301 compose_allow_user_actions (compose, FALSE);
5302 compose->sending = TRUE;
5304 if (compose_check_entries(compose, TRUE) == FALSE) {
5305 if (compose->batch) {
5306 gtk_widget_show_all(compose->window);
5312 val = compose_queue(compose, &msgnum, &folder, &msgpath, TRUE);
5314 if (val != COMPOSE_QUEUE_SUCCESS) {
5315 if (compose->batch) {
5316 gtk_widget_show_all(compose->window);
5319 _display_queue_error(val);
5324 tmsgid = compose->msgid ? g_strdup(compose->msgid) : NULL;
5325 if (discard_window) {
5326 compose->sending = FALSE;
5327 compose_close(compose);
5328 /* No more compose access in the normal codepath
5329 * after this point! */
5334 alertpanel_error(_("The message was queued but could not be "
5335 "sent.\nUse \"Send queued messages\" from "
5336 "the main window to retry."));
5337 if (!discard_window) {
5344 if (msgpath == NULL) {
5345 msgpath = folder_item_fetch_msg(folder, msgnum);
5346 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5349 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5350 claws_unlink(msgpath);
5353 if (!discard_window) {
5355 if (!queued_removed)
5356 folder_item_remove_msg(folder, msgnum);
5357 folder_item_scan(folder);
5359 /* make sure we delete that */
5360 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5362 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5363 folder_item_remove_msg(folder, tmp->msgnum);
5364 procmsg_msginfo_free(&tmp);
5371 if (!queued_removed)
5372 folder_item_remove_msg(folder, msgnum);
5373 folder_item_scan(folder);
5375 /* make sure we delete that */
5376 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5378 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5379 folder_item_remove_msg(folder, tmp->msgnum);
5380 procmsg_msginfo_free(&tmp);
5383 if (!discard_window) {
5384 compose->sending = FALSE;
5385 compose_allow_user_actions (compose, TRUE);
5386 compose_close(compose);
5390 alertpanel_error_log(_("%s\nUse \"Send queued messages\" from "
5391 "the main window to retry."), errstr);
5394 alertpanel_error_log(_("The message was queued but could not be "
5395 "sent.\nUse \"Send queued messages\" from "
5396 "the main window to retry."));
5398 if (!discard_window) {
5407 toolbar_main_set_sensitive(mainwin);
5408 main_window_set_menu_sensitive(mainwin);
5414 compose_allow_user_actions (compose, TRUE);
5415 compose->sending = FALSE;
5416 compose->modified = TRUE;
5417 toolbar_main_set_sensitive(mainwin);
5418 main_window_set_menu_sensitive(mainwin);
5423 static gboolean compose_use_attach(Compose *compose)
5425 GtkTreeModel *model = gtk_tree_view_get_model
5426 (GTK_TREE_VIEW(compose->attach_clist));
5427 return gtk_tree_model_iter_n_children(model, NULL) > 0;
5430 static gint compose_redirect_write_headers_from_headerlist(Compose *compose,
5433 gchar buf[BUFFSIZE];
5435 gboolean first_to_address;
5436 gboolean first_cc_address;
5438 ComposeHeaderEntry *headerentry;
5439 const gchar *headerentryname;
5440 const gchar *cc_hdr;
5441 const gchar *to_hdr;
5442 gboolean err = FALSE;
5444 debug_print("Writing redirect header\n");
5446 cc_hdr = prefs_common_translated_header_name("Cc:");
5447 to_hdr = prefs_common_translated_header_name("To:");
5449 first_to_address = TRUE;
5450 for (list = compose->header_list; list; list = list->next) {
5451 headerentry = ((ComposeHeaderEntry *)list->data);
5452 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5454 if (g_utf8_collate(headerentryname, to_hdr) == 0) {
5455 const gchar *entstr = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5456 Xstrdup_a(str, entstr, return -1);
5458 if (str[0] != '\0') {
5459 compose_convert_header
5460 (compose, buf, sizeof(buf), str,
5461 strlen("Resent-To") + 2, TRUE);
5463 if (first_to_address) {
5464 err |= (fprintf(fp, "Resent-To: ") < 0);
5465 first_to_address = FALSE;
5467 err |= (fprintf(fp, ",") < 0);
5469 err |= (fprintf(fp, "%s", buf) < 0);
5473 if (!first_to_address) {
5474 err |= (fprintf(fp, "\n") < 0);
5477 first_cc_address = TRUE;
5478 for (list = compose->header_list; list; list = list->next) {
5479 headerentry = ((ComposeHeaderEntry *)list->data);
5480 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5482 if (g_utf8_collate(headerentryname, cc_hdr) == 0) {
5483 const gchar *strg = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5484 Xstrdup_a(str, strg, return -1);
5486 if (str[0] != '\0') {
5487 compose_convert_header
5488 (compose, buf, sizeof(buf), str,
5489 strlen("Resent-Cc") + 2, TRUE);
5491 if (first_cc_address) {
5492 err |= (fprintf(fp, "Resent-Cc: ") < 0);
5493 first_cc_address = FALSE;
5495 err |= (fprintf(fp, ",") < 0);
5497 err |= (fprintf(fp, "%s", buf) < 0);
5501 if (!first_cc_address) {
5502 err |= (fprintf(fp, "\n") < 0);
5505 return (err ? -1:0);
5508 static gint compose_redirect_write_headers(Compose *compose, FILE *fp)
5510 gchar date[RFC822_DATE_BUFFSIZE];
5511 gchar buf[BUFFSIZE];
5513 const gchar *entstr;
5514 /* struct utsname utsbuf; */
5515 gboolean err = FALSE;
5517 cm_return_val_if_fail(fp != NULL, -1);
5518 cm_return_val_if_fail(compose->account != NULL, -1);
5519 cm_return_val_if_fail(compose->account->address != NULL, -1);
5522 if (prefs_common.hide_timezone)
5523 get_rfc822_date_hide_tz(date, sizeof(date));
5525 get_rfc822_date(date, sizeof(date));
5526 err |= (fprintf(fp, "Resent-Date: %s\n", date) < 0);
5529 if (compose->account->name && *compose->account->name) {
5530 compose_convert_header
5531 (compose, buf, sizeof(buf), compose->account->name,
5532 strlen("From: "), TRUE);
5533 err |= (fprintf(fp, "Resent-From: %s <%s>\n",
5534 buf, compose->account->address) < 0);
5536 err |= (fprintf(fp, "Resent-From: %s\n", compose->account->address) < 0);
5539 entstr = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5540 if (*entstr != '\0') {
5541 Xstrdup_a(str, entstr, return -1);
5544 compose_convert_header(compose, buf, sizeof(buf), str,
5545 strlen("Subject: "), FALSE);
5546 err |= (fprintf(fp, "Subject: %s\n", buf) < 0);
5550 /* Resent-Message-ID */
5551 if (compose->account->gen_msgid) {
5552 gchar *addr = prefs_account_generate_msgid(compose->account);
5553 err |= (fprintf(fp, "Resent-Message-ID: <%s>\n", addr) < 0);
5555 g_free(compose->msgid);
5556 compose->msgid = addr;
5558 compose->msgid = NULL;
5561 if (compose_redirect_write_headers_from_headerlist(compose, fp))
5564 /* separator between header and body */
5565 err |= (claws_fputs("\n", fp) == EOF);
5567 return (err ? -1:0);
5570 static gint compose_redirect_write_to_file(Compose *compose, FILE *fdest)
5575 gchar rewrite_buf[BUFFSIZE];
5577 gboolean skip = FALSE;
5578 gboolean err = FALSE;
5579 gchar *not_included[]={
5580 "Return-Path:", "Delivered-To:", "Received:",
5581 "Subject:", "X-UIDL:", "AF:",
5582 "NF:", "PS:", "SRH:",
5583 "SFN:", "DSR:", "MID:",
5584 "CFG:", "PT:", "S:",
5585 "RQ:", "SSV:", "NSV:",
5586 "SSH:", "R:", "MAID:",
5587 "NAID:", "RMID:", "FMID:",
5588 "SCF:", "RRCPT:", "NG:",
5589 "X-Claws-Privacy", "X-Claws-Sign:", "X-Claws-Encrypt",
5590 "X-Claws-End-Special-Headers:", "X-Claws-Account-Id:",
5591 "X-Sylpheed-Privacy", "X-Sylpheed-Sign:", "X-Sylpheed-Encrypt",
5592 "X-Sylpheed-End-Special-Headers:", "X-Sylpheed-Account-Id:",
5593 "X-Claws-Auto-Wrapping:", "X-Claws-Auto-Indent:",
5598 if ((fp = claws_fopen(compose->redirect_filename, "rb")) == NULL) {
5599 FILE_OP_ERROR(compose->redirect_filename, "claws_fopen");
5603 while ((ret = procheader_get_one_field_asis(&buf, fp)) != -1) {
5605 for (i = 0; not_included[i] != NULL; i++) {
5606 if (g_ascii_strncasecmp(buf, not_included[i],
5607 strlen(not_included[i])) == 0) {
5617 if (claws_fputs(buf, fdest) == -1) {
5623 if (!prefs_common.redirect_keep_from) {
5624 if (g_ascii_strncasecmp(buf, "From:",
5625 strlen("From:")) == 0) {
5626 err |= (claws_fputs(" (by way of ", fdest) == EOF);
5627 if (compose->account->name
5628 && *compose->account->name) {
5629 gchar buffer[BUFFSIZE];
5631 compose_convert_header
5632 (compose, buffer, sizeof(buffer),
5633 compose->account->name,
5636 err |= (fprintf(fdest, "%s <%s>",
5638 compose->account->address) < 0);
5640 err |= (fprintf(fdest, "%s",
5641 compose->account->address) < 0);
5642 err |= (claws_fputs(")", fdest) == EOF);
5648 if (claws_fputs("\n", fdest) == -1)
5655 if (compose_redirect_write_headers(compose, fdest))
5658 while ((len = claws_fread(rewrite_buf, sizeof(gchar), sizeof(rewrite_buf), fp)) > 0) {
5659 if (claws_fwrite(rewrite_buf, sizeof(gchar), len, fdest) != len)
5673 static gint compose_write_to_file(Compose *compose, FILE *fp, gint action, gboolean attach_parts)
5675 GtkTextBuffer *buffer;
5676 GtkTextIter start, end, tmp;
5677 gchar *chars, *tmp_enc_file, *content;
5679 const gchar *out_codeset;
5680 EncodingType encoding = ENC_UNKNOWN;
5681 MimeInfo *mimemsg, *mimetext;
5683 const gchar *src_codeset = CS_INTERNAL;
5684 gchar *from_addr = NULL;
5685 gchar *from_name = NULL;
5688 if (action == COMPOSE_WRITE_FOR_SEND) {
5689 attach_parts = TRUE;
5691 /* We're sending the message, generate a Message-ID
5693 if (compose->msgid == NULL &&
5694 compose->account->gen_msgid) {
5695 compose->msgid = prefs_account_generate_msgid(compose->account);
5699 /* create message MimeInfo */
5700 mimemsg = procmime_mimeinfo_new();
5701 mimemsg->type = MIMETYPE_MESSAGE;
5702 mimemsg->subtype = g_strdup("rfc822");
5703 mimemsg->content = MIMECONTENT_MEM;
5704 mimemsg->tmp = TRUE; /* must free content later */
5705 mimemsg->data.mem = compose_get_header(compose);
5707 /* Create text part MimeInfo */
5708 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5709 gtk_text_buffer_get_end_iter(buffer, &end);
5712 /* We make sure that there is a newline at the end. */
5713 if (action == COMPOSE_WRITE_FOR_SEND && gtk_text_iter_backward_char(&tmp)) {
5714 chars = gtk_text_buffer_get_text(buffer, &tmp, &end, FALSE);
5715 if (*chars != '\n') {
5716 gtk_text_buffer_insert(buffer, &end, "\n", 1);
5721 /* get all composed text */
5722 gtk_text_buffer_get_start_iter(buffer, &start);
5723 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5725 out_codeset = conv_get_charset_str(compose->out_encoding);
5727 if (!out_codeset && is_ascii_str(chars)) {
5728 out_codeset = CS_US_ASCII;
5729 } else if (prefs_common.outgoing_fallback_to_ascii &&
5730 is_ascii_str(chars)) {
5731 out_codeset = CS_US_ASCII;
5732 encoding = ENC_7BIT;
5736 gchar *test_conv_global_out = NULL;
5737 gchar *test_conv_reply = NULL;
5739 /* automatic mode. be automatic. */
5740 codeconv_set_strict(TRUE);
5742 out_codeset = conv_get_outgoing_charset_str();
5744 debug_print("trying to convert to %s\n", out_codeset);
5745 test_conv_global_out = conv_codeset_strdup(chars, src_codeset, out_codeset);
5748 if (!test_conv_global_out && compose->orig_charset
5749 && strcmp(compose->orig_charset, CS_US_ASCII)) {
5750 out_codeset = compose->orig_charset;
5751 debug_print("failure; trying to convert to %s\n", out_codeset);
5752 test_conv_reply = conv_codeset_strdup(chars, src_codeset, out_codeset);
5755 if (!test_conv_global_out && !test_conv_reply) {
5757 out_codeset = CS_INTERNAL;
5758 debug_print("failure; finally using %s\n", out_codeset);
5760 g_free(test_conv_global_out);
5761 g_free(test_conv_reply);
5762 codeconv_set_strict(FALSE);
5765 if (encoding == ENC_UNKNOWN) {
5766 if (prefs_common.encoding_method == CTE_BASE64)
5767 encoding = ENC_BASE64;
5768 else if (prefs_common.encoding_method == CTE_QUOTED_PRINTABLE)
5769 encoding = ENC_QUOTED_PRINTABLE;
5770 else if (prefs_common.encoding_method == CTE_8BIT)
5771 encoding = ENC_8BIT;
5773 encoding = procmime_get_encoding_for_charset(out_codeset);
5776 debug_print("src encoding = %s, out encoding = %s, transfer encoding = %s\n",
5777 src_codeset, out_codeset, procmime_get_encoding_str(encoding));
5779 if (action == COMPOSE_WRITE_FOR_SEND) {
5780 codeconv_set_strict(TRUE);
5781 buf = conv_codeset_strdup(chars, src_codeset, out_codeset);
5782 codeconv_set_strict(FALSE);
5787 msg = g_strdup_printf(_("Can't convert the character encoding of the message \n"
5788 "to the specified %s charset.\n"
5789 "Send it as %s?"), out_codeset, src_codeset);
5790 aval = alertpanel_full(_("Error"), msg, GTK_STOCK_CANCEL,
5791 _("_Send"), NULL, ALERTFOCUS_SECOND, FALSE,
5795 if (aval != G_ALERTALTERNATE) {
5797 return COMPOSE_QUEUE_ERROR_CHAR_CONVERSION;
5800 out_codeset = src_codeset;
5806 out_codeset = src_codeset;
5811 if (prefs_common.rewrite_first_from && (encoding == ENC_8BIT || encoding == ENC_7BIT)) {
5812 if (!strncmp(buf, "From ", sizeof("From ")-1) ||
5813 strstr(buf, "\nFrom ") != NULL) {
5814 encoding = ENC_QUOTED_PRINTABLE;
5818 mimetext = procmime_mimeinfo_new();
5819 mimetext->content = MIMECONTENT_MEM;
5820 mimetext->tmp = TRUE; /* must free content later */
5821 /* dup'ed because procmime_encode_content can turn it into a tmpfile
5822 * and free the data, which we need later. */
5823 mimetext->data.mem = g_strdup(buf);
5824 mimetext->type = MIMETYPE_TEXT;
5825 mimetext->subtype = g_strdup("plain");
5826 g_hash_table_insert(mimetext->typeparameters, g_strdup("charset"),
5827 g_strdup(out_codeset));
5829 /* protect trailing spaces when signing message */
5830 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5831 privacy_system_can_sign(compose->privacy_system)) {
5832 encoding = ENC_QUOTED_PRINTABLE;
5836 debug_print("main text: %Id bytes encoded as %s in %d\n",
5838 debug_print("main text: %zd bytes encoded as %s in %d\n",
5840 strlen(buf), out_codeset, encoding);
5842 /* check for line length limit */
5843 if (action == COMPOSE_WRITE_FOR_SEND &&
5844 encoding != ENC_QUOTED_PRINTABLE && encoding != ENC_BASE64 &&
5845 check_line_length(buf, 1000, &line) < 0) {
5848 msg = g_strdup_printf
5849 (_("Line %d exceeds the line length limit (998 bytes).\n"
5850 "The contents of the message might be broken on the way to the delivery.\n"
5852 "Send it anyway?"), line + 1);
5853 aval = alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, GTK_STOCK_OK, NULL,
5856 if (aval != G_ALERTALTERNATE) {
5858 return COMPOSE_QUEUE_ERROR_NO_MSG;
5862 if (encoding != ENC_UNKNOWN)
5863 procmime_encode_content(mimetext, encoding);
5865 /* append attachment parts */
5866 if (compose_use_attach(compose) && attach_parts) {
5867 MimeInfo *mimempart;
5868 gchar *boundary = NULL;
5869 mimempart = procmime_mimeinfo_new();
5870 mimempart->content = MIMECONTENT_EMPTY;
5871 mimempart->type = MIMETYPE_MULTIPART;
5872 mimempart->subtype = g_strdup("mixed");
5876 boundary = generate_mime_boundary(NULL);
5877 } while (strstr(buf, boundary) != NULL);
5879 g_hash_table_insert(mimempart->typeparameters, g_strdup("boundary"),
5882 mimetext->disposition = DISPOSITIONTYPE_INLINE;
5884 g_node_append(mimempart->node, mimetext->node);
5885 g_node_append(mimemsg->node, mimempart->node);
5887 if (compose_add_attachments(compose, mimempart) < 0)
5888 return COMPOSE_QUEUE_ERROR_NO_MSG;
5890 g_node_append(mimemsg->node, mimetext->node);
5894 if (strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) != 0) {
5895 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
5896 /* extract name and address */
5897 if (strstr(spec, " <") && strstr(spec, ">")) {
5898 from_addr = g_strdup(strrchr(spec, '<')+1);
5899 *(strrchr(from_addr, '>')) = '\0';
5900 from_name = g_strdup(spec);
5901 *(strrchr(from_name, '<')) = '\0';
5908 /* sign message if sending */
5909 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5910 privacy_system_can_sign(compose->privacy_system))
5911 if (!privacy_sign(compose->privacy_system, mimemsg,
5912 compose->account, from_addr)) {
5915 return COMPOSE_QUEUE_ERROR_SIGNING_FAILED;
5920 if (compose->use_encryption) {
5921 if (compose->encdata != NULL &&
5922 strcmp(compose->encdata, "_DONT_ENCRYPT_")) {
5924 /* First, write an unencrypted copy and save it to outbox, if
5925 * user wants that. */
5926 if (compose->account->save_encrypted_as_clear_text) {
5927 debug_print("saving sent message unencrypted...\n");
5928 FILE *tmpfp = get_tmpfile_in_dir(get_mime_tmp_dir(), &tmp_enc_file);
5930 claws_fclose(tmpfp);
5932 /* fp now points to a file with headers written,
5933 * let's make a copy. */
5935 content = file_read_stream_to_str(fp);
5937 str_write_to_file(content, tmp_enc_file, TRUE);
5940 /* Now write the unencrypted body. */
5941 if ((tmpfp = claws_fopen(tmp_enc_file, "a")) != NULL) {
5942 procmime_write_mimeinfo(mimemsg, tmpfp);
5943 claws_fclose(tmpfp);
5945 outbox = folder_find_item_from_identifier(compose_get_save_to(compose));
5947 outbox = folder_get_default_outbox();
5949 procmsg_save_to_outbox(outbox, tmp_enc_file, TRUE);
5950 claws_unlink(tmp_enc_file);
5952 g_warning("Can't open file '%s'", tmp_enc_file);
5955 g_warning("couldn't get tempfile");
5958 if (!privacy_encrypt(compose->privacy_system, mimemsg, compose->encdata)) {
5959 debug_print("Couldn't encrypt mime structure: %s.\n",
5960 privacy_get_error());
5961 return COMPOSE_QUEUE_ERROR_ENCRYPT_FAILED;
5966 procmime_write_mimeinfo(mimemsg, fp);
5968 procmime_mimeinfo_free_all(&mimemsg);
5973 static gint compose_write_body_to_file(Compose *compose, const gchar *file)
5975 GtkTextBuffer *buffer;
5976 GtkTextIter start, end;
5981 if ((fp = claws_fopen(file, "wb")) == NULL) {
5982 FILE_OP_ERROR(file, "claws_fopen");
5986 /* chmod for security */
5987 if (change_file_mode_rw(fp, file) < 0) {
5988 FILE_OP_ERROR(file, "chmod");
5989 g_warning("can't change file mode");
5992 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5993 gtk_text_buffer_get_start_iter(buffer, &start);
5994 gtk_text_buffer_get_end_iter(buffer, &end);
5995 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5997 chars = conv_codeset_strdup
5998 (tmp, CS_INTERNAL, conv_get_locale_charset_str());
6007 len = strlen(chars);
6008 if (claws_fwrite(chars, sizeof(gchar), len, fp) != len) {
6009 FILE_OP_ERROR(file, "claws_fwrite");
6018 if (claws_safe_fclose(fp) == EOF) {
6019 FILE_OP_ERROR(file, "claws_fclose");
6026 static gint compose_remove_reedit_target(Compose *compose, gboolean force)
6029 MsgInfo *msginfo = compose->targetinfo;
6031 cm_return_val_if_fail(compose->mode == COMPOSE_REEDIT, -1);
6032 if (!msginfo) return -1;
6034 if (!force && MSG_IS_LOCKED(msginfo->flags))
6037 item = msginfo->folder;
6038 cm_return_val_if_fail(item != NULL, -1);
6040 if (procmsg_msg_exist(msginfo) &&
6041 (folder_has_parent_of_type(item, F_QUEUE) ||
6042 folder_has_parent_of_type(item, F_DRAFT)
6043 || msginfo == compose->autosaved_draft)) {
6044 if (folder_item_remove_msg(item, msginfo->msgnum) < 0) {
6045 g_warning("can't remove the old message");
6048 debug_print("removed reedit target %d\n", msginfo->msgnum);
6055 static void compose_remove_draft(Compose *compose)
6058 MsgInfo *msginfo = compose->targetinfo;
6059 drafts = account_get_special_folder(compose->account, F_DRAFT);
6061 if (procmsg_msg_exist(msginfo)) {
6062 folder_item_remove_msg(drafts, msginfo->msgnum);
6067 ComposeQueueResult compose_queue(Compose *compose, gint *msgnum, FolderItem **item, gchar **msgpath,
6068 gboolean remove_reedit_target)
6070 return compose_queue_sub (compose, msgnum, item, msgpath, FALSE, remove_reedit_target);
6073 static gboolean compose_warn_encryption(Compose *compose)
6075 const gchar *warning = privacy_get_encrypt_warning(compose->privacy_system);
6076 AlertValue val = G_ALERTALTERNATE;
6078 if (warning == NULL)
6081 val = alertpanel_full(_("Encryption warning"), warning,
6082 GTK_STOCK_CANCEL, _("C_ontinue"), NULL, ALERTFOCUS_SECOND,
6083 TRUE, NULL, ALERT_WARNING);
6084 if (val & G_ALERTDISABLE) {
6085 val &= ~G_ALERTDISABLE;
6086 if (val == G_ALERTALTERNATE)
6087 privacy_inhibit_encrypt_warning(compose->privacy_system,
6091 if (val == G_ALERTALTERNATE) {
6098 static ComposeQueueResult compose_queue_sub(Compose *compose, gint *msgnum, FolderItem **item,
6099 gchar **msgpath, gboolean perform_checks,
6100 gboolean remove_reedit_target)
6107 PrefsAccount *mailac = NULL, *newsac = NULL;
6108 gboolean err = FALSE;
6110 debug_print("queueing message...\n");
6111 cm_return_val_if_fail(compose->account != NULL, -1);
6113 if (compose_check_entries(compose, perform_checks) == FALSE) {
6114 if (compose->batch) {
6115 gtk_widget_show_all(compose->window);
6117 return COMPOSE_QUEUE_ERROR_NO_MSG;
6120 if (!compose->to_list && !compose->newsgroup_list) {
6121 g_warning("can't get recipient list.");
6122 return COMPOSE_QUEUE_ERROR_NO_MSG;
6125 if (compose->to_list) {
6126 if (compose->account->protocol != A_NNTP)
6127 mailac = compose->account;
6128 else if (cur_account && cur_account->protocol != A_NNTP)
6129 mailac = cur_account;
6130 else if (!(mailac = compose_current_mail_account())) {
6131 alertpanel_error(_("No account for sending mails available!"));
6132 return COMPOSE_QUEUE_ERROR_NO_MSG;
6136 if (compose->newsgroup_list) {
6137 if (compose->account->protocol == A_NNTP)
6138 newsac = compose->account;
6140 alertpanel_error(_("Selected account isn't NNTP: Posting is impossible."));
6141 return COMPOSE_QUEUE_ERROR_NO_MSG;
6145 /* write queue header */
6146 tmp = g_strdup_printf("%s%cqueue.%p%08x", get_tmp_dir(),
6147 G_DIR_SEPARATOR, compose, (guint) rand());
6148 debug_print("queuing to %s\n", tmp);
6149 if ((fp = claws_fopen(tmp, "w+b")) == NULL) {
6150 FILE_OP_ERROR(tmp, "claws_fopen");
6152 return COMPOSE_QUEUE_ERROR_WITH_ERRNO;
6155 if (change_file_mode_rw(fp, tmp) < 0) {
6156 FILE_OP_ERROR(tmp, "chmod");
6157 g_warning("can't change file mode");
6160 /* queueing variables */
6161 err |= (fprintf(fp, "AF:\n") < 0);
6162 err |= (fprintf(fp, "NF:0\n") < 0);
6163 err |= (fprintf(fp, "PS:10\n") < 0);
6164 err |= (fprintf(fp, "SRH:1\n") < 0);
6165 err |= (fprintf(fp, "SFN:\n") < 0);
6166 err |= (fprintf(fp, "DSR:\n") < 0);
6168 err |= (fprintf(fp, "MID:<%s>\n", compose->msgid) < 0);
6170 err |= (fprintf(fp, "MID:\n") < 0);
6171 err |= (fprintf(fp, "CFG:\n") < 0);
6172 err |= (fprintf(fp, "PT:0\n") < 0);
6173 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
6174 err |= (fprintf(fp, "RQ:\n") < 0);
6176 err |= (fprintf(fp, "SSV:%s\n", mailac->smtp_server) < 0);
6178 err |= (fprintf(fp, "SSV:\n") < 0);
6180 err |= (fprintf(fp, "NSV:%s\n", newsac->nntp_server) < 0);
6182 err |= (fprintf(fp, "NSV:\n") < 0);
6183 err |= (fprintf(fp, "SSH:\n") < 0);
6184 /* write recipient list */
6185 if (compose->to_list) {
6186 err |= (fprintf(fp, "R:<%s>", (gchar *)compose->to_list->data) < 0);
6187 for (cur = compose->to_list->next; cur != NULL;
6189 err |= (fprintf(fp, ",<%s>", (gchar *)cur->data) < 0);
6190 err |= (fprintf(fp, "\n") < 0);
6192 /* write newsgroup list */
6193 if (compose->newsgroup_list) {
6194 err |= (fprintf(fp, "NG:") < 0);
6195 err |= (fprintf(fp, "%s", (gchar *)compose->newsgroup_list->data) < 0);
6196 for (cur = compose->newsgroup_list->next; cur != NULL; cur = cur->next)
6197 err |= (fprintf(fp, ",%s", (gchar *)cur->data) < 0);
6198 err |= (fprintf(fp, "\n") < 0);
6202 err |= (fprintf(fp, "MAID:%d\n", mailac->account_id) < 0);
6204 err |= (fprintf(fp, "NAID:%d\n", newsac->account_id) < 0);
6207 if (compose->privacy_system != NULL) {
6208 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
6209 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
6210 if (compose->use_encryption) {
6211 if (!compose_warn_encryption(compose)) {
6215 return COMPOSE_QUEUE_ERROR_NO_MSG;
6217 if (mailac && mailac->encrypt_to_self) {
6218 GSList *tmp_list = g_slist_copy(compose->to_list);
6219 tmp_list = g_slist_append(tmp_list, compose->account->address);
6220 compose->encdata = privacy_get_encrypt_data(compose->privacy_system, tmp_list);
6221 g_slist_free(tmp_list);
6223 compose->encdata = privacy_get_encrypt_data(compose->privacy_system, compose->to_list);
6225 if (compose->encdata != NULL) {
6226 if (strcmp(compose->encdata, "_DONT_ENCRYPT_")) {
6227 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
6228 err |= (fprintf(fp, "X-Claws-Encrypt-Data:%s\n",
6229 compose->encdata) < 0);
6230 } /* else we finally dont want to encrypt */
6232 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
6233 /* and if encdata was null, it means there's been a problem in
6236 g_warning("failed to write queue message");
6240 return COMPOSE_QUEUE_ERROR_NO_ENCRYPTION_KEY;
6245 /* Save copy folder */
6246 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
6247 gchar *savefolderid;
6249 savefolderid = compose_get_save_to(compose);
6250 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
6251 g_free(savefolderid);
6253 /* Save copy folder */
6254 if (compose->return_receipt) {
6255 err |= (fprintf(fp, "RRCPT:1\n") < 0);
6257 /* Message-ID of message replying to */
6258 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
6259 gchar *folderid = NULL;
6261 if (compose->replyinfo->folder)
6262 folderid = folder_item_get_identifier(compose->replyinfo->folder);
6263 if (folderid == NULL)
6264 folderid = g_strdup("NULL");
6266 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
6269 /* Message-ID of message forwarding to */
6270 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
6271 gchar *folderid = NULL;
6273 if (compose->fwdinfo->folder)
6274 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
6275 if (folderid == NULL)
6276 folderid = g_strdup("NULL");
6278 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
6282 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
6283 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
6285 /* end of headers */
6286 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
6288 if (compose->redirect_filename != NULL) {
6289 if (compose_redirect_write_to_file(compose, fp) < 0) {
6293 return COMPOSE_QUEUE_ERROR_WITH_ERRNO;
6297 if ((result = compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_SEND, TRUE)) < 0) {
6305 g_warning("failed to write queue message");
6309 return COMPOSE_QUEUE_ERROR_WITH_ERRNO;
6311 if (claws_safe_fclose(fp) == EOF) {
6312 FILE_OP_ERROR(tmp, "claws_fclose");
6315 return COMPOSE_QUEUE_ERROR_WITH_ERRNO;
6318 if (item && *item) {
6321 queue = account_get_special_folder(compose->account, F_QUEUE);
6324 g_warning("can't find queue folder");
6327 return COMPOSE_QUEUE_ERROR_NO_MSG;
6329 folder_item_scan(queue);
6330 if ((num = folder_item_add_msg(queue, tmp, NULL, FALSE)) < 0) {
6331 g_warning("can't queue the message");
6334 return COMPOSE_QUEUE_ERROR_NO_MSG;
6337 if (msgpath == NULL) {
6343 if (compose->mode == COMPOSE_REEDIT && compose->targetinfo) {
6344 MsgInfo *mi = folder_item_get_msginfo(queue, num);
6346 procmsg_msginfo_change_flags(mi,
6347 compose->targetinfo->flags.perm_flags,
6348 compose->targetinfo->flags.tmp_flags & ~(MSG_COPY | MSG_MOVE | MSG_MOVE_DONE),
6351 g_slist_free(mi->tags);
6352 mi->tags = g_slist_copy(compose->targetinfo->tags);
6353 procmsg_msginfo_free(&mi);
6357 if (compose->mode == COMPOSE_REEDIT && remove_reedit_target) {
6358 compose_remove_reedit_target(compose, FALSE);
6361 if ((msgnum != NULL) && (item != NULL)) {
6366 return COMPOSE_QUEUE_SUCCESS;
6369 static int compose_add_attachments(Compose *compose, MimeInfo *parent)
6372 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
6377 GError *error = NULL;
6382 gchar *type, *subtype;
6383 GtkTreeModel *model;
6386 model = gtk_tree_view_get_model(tree_view);
6388 if (!gtk_tree_model_get_iter_first(model, &iter))
6391 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
6393 if (!is_file_exist(ainfo->file)) {
6394 gchar *msg = g_strdup_printf(_("Attachment %s doesn't exist anymore. Ignore?"), ainfo->file);
6395 AlertValue val = alertpanel_full(_("Warning"), msg,
6396 _("Cancel sending"), _("Ignore attachment"), NULL,
6397 ALERTFOCUS_FIRST, FALSE, NULL, ALERT_WARNING);
6399 if (val == G_ALERTDEFAULT) {
6405 f = g_file_new_for_path(ainfo->file);
6406 fi = g_file_query_info(f, "standard::size",
6407 G_FILE_QUERY_INFO_NONE, NULL, &error);
6408 if (error != NULL) {
6409 g_warning(error->message);
6410 g_error_free(error);
6414 size = g_file_info_get_size(fi);
6418 if (g_stat(ainfo->file, &statbuf) < 0)
6420 size = statbuf.st_size;
6423 mimepart = procmime_mimeinfo_new();
6424 mimepart->content = MIMECONTENT_FILE;
6425 mimepart->data.filename = g_strdup(ainfo->file);
6426 mimepart->tmp = FALSE; /* or we destroy our attachment */
6427 mimepart->offset = 0;
6428 mimepart->length = size;
6430 type = g_strdup(ainfo->content_type);
6432 if (!strchr(type, '/')) {
6434 type = g_strdup("application/octet-stream");
6437 subtype = strchr(type, '/') + 1;
6438 *(subtype - 1) = '\0';
6439 mimepart->type = procmime_get_media_type(type);
6440 mimepart->subtype = g_strdup(subtype);
6443 if (mimepart->type == MIMETYPE_MESSAGE &&
6444 !g_ascii_strcasecmp(mimepart->subtype, "rfc822")) {
6445 mimepart->disposition = DISPOSITIONTYPE_INLINE;
6446 } else if (mimepart->type == MIMETYPE_TEXT) {
6447 if (!ainfo->name && g_ascii_strcasecmp(mimepart->subtype, "plain")) {
6448 /* Text parts with no name come from multipart/alternative
6449 * forwards. Make sure the recipient won't look at the
6450 * original HTML part by mistake. */
6451 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6452 ainfo->name = g_strdup_printf(_("Original %s part"),
6456 g_hash_table_insert(mimepart->typeparameters,
6457 g_strdup("charset"), g_strdup(ainfo->charset));
6459 if (ainfo->name && mimepart->type != MIMETYPE_MESSAGE) {
6460 if (mimepart->type == MIMETYPE_APPLICATION &&
6461 !g_strcmp0(mimepart->subtype, "octet-stream"))
6462 g_hash_table_insert(mimepart->typeparameters,
6463 g_strdup("name"), g_strdup(ainfo->name));
6464 g_hash_table_insert(mimepart->dispositionparameters,
6465 g_strdup("filename"), g_strdup(ainfo->name));
6466 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6469 if (mimepart->type == MIMETYPE_MESSAGE
6470 || mimepart->type == MIMETYPE_MULTIPART)
6471 ainfo->encoding = ENC_BINARY;
6472 else if (compose->use_signing || compose->fwdinfo != NULL) {
6473 if (ainfo->encoding == ENC_7BIT)
6474 ainfo->encoding = ENC_QUOTED_PRINTABLE;
6475 else if (ainfo->encoding == ENC_8BIT)
6476 ainfo->encoding = ENC_BASE64;
6479 procmime_encode_content(mimepart, ainfo->encoding);
6481 g_node_append(parent->node, mimepart->node);
6482 } while (gtk_tree_model_iter_next(model, &iter));
6487 static gchar *compose_quote_list_of_addresses(gchar *str)
6489 GSList *list = NULL, *item = NULL;
6490 gchar *qname = NULL, *faddr = NULL, *result = NULL;
6492 list = address_list_append_with_comments(list, str);
6493 for (item = list; item != NULL; item = item->next) {
6494 gchar *spec = item->data;
6495 gchar *endofname = strstr(spec, " <");
6496 if (endofname != NULL) {
6499 QUOTE_IF_REQUIRED_NORMAL(qname, spec, return NULL);
6500 qqname = escape_internal_quotes(qname, '"');
6502 if (*qname != *spec || qqname != qname) { /* has been quoted, compute new */
6503 gchar *addr = g_strdup(endofname);
6504 gchar *name = (qqname != qname)? qqname: g_strdup(qname);
6505 faddr = g_strconcat(name, addr, NULL);
6508 debug_print("new auto-quoted address: '%s'\n", faddr);
6512 result = g_strdup((faddr != NULL)? faddr: spec);
6514 result = g_strconcat(result,
6516 (faddr != NULL)? faddr: spec,
6519 if (faddr != NULL) {
6524 slist_free_strings_full(list);
6529 #define IS_IN_CUSTOM_HEADER(header) \
6530 (compose->account->add_customhdr && \
6531 custom_header_find(compose->account->customhdr_list, header) != NULL)
6533 static const gchar *compose_untranslated_header_name(gchar *header_name)
6535 /* return the untranslated header name, if header_name is a known
6536 header name, in either its translated or untranslated form, with
6537 or without trailing colon. otherwise, returns header_name. */
6538 gchar *translated_header_name;
6539 gchar *translated_header_name_wcolon;
6540 const gchar *untranslated_header_name;
6541 const gchar *untranslated_header_name_wcolon;
6544 cm_return_val_if_fail(header_name != NULL, NULL);
6546 for (i = 0; HEADERS[i].header_name != NULL; i++) {
6547 untranslated_header_name = HEADERS[i].header_name;
6548 untranslated_header_name_wcolon = HEADERS[i].header_name_w_colon;
6550 translated_header_name = gettext(untranslated_header_name);
6551 translated_header_name_wcolon = gettext(untranslated_header_name_wcolon);
6553 if (!strcmp(header_name, untranslated_header_name) ||
6554 !strcmp(header_name, translated_header_name)) {
6555 return untranslated_header_name;
6557 if (!strcmp(header_name, untranslated_header_name_wcolon) ||
6558 !strcmp(header_name, translated_header_name_wcolon)) {
6559 return untranslated_header_name_wcolon;
6563 debug_print("compose_untranslated_header_name: unknown header '%s'\n", header_name);
6567 static void compose_add_headerfield_from_headerlist(Compose *compose,
6569 const gchar *fieldname,
6570 const gchar *seperator)
6572 gchar *str, *fieldname_w_colon;
6573 gboolean add_field = FALSE;
6575 ComposeHeaderEntry *headerentry;
6576 const gchar *headerentryname;
6577 const gchar *trans_fieldname;
6580 if (IS_IN_CUSTOM_HEADER(fieldname))
6583 debug_print("Adding %s-fields\n", fieldname);
6585 fieldstr = g_string_sized_new(64);
6587 fieldname_w_colon = g_strconcat(fieldname, ":", NULL);
6588 trans_fieldname = prefs_common_translated_header_name(fieldname_w_colon);
6590 for (list = compose->header_list; list; list = list->next) {
6591 headerentry = ((ComposeHeaderEntry *)list->data);
6592 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
6594 if (!g_utf8_collate(trans_fieldname, headerentryname)) {
6595 gchar * ustr = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
6597 str = compose_quote_list_of_addresses(ustr);
6599 if (str != NULL && str[0] != '\0') {
6601 g_string_append(fieldstr, seperator);
6602 g_string_append(fieldstr, str);
6611 buf = g_new0(gchar, fieldstr->len * 4 + 256);
6612 compose_convert_header
6613 (compose, buf, fieldstr->len * 4 + 256, fieldstr->str,
6614 strlen(fieldname) + 2, TRUE);
6615 g_string_append_printf(header, "%s: %s\n", fieldname, buf);
6619 g_free(fieldname_w_colon);
6620 g_string_free(fieldstr, TRUE);
6625 static gchar *compose_get_manual_headers_info(Compose *compose)
6627 GString *sh_header = g_string_new(" ");
6629 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6631 for (list = compose->header_list; list; list = list->next) {
6632 ComposeHeaderEntry *headerentry;
6635 gchar *headername_wcolon;
6636 const gchar *headername_trans;
6638 gboolean standard_header = FALSE;
6640 headerentry = ((ComposeHeaderEntry *)list->data);
6642 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6644 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6649 if (!strstr(tmp, ":")) {
6650 headername_wcolon = g_strconcat(tmp, ":", NULL);
6651 headername = g_strdup(tmp);
6653 headername_wcolon = g_strdup(tmp);
6654 headername = g_strdup(strtok(tmp, ":"));
6658 string = std_headers;
6659 while (*string != NULL) {
6660 headername_trans = prefs_common_translated_header_name(*string);
6661 if (!strcmp(headername_trans, headername_wcolon))
6662 standard_header = TRUE;
6665 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
6666 g_string_append_printf(sh_header, "%s ", headername);
6668 g_free(headername_wcolon);
6670 g_string_truncate(sh_header, strlen(sh_header->str) - 1); /* remove last space */
6671 return g_string_free(sh_header, FALSE);
6674 static gchar *compose_get_header(Compose *compose)
6676 gchar date[RFC822_DATE_BUFFSIZE];
6677 gchar buf[BUFFSIZE];
6678 const gchar *entry_str;
6682 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6684 gchar *from_name = NULL, *from_address = NULL;
6687 cm_return_val_if_fail(compose->account != NULL, NULL);
6688 cm_return_val_if_fail(compose->account->address != NULL, NULL);
6690 header = g_string_sized_new(64);
6693 if (prefs_common.hide_timezone)
6694 get_rfc822_date_hide_tz(date, sizeof(date));
6696 get_rfc822_date(date, sizeof(date));
6697 g_string_append_printf(header, "Date: %s\n", date);
6701 if (compose->account->name && *compose->account->name) {
6703 QUOTE_IF_REQUIRED(buf, compose->account->name);
6704 tmp = g_strdup_printf("%s <%s>",
6705 buf, compose->account->address);
6707 tmp = g_strdup_printf("%s",
6708 compose->account->address);
6710 if (!strcmp(gtk_entry_get_text(GTK_ENTRY(compose->from_name)), tmp)
6711 || strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) == 0) {
6713 from_name = compose->account->name ? g_strdup(compose->account->name):NULL;
6714 from_address = g_strdup(compose->account->address);
6716 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
6717 /* extract name and address */
6718 if (strstr(spec, " <") && strstr(spec, ">")) {
6719 from_address = g_strdup(strrchr(spec, '<')+1);
6720 *(strrchr(from_address, '>')) = '\0';
6721 from_name = g_strdup(spec);
6722 *(strrchr(from_name, '<')) = '\0';
6725 from_address = g_strdup(spec);
6732 if (from_name && *from_name) {
6734 compose_convert_header
6735 (compose, buf, sizeof(buf), from_name,
6736 strlen("From: "), TRUE);
6737 QUOTE_IF_REQUIRED(name, buf);
6738 qname = escape_internal_quotes(name, '"');
6740 g_string_append_printf(header, "From: %s <%s>\n",
6741 qname, from_address);
6742 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To") &&
6743 compose->return_receipt) {
6744 compose_convert_header(compose, buf, sizeof(buf), from_name,
6745 strlen("Disposition-Notification-To: "),
6747 g_string_append_printf(header, "Disposition-Notification-To: %s <%s>\n", buf, from_address);
6752 g_string_append_printf(header, "From: %s\n", from_address);
6753 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To") &&
6754 compose->return_receipt)
6755 g_string_append_printf(header, "Disposition-Notification-To: %s\n", from_address);
6759 g_free(from_address);
6762 compose_add_headerfield_from_headerlist(compose, header, "To", ", ");
6765 compose_add_headerfield_from_headerlist(compose, header, "Newsgroups", ",");
6768 compose_add_headerfield_from_headerlist(compose, header, "Cc", ", ");
6772 * If this account is a NNTP account remove Bcc header from
6773 * message body since it otherwise will be publicly shown
6775 if (compose->account->protocol != A_NNTP)
6776 compose_add_headerfield_from_headerlist(compose, header, "Bcc", ", ");
6779 str = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
6781 if (*str != '\0' && !IS_IN_CUSTOM_HEADER("Subject")) {
6784 compose_convert_header(compose, buf, sizeof(buf), str,
6785 strlen("Subject: "), FALSE);
6786 g_string_append_printf(header, "Subject: %s\n", buf);
6792 if (compose->msgid != NULL && strlen(compose->msgid) > 0) {
6793 g_string_append_printf(header, "Message-ID: <%s>\n",
6797 if (compose->remove_references == FALSE) {
6799 if (compose->inreplyto && compose->to_list)
6800 g_string_append_printf(header, "In-Reply-To: <%s>\n", compose->inreplyto);
6803 if (compose->references)
6804 g_string_append_printf(header, "References: %s\n", compose->references);
6808 compose_add_headerfield_from_headerlist(compose, header, "Followup-To", ",");
6811 compose_add_headerfield_from_headerlist(compose, header, "Reply-To", ", ");
6814 if (compose->account->organization &&
6815 strlen(compose->account->organization) &&
6816 !IS_IN_CUSTOM_HEADER("Organization")) {
6817 compose_convert_header(compose, buf, sizeof(buf),
6818 compose->account->organization,
6819 strlen("Organization: "), FALSE);
6820 g_string_append_printf(header, "Organization: %s\n", buf);
6823 /* Program version and system info */
6824 if (compose->account->gen_xmailer &&
6825 g_slist_length(compose->to_list) && !IS_IN_CUSTOM_HEADER("X-Mailer") &&
6826 !compose->newsgroup_list) {
6827 g_string_append_printf(header, "X-Mailer: %s (GTK+ %d.%d.%d; %s)\n",
6829 gtk_major_version, gtk_minor_version, gtk_micro_version,
6832 if (compose->account->gen_xmailer &&
6833 g_slist_length(compose->newsgroup_list) && !IS_IN_CUSTOM_HEADER("X-Newsreader")) {
6834 g_string_append_printf(header, "X-Newsreader: %s (GTK+ %d.%d.%d; %s)\n",
6836 gtk_major_version, gtk_minor_version, gtk_micro_version,
6840 /* custom headers */
6841 if (compose->account->add_customhdr) {
6844 for (cur = compose->account->customhdr_list; cur != NULL;
6846 CustomHeader *chdr = (CustomHeader *)cur->data;
6848 if (custom_header_is_allowed(chdr->name)
6849 && chdr->value != NULL
6850 && *(chdr->value) != '\0') {
6851 compose_convert_header
6852 (compose, buf, sizeof(buf),
6854 strlen(chdr->name) + 2, FALSE);
6855 g_string_append_printf(header, "%s: %s\n", chdr->name, buf);
6860 /* Automatic Faces and X-Faces */
6861 if (get_account_xface (buf, sizeof(buf), compose->account->account_name) == 0) {
6862 g_string_append_printf(header, "X-Face: %s\n", buf);
6864 else if (get_default_xface (buf, sizeof(buf)) == 0) {
6865 g_string_append_printf(header, "X-Face: %s\n", buf);
6867 if (get_account_face (buf, sizeof(buf), compose->account->account_name) == 0) {
6868 g_string_append_printf(header, "Face: %s\n", buf);
6870 else if (get_default_face (buf, sizeof(buf)) == 0) {
6871 g_string_append_printf(header, "Face: %s\n", buf);
6875 switch (compose->priority) {
6876 case PRIORITY_HIGHEST: g_string_append_printf(header, "Importance: high\n"
6877 "X-Priority: 1 (Highest)\n");
6879 case PRIORITY_HIGH: g_string_append_printf(header, "Importance: high\n"
6880 "X-Priority: 2 (High)\n");
6882 case PRIORITY_NORMAL: break;
6883 case PRIORITY_LOW: g_string_append_printf(header, "Importance: low\n"
6884 "X-Priority: 4 (Low)\n");
6886 case PRIORITY_LOWEST: g_string_append_printf(header, "Importance: low\n"
6887 "X-Priority: 5 (Lowest)\n");
6889 default: debug_print("compose: priority unknown : %d\n",
6893 /* get special headers */
6894 for (list = compose->header_list; list; list = list->next) {
6895 ComposeHeaderEntry *headerentry;
6898 gchar *headername_wcolon;
6899 const gchar *headername_trans;
6902 gboolean standard_header = FALSE;
6904 headerentry = ((ComposeHeaderEntry *)list->data);
6906 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6908 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6913 if (!strstr(tmp, ":")) {
6914 headername_wcolon = g_strconcat(tmp, ":", NULL);
6915 headername = g_strdup(tmp);
6917 headername_wcolon = g_strdup(tmp);
6918 headername = g_strdup(strtok(tmp, ":"));
6922 entry_str = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
6923 Xstrdup_a(headervalue, entry_str, return NULL);
6924 subst_char(headervalue, '\r', ' ');
6925 subst_char(headervalue, '\n', ' ');
6926 g_strstrip(headervalue);
6927 if (*headervalue != '\0') {
6928 string = std_headers;
6929 while (*string != NULL && !standard_header) {
6930 headername_trans = prefs_common_translated_header_name(*string);
6931 /* support mixed translated and untranslated headers */
6932 if (!strcmp(headername_trans, headername_wcolon) || !strcmp(*string, headername_wcolon))
6933 standard_header = TRUE;
6936 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername)) {
6937 /* store untranslated header name */
6938 g_string_append_printf(header, "%s %s\n",
6939 compose_untranslated_header_name(headername_wcolon), headervalue);
6943 g_free(headername_wcolon);
6947 g_string_free(header, FALSE);
6952 #undef IS_IN_CUSTOM_HEADER
6954 static void compose_convert_header(Compose *compose, gchar *dest, gint len, gchar *src,
6955 gint header_len, gboolean addr_field)
6957 gchar *tmpstr = NULL;
6958 const gchar *out_codeset = NULL;
6960 cm_return_if_fail(src != NULL);
6961 cm_return_if_fail(dest != NULL);
6963 if (len < 1) return;
6965 tmpstr = g_strdup(src);
6967 subst_char(tmpstr, '\n', ' ');
6968 subst_char(tmpstr, '\r', ' ');
6971 if (!g_utf8_validate(tmpstr, -1, NULL)) {
6972 gchar *mybuf = g_malloc(strlen(tmpstr)*2 +1);
6973 conv_localetodisp(mybuf, strlen(tmpstr)*2 +1, tmpstr);
6978 codeconv_set_strict(TRUE);
6979 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6980 conv_get_charset_str(compose->out_encoding));
6981 codeconv_set_strict(FALSE);
6983 if (!dest || *dest == '\0') {
6984 gchar *test_conv_global_out = NULL;
6985 gchar *test_conv_reply = NULL;
6987 /* automatic mode. be automatic. */
6988 codeconv_set_strict(TRUE);
6990 out_codeset = conv_get_outgoing_charset_str();
6992 debug_print("trying to convert to %s\n", out_codeset);
6993 test_conv_global_out = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6996 if (!test_conv_global_out && compose->orig_charset
6997 && strcmp(compose->orig_charset, CS_US_ASCII)) {
6998 out_codeset = compose->orig_charset;
6999 debug_print("failure; trying to convert to %s\n", out_codeset);
7000 test_conv_reply = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
7003 if (!test_conv_global_out && !test_conv_reply) {
7005 out_codeset = CS_INTERNAL;
7006 debug_print("finally using %s\n", out_codeset);
7008 g_free(test_conv_global_out);
7009 g_free(test_conv_reply);
7010 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
7012 codeconv_set_strict(FALSE);
7017 static void compose_add_to_addressbook_cb(GtkMenuItem *menuitem, gpointer user_data)
7021 cm_return_if_fail(user_data != NULL);
7023 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(user_data)));
7024 g_strstrip(address);
7025 if (*address != '\0') {
7026 gchar *name = procheader_get_fromname(address);
7027 extract_address(address);
7028 #ifndef USE_ALT_ADDRBOOK
7029 addressbook_add_contact(name, address, NULL, NULL);
7031 debug_print("%s: %s\n", name, address);
7032 if (addressadd_selection(name, address, NULL, NULL)) {
7033 debug_print( "addressbook_add_contact - added\n" );
7040 static void compose_entry_popup_extend(GtkEntry *entry, GtkMenu *menu, gpointer user_data)
7042 GtkWidget *menuitem;
7045 cm_return_if_fail(menu != NULL);
7046 cm_return_if_fail(GTK_IS_MENU_SHELL(menu));
7048 menuitem = gtk_separator_menu_item_new();
7049 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
7050 gtk_widget_show(menuitem);
7052 menuitem = gtk_menu_item_new_with_mnemonic(_("Add to address _book"));
7053 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
7055 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
7056 g_strstrip(address);
7057 if (*address == '\0') {
7058 gtk_widget_set_sensitive(GTK_WIDGET(menuitem), FALSE);
7061 g_signal_connect(G_OBJECT(menuitem), "activate",
7062 G_CALLBACK(compose_add_to_addressbook_cb), entry);
7063 gtk_widget_show(menuitem);
7066 void compose_add_extra_header(gchar *header, GtkListStore *model)
7069 if (strcmp(header, "")) {
7070 COMBOBOX_ADD(model, header, COMPOSE_TO);
7074 void compose_add_extra_header_entries(GtkListStore *model)
7078 gchar buf[BUFFSIZE];
7081 if (extra_headers == NULL) {
7082 exhrc = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, "extraheaderrc", NULL);
7083 if ((exh = claws_fopen(exhrc, "rb")) == NULL) {
7084 debug_print("extra headers file not found\n");
7085 goto extra_headers_done;
7087 while (claws_fgets(buf, BUFFSIZE, exh) != NULL) {
7088 lastc = strlen(buf) - 1; /* remove trailing control chars */
7089 while (lastc >= 0 && buf[lastc] != ':')
7090 buf[lastc--] = '\0';
7091 if (lastc > 0 && buf[0] != '#' && buf[lastc] == ':') {
7092 buf[lastc] = '\0'; /* remove trailing : for comparison */
7093 if (custom_header_is_allowed(buf)) {
7095 extra_headers = g_slist_prepend(extra_headers, g_strdup(buf));
7098 g_message("disallowed extra header line: %s\n", buf);
7102 g_message("invalid extra header line: %s\n", buf);
7108 extra_headers = g_slist_prepend(extra_headers, g_strdup("")); /* end of list */
7109 extra_headers = g_slist_reverse(extra_headers);
7111 g_slist_foreach(extra_headers, (GFunc)compose_add_extra_header, (gpointer)model);
7115 static void _ldap_srv_func(gpointer data, gpointer user_data)
7117 LdapServer *server = (LdapServer *)data;
7118 gboolean *enable = (gboolean *)user_data;
7120 debug_print("%s server '%s'\n", (*enable == TRUE ? "enabling" : "disabling"), server->control->hostName);
7121 server->searchFlag = *enable;
7125 static void compose_create_header_entry(Compose *compose)
7127 gchar *headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
7134 const gchar *header = NULL;
7135 ComposeHeaderEntry *headerentry;
7136 gboolean standard_header = FALSE;
7137 GtkListStore *model;
7140 headerentry = g_new0(ComposeHeaderEntry, 1);
7142 /* Combo box model */
7143 model = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_BOOLEAN);
7144 COMBOBOX_ADD(model, prefs_common_translated_header_name("To:"),
7146 COMBOBOX_ADD(model, prefs_common_translated_header_name("Cc:"),
7148 COMBOBOX_ADD(model, prefs_common_translated_header_name("Bcc:"),
7150 COMBOBOX_ADD(model, prefs_common_translated_header_name("Newsgroups:"),
7151 COMPOSE_NEWSGROUPS);
7152 COMBOBOX_ADD(model, prefs_common_translated_header_name("Reply-To:"),
7154 COMBOBOX_ADD(model, prefs_common_translated_header_name("Followup-To:"),
7155 COMPOSE_FOLLOWUPTO);
7156 compose_add_extra_header_entries(model);
7159 combo = gtk_combo_box_new_with_model_and_entry(GTK_TREE_MODEL(model));
7160 GtkCellRenderer *cell = gtk_cell_renderer_text_new();
7161 gtk_cell_renderer_set_alignment(cell, 0.0, 0.5);
7162 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), cell, TRUE);
7163 gtk_combo_box_set_entry_text_column(GTK_COMBO_BOX(combo), 0);
7164 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
7165 g_signal_connect(G_OBJECT(gtk_bin_get_child(GTK_BIN(combo))), "grab_focus",
7166 G_CALLBACK(compose_grab_focus_cb), compose);
7167 gtk_widget_show(combo);
7169 /* Putting only the combobox child into focus chain of its parent causes
7170 * the parent to be skipped when changing focus via Tab or Shift+Tab.
7171 * This eliminates need to pres Tab twice in order to really get from the
7172 * combobox to next widget. */
7174 l = g_list_prepend(l, gtk_bin_get_child(GTK_BIN(combo)));
7175 gtk_container_set_focus_chain(GTK_CONTAINER(combo), l);
7178 gtk_table_attach(GTK_TABLE(compose->header_table), combo, 0, 1,
7179 compose->header_nextrow, compose->header_nextrow+1,
7180 GTK_SHRINK, GTK_FILL, 0, 0);
7181 if (compose->header_last && (compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN)) {
7182 const gchar *last_header_entry = gtk_entry_get_text(
7183 GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
7185 while (*string != NULL) {
7186 if (!strcmp(prefs_common_translated_header_name(*string), last_header_entry))
7187 standard_header = TRUE;
7190 if (standard_header)
7191 header = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
7193 if (!compose->header_last || !standard_header) {
7194 switch(compose->account->protocol) {
7196 header = prefs_common_translated_header_name("Newsgroups:");
7199 header = prefs_common_translated_header_name("To:");
7204 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((combo)))), header);
7206 gtk_editable_set_editable(
7207 GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((combo)))),
7208 prefs_common.type_any_header);
7210 g_signal_connect_after(G_OBJECT(gtk_bin_get_child(GTK_BIN((combo)))), "grab_focus",
7211 G_CALLBACK(compose_grab_focus_cb), compose);
7213 /* Entry field with cleanup button */
7214 button = gtk_button_new();
7215 gtk_button_set_image(GTK_BUTTON(button),
7216 gtk_image_new_from_stock(GTK_STOCK_CLEAR, GTK_ICON_SIZE_MENU));
7217 gtk_widget_show(button);
7218 CLAWS_SET_TIP(button,
7219 _("Delete entry contents"));
7220 entry = gtk_entry_new();
7221 gtk_widget_show(entry);
7222 CLAWS_SET_TIP(entry,
7223 _("Use <tab> to autocomplete from addressbook"));
7224 hbox = gtk_hbox_new (FALSE, 0);
7225 gtk_widget_show(hbox);
7226 gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, TRUE, 0);
7227 gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
7228 gtk_table_attach(GTK_TABLE(compose->header_table), hbox, 1, 2,
7229 compose->header_nextrow, compose->header_nextrow+1,
7230 GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
7232 g_signal_connect(G_OBJECT(entry), "key-press-event",
7233 G_CALLBACK(compose_headerentry_key_press_event_cb),
7235 g_signal_connect(G_OBJECT(entry), "changed",
7236 G_CALLBACK(compose_headerentry_changed_cb),
7238 g_signal_connect_after(G_OBJECT(entry), "grab_focus",
7239 G_CALLBACK(compose_grab_focus_cb), compose);
7241 g_signal_connect(G_OBJECT(button), "clicked",
7242 G_CALLBACK(compose_headerentry_button_clicked_cb),
7246 gtk_drag_dest_set(entry, GTK_DEST_DEFAULT_ALL, compose_mime_types,
7247 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7248 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7249 g_signal_connect(G_OBJECT(entry), "drag_data_received",
7250 G_CALLBACK(compose_header_drag_received_cb),
7252 g_signal_connect(G_OBJECT(entry), "drag-drop",
7253 G_CALLBACK(compose_drag_drop),
7255 g_signal_connect(G_OBJECT(entry), "populate-popup",
7256 G_CALLBACK(compose_entry_popup_extend),
7260 #ifndef PASSWORD_CRYPTO_OLD
7261 GSList *pwd_servers = addrindex_get_password_protected_ldap_servers();
7262 if (pwd_servers != NULL && master_passphrase() == NULL) {
7263 gboolean enable = FALSE;
7264 debug_print("Master passphrase not available, disabling password-protected LDAP servers for this compose window.\n");
7265 /* Temporarily disable password-protected LDAP servers,
7266 * because user did not provide a master passphrase.
7267 * We can safely enable searchFlag on all servers in this list
7268 * later, since addrindex_get_password_protected_ldap_servers()
7269 * includes servers which have it enabled initially. */
7270 g_slist_foreach(pwd_servers, _ldap_srv_func, &enable);
7271 compose->passworded_ldap_servers = pwd_servers;
7273 #endif /* PASSWORD_CRYPTO_OLD */
7274 #endif /* USE_LDAP */
7276 address_completion_register_entry(GTK_ENTRY(entry), TRUE);
7278 headerentry->compose = compose;
7279 headerentry->combo = combo;
7280 headerentry->entry = entry;
7281 headerentry->button = button;
7282 headerentry->hbox = hbox;
7283 headerentry->headernum = compose->header_nextrow;
7284 headerentry->type = PREF_NONE;
7286 compose->header_nextrow++;
7287 compose->header_last = headerentry;
7288 compose->header_list =
7289 g_slist_append(compose->header_list,
7293 static void compose_add_header_entry(Compose *compose, const gchar *header,
7294 gchar *text, ComposePrefType pref_type)
7296 ComposeHeaderEntry *last_header = compose->header_last;
7297 gchar *tmp = g_strdup(text), *email;
7298 gboolean replyto_hdr;
7300 replyto_hdr = (!strcasecmp(header,
7301 prefs_common_translated_header_name("Reply-To:")) ||
7303 prefs_common_translated_header_name("Followup-To:")) ||
7305 prefs_common_translated_header_name("In-Reply-To:")));
7307 extract_address(tmp);
7308 email = g_utf8_strdown(tmp, -1);
7310 if (replyto_hdr == FALSE &&
7311 g_hash_table_lookup(compose->email_hashtable, email) != NULL)
7313 debug_print("Ignoring duplicate address - %s %s, pref_type: %d\n",
7314 header, text, (gint) pref_type);
7320 if (!strcasecmp(header, prefs_common_translated_header_name("In-Reply-To:")))
7321 gtk_entry_set_text(GTK_ENTRY(
7322 gtk_bin_get_child(GTK_BIN(last_header->combo))), header);
7324 combobox_select_by_text(GTK_COMBO_BOX(last_header->combo), header);
7325 gtk_entry_set_text(GTK_ENTRY(last_header->entry), text);
7326 last_header->type = pref_type;
7328 if (replyto_hdr == FALSE)
7329 g_hash_table_insert(compose->email_hashtable, email,
7330 GUINT_TO_POINTER(1));
7337 static void compose_destroy_headerentry(Compose *compose,
7338 ComposeHeaderEntry *headerentry)
7340 gchar *text = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
7343 extract_address(text);
7344 email = g_utf8_strdown(text, -1);
7345 g_hash_table_remove(compose->email_hashtable, email);
7349 gtk_widget_destroy(headerentry->combo);
7350 gtk_widget_destroy(headerentry->entry);
7351 gtk_widget_destroy(headerentry->button);
7352 gtk_widget_destroy(headerentry->hbox);
7353 g_free(headerentry);
7356 static void compose_remove_header_entries(Compose *compose)
7359 for (list = compose->header_list; list; list = list->next)
7360 compose_destroy_headerentry(compose, (ComposeHeaderEntry *)list->data);
7362 compose->header_last = NULL;
7363 g_slist_free(compose->header_list);
7364 compose->header_list = NULL;
7365 compose->header_nextrow = 1;
7366 compose_create_header_entry(compose);
7369 static GtkWidget *compose_create_header(Compose *compose)
7371 GtkWidget *from_optmenu_hbox;
7372 GtkWidget *header_table_main;
7373 GtkWidget *header_scrolledwin;
7374 GtkWidget *header_table;
7376 /* parent with account selection and from header */
7377 header_table_main = gtk_table_new(2, 2, FALSE);
7378 gtk_widget_show(header_table_main);
7379 gtk_container_set_border_width(GTK_CONTAINER(header_table_main), BORDER_WIDTH);
7381 from_optmenu_hbox = compose_account_option_menu_create(compose);
7382 gtk_table_attach(GTK_TABLE(header_table_main), from_optmenu_hbox,
7383 0, 2, 0, 1, GTK_EXPAND | GTK_FILL, GTK_SHRINK, 0, 0);
7385 /* child with header labels and entries */
7386 header_scrolledwin = gtk_scrolled_window_new(NULL, NULL);
7387 gtk_widget_show(header_scrolledwin);
7388 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
7390 header_table = gtk_table_new(2, 2, FALSE);
7391 gtk_widget_show(header_table);
7392 gtk_container_set_border_width(GTK_CONTAINER(header_table), 0);
7393 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin), header_table);
7394 gtk_container_set_focus_vadjustment(GTK_CONTAINER(header_table),
7395 gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(header_scrolledwin)));
7396 gtk_viewport_set_shadow_type(GTK_VIEWPORT(gtk_bin_get_child(GTK_BIN(header_scrolledwin))), GTK_SHADOW_NONE);
7398 gtk_table_attach(GTK_TABLE(header_table_main), header_scrolledwin,
7399 0, 2, 1, 2, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 2);
7401 compose->header_table = header_table;
7402 compose->header_list = NULL;
7403 compose->header_nextrow = 0;
7405 compose_create_header_entry(compose);
7407 compose->table = NULL;
7409 return header_table_main;
7412 static gboolean popup_attach_button_pressed(GtkWidget *widget, gpointer data)
7414 Compose *compose = (Compose *)data;
7415 GdkEventButton event;
7418 event.time = gtk_get_current_event_time();
7420 return attach_button_pressed(compose->attach_clist, &event, compose);
7423 static GtkWidget *compose_create_attach(Compose *compose)
7425 GtkWidget *attach_scrwin;
7426 GtkWidget *attach_clist;
7428 GtkListStore *store;
7429 GtkCellRenderer *renderer;
7430 GtkTreeViewColumn *column;
7431 GtkTreeSelection *selection;
7433 /* attachment list */
7434 attach_scrwin = gtk_scrolled_window_new(NULL, NULL);
7435 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(attach_scrwin),
7436 GTK_POLICY_AUTOMATIC,
7437 GTK_POLICY_AUTOMATIC);
7438 gtk_widget_set_size_request(attach_scrwin, -1, 80);
7440 store = gtk_list_store_new(N_ATTACH_COLS,
7446 G_TYPE_AUTO_POINTER,
7448 attach_clist = GTK_WIDGET(gtk_tree_view_new_with_model
7449 (GTK_TREE_MODEL(store)));
7450 gtk_container_add(GTK_CONTAINER(attach_scrwin), attach_clist);
7451 g_object_unref(store);
7453 renderer = gtk_cell_renderer_text_new();
7454 column = gtk_tree_view_column_new_with_attributes
7455 (_("Mime type"), renderer, "text",
7456 COL_MIMETYPE, NULL);
7457 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7459 renderer = gtk_cell_renderer_text_new();
7460 column = gtk_tree_view_column_new_with_attributes
7461 (_("Size"), renderer, "text",
7463 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7465 renderer = gtk_cell_renderer_text_new();
7466 column = gtk_tree_view_column_new_with_attributes
7467 (_("Name"), renderer, "text",
7469 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7471 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(attach_clist),
7472 prefs_common.use_stripes_everywhere);
7473 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(attach_clist));
7474 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
7476 g_signal_connect(G_OBJECT(attach_clist), "row_activated",
7477 G_CALLBACK(attach_selected), compose);
7478 g_signal_connect(G_OBJECT(attach_clist), "button_press_event",
7479 G_CALLBACK(attach_button_pressed), compose);
7480 g_signal_connect(G_OBJECT(attach_clist), "popup-menu",
7481 G_CALLBACK(popup_attach_button_pressed), compose);
7482 g_signal_connect(G_OBJECT(attach_clist), "key_press_event",
7483 G_CALLBACK(attach_key_pressed), compose);
7486 gtk_drag_dest_set(attach_clist,
7487 GTK_DEST_DEFAULT_ALL, compose_mime_types,
7488 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7489 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7490 g_signal_connect(G_OBJECT(attach_clist), "drag_data_received",
7491 G_CALLBACK(compose_attach_drag_received_cb),
7493 g_signal_connect(G_OBJECT(attach_clist), "drag-drop",
7494 G_CALLBACK(compose_drag_drop),
7497 compose->attach_scrwin = attach_scrwin;
7498 compose->attach_clist = attach_clist;
7500 return attach_scrwin;
7503 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose);
7505 static GtkWidget *compose_create_others(Compose *compose)
7508 GtkWidget *savemsg_checkbtn;
7509 GtkWidget *savemsg_combo;
7510 GtkWidget *savemsg_select;
7513 gchar *folderidentifier;
7515 /* Table for settings */
7516 table = gtk_table_new(3, 1, FALSE);
7517 gtk_container_set_border_width(GTK_CONTAINER(table), BORDER_WIDTH);
7518 gtk_widget_show(table);
7519 gtk_table_set_row_spacings(GTK_TABLE(table), VSPACING_NARROW);
7522 /* Save Message to folder */
7523 savemsg_checkbtn = gtk_check_button_new_with_label(_("Save Message to "));
7524 gtk_widget_show(savemsg_checkbtn);
7525 gtk_table_attach(GTK_TABLE(table), savemsg_checkbtn, 0, 1, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7526 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7527 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), prefs_common.savemsg);
7530 savemsg_combo = gtk_combo_box_text_new_with_entry();
7531 compose->savemsg_checkbtn = savemsg_checkbtn;
7532 compose->savemsg_combo = savemsg_combo;
7533 gtk_widget_show(savemsg_combo);
7535 if (prefs_common.compose_save_to_history)
7536 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(savemsg_combo),
7537 prefs_common.compose_save_to_history);
7538 gtk_table_attach(GTK_TABLE(table), savemsg_combo, 1, 2, rowcount, rowcount + 1, GTK_FILL|GTK_EXPAND, GTK_SHRINK, 0, 0);
7539 gtk_widget_set_sensitive(GTK_WIDGET(savemsg_combo), prefs_common.savemsg);
7540 g_signal_connect_after(G_OBJECT(savemsg_combo), "grab_focus",
7541 G_CALLBACK(compose_grab_focus_cb), compose);
7542 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7543 if (compose->account->set_sent_folder || prefs_common.savemsg)
7544 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), TRUE);
7546 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), FALSE);
7547 gtk_widget_set_sensitive(GTK_WIDGET(savemsg_combo), TRUE);
7548 folderidentifier = folder_item_get_identifier(account_get_special_folder
7549 (compose->account, F_OUTBOX));
7550 compose_set_save_to(compose, folderidentifier);
7551 g_free(folderidentifier);
7554 savemsg_select = gtkut_get_browse_file_btn(_("_Browse"));
7555 gtk_widget_show(savemsg_select);
7556 gtk_table_attach(GTK_TABLE(table), savemsg_select, 2, 3, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7557 g_signal_connect(G_OBJECT(savemsg_select), "clicked",
7558 G_CALLBACK(compose_savemsg_select_cb),
7564 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose)
7569 dest = foldersel_folder_sel(NULL, FOLDER_SEL_COPY, NULL, FALSE,
7570 _("Select folder to save message to"));
7573 path = folder_item_get_identifier(dest);
7575 compose_set_save_to(compose, path);
7579 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry, gboolean wrap,
7580 GdkAtom clip, GtkTextIter *insert_place);
7583 static gboolean text_clicked(GtkWidget *text, GdkEventButton *event,
7587 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
7589 if (event->button == 3) {
7591 GtkTextIter sel_start, sel_end;
7592 gboolean stuff_selected;
7594 /* move the cursor to allow GtkAspell to check the word
7595 * under the mouse */
7596 if (event->x && event->y) {
7597 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7598 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7600 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7603 GtkTextMark *mark = gtk_text_buffer_get_insert(buffer);
7604 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7607 stuff_selected = gtk_text_buffer_get_selection_bounds(
7609 &sel_start, &sel_end);
7611 gtk_text_buffer_place_cursor (buffer, &iter);
7612 /* reselect stuff */
7614 && gtk_text_iter_in_range(&iter, &sel_start, &sel_end)) {
7615 gtk_text_buffer_select_range(buffer,
7616 &sel_start, &sel_end);
7618 return FALSE; /* pass the event so that the right-click goes through */
7621 if (event->button == 2) {
7626 /* get the middle-click position to paste at the correct place */
7627 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7628 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7630 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7633 entry_paste_clipboard(compose, text,
7634 prefs_common.linewrap_pastes,
7635 GDK_SELECTION_PRIMARY, &iter);
7643 static void compose_spell_menu_changed(void *data)
7645 Compose *compose = (Compose *)data;
7647 GtkWidget *menuitem;
7648 GtkWidget *parent_item;
7649 GtkMenu *menu = GTK_MENU(gtk_menu_new());
7652 if (compose->gtkaspell == NULL)
7655 parent_item = gtk_ui_manager_get_widget(compose->ui_manager,
7656 "/Menu/Spelling/Options");
7658 /* setting the submenu removes /Spelling/Options from the factory
7659 * so we need to save it */
7661 if (parent_item == NULL) {
7662 parent_item = compose->aspell_options_menu;
7663 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), NULL);
7665 compose->aspell_options_menu = parent_item;
7667 spell_menu = gtkaspell_make_config_menu(compose->gtkaspell);
7669 spell_menu = g_slist_reverse(spell_menu);
7670 for (items = spell_menu;
7671 items; items = items->next) {
7672 menuitem = GTK_WIDGET(GTK_MENU_ITEM(items->data));
7673 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), GTK_WIDGET(menuitem));
7674 gtk_widget_show(GTK_WIDGET(menuitem));
7676 g_slist_free(spell_menu);
7678 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), GTK_WIDGET(menu));
7679 gtk_widget_show(parent_item);
7682 static void compose_dict_changed(void *data)
7684 Compose *compose = (Compose *) data;
7686 if(!compose->gtkaspell)
7688 if(compose->gtkaspell->recheck_when_changing_dict == FALSE)
7691 gtkaspell_highlight_all(compose->gtkaspell);
7692 claws_spell_entry_recheck_all(CLAWS_SPELL_ENTRY(compose->subject_entry));
7696 static gboolean compose_popup_menu(GtkWidget *widget, gpointer data)
7698 Compose *compose = (Compose *)data;
7699 GdkEventButton event;
7702 event.time = gtk_get_current_event_time();
7706 return text_clicked(compose->text, &event, compose);
7709 static gboolean compose_force_window_origin = TRUE;
7710 static Compose *compose_create(PrefsAccount *account,
7719 GtkWidget *handlebox;
7721 GtkWidget *notebook;
7723 GtkWidget *attach_hbox;
7724 GtkWidget *attach_lab1;
7725 GtkWidget *attach_lab2;
7730 GtkWidget *subject_hbox;
7731 GtkWidget *subject_frame;
7732 GtkWidget *subject_entry;
7736 GtkWidget *edit_vbox;
7737 GtkWidget *ruler_hbox;
7739 GtkWidget *scrolledwin;
7741 GtkTextBuffer *buffer;
7742 GtkClipboard *clipboard;
7744 UndoMain *undostruct;
7746 GtkWidget *popupmenu;
7747 GtkWidget *tmpl_menu;
7748 GtkActionGroup *action_group = NULL;
7751 GtkAspell * gtkaspell = NULL;
7754 static GdkGeometry geometry;
7756 cm_return_val_if_fail(account != NULL, NULL);
7758 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_DEFAULT_HEADER_BG],
7759 &default_header_bgcolor);
7760 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_DEFAULT_HEADER],
7761 &default_header_color);
7763 debug_print("Creating compose window...\n");
7764 compose = g_new0(Compose, 1);
7766 compose->batch = batch;
7767 compose->account = account;
7768 compose->folder = folder;
7770 compose->mutex = cm_mutex_new();
7771 compose->set_cursor_pos = -1;
7773 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose");
7775 gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
7776 gtk_widget_set_size_request(window, prefs_common.compose_width,
7777 prefs_common.compose_height);
7779 if (!geometry.max_width) {
7780 geometry.max_width = gdk_screen_width();
7781 geometry.max_height = gdk_screen_height();
7784 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7785 &geometry, GDK_HINT_MAX_SIZE);
7786 if (!geometry.min_width) {
7787 geometry.min_width = 600;
7788 geometry.min_height = 440;
7790 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7791 &geometry, GDK_HINT_MIN_SIZE);
7793 #ifndef GENERIC_UMPC
7794 if (compose_force_window_origin)
7795 gtk_window_move(GTK_WINDOW(window), prefs_common.compose_x,
7796 prefs_common.compose_y);
7798 g_signal_connect(G_OBJECT(window), "delete_event",
7799 G_CALLBACK(compose_delete_cb), compose);
7800 MANAGE_WINDOW_SIGNALS_CONNECT(window);
7801 gtk_widget_realize(window);
7803 gtkut_widget_set_composer_icon(window);
7805 vbox = gtk_vbox_new(FALSE, 0);
7806 gtk_container_add(GTK_CONTAINER(window), vbox);
7808 compose->ui_manager = gtk_ui_manager_new();
7809 action_group = cm_menu_create_action_group_full(compose->ui_manager,"Menu", compose_entries,
7810 G_N_ELEMENTS(compose_entries), (gpointer)compose);
7811 gtk_action_group_add_toggle_actions(action_group, compose_toggle_entries,
7812 G_N_ELEMENTS(compose_toggle_entries), (gpointer)compose);
7813 gtk_action_group_add_radio_actions(action_group, compose_radio_rm_entries,
7814 G_N_ELEMENTS(compose_radio_rm_entries), COMPOSE_REPLY, G_CALLBACK(compose_reply_change_mode_cb), (gpointer)compose);
7815 gtk_action_group_add_radio_actions(action_group, compose_radio_prio_entries,
7816 G_N_ELEMENTS(compose_radio_prio_entries), PRIORITY_NORMAL, G_CALLBACK(compose_set_priority_cb), (gpointer)compose);
7817 gtk_action_group_add_radio_actions(action_group, compose_radio_enc_entries,
7818 G_N_ELEMENTS(compose_radio_enc_entries), C_AUTO, G_CALLBACK(compose_set_encoding_cb), (gpointer)compose);
7820 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Menu", NULL, GTK_UI_MANAGER_MENUBAR)
7822 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Message", "Message", GTK_UI_MANAGER_MENU)
7823 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Edit", "Edit", GTK_UI_MANAGER_MENU)
7825 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Spelling", "Spelling", GTK_UI_MANAGER_MENU)
7827 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Options", "Options", GTK_UI_MANAGER_MENU)
7828 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Tools", "Tools", GTK_UI_MANAGER_MENU)
7829 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Help", "Help", GTK_UI_MANAGER_MENU)
7832 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Send", "Message/Send", GTK_UI_MANAGER_MENUITEM)
7833 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "SendLater", "Message/SendLater", GTK_UI_MANAGER_MENUITEM)
7834 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator1", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7835 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "AttachFile", "Message/AttachFile", GTK_UI_MANAGER_MENUITEM)
7836 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertFile", "Message/InsertFile", GTK_UI_MANAGER_MENUITEM)
7837 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertSig", "Message/InsertSig", GTK_UI_MANAGER_MENUITEM)
7838 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "ReplaceSig", "Message/ReplaceSig", GTK_UI_MANAGER_MENUITEM)
7839 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator2", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7840 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Save", "Message/Save", GTK_UI_MANAGER_MENUITEM)
7841 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator3", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7842 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Print", "Message/Print", GTK_UI_MANAGER_MENUITEM)
7843 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator4", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7844 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Close", "Message/Close", GTK_UI_MANAGER_MENUITEM)
7847 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Undo", "Edit/Undo", GTK_UI_MANAGER_MENUITEM)
7848 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Redo", "Edit/Redo", GTK_UI_MANAGER_MENUITEM)
7849 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator1", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7851 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Cut", "Edit/Cut", GTK_UI_MANAGER_MENUITEM)
7852 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Copy", "Edit/Copy", GTK_UI_MANAGER_MENUITEM)
7853 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Paste", "Edit/Paste", GTK_UI_MANAGER_MENUITEM)
7855 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SpecialPaste", "Edit/SpecialPaste", GTK_UI_MANAGER_MENU)
7856 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "AsQuotation", "Edit/SpecialPaste/AsQuotation", GTK_UI_MANAGER_MENUITEM)
7857 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Wrapped", "Edit/SpecialPaste/Wrapped", GTK_UI_MANAGER_MENUITEM)
7858 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Unwrapped", "Edit/SpecialPaste/Unwrapped", GTK_UI_MANAGER_MENUITEM)
7860 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SelectAll", "Edit/SelectAll", GTK_UI_MANAGER_MENUITEM)
7862 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Advanced", "Edit/Advanced", GTK_UI_MANAGER_MENU)
7863 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackChar", "Edit/Advanced/BackChar", GTK_UI_MANAGER_MENUITEM)
7864 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwChar", "Edit/Advanced/ForwChar", GTK_UI_MANAGER_MENUITEM)
7865 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackWord", "Edit/Advanced/BackWord", GTK_UI_MANAGER_MENUITEM)
7866 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwWord", "Edit/Advanced/ForwWord", GTK_UI_MANAGER_MENUITEM)
7867 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BegLine", "Edit/Advanced/BegLine", GTK_UI_MANAGER_MENUITEM)
7868 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "EndLine", "Edit/Advanced/EndLine", GTK_UI_MANAGER_MENUITEM)
7869 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "PrevLine", "Edit/Advanced/PrevLine", GTK_UI_MANAGER_MENUITEM)
7870 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "NextLine", "Edit/Advanced/NextLine", GTK_UI_MANAGER_MENUITEM)
7871 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackChar", "Edit/Advanced/DelBackChar", GTK_UI_MANAGER_MENUITEM)
7872 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwChar", "Edit/Advanced/DelForwChar", GTK_UI_MANAGER_MENUITEM)
7873 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackWord", "Edit/Advanced/DelBackWord", GTK_UI_MANAGER_MENUITEM)
7874 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwWord", "Edit/Advanced/DelForwWord", GTK_UI_MANAGER_MENUITEM)
7875 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelLine", "Edit/Advanced/DelLine", GTK_UI_MANAGER_MENUITEM)
7876 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelEndLine", "Edit/Advanced/DelEndLine", GTK_UI_MANAGER_MENUITEM)
7878 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator2", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7880 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Find", "Edit/Find", GTK_UI_MANAGER_MENUITEM)
7881 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapPara", "Edit/WrapPara", GTK_UI_MANAGER_MENUITEM)
7882 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapAllLines", "Edit/WrapAllLines", GTK_UI_MANAGER_MENUITEM)
7883 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoWrap", "Edit/AutoWrap", GTK_UI_MANAGER_MENUITEM)
7884 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoIndent", "Edit/AutoIndent", GTK_UI_MANAGER_MENUITEM)
7886 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator3", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7888 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "ExtEditor", "Edit/ExtEditor", GTK_UI_MANAGER_MENUITEM)
7892 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckAllSel", "Spelling/CheckAllSel", GTK_UI_MANAGER_MENUITEM)
7893 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "HighlightAll", "Spelling/HighlightAll", GTK_UI_MANAGER_MENUITEM)
7894 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckBackwards", "Spelling/CheckBackwards", GTK_UI_MANAGER_MENUITEM)
7895 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "ForwardNext", "Spelling/ForwardNext", GTK_UI_MANAGER_MENUITEM)
7896 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Separator1", "Spelling/---", GTK_UI_MANAGER_SEPARATOR)
7897 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Options", "Spelling/Options", GTK_UI_MANAGER_MENU)
7901 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "ReplyMode", "Options/ReplyMode", GTK_UI_MANAGER_MENU)
7902 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Normal", "Options/ReplyMode/Normal", GTK_UI_MANAGER_MENUITEM)
7903 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "All", "Options/ReplyMode/All", GTK_UI_MANAGER_MENUITEM)
7904 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Sender", "Options/ReplyMode/Sender", GTK_UI_MANAGER_MENUITEM)
7905 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "List", "Options/ReplyMode/List", GTK_UI_MANAGER_MENUITEM)
7907 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator1", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7908 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "PrivacySystem", "Options/PrivacySystem", GTK_UI_MANAGER_MENU)
7909 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/PrivacySystem", "PlaceHolder", "Options/PrivacySystem/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7910 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Sign", "Options/Sign", GTK_UI_MANAGER_MENUITEM)
7911 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encrypt", "Options/Encrypt", GTK_UI_MANAGER_MENUITEM)
7914 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator2", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7915 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Priority", "Options/Priority", GTK_UI_MANAGER_MENU)
7916 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Highest", "Options/Priority/Highest", GTK_UI_MANAGER_MENUITEM)
7917 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "High", "Options/Priority/High", GTK_UI_MANAGER_MENUITEM)
7918 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Normal", "Options/Priority/Normal", GTK_UI_MANAGER_MENUITEM)
7919 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Low", "Options/Priority/Low", GTK_UI_MANAGER_MENUITEM)
7920 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Lowest", "Options/Priority/Lowest", GTK_UI_MANAGER_MENUITEM)
7922 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator3", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7923 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RequestRetRcpt", "Options/RequestRetRcpt", GTK_UI_MANAGER_MENUITEM)
7924 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator4", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7925 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RemoveReferences", "Options/RemoveReferences", GTK_UI_MANAGER_MENUITEM)
7926 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator5", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7928 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encoding", "Options/Encoding", GTK_UI_MANAGER_MENU)
7930 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_AUTO, "Options/Encoding/"CS_AUTO, GTK_UI_MANAGER_MENUITEM)
7931 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator1", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7932 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_US_ASCII, "Options/Encoding/"CS_US_ASCII, GTK_UI_MANAGER_MENUITEM)
7933 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_UTF_8, "Options/Encoding/"CS_UTF_8, GTK_UI_MANAGER_MENUITEM)
7934 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator2", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7936 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Western", "Options/Encoding/Western", GTK_UI_MANAGER_MENU)
7937 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)
7938 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)
7939 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Western", CS_WINDOWS_1252, "Options/Encoding/Western/"CS_WINDOWS_1252, GTK_UI_MANAGER_MENUITEM)
7941 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_2, "Options/Encoding/"CS_ISO_8859_2, GTK_UI_MANAGER_MENUITEM)
7943 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Baltic", "Options/Encoding/Baltic", GTK_UI_MANAGER_MENU)
7944 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)
7945 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)
7947 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_7, "Options/Encoding/"CS_ISO_8859_7, GTK_UI_MANAGER_MENUITEM)
7949 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Hebrew", "Options/Encoding/Hebrew", GTK_UI_MANAGER_MENU)
7950 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)
7951 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Hebrew", CS_WINDOWS_1255, "Options/Encoding/Hebrew/"CS_WINDOWS_1255, GTK_UI_MANAGER_MENUITEM)
7953 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Arabic", "Options/Encoding/Arabic", GTK_UI_MANAGER_MENU)
7954 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)
7955 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Arabic", CS_WINDOWS_1256, "Options/Encoding/Arabic/"CS_WINDOWS_1256, GTK_UI_MANAGER_MENUITEM)
7957 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_9, "Options/Encoding/"CS_ISO_8859_9, GTK_UI_MANAGER_MENUITEM)
7959 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Cyrillic", "Options/Encoding/Cyrillic", GTK_UI_MANAGER_MENU)
7960 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)
7961 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_R, "Options/Encoding/Cyrillic/"CS_KOI8_R, GTK_UI_MANAGER_MENUITEM)
7962 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_MACCYR, "Options/Encoding/Cyrillic/"CS_MACCYR, GTK_UI_MANAGER_MENUITEM)
7963 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_U, "Options/Encoding/Cyrillic/"CS_KOI8_U, GTK_UI_MANAGER_MENUITEM)
7964 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_WINDOWS_1251, "Options/Encoding/Cyrillic/"CS_WINDOWS_1251, GTK_UI_MANAGER_MENUITEM)
7966 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Japanese", "Options/Encoding/Japanese", GTK_UI_MANAGER_MENU)
7967 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)
7968 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)
7969 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_EUC_JP, "Options/Encoding/Japanese/"CS_EUC_JP, GTK_UI_MANAGER_MENUITEM)
7970 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_SHIFT_JIS, "Options/Encoding/Japanese/"CS_SHIFT_JIS, GTK_UI_MANAGER_MENUITEM)
7972 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Chinese", "Options/Encoding/Chinese", GTK_UI_MANAGER_MENU)
7973 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB18030, "Options/Encoding/Chinese/"CS_GB18030, GTK_UI_MANAGER_MENUITEM)
7974 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB2312, "Options/Encoding/Chinese/"CS_GB2312, GTK_UI_MANAGER_MENUITEM)
7975 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GBK, "Options/Encoding/Chinese/"CS_GBK, GTK_UI_MANAGER_MENUITEM)
7976 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_BIG5, "Options/Encoding/Chinese/"CS_BIG5, GTK_UI_MANAGER_MENUITEM)
7977 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_EUC_TW, "Options/Encoding/Chinese/"CS_EUC_TW, GTK_UI_MANAGER_MENUITEM)
7979 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Korean", "Options/Encoding/Korean", GTK_UI_MANAGER_MENU)
7980 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Korean", CS_EUC_KR, "Options/Encoding/Korean/"CS_EUC_KR, GTK_UI_MANAGER_MENUITEM)
7981 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)
7983 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Thai", "Options/Encoding/Thai", GTK_UI_MANAGER_MENU)
7984 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_TIS_620, "Options/Encoding/Thai/"CS_TIS_620, GTK_UI_MANAGER_MENUITEM)
7985 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_WINDOWS_874, "Options/Encoding/Thai/"CS_WINDOWS_874, GTK_UI_MANAGER_MENUITEM)
7989 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "ShowRuler", "Tools/ShowRuler", GTK_UI_MANAGER_MENUITEM)
7990 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "AddressBook", "Tools/AddressBook", GTK_UI_MANAGER_MENUITEM)
7991 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Template", "Tools/Template", GTK_UI_MANAGER_MENU)
7992 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Template", "PlaceHolder", "Tools/Template/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7993 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Actions", "Tools/Actions", GTK_UI_MANAGER_MENU)
7994 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Actions", "PlaceHolder", "Tools/Actions/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7997 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Help", "About", "Help/About", GTK_UI_MANAGER_MENUITEM)
7999 menubar = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu");
8000 gtk_widget_show_all(menubar);
8002 gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(compose->ui_manager));
8003 gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, TRUE, 0);
8005 if (prefs_common.toolbar_detachable) {
8006 handlebox = gtk_handle_box_new();
8008 handlebox = gtk_hbox_new(FALSE, 0);
8010 gtk_box_pack_start(GTK_BOX(vbox), handlebox, FALSE, FALSE, 0);
8012 gtk_widget_realize(handlebox);
8013 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, handlebox,
8016 vbox2 = gtk_vbox_new(FALSE, 2);
8017 gtk_box_pack_start(GTK_BOX(vbox), vbox2, TRUE, TRUE, 0);
8018 gtk_container_set_border_width(GTK_CONTAINER(vbox2), 0);
8021 notebook = gtk_notebook_new();
8022 gtk_widget_show(notebook);
8024 /* header labels and entries */
8025 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
8026 compose_create_header(compose),
8027 gtk_label_new_with_mnemonic(_("Hea_der")));
8028 /* attachment list */
8029 attach_hbox = gtk_hbox_new(FALSE, 0);
8030 gtk_widget_show(attach_hbox);
8032 attach_lab1 = gtk_label_new_with_mnemonic(_("_Attachments"));
8033 gtk_widget_show(attach_lab1);
8034 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab1, TRUE, TRUE, 0);
8036 attach_lab2 = gtk_label_new("");
8037 gtk_widget_show(attach_lab2);
8038 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab2, FALSE, FALSE, 0);
8040 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
8041 compose_create_attach(compose),
8044 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
8045 compose_create_others(compose),
8046 gtk_label_new_with_mnemonic(_("Othe_rs")));
8049 subject_hbox = gtk_hbox_new(FALSE, 0);
8050 gtk_widget_show(subject_hbox);
8052 subject_frame = gtk_frame_new(NULL);
8053 gtk_frame_set_shadow_type(GTK_FRAME(subject_frame), GTK_SHADOW_NONE);
8054 gtk_box_pack_start(GTK_BOX(subject_hbox), subject_frame, TRUE, TRUE, 0);
8055 gtk_widget_show(subject_frame);
8057 subject = gtk_hbox_new(FALSE, HSPACING_NARROW);
8058 gtk_container_set_border_width(GTK_CONTAINER(subject), 0);
8059 gtk_widget_show(subject);
8061 label = gtk_label_new_with_mnemonic(_("S_ubject:"));
8062 gtk_box_pack_start(GTK_BOX(subject), label, FALSE, FALSE, 0);
8063 gtk_widget_show(label);
8066 subject_entry = claws_spell_entry_new();
8068 subject_entry = gtk_entry_new();
8070 gtk_box_pack_start(GTK_BOX(subject), subject_entry, TRUE, TRUE, 0);
8071 g_signal_connect_after(G_OBJECT(subject_entry), "grab_focus",
8072 G_CALLBACK(compose_grab_focus_cb), compose);
8073 gtk_label_set_mnemonic_widget(GTK_LABEL(label), subject_entry);
8074 gtk_widget_show(subject_entry);
8075 compose->subject_entry = subject_entry;
8076 gtk_container_add(GTK_CONTAINER(subject_frame), subject);
8078 edit_vbox = gtk_vbox_new(FALSE, 0);
8080 gtk_box_pack_start(GTK_BOX(edit_vbox), subject_hbox, FALSE, FALSE, 0);
8083 ruler_hbox = gtk_hbox_new(FALSE, 0);
8084 gtk_box_pack_start(GTK_BOX(edit_vbox), ruler_hbox, FALSE, FALSE, 0);
8086 ruler = gtk_shruler_new(GTK_ORIENTATION_HORIZONTAL);
8087 gtk_shruler_set_range(GTK_SHRULER(ruler), 0.0, 100.0, 1.0);
8088 gtk_box_pack_start(GTK_BOX(ruler_hbox), ruler, TRUE, TRUE,
8092 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
8093 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
8094 GTK_POLICY_AUTOMATIC,
8095 GTK_POLICY_AUTOMATIC);
8096 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
8098 gtk_box_pack_start(GTK_BOX(edit_vbox), scrolledwin, TRUE, TRUE, 0);
8100 text = gtk_text_view_new();
8101 if (prefs_common.show_compose_margin) {
8102 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text), 6);
8103 gtk_text_view_set_right_margin(GTK_TEXT_VIEW(text), 6);
8105 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
8106 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
8107 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), TRUE);
8108 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
8109 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
8111 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
8112 g_signal_connect_after(G_OBJECT(text), "size_allocate",
8113 G_CALLBACK(compose_edit_size_alloc),
8115 g_signal_connect(G_OBJECT(buffer), "changed",
8116 G_CALLBACK(compose_changed_cb), compose);
8117 g_signal_connect(G_OBJECT(text), "grab_focus",
8118 G_CALLBACK(compose_grab_focus_cb), compose);
8119 g_signal_connect(G_OBJECT(buffer), "insert_text",
8120 G_CALLBACK(text_inserted), compose);
8121 g_signal_connect(G_OBJECT(text), "button_press_event",
8122 G_CALLBACK(text_clicked), compose);
8123 g_signal_connect(G_OBJECT(text), "popup-menu",
8124 G_CALLBACK(compose_popup_menu), compose);
8125 g_signal_connect(G_OBJECT(subject_entry), "changed",
8126 G_CALLBACK(compose_changed_cb), compose);
8127 g_signal_connect(G_OBJECT(subject_entry), "activate",
8128 G_CALLBACK(compose_subject_entry_activated), compose);
8131 gtk_drag_dest_set(text, GTK_DEST_DEFAULT_ALL, compose_mime_types,
8132 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
8133 GDK_ACTION_COPY | GDK_ACTION_MOVE);
8134 g_signal_connect(G_OBJECT(text), "drag_data_received",
8135 G_CALLBACK(compose_insert_drag_received_cb),
8137 g_signal_connect(G_OBJECT(text), "drag-drop",
8138 G_CALLBACK(compose_drag_drop),
8140 g_signal_connect(G_OBJECT(text), "key-press-event",
8141 G_CALLBACK(completion_set_focus_to_subject),
8143 gtk_widget_show_all(vbox);
8145 /* pane between attach clist and text */
8146 paned = gtk_vpaned_new();
8147 gtk_container_add(GTK_CONTAINER(vbox2), paned);
8148 gtk_paned_pack1(GTK_PANED(paned), notebook, FALSE, FALSE);
8149 gtk_paned_pack2(GTK_PANED(paned), edit_vbox, TRUE, FALSE);
8150 gtk_paned_set_position(GTK_PANED(paned), prefs_common.compose_notebook_height);
8151 g_signal_connect(G_OBJECT(notebook), "size_allocate",
8152 G_CALLBACK(compose_notebook_size_alloc), paned);
8154 gtk_widget_show_all(paned);
8157 if (prefs_common.textfont) {
8158 PangoFontDescription *font_desc;
8160 font_desc = pango_font_description_from_string
8161 (prefs_common.textfont);
8163 gtk_widget_modify_font(text, font_desc);
8164 pango_font_description_free(font_desc);
8168 gtk_action_group_add_actions(action_group, compose_popup_entries,
8169 G_N_ELEMENTS(compose_popup_entries), (gpointer)compose);
8170 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Popup", NULL, GTK_UI_MANAGER_MENUBAR)
8171 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup", "Compose", "Compose", GTK_UI_MANAGER_MENU)
8172 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Add", "Compose/Add", GTK_UI_MANAGER_MENUITEM)
8173 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Remove", "Compose/Remove", GTK_UI_MANAGER_MENUITEM)
8174 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Separator1", "Compose/---", GTK_UI_MANAGER_SEPARATOR)
8175 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Properties", "Compose/Properties", GTK_UI_MANAGER_MENUITEM)
8177 popupmenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(gtk_ui_manager_get_widget(compose->ui_manager, "/Popup/Compose")));
8179 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
8180 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
8181 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
8183 tmpl_menu = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu/Tools/Template");
8185 undostruct = undo_init(text);
8186 undo_set_change_state_func(undostruct, &compose_undo_state_changed,
8189 address_completion_start(window);
8191 compose->window = window;
8192 compose->vbox = vbox;
8193 compose->menubar = menubar;
8194 compose->handlebox = handlebox;
8196 compose->vbox2 = vbox2;
8198 compose->paned = paned;
8200 compose->attach_label = attach_lab2;
8202 compose->notebook = notebook;
8203 compose->edit_vbox = edit_vbox;
8204 compose->ruler_hbox = ruler_hbox;
8205 compose->ruler = ruler;
8206 compose->scrolledwin = scrolledwin;
8207 compose->text = text;
8209 compose->focused_editable = NULL;
8211 compose->popupmenu = popupmenu;
8213 compose->tmpl_menu = tmpl_menu;
8215 compose->mode = mode;
8216 compose->rmode = mode;
8218 compose->targetinfo = NULL;
8219 compose->replyinfo = NULL;
8220 compose->fwdinfo = NULL;
8222 compose->email_hashtable = g_hash_table_new_full(g_str_hash,
8223 g_str_equal, (GDestroyNotify) g_free, NULL);
8225 compose->replyto = NULL;
8227 compose->bcc = NULL;
8228 compose->followup_to = NULL;
8230 compose->ml_post = NULL;
8232 compose->inreplyto = NULL;
8233 compose->references = NULL;
8234 compose->msgid = NULL;
8235 compose->boundary = NULL;
8237 compose->autowrap = prefs_common.autowrap;
8238 compose->autoindent = prefs_common.auto_indent;
8239 compose->use_signing = FALSE;
8240 compose->use_encryption = FALSE;
8241 compose->privacy_system = NULL;
8242 compose->encdata = NULL;
8244 compose->modified = FALSE;
8246 compose->return_receipt = FALSE;
8248 compose->to_list = NULL;
8249 compose->newsgroup_list = NULL;
8251 compose->undostruct = undostruct;
8253 compose->sig_str = NULL;
8255 compose->exteditor_file = NULL;
8256 compose->exteditor_pid = -1;
8257 compose->exteditor_tag = -1;
8258 compose->exteditor_socket = NULL;
8259 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN; /* inhibit auto-drafting while loading */
8261 compose->folder_update_callback_id =
8262 hooks_register_hook(FOLDER_UPDATE_HOOKLIST,
8263 compose_update_folder_hook,
8264 (gpointer) compose);
8267 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
8268 if (mode != COMPOSE_REDIRECT) {
8269 if (prefs_common.enable_aspell && prefs_common.dictionary &&
8270 strcmp(prefs_common.dictionary, "")) {
8271 gtkaspell = gtkaspell_new(prefs_common.dictionary,
8272 prefs_common.alt_dictionary,
8273 conv_get_locale_charset_str(),
8274 prefs_common.color[COL_MISSPELLED],
8275 prefs_common.check_while_typing,
8276 prefs_common.recheck_when_changing_dict,
8277 prefs_common.use_alternate,
8278 prefs_common.use_both_dicts,
8279 GTK_TEXT_VIEW(text),
8280 GTK_WINDOW(compose->window),
8281 compose_dict_changed,
8282 compose_spell_menu_changed,
8285 alertpanel_error(_("Spell checker could not "
8287 gtkaspell_checkers_strerror());
8288 gtkaspell_checkers_reset_error();
8290 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", TRUE);
8294 compose->gtkaspell = gtkaspell;
8295 compose_spell_menu_changed(compose);
8296 claws_spell_entry_set_gtkaspell(CLAWS_SPELL_ENTRY(subject_entry), gtkaspell);
8299 compose_select_account(compose, account, TRUE);
8301 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", prefs_common.autowrap);
8302 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", prefs_common.auto_indent);
8304 if (account->set_autocc && account->auto_cc && mode != COMPOSE_REEDIT)
8305 compose_entry_append(compose, account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8307 if (account->set_autobcc && account->auto_bcc && mode != COMPOSE_REEDIT)
8308 compose_entry_append(compose, account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8310 if (account->set_autoreplyto && account->auto_replyto && mode != COMPOSE_REEDIT)
8311 compose_entry_append(compose, account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8313 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", compose->mode == COMPOSE_REPLY);
8314 if (account->protocol != A_NNTP)
8315 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
8316 prefs_common_translated_header_name("To:"));
8318 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
8319 prefs_common_translated_header_name("Newsgroups:"));
8321 #ifndef USE_ALT_ADDRBOOK
8322 addressbook_set_target_compose(compose);
8324 if (mode != COMPOSE_REDIRECT)
8325 compose_set_template_menu(compose);
8327 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Template", FALSE);
8330 compose_list = g_list_append(compose_list, compose);
8332 if (!prefs_common.show_ruler)
8333 gtk_widget_hide(ruler_hbox);
8335 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Tools/ShowRuler", prefs_common.show_ruler);
8338 compose->priority = PRIORITY_NORMAL;
8339 compose_update_priority_menu_item(compose);
8341 compose_set_out_encoding(compose);
8344 compose_update_actions_menu(compose);
8346 /* Privacy Systems menu */
8347 compose_update_privacy_systems_menu(compose);
8348 compose_activate_privacy_system(compose, account, TRUE);
8350 toolbar_set_style(compose->toolbar->toolbar, compose->handlebox, prefs_common.toolbar_style);
8352 gtk_widget_realize(window);
8354 gtk_widget_show(window);
8360 static GtkWidget *compose_account_option_menu_create(Compose *compose)
8365 GtkWidget *optmenubox;
8366 GtkWidget *fromlabel;
8369 GtkWidget *from_name = NULL;
8371 gint num = 0, def_menu = 0;
8373 accounts = account_get_list();
8374 cm_return_val_if_fail(accounts != NULL, NULL);
8376 optmenubox = gtk_event_box_new();
8377 optmenu = gtkut_sc_combobox_create(optmenubox, FALSE);
8378 menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
8380 hbox = gtk_hbox_new(FALSE, 4);
8381 from_name = gtk_entry_new();
8383 g_signal_connect_after(G_OBJECT(from_name), "grab_focus",
8384 G_CALLBACK(compose_grab_focus_cb), compose);
8385 g_signal_connect_after(G_OBJECT(from_name), "activate",
8386 G_CALLBACK(from_name_activate_cb), optmenu);
8388 for (; accounts != NULL; accounts = accounts->next, num++) {
8389 PrefsAccount *ac = (PrefsAccount *)accounts->data;
8390 gchar *name, *from = NULL;
8392 if (ac == compose->account) def_menu = num;
8394 name = g_markup_printf_escaped("<i>%s</i>",
8397 if (ac == compose->account) {
8398 if (ac->name && *ac->name) {
8400 QUOTE_IF_REQUIRED_NORMAL(buf, ac->name, return NULL);
8401 from = g_strdup_printf("%s <%s>",
8403 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8405 from = g_strdup_printf("%s",
8407 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8409 if (cur_account != compose->account) {
8410 gtk_widget_modify_base(
8411 GTK_WIDGET(from_name),
8412 GTK_STATE_NORMAL, &default_header_bgcolor);
8413 gtk_widget_modify_text(
8414 GTK_WIDGET(from_name),
8415 GTK_STATE_NORMAL, &default_header_color);
8418 COMBOBOX_ADD(menu, name, ac->account_id);
8423 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), def_menu);
8425 g_signal_connect(G_OBJECT(optmenu), "changed",
8426 G_CALLBACK(account_activated),
8428 g_signal_connect(G_OBJECT(from_name), "populate-popup",
8429 G_CALLBACK(compose_entry_popup_extend),
8432 fromlabel = gtk_label_new_with_mnemonic(_("_From:"));
8433 gtk_label_set_mnemonic_widget(GTK_LABEL(fromlabel), from_name);
8435 gtk_box_pack_start(GTK_BOX(hbox), fromlabel, FALSE, FALSE, 4);
8436 gtk_box_pack_start(GTK_BOX(hbox), optmenubox, FALSE, FALSE, 0);
8437 gtk_box_pack_start(GTK_BOX(hbox), from_name, TRUE, TRUE, 0);
8439 /* Putting only the GtkEntry into focus chain of parent hbox causes
8440 * the account selector combobox next to it to be unreachable when
8441 * navigating widgets in GtkTable with up/down arrow keys.
8442 * Note: gtk_widget_set_can_focus() was not enough. */
8444 l = g_list_prepend(l, from_name);
8445 gtk_container_set_focus_chain(GTK_CONTAINER(hbox), l);
8448 CLAWS_SET_TIP(optmenubox,
8449 _("Account to use for this email"));
8450 CLAWS_SET_TIP(from_name,
8451 _("Sender address to be used"));
8453 compose->account_combo = optmenu;
8454 compose->from_name = from_name;
8459 static void compose_set_priority_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8461 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8462 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8463 Compose *compose = (Compose *) data;
8465 compose->priority = value;
8469 static void compose_reply_change_mode(Compose *compose,
8472 gboolean was_modified = compose->modified;
8474 gboolean all = FALSE, ml = FALSE, sender = FALSE, followup = FALSE;
8476 cm_return_if_fail(compose->replyinfo != NULL);
8478 if (action == COMPOSE_REPLY && prefs_common.default_reply_list)
8480 if (action == COMPOSE_REPLY && compose->rmode == COMPOSE_FOLLOWUP_AND_REPLY_TO)
8482 if (action == COMPOSE_REPLY_TO_ALL)
8484 if (action == COMPOSE_REPLY_TO_SENDER)
8486 if (action == COMPOSE_REPLY_TO_LIST)
8489 compose_remove_header_entries(compose);
8490 compose_reply_set_entry(compose, compose->replyinfo, all, ml, sender, followup);
8491 if (compose->account->set_autocc && compose->account->auto_cc)
8492 compose_entry_append(compose, compose->account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8494 if (compose->account->set_autobcc && compose->account->auto_bcc)
8495 compose_entry_append(compose, compose->account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8497 if (compose->account->set_autoreplyto && compose->account->auto_replyto)
8498 compose_entry_append(compose, compose->account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8499 compose_show_first_last_header(compose, TRUE);
8500 compose->modified = was_modified;
8501 compose_set_title(compose);
8504 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8506 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8507 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8508 Compose *compose = (Compose *) data;
8511 compose_reply_change_mode(compose, value);
8514 static void compose_update_priority_menu_item(Compose * compose)
8516 GtkWidget *menuitem = NULL;
8517 switch (compose->priority) {
8518 case PRIORITY_HIGHEST:
8519 menuitem = gtk_ui_manager_get_widget
8520 (compose->ui_manager, "/Menu/Options/Priority/Highest");
8523 menuitem = gtk_ui_manager_get_widget
8524 (compose->ui_manager, "/Menu/Options/Priority/High");
8526 case PRIORITY_NORMAL:
8527 menuitem = gtk_ui_manager_get_widget
8528 (compose->ui_manager, "/Menu/Options/Priority/Normal");
8531 menuitem = gtk_ui_manager_get_widget
8532 (compose->ui_manager, "/Menu/Options/Priority/Low");
8534 case PRIORITY_LOWEST:
8535 menuitem = gtk_ui_manager_get_widget
8536 (compose->ui_manager, "/Menu/Options/Priority/Lowest");
8539 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8542 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data)
8544 Compose *compose = (Compose *) data;
8546 gboolean can_sign = FALSE, can_encrypt = FALSE;
8548 cm_return_if_fail(GTK_IS_CHECK_MENU_ITEM(widget));
8550 if (!gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget)))
8553 systemid = g_object_get_data(G_OBJECT(widget), "privacy_system");
8554 g_free(compose->privacy_system);
8555 compose->privacy_system = NULL;
8556 g_free(compose->encdata);
8557 compose->encdata = NULL;
8558 if (systemid != NULL) {
8559 compose->privacy_system = g_strdup(systemid);
8561 can_sign = privacy_system_can_sign(systemid);
8562 can_encrypt = privacy_system_can_encrypt(systemid);
8565 debug_print("activated privacy system: %s\n", systemid != NULL ? systemid : "None");
8567 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8568 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8569 if (compose->toolbar->privacy_sign_btn != NULL) {
8570 gtk_widget_set_sensitive(
8571 GTK_WIDGET(compose->toolbar->privacy_sign_btn),
8573 gtk_toggle_tool_button_set_active(
8574 GTK_TOGGLE_TOOL_BUTTON(compose->toolbar->privacy_sign_btn),
8575 can_sign ? compose->use_signing : FALSE);
8577 if (compose->toolbar->privacy_encrypt_btn != NULL) {
8578 gtk_widget_set_sensitive(
8579 GTK_WIDGET(compose->toolbar->privacy_encrypt_btn),
8581 gtk_toggle_tool_button_set_active(
8582 GTK_TOGGLE_TOOL_BUTTON(compose->toolbar->privacy_encrypt_btn),
8583 can_encrypt ? compose->use_encryption : FALSE);
8587 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn)
8589 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8590 GtkWidget *menuitem = NULL;
8591 GList *children, *amenu;
8592 gboolean can_sign = FALSE, can_encrypt = FALSE;
8593 gboolean found = FALSE;
8595 if (compose->privacy_system != NULL) {
8597 menuitem = gtk_menu_item_get_submenu(GTK_MENU_ITEM(
8598 gtk_ui_manager_get_widget(compose->ui_manager, branch_path)));
8599 cm_return_if_fail(menuitem != NULL);
8601 children = gtk_container_get_children(GTK_CONTAINER(GTK_MENU_SHELL(menuitem)));
8604 while (amenu != NULL) {
8605 systemid = g_object_get_data(G_OBJECT(amenu->data), "privacy_system");
8606 if (systemid != NULL) {
8607 if (strcmp(systemid, compose->privacy_system) == 0 &&
8608 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8609 menuitem = GTK_WIDGET(amenu->data);
8611 can_sign = privacy_system_can_sign(systemid);
8612 can_encrypt = privacy_system_can_encrypt(systemid);
8616 } else if (strlen(compose->privacy_system) == 0 &&
8617 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8618 menuitem = GTK_WIDGET(amenu->data);
8621 can_encrypt = FALSE;
8626 amenu = amenu->next;
8628 g_list_free(children);
8629 if (menuitem != NULL)
8630 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8632 if (warn && !found && strlen(compose->privacy_system)) {
8633 alertpanel_warning(_("The privacy system '%s' cannot be loaded. You "
8634 "will not be able to sign or encrypt this message."),
8635 compose->privacy_system);
8639 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8640 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8641 if (compose->toolbar->privacy_sign_btn != NULL) {
8642 gtk_widget_set_sensitive(
8643 GTK_WIDGET(compose->toolbar->privacy_sign_btn),
8646 if (compose->toolbar->privacy_encrypt_btn != NULL) {
8647 gtk_widget_set_sensitive(
8648 GTK_WIDGET(compose->toolbar->privacy_encrypt_btn),
8653 static void compose_set_out_encoding(Compose *compose)
8655 CharSet out_encoding;
8656 const gchar *branch = NULL;
8657 out_encoding = conv_get_charset_from_str(prefs_common.outgoing_charset);
8659 switch(out_encoding) {
8660 case C_AUTO: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8661 case C_US_ASCII: branch = "Menu/Options/Encoding/" CS_US_ASCII; break;
8662 case C_UTF_8: branch = "Menu/Options/Encoding/" CS_UTF_8; break;
8663 case C_ISO_8859_2: branch = "Menu/Options/Encoding/" CS_ISO_8859_2; break;
8664 case C_ISO_8859_7: branch = "Menu/Options/Encoding/" CS_ISO_8859_7; break;
8665 case C_ISO_8859_9: branch = "Menu/Options/Encoding/" CS_ISO_8859_9; break;
8666 case C_ISO_8859_1: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_1; break;
8667 case C_ISO_8859_15: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_15; break;
8668 case C_WINDOWS_1252: branch = "Menu/Options/Encoding/Western/" CS_WINDOWS_1252; break;
8669 case C_ISO_8859_13: branch = "Menu/Options/Encoding/Baltic/" CS_ISO_8859_13; break;
8670 case C_ISO_8859_4: branch = "Menu/Options/Encoding/Baltic" CS_ISO_8859_4; break;
8671 case C_ISO_8859_8: branch = "Menu/Options/Encoding/Hebrew/" CS_ISO_8859_8; break;
8672 case C_WINDOWS_1255: branch = "Menu/Options/Encoding/Hebrew/" CS_WINDOWS_1255; break;
8673 case C_ISO_8859_6: branch = "Menu/Options/Encoding/Arabic/" CS_ISO_8859_6; break;
8674 case C_WINDOWS_1256: branch = "Menu/Options/Encoding/Arabic/" CS_WINDOWS_1256; break;
8675 case C_ISO_8859_5: branch = "Menu/Options/Encoding/Cyrillic/" CS_ISO_8859_5; break;
8676 case C_KOI8_R: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_R; break;
8677 case C_MACCYR: branch = "Menu/Options/Encoding/Cyrillic/" CS_MACCYR; break;
8678 case C_KOI8_U: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_U; break;
8679 case C_WINDOWS_1251: branch = "Menu/Options/Encoding/Cyrillic/" CS_WINDOWS_1251; break;
8680 case C_ISO_2022_JP: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP; break;
8681 case C_ISO_2022_JP_2: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP_2; break;
8682 case C_EUC_JP: branch = "Menu/Options/Encoding/Japanese/" CS_EUC_JP; break;
8683 case C_SHIFT_JIS: branch = "Menu/Options/Encoding/Japanese/" CS_SHIFT_JIS; break;
8684 case C_GB18030: branch = "Menu/Options/Encoding/Chinese/" CS_GB18030; break;
8685 case C_GB2312: branch = "Menu/Options/Encoding/Chinese/" CS_GB2312; break;
8686 case C_GBK: branch = "Menu/Options/Encoding/Chinese/" CS_GBK; break;
8687 case C_BIG5: branch = "Menu/Options/Encoding/Chinese/" CS_BIG5; break;
8688 case C_EUC_TW: branch = "Menu/Options/Encoding/Chinese/" CS_EUC_TW; break;
8689 case C_EUC_KR: branch = "Menu/Options/Encoding/Korean/" CS_EUC_KR; break;
8690 case C_ISO_2022_KR: branch = "Menu/Options/Encoding/Korean/" CS_ISO_2022_KR; break;
8691 case C_TIS_620: branch = "Menu/Options/Encoding/Thai/" CS_TIS_620; break;
8692 case C_WINDOWS_874: branch = "Menu/Options/Encoding/Thai/" CS_WINDOWS_874; break;
8693 default: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8695 cm_toggle_menu_set_active_full(compose->ui_manager, (gchar *)branch, TRUE);
8698 static void compose_set_template_menu(Compose *compose)
8700 GSList *tmpl_list, *cur;
8704 tmpl_list = template_get_config();
8706 menu = gtk_menu_new();
8708 gtk_menu_set_accel_group (GTK_MENU (menu),
8709 gtk_ui_manager_get_accel_group(compose->ui_manager));
8710 for (cur = tmpl_list; cur != NULL; cur = cur->next) {
8711 Template *tmpl = (Template *)cur->data;
8712 gchar *accel_path = NULL;
8713 item = gtk_menu_item_new_with_label(tmpl->name);
8714 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
8715 g_signal_connect(G_OBJECT(item), "activate",
8716 G_CALLBACK(compose_template_activate_cb),
8718 g_object_set_data(G_OBJECT(item), "template", tmpl);
8719 gtk_widget_show(item);
8720 accel_path = g_strconcat("<ComposeTemplates>" , "/", tmpl->name, NULL);
8721 gtk_menu_item_set_accel_path(GTK_MENU_ITEM(item), accel_path);
8725 gtk_widget_show(menu);
8726 gtk_menu_item_set_submenu(GTK_MENU_ITEM(compose->tmpl_menu), menu);
8729 void compose_update_actions_menu(Compose *compose)
8731 action_update_compose_menu(compose->ui_manager, "/Menu/Tools/Actions", compose);
8734 static void compose_update_privacy_systems_menu(Compose *compose)
8736 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8737 GSList *systems, *cur;
8739 GtkWidget *system_none;
8741 GtkWidget *privacy_menuitem = gtk_ui_manager_get_widget(compose->ui_manager, branch_path);
8742 GtkWidget *privacy_menu = gtk_menu_new();
8744 system_none = gtk_radio_menu_item_new_with_mnemonic(NULL, _("_None"));
8745 g_object_set_data_full(G_OBJECT(system_none), "privacy_system", NULL, NULL);
8747 g_signal_connect(G_OBJECT(system_none), "activate",
8748 G_CALLBACK(compose_set_privacy_system_cb), compose);
8750 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), system_none);
8751 gtk_widget_show(system_none);
8753 systems = privacy_get_system_ids();
8754 for (cur = systems; cur != NULL; cur = g_slist_next(cur)) {
8755 gchar *systemid = cur->data;
8757 group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(system_none));
8758 widget = gtk_radio_menu_item_new_with_label(group,
8759 privacy_system_get_name(systemid));
8760 g_object_set_data_full(G_OBJECT(widget), "privacy_system",
8761 g_strdup(systemid), g_free);
8762 g_signal_connect(G_OBJECT(widget), "activate",
8763 G_CALLBACK(compose_set_privacy_system_cb), compose);
8765 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), widget);
8766 gtk_widget_show(widget);
8769 g_slist_free(systems);
8770 gtk_menu_item_set_submenu(GTK_MENU_ITEM(privacy_menuitem), privacy_menu);
8771 gtk_widget_show_all(privacy_menu);
8772 gtk_widget_show_all(privacy_menuitem);
8775 void compose_reflect_prefs_all(void)
8780 for (cur = compose_list; cur != NULL; cur = cur->next) {
8781 compose = (Compose *)cur->data;
8782 compose_set_template_menu(compose);
8786 void compose_reflect_prefs_pixmap_theme(void)
8791 for (cur = compose_list; cur != NULL; cur = cur->next) {
8792 compose = (Compose *)cur->data;
8793 toolbar_update(TOOLBAR_COMPOSE, compose);
8797 static const gchar *compose_quote_char_from_context(Compose *compose)
8799 const gchar *qmark = NULL;
8801 cm_return_val_if_fail(compose != NULL, NULL);
8803 switch (compose->mode) {
8804 /* use forward-specific quote char */
8805 case COMPOSE_FORWARD:
8806 case COMPOSE_FORWARD_AS_ATTACH:
8807 case COMPOSE_FORWARD_INLINE:
8808 if (compose->folder && compose->folder->prefs &&
8809 compose->folder->prefs->forward_with_format)
8810 qmark = compose->folder->prefs->forward_quotemark;
8811 else if (compose->account->forward_with_format)
8812 qmark = compose->account->forward_quotemark;
8814 qmark = prefs_common.fw_quotemark;
8817 /* use reply-specific quote char in all other modes */
8819 if (compose->folder && compose->folder->prefs &&
8820 compose->folder->prefs->reply_with_format)
8821 qmark = compose->folder->prefs->reply_quotemark;
8822 else if (compose->account->reply_with_format)
8823 qmark = compose->account->reply_quotemark;
8825 qmark = prefs_common.quotemark;
8829 if (qmark == NULL || *qmark == '\0')
8835 static void compose_template_apply(Compose *compose, Template *tmpl,
8839 GtkTextBuffer *buffer;
8843 gchar *parsed_str = NULL;
8844 gint cursor_pos = 0;
8845 const gchar *err_msg = _("The body of the template has an error at line %d.");
8848 /* process the body */
8850 text = GTK_TEXT_VIEW(compose->text);
8851 buffer = gtk_text_view_get_buffer(text);
8854 qmark = compose_quote_char_from_context(compose);
8856 if (compose->replyinfo != NULL) {
8859 gtk_text_buffer_set_text(buffer, "", -1);
8860 mark = gtk_text_buffer_get_insert(buffer);
8861 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8863 parsed_str = compose_quote_fmt(compose, compose->replyinfo,
8864 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8866 } else if (compose->fwdinfo != NULL) {
8869 gtk_text_buffer_set_text(buffer, "", -1);
8870 mark = gtk_text_buffer_get_insert(buffer);
8871 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8873 parsed_str = compose_quote_fmt(compose, compose->fwdinfo,
8874 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8877 MsgInfo* dummyinfo = compose_msginfo_new_from_compose(compose);
8879 GtkTextIter start, end;
8882 gtk_text_buffer_get_start_iter(buffer, &start);
8883 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
8884 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
8886 /* clear the buffer now */
8888 gtk_text_buffer_set_text(buffer, "", -1);
8890 parsed_str = compose_quote_fmt(compose, dummyinfo,
8891 tmpl->value, qmark, tmp, FALSE, FALSE, err_msg);
8892 procmsg_msginfo_free( &dummyinfo );
8898 gtk_text_buffer_set_text(buffer, "", -1);
8899 mark = gtk_text_buffer_get_insert(buffer);
8900 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8903 if (replace && parsed_str && compose->account->auto_sig)
8904 compose_insert_sig(compose, FALSE);
8906 if (replace && parsed_str) {
8907 gtk_text_buffer_get_start_iter(buffer, &iter);
8908 gtk_text_buffer_place_cursor(buffer, &iter);
8912 cursor_pos = quote_fmt_get_cursor_pos();
8913 compose->set_cursor_pos = cursor_pos;
8914 if (cursor_pos == -1)
8916 gtk_text_buffer_get_start_iter(buffer, &iter);
8917 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
8918 gtk_text_buffer_place_cursor(buffer, &iter);
8921 /* process the other fields */
8923 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
8924 compose_template_apply_fields(compose, tmpl);
8925 quote_fmt_reset_vartable();
8926 quote_fmtlex_destroy();
8928 compose_changed_cb(NULL, compose);
8931 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
8932 gtkaspell_highlight_all(compose->gtkaspell);
8936 static void compose_template_apply_fields_error(const gchar *header)
8941 tr = g_strdup(C_("'%s' stands for a header name",
8942 "Template '%s' format error."));
8943 text = g_strdup_printf(tr, prefs_common_translated_header_name(header));
8944 alertpanel_error("%s", text);
8950 static void compose_template_apply_fields(Compose *compose, Template *tmpl)
8952 MsgInfo* dummyinfo = NULL;
8953 MsgInfo *msginfo = NULL;
8956 if (compose->replyinfo != NULL)
8957 msginfo = compose->replyinfo;
8958 else if (compose->fwdinfo != NULL)
8959 msginfo = compose->fwdinfo;
8961 dummyinfo = compose_msginfo_new_from_compose(compose);
8962 msginfo = dummyinfo;
8965 if (tmpl->from && *tmpl->from != '\0') {
8967 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8968 compose->gtkaspell);
8970 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8972 quote_fmt_scan_string(tmpl->from);
8975 buf = quote_fmt_get_buffer();
8977 compose_template_apply_fields_error("From");
8979 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
8982 quote_fmt_reset_vartable();
8983 quote_fmtlex_destroy();
8986 if (tmpl->to && *tmpl->to != '\0') {
8988 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8989 compose->gtkaspell);
8991 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8993 quote_fmt_scan_string(tmpl->to);
8996 buf = quote_fmt_get_buffer();
8998 compose_template_apply_fields_error("To");
9000 compose_entry_append(compose, buf, COMPOSE_TO, PREF_TEMPLATE);
9003 quote_fmt_reset_vartable();
9004 quote_fmtlex_destroy();
9007 if (tmpl->cc && *tmpl->cc != '\0') {
9009 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
9010 compose->gtkaspell);
9012 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
9014 quote_fmt_scan_string(tmpl->cc);
9017 buf = quote_fmt_get_buffer();
9019 compose_template_apply_fields_error("Cc");
9021 compose_entry_append(compose, buf, COMPOSE_CC, PREF_TEMPLATE);
9024 quote_fmt_reset_vartable();
9025 quote_fmtlex_destroy();
9028 if (tmpl->bcc && *tmpl->bcc != '\0') {
9030 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
9031 compose->gtkaspell);
9033 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
9035 quote_fmt_scan_string(tmpl->bcc);
9038 buf = quote_fmt_get_buffer();
9040 compose_template_apply_fields_error("Bcc");
9042 compose_entry_append(compose, buf, COMPOSE_BCC, PREF_TEMPLATE);
9045 quote_fmt_reset_vartable();
9046 quote_fmtlex_destroy();
9049 if (tmpl->replyto && *tmpl->replyto != '\0') {
9051 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
9052 compose->gtkaspell);
9054 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
9056 quote_fmt_scan_string(tmpl->replyto);
9059 buf = quote_fmt_get_buffer();
9061 compose_template_apply_fields_error("Reply-To");
9063 compose_entry_append(compose, buf, COMPOSE_REPLYTO, PREF_TEMPLATE);
9066 quote_fmt_reset_vartable();
9067 quote_fmtlex_destroy();
9070 /* process the subject */
9071 if (tmpl->subject && *tmpl->subject != '\0') {
9073 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
9074 compose->gtkaspell);
9076 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
9078 quote_fmt_scan_string(tmpl->subject);
9081 buf = quote_fmt_get_buffer();
9083 compose_template_apply_fields_error("Subject");
9085 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
9088 quote_fmt_reset_vartable();
9089 quote_fmtlex_destroy();
9092 procmsg_msginfo_free( &dummyinfo );
9095 static void compose_destroy(Compose *compose)
9097 GtkAllocation allocation;
9098 GtkTextBuffer *buffer;
9099 GtkClipboard *clipboard;
9101 compose_list = g_list_remove(compose_list, compose);
9104 gboolean enable = TRUE;
9105 g_slist_foreach(compose->passworded_ldap_servers,
9106 _ldap_srv_func, &enable);
9107 g_slist_free(compose->passworded_ldap_servers);
9110 if (compose->updating) {
9111 debug_print("danger, not destroying anything now\n");
9112 compose->deferred_destroy = TRUE;
9116 /* NOTE: address_completion_end() does nothing with the window
9117 * however this may change. */
9118 address_completion_end(compose->window);
9120 slist_free_strings_full(compose->to_list);
9121 slist_free_strings_full(compose->newsgroup_list);
9122 slist_free_strings_full(compose->header_list);
9124 slist_free_strings_full(extra_headers);
9125 extra_headers = NULL;
9127 compose->header_list = compose->newsgroup_list = compose->to_list = NULL;
9129 g_hash_table_destroy(compose->email_hashtable);
9131 hooks_unregister_hook(FOLDER_UPDATE_HOOKLIST,
9132 compose->folder_update_callback_id);
9134 procmsg_msginfo_free(&(compose->targetinfo));
9135 procmsg_msginfo_free(&(compose->replyinfo));
9136 procmsg_msginfo_free(&(compose->fwdinfo));
9138 g_free(compose->replyto);
9139 g_free(compose->cc);
9140 g_free(compose->bcc);
9141 g_free(compose->newsgroups);
9142 g_free(compose->followup_to);
9144 g_free(compose->ml_post);
9146 g_free(compose->inreplyto);
9147 g_free(compose->references);
9148 g_free(compose->msgid);
9149 g_free(compose->boundary);
9151 g_free(compose->redirect_filename);
9152 if (compose->undostruct)
9153 undo_destroy(compose->undostruct);
9155 g_free(compose->sig_str);
9157 g_free(compose->exteditor_file);
9159 g_free(compose->orig_charset);
9161 g_free(compose->privacy_system);
9162 g_free(compose->encdata);
9164 #ifndef USE_ALT_ADDRBOOK
9165 if (addressbook_get_target_compose() == compose)
9166 addressbook_set_target_compose(NULL);
9169 if (compose->gtkaspell) {
9170 gtkaspell_delete(compose->gtkaspell);
9171 compose->gtkaspell = NULL;
9175 if (!compose->batch) {
9176 gtk_widget_get_allocation(compose->window, &allocation);
9177 prefs_common.compose_width = allocation.width;
9178 prefs_common.compose_height = allocation.height;
9181 if (!gtk_widget_get_parent(compose->paned))
9182 gtk_widget_destroy(compose->paned);
9183 gtk_widget_destroy(compose->popupmenu);
9185 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
9186 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
9187 gtk_text_buffer_remove_selection_clipboard(buffer, clipboard);
9189 message_search_close(compose);
9190 gtk_widget_destroy(compose->window);
9191 toolbar_destroy(compose->toolbar);
9192 g_free(compose->toolbar);
9193 cm_mutex_free(compose->mutex);
9197 static void compose_attach_info_free(AttachInfo *ainfo)
9199 g_free(ainfo->file);
9200 g_free(ainfo->content_type);
9201 g_free(ainfo->name);
9202 g_free(ainfo->charset);
9206 static void compose_attach_update_label(Compose *compose)
9211 GtkTreeModel *model;
9215 if (compose == NULL)
9218 model = gtk_tree_view_get_model(GTK_TREE_VIEW(compose->attach_clist));
9219 if (!gtk_tree_model_get_iter_first(model, &iter)) {
9220 gtk_label_set_text(GTK_LABEL(compose->attach_label), "");
9224 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
9225 total_size = ainfo->size;
9226 while(gtk_tree_model_iter_next(model, &iter)) {
9227 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
9228 total_size += ainfo->size;
9231 text = g_strdup_printf(" (%d/%s)", i, to_human_readable(total_size));
9232 gtk_label_set_text(GTK_LABEL(compose->attach_label), text);
9236 static void compose_attach_remove_selected(GtkAction *action, gpointer data)
9238 Compose *compose = (Compose *)data;
9239 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
9240 GtkTreeSelection *selection;
9242 GtkTreeModel *model;
9244 selection = gtk_tree_view_get_selection(tree_view);
9245 sel = gtk_tree_selection_get_selected_rows(selection, &model);
9246 cm_return_if_fail(sel);
9248 for (cur = sel; cur != NULL; cur = cur->next) {
9249 GtkTreePath *path = cur->data;
9250 GtkTreeRowReference *ref = gtk_tree_row_reference_new
9253 gtk_tree_path_free(path);
9256 for (cur = sel; cur != NULL; cur = cur->next) {
9257 GtkTreeRowReference *ref = cur->data;
9258 GtkTreePath *path = gtk_tree_row_reference_get_path(ref);
9261 if (gtk_tree_model_get_iter(model, &iter, path))
9262 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
9264 gtk_tree_path_free(path);
9265 gtk_tree_row_reference_free(ref);
9269 compose_attach_update_label(compose);
9272 static struct _AttachProperty
9275 GtkWidget *mimetype_entry;
9276 GtkWidget *encoding_optmenu;
9277 GtkWidget *path_entry;
9278 GtkWidget *filename_entry;
9280 GtkWidget *cancel_btn;
9283 static void gtk_tree_path_free_(gpointer ptr, gpointer data)
9285 gtk_tree_path_free((GtkTreePath *)ptr);
9288 static void compose_attach_property(GtkAction *action, gpointer data)
9290 Compose *compose = (Compose *)data;
9291 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
9293 GtkComboBox *optmenu;
9294 GtkTreeSelection *selection;
9296 GtkTreeModel *model;
9299 static gboolean cancelled;
9301 /* only if one selected */
9302 selection = gtk_tree_view_get_selection(tree_view);
9303 if (gtk_tree_selection_count_selected_rows(selection) != 1)
9306 sel = gtk_tree_selection_get_selected_rows(selection, &model);
9307 cm_return_if_fail(sel);
9309 path = (GtkTreePath *) sel->data;
9310 gtk_tree_model_get_iter(model, &iter, path);
9311 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
9314 g_list_foreach(sel, gtk_tree_path_free_, NULL);
9320 if (!attach_prop.window)
9321 compose_attach_property_create(&cancelled);
9322 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), TRUE);
9323 gtk_widget_grab_focus(attach_prop.ok_btn);
9324 gtk_widget_show(attach_prop.window);
9325 gtk_window_set_transient_for(GTK_WINDOW(attach_prop.window),
9326 GTK_WINDOW(compose->window));
9328 optmenu = GTK_COMBO_BOX(attach_prop.encoding_optmenu);
9329 if (ainfo->encoding == ENC_UNKNOWN)
9330 combobox_select_by_data(optmenu, ENC_BASE64);
9332 combobox_select_by_data(optmenu, ainfo->encoding);
9334 gtk_entry_set_text(GTK_ENTRY(attach_prop.mimetype_entry),
9335 ainfo->content_type ? ainfo->content_type : "");
9336 gtk_entry_set_text(GTK_ENTRY(attach_prop.path_entry),
9337 ainfo->file ? ainfo->file : "");
9338 gtk_entry_set_text(GTK_ENTRY(attach_prop.filename_entry),
9339 ainfo->name ? ainfo->name : "");
9342 const gchar *entry_text;
9344 gchar *cnttype = NULL;
9351 gtk_widget_hide(attach_prop.window);
9352 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), FALSE);
9357 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.mimetype_entry));
9358 if (*entry_text != '\0') {
9361 text = g_strstrip(g_strdup(entry_text));
9362 if ((p = strchr(text, '/')) && !strchr(p + 1, '/')) {
9363 cnttype = g_strdup(text);
9366 alertpanel_error(_("Invalid MIME type."));
9372 ainfo->encoding = combobox_get_active_data(optmenu);
9374 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.path_entry));
9375 if (*entry_text != '\0') {
9376 if (is_file_exist(entry_text) &&
9377 (size = get_file_size(entry_text)) > 0)
9378 file = g_strdup(entry_text);
9381 (_("File doesn't exist or is empty."));
9387 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.filename_entry));
9388 if (*entry_text != '\0') {
9389 g_free(ainfo->name);
9390 ainfo->name = g_strdup(entry_text);
9394 g_free(ainfo->content_type);
9395 ainfo->content_type = cnttype;
9398 g_free(ainfo->file);
9402 ainfo->size = (goffset)size;
9404 /* update tree store */
9405 text = to_human_readable(ainfo->size);
9406 gtk_tree_model_get_iter(model, &iter, path);
9407 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
9408 COL_MIMETYPE, ainfo->content_type,
9410 COL_NAME, ainfo->name,
9411 COL_CHARSET, ainfo->charset,
9417 gtk_tree_path_free(path);
9420 #define SET_LABEL_AND_ENTRY(str, entry, top) \
9422 label = gtk_label_new(str); \
9423 gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), \
9424 GTK_FILL, 0, 0, 0); \
9425 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); \
9427 entry = gtk_entry_new(); \
9428 gtk_table_attach(GTK_TABLE(table), entry, 1, 2, top, (top + 1), \
9429 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); \
9432 static void compose_attach_property_create(gboolean *cancelled)
9438 GtkWidget *mimetype_entry;
9441 GtkListStore *optmenu_menu;
9442 GtkWidget *path_entry;
9443 GtkWidget *filename_entry;
9446 GtkWidget *cancel_btn;
9447 GList *mime_type_list, *strlist;
9450 debug_print("Creating attach_property window...\n");
9452 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose_attach_property");
9453 gtk_widget_set_size_request(window, 480, -1);
9454 gtk_container_set_border_width(GTK_CONTAINER(window), 8);
9455 gtk_window_set_title(GTK_WINDOW(window), _("Properties"));
9456 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
9457 gtk_window_set_type_hint(GTK_WINDOW(window), GDK_WINDOW_TYPE_HINT_DIALOG);
9458 g_signal_connect(G_OBJECT(window), "delete_event",
9459 G_CALLBACK(attach_property_delete_event),
9461 g_signal_connect(G_OBJECT(window), "key_press_event",
9462 G_CALLBACK(attach_property_key_pressed),
9465 vbox = gtk_vbox_new(FALSE, 8);
9466 gtk_container_add(GTK_CONTAINER(window), vbox);
9468 table = gtk_table_new(4, 2, FALSE);
9469 gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
9470 gtk_table_set_row_spacings(GTK_TABLE(table), 8);
9471 gtk_table_set_col_spacings(GTK_TABLE(table), 8);
9473 label = gtk_label_new(_("MIME type"));
9474 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, (0 + 1),
9476 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
9477 mimetype_entry = gtk_combo_box_text_new_with_entry();
9478 gtk_table_attach(GTK_TABLE(table), mimetype_entry, 1, 2, 0, (0 + 1),
9479 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
9481 /* stuff with list */
9482 mime_type_list = procmime_get_mime_type_list();
9484 for (; mime_type_list != NULL; mime_type_list = mime_type_list->next) {
9485 MimeType *type = (MimeType *) mime_type_list->data;
9488 tmp = g_strdup_printf("%s/%s", type->type, type->sub_type);
9490 if (g_list_find_custom(strlist, tmp, (GCompareFunc)g_strcmp0))
9493 strlist = g_list_insert_sorted(strlist, (gpointer)tmp,
9494 (GCompareFunc)g_strcmp0);
9497 for (mime_type_list = strlist; mime_type_list != NULL;
9498 mime_type_list = mime_type_list->next) {
9499 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(mimetype_entry), mime_type_list->data);
9500 g_free(mime_type_list->data);
9502 g_list_free(strlist);
9503 gtk_combo_box_set_active(GTK_COMBO_BOX(mimetype_entry), 0);
9504 mimetype_entry = gtk_bin_get_child(GTK_BIN((mimetype_entry)));
9506 label = gtk_label_new(_("Encoding"));
9507 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2,
9509 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
9511 hbox = gtk_hbox_new(FALSE, 0);
9512 gtk_table_attach(GTK_TABLE(table), hbox, 1, 2, 1, 2,
9513 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
9515 optmenu = gtkut_sc_combobox_create(NULL, TRUE);
9516 optmenu_menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
9518 COMBOBOX_ADD(optmenu_menu, "7bit", ENC_7BIT);
9519 COMBOBOX_ADD(optmenu_menu, "8bit", ENC_8BIT);
9520 COMBOBOX_ADD(optmenu_menu, "quoted-printable", ENC_QUOTED_PRINTABLE);
9521 COMBOBOX_ADD(optmenu_menu, "base64", ENC_BASE64);
9522 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), 0);
9524 gtk_box_pack_start(GTK_BOX(hbox), optmenu, TRUE, TRUE, 0);
9526 SET_LABEL_AND_ENTRY(_("Path"), path_entry, 2);
9527 SET_LABEL_AND_ENTRY(_("File name"), filename_entry, 3);
9529 gtkut_stock_button_set_create(&hbbox, &cancel_btn, GTK_STOCK_CANCEL,
9530 &ok_btn, GTK_STOCK_OK,
9532 gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
9533 gtk_widget_grab_default(ok_btn);
9535 g_signal_connect(G_OBJECT(ok_btn), "clicked",
9536 G_CALLBACK(attach_property_ok),
9538 g_signal_connect(G_OBJECT(cancel_btn), "clicked",
9539 G_CALLBACK(attach_property_cancel),
9542 gtk_widget_show_all(vbox);
9544 attach_prop.window = window;
9545 attach_prop.mimetype_entry = mimetype_entry;
9546 attach_prop.encoding_optmenu = optmenu;
9547 attach_prop.path_entry = path_entry;
9548 attach_prop.filename_entry = filename_entry;
9549 attach_prop.ok_btn = ok_btn;
9550 attach_prop.cancel_btn = cancel_btn;
9553 #undef SET_LABEL_AND_ENTRY
9555 static void attach_property_ok(GtkWidget *widget, gboolean *cancelled)
9561 static void attach_property_cancel(GtkWidget *widget, gboolean *cancelled)
9567 static gint attach_property_delete_event(GtkWidget *widget, GdkEventAny *event,
9568 gboolean *cancelled)
9576 static gboolean attach_property_key_pressed(GtkWidget *widget,
9578 gboolean *cancelled)
9580 if (event && event->keyval == GDK_KEY_Escape) {
9584 if (event && event->keyval == GDK_KEY_Return) {
9592 static void compose_exec_ext_editor(Compose *compose)
9597 GdkNativeWindow socket_wid = 0;
9601 tmp = g_strdup_printf("%s%ctmpmsg.%p", get_tmp_dir(),
9602 G_DIR_SEPARATOR, compose);
9604 if (compose_get_ext_editor_uses_socket()) {
9605 /* Only allow one socket */
9606 if (compose->exteditor_socket != NULL) {
9607 if (gtk_widget_is_focus(compose->exteditor_socket)) {
9608 /* Move the focus off of the socket */
9609 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9614 /* Create the receiving GtkSocket */
9615 socket = gtk_socket_new ();
9616 g_signal_connect (GTK_OBJECT(socket), "plug-removed",
9617 G_CALLBACK(compose_ext_editor_plug_removed_cb),
9619 gtk_box_pack_start(GTK_BOX(compose->edit_vbox), socket, TRUE, TRUE, 0);
9620 gtk_widget_set_size_request(socket, prefs_common.compose_width, -1);
9621 /* Realize the socket so that we can use its ID */
9622 gtk_widget_realize(socket);
9623 socket_wid = gtk_socket_get_id(GTK_SOCKET (socket));
9624 compose->exteditor_socket = socket;
9627 if (pipe(pipe_fds) < 0) {
9633 if ((pid = fork()) < 0) {
9640 /* close the write side of the pipe */
9643 compose->exteditor_file = g_strdup(tmp);
9644 compose->exteditor_pid = pid;
9646 compose_set_ext_editor_sensitive(compose, FALSE);
9649 compose->exteditor_ch = g_io_channel_unix_new(pipe_fds[0]);
9651 compose->exteditor_ch = g_io_channel_win32_new_fd(pipe_fds[0]);
9653 compose->exteditor_tag = g_io_add_watch(compose->exteditor_ch,
9657 } else { /* process-monitoring process */
9663 /* close the read side of the pipe */
9666 if (compose_write_body_to_file(compose, tmp) < 0) {
9667 fd_write_all(pipe_fds[1], "2\n", 2);
9671 pid_ed = compose_exec_ext_editor_real(tmp, socket_wid);
9673 fd_write_all(pipe_fds[1], "1\n", 2);
9677 /* wait until editor is terminated */
9678 waitpid(pid_ed, NULL, 0);
9680 fd_write_all(pipe_fds[1], "0\n", 2);
9687 #endif /* G_OS_UNIX */
9690 static gboolean compose_can_autosave(Compose *compose)
9692 if (compose->privacy_system && compose->use_encryption)
9693 return prefs_common.autosave && prefs_common.autosave_encrypted;
9695 return prefs_common.autosave;
9699 static gboolean compose_get_ext_editor_cmd_valid()
9701 gboolean has_s = FALSE;
9702 gboolean has_w = FALSE;
9703 const gchar *p = prefs_common_get_ext_editor_cmd();
9706 while ((p = strchr(p, '%'))) {
9712 } else if (*p == 'w') {
9723 static gint compose_exec_ext_editor_real(const gchar *file, GdkNativeWindow socket_wid)
9730 cm_return_val_if_fail(file != NULL, -1);
9732 if ((pid = fork()) < 0) {
9737 if (pid != 0) return pid;
9739 /* grandchild process */
9741 if (setpgid(0, getppid()))
9744 if (compose_get_ext_editor_cmd_valid()) {
9745 if (compose_get_ext_editor_uses_socket()) {
9746 p = g_strdup(prefs_common_get_ext_editor_cmd());
9747 s = strstr(p, "%w");
9749 if (strstr(p, "%s") < s)
9750 buf = g_strdup_printf(p, file, socket_wid);
9752 buf = g_strdup_printf(p, socket_wid, file);
9755 buf = g_strdup_printf(prefs_common_get_ext_editor_cmd(), file);
9758 if (prefs_common_get_ext_editor_cmd())
9759 g_warning("External editor command-line is invalid: '%s'",
9760 prefs_common_get_ext_editor_cmd());
9761 buf = g_strdup_printf(DEFAULT_EDITOR_CMD, file);
9764 cmdline = strsplit_with_quote(buf, " ", 0);
9766 execvp(cmdline[0], cmdline);
9769 g_strfreev(cmdline);
9774 static gboolean compose_ext_editor_kill(Compose *compose)
9776 pid_t pgid = compose->exteditor_pid * -1;
9779 ret = kill(pgid, 0);
9781 if (ret == 0 || (ret == -1 && EPERM == errno)) {
9785 msg = g_strdup_printf
9786 (_("The external editor is still working.\n"
9787 "Force terminating the process?\n"
9788 "process group id: %d"), -pgid);
9789 val = alertpanel_full(_("Notice"), msg, GTK_STOCK_NO, GTK_STOCK_YES,
9790 NULL, ALERTFOCUS_FIRST, FALSE, NULL,
9795 if (val == G_ALERTALTERNATE) {
9796 g_source_remove(compose->exteditor_tag);
9797 g_io_channel_shutdown(compose->exteditor_ch,
9799 g_io_channel_unref(compose->exteditor_ch);
9801 if (kill(pgid, SIGTERM) < 0) perror("kill");
9802 waitpid(compose->exteditor_pid, NULL, 0);
9804 g_warning("Terminated process group id: %d. "
9805 "Temporary file: %s", -pgid, compose->exteditor_file);
9807 compose_set_ext_editor_sensitive(compose, TRUE);
9809 g_free(compose->exteditor_file);
9810 compose->exteditor_file = NULL;
9811 compose->exteditor_pid = -1;
9812 compose->exteditor_ch = NULL;
9813 compose->exteditor_tag = -1;
9821 static gboolean compose_input_cb(GIOChannel *source, GIOCondition condition,
9825 Compose *compose = (Compose *)data;
9828 debug_print("Compose: input from monitoring process\n");
9830 if (g_io_channel_read_chars(source, buf, sizeof(buf), &bytes_read, NULL) != G_IO_STATUS_NORMAL) {
9835 g_io_channel_shutdown(source, FALSE, NULL);
9836 g_io_channel_unref(source);
9838 waitpid(compose->exteditor_pid, NULL, 0);
9840 if (buf[0] == '0') { /* success */
9841 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9842 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
9843 GtkTextIter start, end;
9846 gtk_text_buffer_set_text(buffer, "", -1);
9847 compose_insert_file(compose, compose->exteditor_file);
9848 compose_changed_cb(NULL, compose);
9850 /* Check if we should save the draft or not */
9851 if (compose_can_autosave(compose))
9852 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
9854 if (claws_unlink(compose->exteditor_file) < 0)
9855 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9857 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
9858 gtk_text_buffer_get_start_iter(buffer, &start);
9859 gtk_text_buffer_get_end_iter(buffer, &end);
9860 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
9861 if (chars && strlen(chars) > 0)
9862 compose->modified = TRUE;
9864 } else if (buf[0] == '1') { /* failed */
9865 g_warning("Couldn't exec external editor");
9866 if (claws_unlink(compose->exteditor_file) < 0)
9867 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9868 } else if (buf[0] == '2') {
9869 g_warning("Couldn't write to file");
9870 } else if (buf[0] == '3') {
9871 g_warning("Pipe read failed");
9874 compose_set_ext_editor_sensitive(compose, TRUE);
9876 g_free(compose->exteditor_file);
9877 compose->exteditor_file = NULL;
9878 compose->exteditor_pid = -1;
9879 compose->exteditor_ch = NULL;
9880 compose->exteditor_tag = -1;
9881 if (compose->exteditor_socket) {
9882 gtk_widget_destroy(compose->exteditor_socket);
9883 compose->exteditor_socket = NULL;
9890 static char *ext_editor_menu_entries[] = {
9891 "Menu/Message/Send",
9892 "Menu/Message/SendLater",
9893 "Menu/Message/InsertFile",
9894 "Menu/Message/InsertSig",
9895 "Menu/Message/ReplaceSig",
9896 "Menu/Message/Save",
9897 "Menu/Message/Print",
9902 "Menu/Tools/ShowRuler",
9903 "Menu/Tools/Actions",
9908 static void compose_set_ext_editor_sensitive(Compose *compose,
9913 for (i = 0; ext_editor_menu_entries[i]; ++i) {
9914 cm_menu_set_sensitive_full(compose->ui_manager,
9915 ext_editor_menu_entries[i], sensitive);
9918 if (compose_get_ext_editor_uses_socket()) {
9920 if (compose->exteditor_socket)
9921 gtk_widget_hide(compose->exteditor_socket);
9922 gtk_widget_show(compose->scrolledwin);
9923 if (prefs_common.show_ruler)
9924 gtk_widget_show(compose->ruler_hbox);
9925 /* Fix the focus, as it doesn't go anywhere when the
9926 * socket is hidden or destroyed */
9927 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9929 g_assert (compose->exteditor_socket != NULL);
9930 /* Fix the focus, as it doesn't go anywhere when the
9931 * edit box is hidden */
9932 if (gtk_widget_is_focus(compose->text))
9933 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9934 gtk_widget_hide(compose->scrolledwin);
9935 gtk_widget_hide(compose->ruler_hbox);
9936 gtk_widget_show(compose->exteditor_socket);
9939 gtk_widget_set_sensitive(compose->text, sensitive);
9941 if (compose->toolbar->send_btn)
9942 gtk_widget_set_sensitive(compose->toolbar->send_btn, sensitive);
9943 if (compose->toolbar->sendl_btn)
9944 gtk_widget_set_sensitive(compose->toolbar->sendl_btn, sensitive);
9945 if (compose->toolbar->draft_btn)
9946 gtk_widget_set_sensitive(compose->toolbar->draft_btn, sensitive);
9947 if (compose->toolbar->insert_btn)
9948 gtk_widget_set_sensitive(compose->toolbar->insert_btn, sensitive);
9949 if (compose->toolbar->sig_btn)
9950 gtk_widget_set_sensitive(compose->toolbar->sig_btn, sensitive);
9951 if (compose->toolbar->exteditor_btn)
9952 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, sensitive);
9953 if (compose->toolbar->linewrap_current_btn)
9954 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, sensitive);
9955 if (compose->toolbar->linewrap_all_btn)
9956 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, sensitive);
9959 static gboolean compose_get_ext_editor_uses_socket()
9961 return (prefs_common_get_ext_editor_cmd() &&
9962 strstr(prefs_common_get_ext_editor_cmd(), "%w"));
9965 static gboolean compose_ext_editor_plug_removed_cb(GtkSocket *socket, Compose *compose)
9967 compose->exteditor_socket = NULL;
9968 /* returning FALSE allows destruction of the socket */
9971 #endif /* G_OS_UNIX */
9974 * compose_undo_state_changed:
9976 * Change the sensivity of the menuentries undo and redo
9978 static void compose_undo_state_changed(UndoMain *undostruct, gint undo_state,
9979 gint redo_state, gpointer data)
9981 Compose *compose = (Compose *)data;
9983 switch (undo_state) {
9984 case UNDO_STATE_TRUE:
9985 if (!undostruct->undo_state) {
9986 undostruct->undo_state = TRUE;
9987 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", TRUE);
9990 case UNDO_STATE_FALSE:
9991 if (undostruct->undo_state) {
9992 undostruct->undo_state = FALSE;
9993 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
9996 case UNDO_STATE_UNCHANGED:
9998 case UNDO_STATE_REFRESH:
9999 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", undostruct->undo_state);
10002 g_warning("Undo state not recognized");
10006 switch (redo_state) {
10007 case UNDO_STATE_TRUE:
10008 if (!undostruct->redo_state) {
10009 undostruct->redo_state = TRUE;
10010 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", TRUE);
10013 case UNDO_STATE_FALSE:
10014 if (undostruct->redo_state) {
10015 undostruct->redo_state = FALSE;
10016 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
10019 case UNDO_STATE_UNCHANGED:
10021 case UNDO_STATE_REFRESH:
10022 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", undostruct->redo_state);
10025 g_warning("Redo state not recognized");
10030 /* callback functions */
10032 static void compose_notebook_size_alloc(GtkNotebook *notebook,
10033 GtkAllocation *allocation,
10036 prefs_common.compose_notebook_height = gtk_paned_get_position(paned);
10039 /* compose_edit_size_alloc() - called when resized. don't know whether Gtk
10040 * includes "non-client" (windows-izm) in calculation, so this calculation
10041 * may not be accurate.
10043 static gboolean compose_edit_size_alloc(GtkEditable *widget,
10044 GtkAllocation *allocation,
10045 GtkSHRuler *shruler)
10047 if (prefs_common.show_ruler) {
10048 gint char_width = 0, char_height = 0;
10049 gint line_width_in_chars;
10051 gtkut_get_font_size(GTK_WIDGET(widget),
10052 &char_width, &char_height);
10053 line_width_in_chars =
10054 (allocation->width - allocation->x) / char_width;
10056 /* got the maximum */
10057 gtk_shruler_set_range(GTK_SHRULER(shruler),
10058 0.0, line_width_in_chars, 0);
10067 ComposePrefType type;
10068 gboolean entry_marked;
10069 } HeaderEntryState;
10071 static void account_activated(GtkComboBox *optmenu, gpointer data)
10073 Compose *compose = (Compose *)data;
10076 gchar *folderidentifier;
10077 gint account_id = 0;
10078 GtkTreeModel *menu;
10080 GSList *list, *saved_list = NULL;
10081 HeaderEntryState *state;
10083 /* Get ID of active account in the combo box */
10084 menu = gtk_combo_box_get_model(optmenu);
10085 cm_return_if_fail(gtk_combo_box_get_active_iter(optmenu, &iter));
10086 gtk_tree_model_get(menu, &iter, 1, &account_id, -1);
10088 ac = account_find_from_id(account_id);
10089 cm_return_if_fail(ac != NULL);
10091 if (ac != compose->account) {
10092 compose_select_account(compose, ac, FALSE);
10094 for (list = compose->header_list; list; list = list->next) {
10095 ComposeHeaderEntry *hentry=(ComposeHeaderEntry *)list->data;
10097 if (hentry->type == PREF_ACCOUNT || !list->next) {
10098 compose_destroy_headerentry(compose, hentry);
10101 state = g_malloc0(sizeof(HeaderEntryState));
10102 state->header = gtk_editable_get_chars(GTK_EDITABLE(
10103 gtk_bin_get_child(GTK_BIN(hentry->combo))), 0, -1);
10104 state->entry = gtk_editable_get_chars(
10105 GTK_EDITABLE(hentry->entry), 0, -1);
10106 state->type = hentry->type;
10108 saved_list = g_slist_append(saved_list, state);
10109 compose_destroy_headerentry(compose, hentry);
10112 compose->header_last = NULL;
10113 g_slist_free(compose->header_list);
10114 compose->header_list = NULL;
10115 compose->header_nextrow = 1;
10116 compose_create_header_entry(compose);
10118 if (ac->set_autocc && ac->auto_cc)
10119 compose_entry_append(compose, ac->auto_cc,
10120 COMPOSE_CC, PREF_ACCOUNT);
10121 if (ac->set_autobcc && ac->auto_bcc)
10122 compose_entry_append(compose, ac->auto_bcc,
10123 COMPOSE_BCC, PREF_ACCOUNT);
10124 if (ac->set_autoreplyto && ac->auto_replyto)
10125 compose_entry_append(compose, ac->auto_replyto,
10126 COMPOSE_REPLYTO, PREF_ACCOUNT);
10128 for (list = saved_list; list; list = list->next) {
10129 state = (HeaderEntryState *) list->data;
10131 compose_add_header_entry(compose, state->header,
10132 state->entry, state->type);
10134 g_free(state->header);
10135 g_free(state->entry);
10138 g_slist_free(saved_list);
10140 combobox_select_by_data(GTK_COMBO_BOX(compose->header_last->combo),
10141 (ac->protocol == A_NNTP) ?
10142 COMPOSE_NEWSGROUPS : COMPOSE_TO);
10145 /* Set message save folder */
10146 compose_set_save_to(compose, NULL);
10147 if (compose->folder && compose->folder->prefs && compose->folder->prefs->save_copy_to_folder) {
10148 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
10149 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo), TRUE);
10150 folderidentifier = folder_item_get_identifier(compose->folder);
10151 compose_set_save_to(compose, folderidentifier);
10152 g_free(folderidentifier);
10153 } else if (account_get_special_folder(compose->account, F_OUTBOX)) {
10154 if (compose->account->set_sent_folder || prefs_common.savemsg)
10155 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
10157 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), FALSE);
10158 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo), TRUE);
10159 folderidentifier = folder_item_get_identifier(account_get_special_folder
10160 (compose->account, F_OUTBOX));
10161 compose_set_save_to(compose, folderidentifier);
10162 g_free(folderidentifier);
10166 static void attach_selected(GtkTreeView *tree_view, GtkTreePath *tree_path,
10167 GtkTreeViewColumn *column, Compose *compose)
10169 compose_attach_property(NULL, compose);
10172 static gboolean attach_button_pressed(GtkWidget *widget, GdkEventButton *event,
10175 Compose *compose = (Compose *)data;
10176 GtkTreeSelection *attach_selection;
10177 gint attach_nr_selected;
10180 if (!event) return FALSE;
10182 if (event->button == 3) {
10183 attach_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
10184 attach_nr_selected = gtk_tree_selection_count_selected_rows(attach_selection);
10186 /* If no rows, or just one row is selected, right-click should
10187 * open menu relevant to the row being right-clicked on. We
10188 * achieve that by selecting the clicked row first. If more
10189 * than one row is selected, we shouldn't modify the selection,
10190 * as user may want to remove selected rows (attachments). */
10191 if (attach_nr_selected < 2) {
10192 gtk_tree_selection_unselect_all(attach_selection);
10193 attach_nr_selected = 0;
10194 gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget),
10195 event->x, event->y, &path, NULL, NULL, NULL);
10196 if (path != NULL) {
10197 gtk_tree_selection_select_path(attach_selection, path);
10198 gtk_tree_path_free(path);
10199 attach_nr_selected++;
10203 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", (attach_nr_selected > 0));
10204 /* Properties menu item makes no sense with more than one row
10205 * selected, the properties dialog can only edit one attachment. */
10206 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", (attach_nr_selected == 1));
10208 gtk_menu_popup(GTK_MENU(compose->popupmenu), NULL, NULL,
10209 NULL, NULL, event->button, event->time);
10216 static gboolean attach_key_pressed(GtkWidget *widget, GdkEventKey *event,
10219 Compose *compose = (Compose *)data;
10221 if (!event) return FALSE;
10223 switch (event->keyval) {
10224 case GDK_KEY_Delete:
10225 compose_attach_remove_selected(NULL, compose);
10231 static void compose_allow_user_actions (Compose *compose, gboolean allow)
10233 toolbar_comp_set_sensitive(compose, allow);
10234 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message", allow);
10235 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", allow);
10237 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", allow);
10239 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", allow);
10240 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools", allow);
10241 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Help", allow);
10243 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), allow);
10247 static void compose_send_cb(GtkAction *action, gpointer data)
10249 Compose *compose = (Compose *)data;
10252 if (compose->exteditor_tag != -1) {
10253 debug_print("ignoring send: external editor still open\n");
10257 if (prefs_common.work_offline &&
10258 !inc_offline_should_override(TRUE,
10259 _("Claws Mail needs network access in order "
10260 "to send this email.")))
10263 if (compose->draft_timeout_tag >= 0) { /* CLAWS: disable draft timeout */
10264 g_source_remove(compose->draft_timeout_tag);
10265 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
10268 compose_send(compose);
10271 static void compose_send_later_cb(GtkAction *action, gpointer data)
10273 Compose *compose = (Compose *)data;
10274 ComposeQueueResult val;
10277 compose_allow_user_actions(compose, FALSE);
10278 val = compose_queue_sub(compose, NULL, NULL, NULL, TRUE, TRUE);
10279 compose_allow_user_actions(compose, TRUE);
10282 if (val == COMPOSE_QUEUE_SUCCESS) {
10283 compose_close(compose);
10285 _display_queue_error(val);
10288 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
10291 #define DRAFTED_AT_EXIT "drafted_at_exit"
10292 static void compose_register_draft(MsgInfo *info)
10294 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10295 DRAFTED_AT_EXIT, NULL);
10296 FILE *fp = claws_fopen(filepath, "ab");
10299 fprintf(fp, "%s\t%d\n", folder_item_get_identifier(info->folder),
10307 gboolean compose_draft (gpointer data, guint action)
10309 Compose *compose = (Compose *)data;
10314 MsgFlags flag = {0, 0};
10315 static gboolean lock = FALSE;
10316 MsgInfo *newmsginfo;
10318 gboolean target_locked = FALSE;
10319 gboolean err = FALSE;
10321 if (lock) return FALSE;
10323 if (compose->sending)
10326 draft = account_get_special_folder(compose->account, F_DRAFT);
10327 cm_return_val_if_fail(draft != NULL, FALSE);
10329 if (!g_mutex_trylock(compose->mutex)) {
10330 /* we don't want to lock the mutex once it's available,
10331 * because as the only other part of compose.c locking
10332 * it is compose_close - which means once unlocked,
10333 * the compose struct will be freed */
10334 debug_print("couldn't lock mutex, probably sending\n");
10340 tmp = g_strdup_printf("%s%cdraft.%p", get_tmp_dir(),
10341 G_DIR_SEPARATOR, compose);
10342 if ((fp = claws_fopen(tmp, "wb")) == NULL) {
10343 FILE_OP_ERROR(tmp, "claws_fopen");
10347 /* chmod for security */
10348 if (change_file_mode_rw(fp, tmp) < 0) {
10349 FILE_OP_ERROR(tmp, "chmod");
10350 g_warning("can't change file mode");
10353 /* Save draft infos */
10354 err |= (fprintf(fp, "X-Claws-Account-Id:%d\n", compose->account->account_id) < 0);
10355 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
10357 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
10358 gchar *savefolderid;
10360 savefolderid = compose_get_save_to(compose);
10361 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
10362 g_free(savefolderid);
10364 if (compose->return_receipt) {
10365 err |= (fprintf(fp, "RRCPT:1\n") < 0);
10367 if (compose->privacy_system) {
10368 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
10369 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
10370 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
10373 /* Message-ID of message replying to */
10374 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
10375 gchar *folderid = NULL;
10377 if (compose->replyinfo->folder)
10378 folderid = folder_item_get_identifier(compose->replyinfo->folder);
10379 if (folderid == NULL)
10380 folderid = g_strdup("NULL");
10382 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
10385 /* Message-ID of message forwarding to */
10386 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
10387 gchar *folderid = NULL;
10389 if (compose->fwdinfo->folder)
10390 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
10391 if (folderid == NULL)
10392 folderid = g_strdup("NULL");
10394 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
10398 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
10399 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
10401 sheaders = compose_get_manual_headers_info(compose);
10402 err |= (fprintf(fp, "X-Claws-Manual-Headers:%s\n", sheaders) < 0);
10405 /* end of headers */
10406 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
10413 if (compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_STORE, action != COMPOSE_AUTO_SAVE) < 0) {
10417 if (claws_safe_fclose(fp) == EOF) {
10421 flag.perm_flags = MSG_NEW|MSG_UNREAD;
10422 if (compose->targetinfo) {
10423 target_locked = MSG_IS_LOCKED(compose->targetinfo->flags);
10425 flag.perm_flags |= MSG_LOCKED;
10427 flag.tmp_flags = MSG_DRAFT;
10429 folder_item_scan(draft);
10430 if ((msgnum = folder_item_add_msg(draft, tmp, &flag, TRUE)) < 0) {
10431 MsgInfo *tmpinfo = NULL;
10432 debug_print("didn't get msgnum after adding draft [%s]\n", compose->msgid?compose->msgid:"no msgid");
10433 if (compose->msgid) {
10434 tmpinfo = folder_item_get_msginfo_by_msgid(draft, compose->msgid);
10437 msgnum = tmpinfo->msgnum;
10438 procmsg_msginfo_free(&tmpinfo);
10439 debug_print("got draft msgnum %d from scanning\n", msgnum);
10441 debug_print("didn't get draft msgnum after scanning\n");
10444 debug_print("got draft msgnum %d from adding\n", msgnum);
10450 if (action != COMPOSE_AUTO_SAVE) {
10451 if (action != COMPOSE_DRAFT_FOR_EXIT)
10452 alertpanel_error(_("Could not save draft."));
10455 gtkut_window_popup(compose->window);
10456 val = alertpanel_full(_("Could not save draft"),
10457 _("Could not save draft.\n"
10458 "Do you want to cancel exit or discard this email?"),
10459 _("_Cancel exit"), _("_Discard email"), NULL, ALERTFOCUS_FIRST,
10460 FALSE, NULL, ALERT_QUESTION);
10461 if (val == G_ALERTALTERNATE) {
10463 g_mutex_unlock(compose->mutex); /* must be done before closing */
10464 compose_close(compose);
10468 g_mutex_unlock(compose->mutex); /* must be done before closing */
10477 if (compose->mode == COMPOSE_REEDIT) {
10478 compose_remove_reedit_target(compose, TRUE);
10481 newmsginfo = folder_item_get_msginfo(draft, msgnum);
10484 procmsg_msginfo_unset_flags(newmsginfo, ~0, ~0);
10486 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD|MSG_LOCKED, MSG_DRAFT);
10488 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD, MSG_DRAFT);
10489 if (compose_use_attach(compose) && action != COMPOSE_AUTO_SAVE)
10490 procmsg_msginfo_set_flags(newmsginfo, 0,
10491 MSG_HAS_ATTACHMENT);
10493 if (action == COMPOSE_DRAFT_FOR_EXIT) {
10494 compose_register_draft(newmsginfo);
10496 procmsg_msginfo_free(&newmsginfo);
10499 folder_item_scan(draft);
10501 if (action == COMPOSE_QUIT_EDITING || action == COMPOSE_DRAFT_FOR_EXIT) {
10503 g_mutex_unlock(compose->mutex); /* must be done before closing */
10504 compose_close(compose);
10511 GError *error = NULL;
10516 goffset size, mtime;
10518 path = folder_item_fetch_msg(draft, msgnum);
10519 if (path == NULL) {
10520 debug_print("can't fetch %s:%d\n", draft->path, msgnum);
10524 f = g_file_new_for_path(path);
10525 fi = g_file_query_info(f, "standard::size,time::modified",
10526 G_FILE_QUERY_INFO_NONE, NULL, &error);
10527 if (error != NULL) {
10528 debug_print("couldn't query file info for '%s': %s\n",
10529 path, error->message);
10530 g_error_free(error);
10535 size = g_file_info_get_size(fi);
10536 g_file_info_get_modification_time(fi, &tv);
10538 g_object_unref(fi);
10541 if (g_stat(path, &s) < 0) {
10542 FILE_OP_ERROR(path, "stat");
10547 mtime = s.st_mtime;
10551 procmsg_msginfo_free(&(compose->targetinfo));
10552 compose->targetinfo = procmsg_msginfo_new();
10553 compose->targetinfo->msgnum = msgnum;
10554 compose->targetinfo->size = size;
10555 compose->targetinfo->mtime = mtime;
10556 compose->targetinfo->folder = draft;
10558 procmsg_msginfo_set_flags(compose->targetinfo, MSG_LOCKED, 0);
10559 compose->mode = COMPOSE_REEDIT;
10561 if (action == COMPOSE_AUTO_SAVE) {
10562 compose->autosaved_draft = compose->targetinfo;
10564 compose->modified = FALSE;
10565 compose_set_title(compose);
10569 g_mutex_unlock(compose->mutex);
10573 void compose_clear_exit_drafts(void)
10575 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10576 DRAFTED_AT_EXIT, NULL);
10577 if (is_file_exist(filepath))
10578 claws_unlink(filepath);
10583 void compose_reopen_exit_drafts(void)
10585 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10586 DRAFTED_AT_EXIT, NULL);
10587 FILE *fp = claws_fopen(filepath, "rb");
10591 while (claws_fgets(buf, sizeof(buf), fp)) {
10592 gchar **parts = g_strsplit(buf, "\t", 2);
10593 const gchar *folder = parts[0];
10594 int msgnum = parts[1] ? atoi(parts[1]):-1;
10596 if (folder && *folder && msgnum > -1) {
10597 FolderItem *item = folder_find_item_from_identifier(folder);
10598 MsgInfo *info = folder_item_get_msginfo(item, msgnum);
10600 compose_reedit(info, FALSE);
10607 compose_clear_exit_drafts();
10610 static void compose_save_cb(GtkAction *action, gpointer data)
10612 Compose *compose = (Compose *)data;
10613 compose_draft(compose, COMPOSE_KEEP_EDITING);
10614 compose->rmode = COMPOSE_REEDIT;
10617 void compose_attach_from_list(Compose *compose, GList *file_list, gboolean free_data)
10619 if (compose && file_list) {
10622 for ( tmp = file_list; tmp; tmp = tmp->next) {
10623 gchar *file = (gchar *) tmp->data;
10624 gchar *utf8_filename = conv_filename_to_utf8(file);
10625 compose_attach_append(compose, file, utf8_filename, NULL, NULL);
10626 compose_changed_cb(NULL, compose);
10631 g_free(utf8_filename);
10636 static void compose_attach_cb(GtkAction *action, gpointer data)
10638 Compose *compose = (Compose *)data;
10641 if (compose->redirect_filename != NULL)
10644 /* Set focus_window properly, in case we were called via popup menu,
10645 * which unsets it (via focus_out_event callback on compose window). */
10646 manage_window_focus_in(compose->window, NULL, NULL);
10648 file_list = filesel_select_multiple_files_open(_("Select file"), NULL);
10651 compose_attach_from_list(compose, file_list, TRUE);
10652 g_list_free(file_list);
10656 static void compose_insert_file_cb(GtkAction *action, gpointer data)
10658 Compose *compose = (Compose *)data;
10660 gint files_inserted = 0;
10662 file_list = filesel_select_multiple_files_open(_("Select file"), NULL);
10667 for ( tmp = file_list; tmp; tmp = tmp->next) {
10668 gchar *file = (gchar *) tmp->data;
10669 gchar *filedup = g_strdup(file);
10670 gchar *shortfile = g_path_get_basename(filedup);
10671 ComposeInsertResult res;
10672 /* insert the file if the file is short or if the user confirmed that
10673 he/she wants to insert the large file */
10674 res = compose_insert_file(compose, file);
10675 if (res == COMPOSE_INSERT_READ_ERROR) {
10676 alertpanel_error(_("File '%s' could not be read."), shortfile);
10677 } else if (res == COMPOSE_INSERT_INVALID_CHARACTER) {
10678 alertpanel_error(_("File '%s' contained invalid characters\n"
10679 "for the current encoding, insertion may be incorrect."),
10681 } else if (res == COMPOSE_INSERT_SUCCESS)
10688 g_list_free(file_list);
10692 if (files_inserted > 0 && compose->gtkaspell &&
10693 compose->gtkaspell->check_while_typing)
10694 gtkaspell_highlight_all(compose->gtkaspell);
10698 static void compose_insert_sig_cb(GtkAction *action, gpointer data)
10700 Compose *compose = (Compose *)data;
10702 compose_insert_sig(compose, FALSE);
10705 static void compose_replace_sig_cb(GtkAction *action, gpointer data)
10707 Compose *compose = (Compose *)data;
10709 compose_insert_sig(compose, TRUE);
10712 static gint compose_delete_cb(GtkWidget *widget, GdkEventAny *event,
10716 Compose *compose = (Compose *)data;
10718 gtkut_widget_get_uposition(widget, &x, &y);
10719 if (!compose->batch) {
10720 prefs_common.compose_x = x;
10721 prefs_common.compose_y = y;
10723 if (compose->sending || compose->updating)
10725 compose_close_cb(NULL, compose);
10729 void compose_close_toolbar(Compose *compose)
10731 compose_close_cb(NULL, compose);
10734 static void compose_close_cb(GtkAction *action, gpointer data)
10736 Compose *compose = (Compose *)data;
10740 if (compose->exteditor_tag != -1) {
10741 if (!compose_ext_editor_kill(compose))
10746 if (compose->modified) {
10747 gboolean reedit = (compose->rmode == COMPOSE_REEDIT);
10748 if (!g_mutex_trylock(compose->mutex)) {
10749 /* we don't want to lock the mutex once it's available,
10750 * because as the only other part of compose.c locking
10751 * it is compose_close - which means once unlocked,
10752 * the compose struct will be freed */
10753 debug_print("couldn't lock mutex, probably sending\n");
10756 if (!reedit || compose->folder->stype == F_DRAFT) {
10757 val = alertpanel(_("Discard message"),
10758 _("This message has been modified. Discard it?"),
10759 _("_Discard"), _("_Save to Drafts"), GTK_STOCK_CANCEL,
10762 val = alertpanel(_("Save changes"),
10763 _("This message has been modified. Save the latest changes?"),
10764 _("_Don't save"), _("_Save to Drafts"), GTK_STOCK_CANCEL,
10765 ALERTFOCUS_SECOND);
10767 g_mutex_unlock(compose->mutex);
10769 case G_ALERTDEFAULT:
10770 if (compose_can_autosave(compose) && !reedit)
10771 compose_remove_draft(compose);
10773 case G_ALERTALTERNATE:
10774 compose_draft(data, COMPOSE_QUIT_EDITING);
10781 compose_close(compose);
10784 static void compose_print_cb(GtkAction *action, gpointer data)
10786 Compose *compose = (Compose *) data;
10788 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
10789 if (compose->targetinfo)
10790 messageview_print(compose->targetinfo, FALSE, -1, -1, 0);
10793 static void compose_set_encoding_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
10795 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
10796 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
10797 Compose *compose = (Compose *) data;
10800 compose->out_encoding = (CharSet)value;
10803 static void compose_address_cb(GtkAction *action, gpointer data)
10805 Compose *compose = (Compose *)data;
10807 #ifndef USE_ALT_ADDRBOOK
10808 addressbook_open(compose);
10810 GError* error = NULL;
10811 addressbook_connect_signals(compose);
10812 addressbook_dbus_open(TRUE, &error);
10814 g_warning("%s", error->message);
10815 g_error_free(error);
10820 static void about_show_cb(GtkAction *action, gpointer data)
10825 static void compose_template_activate_cb(GtkWidget *widget, gpointer data)
10827 Compose *compose = (Compose *)data;
10832 tmpl = g_object_get_data(G_OBJECT(widget), "template");
10833 cm_return_if_fail(tmpl != NULL);
10835 msg = g_strdup_printf(_("Do you want to apply the template '%s'?"),
10837 val = alertpanel(_("Apply template"), msg,
10838 _("_Replace"), _("_Insert"), GTK_STOCK_CANCEL, ALERTFOCUS_FIRST);
10841 if (val == G_ALERTDEFAULT)
10842 compose_template_apply(compose, tmpl, TRUE);
10843 else if (val == G_ALERTALTERNATE)
10844 compose_template_apply(compose, tmpl, FALSE);
10847 static void compose_ext_editor_cb(GtkAction *action, gpointer data)
10849 Compose *compose = (Compose *)data;
10852 if (compose->exteditor_tag != -1) {
10853 debug_print("ignoring open external editor: external editor still open\n");
10857 compose_exec_ext_editor(compose);
10860 static void compose_undo_cb(GtkAction *action, gpointer data)
10862 Compose *compose = (Compose *)data;
10863 gboolean prev_autowrap = compose->autowrap;
10865 compose->autowrap = FALSE;
10866 undo_undo(compose->undostruct);
10867 compose->autowrap = prev_autowrap;
10870 static void compose_redo_cb(GtkAction *action, gpointer data)
10872 Compose *compose = (Compose *)data;
10873 gboolean prev_autowrap = compose->autowrap;
10875 compose->autowrap = FALSE;
10876 undo_redo(compose->undostruct);
10877 compose->autowrap = prev_autowrap;
10880 static void entry_cut_clipboard(GtkWidget *entry)
10882 if (GTK_IS_EDITABLE(entry))
10883 gtk_editable_cut_clipboard (GTK_EDITABLE(entry));
10884 else if (GTK_IS_TEXT_VIEW(entry))
10885 gtk_text_buffer_cut_clipboard(
10886 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10887 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
10891 static void entry_copy_clipboard(GtkWidget *entry)
10893 if (GTK_IS_EDITABLE(entry))
10894 gtk_editable_copy_clipboard (GTK_EDITABLE(entry));
10895 else if (GTK_IS_TEXT_VIEW(entry))
10896 gtk_text_buffer_copy_clipboard(
10897 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10898 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
10901 static void paste_text(Compose *compose, GtkWidget *entry,
10902 gboolean wrap, GdkAtom clip, GtkTextIter *insert_place,
10903 const gchar *contents)
10905 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10906 GtkTextMark *mark_start = gtk_text_buffer_get_insert(buffer);
10907 GtkTextIter start_iter, end_iter;
10910 if (contents == NULL)
10913 /* we shouldn't delete the selection when middle-click-pasting, or we
10914 * can't mid-click-paste our own selection */
10915 if (clip != GDK_SELECTION_PRIMARY) {
10916 undo_paste_clipboard(GTK_TEXT_VIEW(compose->text), compose->undostruct);
10917 gtk_text_buffer_delete_selection(buffer, FALSE, TRUE);
10920 if (insert_place == NULL) {
10921 /* if insert_place isn't specified, insert at the cursor.
10922 * used for Ctrl-V pasting */
10923 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10924 start = gtk_text_iter_get_offset(&start_iter);
10925 gtk_text_buffer_insert(buffer, &start_iter, contents, strlen(contents));
10927 /* if insert_place is specified, paste here.
10928 * used for mid-click-pasting */
10929 start = gtk_text_iter_get_offset(insert_place);
10930 gtk_text_buffer_insert(buffer, insert_place, contents, strlen(contents));
10931 if (prefs_common.primary_paste_unselects)
10932 gtk_text_buffer_select_range(buffer, insert_place, insert_place);
10936 /* paste unwrapped: mark the paste so it's not wrapped later */
10937 end = start + strlen(contents);
10938 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, start);
10939 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, end);
10940 gtk_text_buffer_apply_tag_by_name(buffer, "no_wrap", &start_iter, &end_iter);
10941 } else if (wrap && clip == GDK_SELECTION_PRIMARY) {
10942 /* rewrap paragraph now (after a mid-click-paste) */
10943 mark_start = gtk_text_buffer_get_insert(buffer);
10944 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10945 gtk_text_iter_backward_char(&start_iter);
10946 compose_beautify_paragraph(compose, &start_iter, TRUE);
10948 compose->modified = TRUE;
10951 static void attach_uri_list(Compose *compose, GtkSelectionData *data)
10955 list = uri_list_extract_filenames(
10956 (const gchar *)gtk_selection_data_get_data(data));
10957 for (tmp = list; tmp != NULL; tmp = tmp->next) {
10958 gchar *utf8_filename = conv_filename_to_utf8((const gchar *)tmp->data);
10959 compose_attach_append
10960 (compose, (const gchar *)tmp->data,
10961 utf8_filename, NULL, NULL);
10962 g_free(utf8_filename);
10965 compose_changed_cb(NULL, compose);
10967 list_free_strings_full(list);
10970 int attach_image(Compose *compose, GtkSelectionData *data, const gchar *subtype)
10973 const guchar *contents;
10979 cm_return_val_if_fail(data != NULL, -1);
10981 contents = gtk_selection_data_get_data(data);
10982 len = gtk_selection_data_get_length(data);
10984 file = g_strconcat(get_tmp_file(), "-image.", subtype, NULL);
10986 debug_print("writing image to %s\n", file);
10988 if ((fp = claws_fopen(file, "wb")) == NULL) {
10989 FILE_OP_ERROR(file, "claws_fopen");
10993 if (claws_fwrite(contents, 1, len, fp) != len) {
10994 FILE_OP_ERROR(file, "claws_fwrite");
10996 claws_unlink(file);
11000 r = claws_safe_fclose(fp);
11003 FILE_OP_ERROR(file, "claws_fclose");
11004 claws_unlink(file);
11008 type = g_strconcat("image/", subtype, NULL);
11010 compose_attach_append(compose, (const gchar *)file,
11011 (const gchar *)file, type, NULL);
11013 alertpanel_notice(_("The pasted image has been attached as: \n%s"), file);
11021 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry,
11022 gboolean wrap, GdkAtom clip, GtkTextIter *insert_place)
11024 if (GTK_IS_TEXT_VIEW(entry)) {
11025 GdkAtom types = gdk_atom_intern ("MULTIPLE", FALSE);
11026 GdkAtom *targets = NULL;
11027 int n_targets = 0, i;
11028 gboolean paste_done = FALSE;
11029 GtkClipboard *clipboard = gtk_clipboard_get(clip);
11031 GtkSelectionData *contents = gtk_clipboard_wait_for_contents(
11034 if (contents != NULL) {
11035 gtk_selection_data_get_targets(contents, &targets, &n_targets);
11036 gtk_selection_data_free(contents);
11039 for (i = 0; i < n_targets; i++) {
11040 GdkAtom atom = targets[i];
11041 gchar *atom_type = gdk_atom_name(atom);
11043 if (atom_type != NULL) {
11044 GtkSelectionData *data = gtk_clipboard_wait_for_contents(
11046 debug_print("got contents of type %s\n", atom_type);
11047 if (!strcmp(atom_type, "text/plain")) {
11048 /* let the default text handler handle it */
11050 } else if (!strcmp(atom_type, "text/uri-list")) {
11051 attach_uri_list(compose, data);
11055 } else if (!strncmp(atom_type, "image/", strlen("image/"))) {
11056 gchar *subtype = g_strdup((gchar *)(strstr(atom_type, "/")+1));
11057 debug_print("image of type %s\n", subtype);
11059 attach_image(compose, data, subtype);
11068 gchar *def_text = gtk_clipboard_wait_for_text(clipboard);
11069 paste_text(compose, entry, wrap, clip,
11070 insert_place, def_text);
11075 } else if (GTK_IS_EDITABLE(entry)) {
11076 gtk_editable_paste_clipboard (GTK_EDITABLE(entry));
11077 compose->modified = TRUE;
11081 static void entry_allsel(GtkWidget *entry)
11083 if (GTK_IS_EDITABLE(entry))
11084 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
11085 else if (GTK_IS_TEXT_VIEW(entry)) {
11086 GtkTextIter startiter, enditer;
11087 GtkTextBuffer *textbuf;
11089 textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
11090 gtk_text_buffer_get_start_iter(textbuf, &startiter);
11091 gtk_text_buffer_get_end_iter(textbuf, &enditer);
11093 gtk_text_buffer_move_mark_by_name(textbuf,
11094 "selection_bound", &startiter);
11095 gtk_text_buffer_move_mark_by_name(textbuf,
11096 "insert", &enditer);
11100 static void compose_cut_cb(GtkAction *action, gpointer data)
11102 Compose *compose = (Compose *)data;
11103 if (compose->focused_editable
11104 #ifndef GENERIC_UMPC
11105 && gtk_widget_has_focus(compose->focused_editable)
11108 entry_cut_clipboard(compose->focused_editable);
11111 static void compose_copy_cb(GtkAction *action, gpointer data)
11113 Compose *compose = (Compose *)data;
11114 if (compose->focused_editable
11115 #ifndef GENERIC_UMPC
11116 && gtk_widget_has_focus(compose->focused_editable)
11119 entry_copy_clipboard(compose->focused_editable);
11122 static void compose_paste_cb(GtkAction *action, gpointer data)
11124 Compose *compose = (Compose *)data;
11125 gint prev_autowrap;
11126 GtkTextBuffer *buffer;
11128 if (compose->focused_editable
11129 #ifndef GENERIC_UMPC
11130 && gtk_widget_has_focus(compose->focused_editable)
11133 entry_paste_clipboard(compose, compose->focused_editable,
11134 prefs_common.linewrap_pastes,
11135 GDK_SELECTION_CLIPBOARD, NULL);
11140 #ifndef GENERIC_UMPC
11141 gtk_widget_has_focus(compose->text) &&
11143 compose->gtkaspell &&
11144 compose->gtkaspell->check_while_typing)
11145 gtkaspell_highlight_all(compose->gtkaspell);
11149 static void compose_paste_as_quote_cb(GtkAction *action, gpointer data)
11151 Compose *compose = (Compose *)data;
11152 gint wrap_quote = prefs_common.linewrap_quote;
11153 if (compose->focused_editable
11154 #ifndef GENERIC_UMPC
11155 && gtk_widget_has_focus(compose->focused_editable)
11158 /* let text_insert() (called directly or at a later time
11159 * after the gtk_editable_paste_clipboard) know that
11160 * text is to be inserted as a quotation. implemented
11161 * by using a simple refcount... */
11162 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data(
11163 G_OBJECT(compose->focused_editable),
11164 "paste_as_quotation"));
11165 g_object_set_data(G_OBJECT(compose->focused_editable),
11166 "paste_as_quotation",
11167 GINT_TO_POINTER(paste_as_quotation + 1));
11168 prefs_common.linewrap_quote = prefs_common.linewrap_pastes;
11169 entry_paste_clipboard(compose, compose->focused_editable,
11170 prefs_common.linewrap_pastes,
11171 GDK_SELECTION_CLIPBOARD, NULL);
11172 prefs_common.linewrap_quote = wrap_quote;
11176 static void compose_paste_no_wrap_cb(GtkAction *action, gpointer data)
11178 Compose *compose = (Compose *)data;
11179 gint prev_autowrap;
11180 GtkTextBuffer *buffer;
11182 if (compose->focused_editable
11183 #ifndef GENERIC_UMPC
11184 && gtk_widget_has_focus(compose->focused_editable)
11187 entry_paste_clipboard(compose, compose->focused_editable, FALSE,
11188 GDK_SELECTION_CLIPBOARD, NULL);
11193 #ifndef GENERIC_UMPC
11194 gtk_widget_has_focus(compose->text) &&
11196 compose->gtkaspell &&
11197 compose->gtkaspell->check_while_typing)
11198 gtkaspell_highlight_all(compose->gtkaspell);
11202 static void compose_paste_wrap_cb(GtkAction *action, gpointer data)
11204 Compose *compose = (Compose *)data;
11205 gint prev_autowrap;
11206 GtkTextBuffer *buffer;
11208 if (compose->focused_editable
11209 #ifndef GENERIC_UMPC
11210 && gtk_widget_has_focus(compose->focused_editable)
11213 entry_paste_clipboard(compose, compose->focused_editable, TRUE,
11214 GDK_SELECTION_CLIPBOARD, NULL);
11219 #ifndef GENERIC_UMPC
11220 gtk_widget_has_focus(compose->text) &&
11222 compose->gtkaspell &&
11223 compose->gtkaspell->check_while_typing)
11224 gtkaspell_highlight_all(compose->gtkaspell);
11228 static void compose_allsel_cb(GtkAction *action, gpointer data)
11230 Compose *compose = (Compose *)data;
11231 if (compose->focused_editable
11232 #ifndef GENERIC_UMPC
11233 && gtk_widget_has_focus(compose->focused_editable)
11236 entry_allsel(compose->focused_editable);
11239 static void textview_move_beginning_of_line (GtkTextView *text)
11241 GtkTextBuffer *buffer;
11245 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11247 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11248 mark = gtk_text_buffer_get_insert(buffer);
11249 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11250 gtk_text_iter_set_line_offset(&ins, 0);
11251 gtk_text_buffer_place_cursor(buffer, &ins);
11254 static void textview_move_forward_character (GtkTextView *text)
11256 GtkTextBuffer *buffer;
11260 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11262 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11263 mark = gtk_text_buffer_get_insert(buffer);
11264 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11265 if (gtk_text_iter_forward_cursor_position(&ins))
11266 gtk_text_buffer_place_cursor(buffer, &ins);
11269 static void textview_move_backward_character (GtkTextView *text)
11271 GtkTextBuffer *buffer;
11275 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11277 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11278 mark = gtk_text_buffer_get_insert(buffer);
11279 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11280 if (gtk_text_iter_backward_cursor_position(&ins))
11281 gtk_text_buffer_place_cursor(buffer, &ins);
11284 static void textview_move_forward_word (GtkTextView *text)
11286 GtkTextBuffer *buffer;
11291 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11293 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11294 mark = gtk_text_buffer_get_insert(buffer);
11295 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11296 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
11297 if (gtk_text_iter_forward_word_ends(&ins, count)) {
11298 gtk_text_iter_backward_word_start(&ins);
11299 gtk_text_buffer_place_cursor(buffer, &ins);
11303 static void textview_move_backward_word (GtkTextView *text)
11305 GtkTextBuffer *buffer;
11309 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11311 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11312 mark = gtk_text_buffer_get_insert(buffer);
11313 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11314 if (gtk_text_iter_backward_word_starts(&ins, 1))
11315 gtk_text_buffer_place_cursor(buffer, &ins);
11318 static void textview_move_end_of_line (GtkTextView *text)
11320 GtkTextBuffer *buffer;
11324 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11326 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11327 mark = gtk_text_buffer_get_insert(buffer);
11328 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11329 if (gtk_text_iter_forward_to_line_end(&ins))
11330 gtk_text_buffer_place_cursor(buffer, &ins);
11333 static void textview_move_next_line (GtkTextView *text)
11335 GtkTextBuffer *buffer;
11340 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11342 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11343 mark = gtk_text_buffer_get_insert(buffer);
11344 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11345 offset = gtk_text_iter_get_line_offset(&ins);
11346 if (gtk_text_iter_forward_line(&ins)) {
11347 gtk_text_iter_set_line_offset(&ins, offset);
11348 gtk_text_buffer_place_cursor(buffer, &ins);
11352 static void textview_move_previous_line (GtkTextView *text)
11354 GtkTextBuffer *buffer;
11359 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11361 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11362 mark = gtk_text_buffer_get_insert(buffer);
11363 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11364 offset = gtk_text_iter_get_line_offset(&ins);
11365 if (gtk_text_iter_backward_line(&ins)) {
11366 gtk_text_iter_set_line_offset(&ins, offset);
11367 gtk_text_buffer_place_cursor(buffer, &ins);
11371 static void textview_delete_forward_character (GtkTextView *text)
11373 GtkTextBuffer *buffer;
11375 GtkTextIter ins, end_iter;
11377 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11379 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11380 mark = gtk_text_buffer_get_insert(buffer);
11381 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11383 if (gtk_text_iter_forward_char(&end_iter)) {
11384 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11388 static void textview_delete_backward_character (GtkTextView *text)
11390 GtkTextBuffer *buffer;
11392 GtkTextIter ins, end_iter;
11394 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11396 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11397 mark = gtk_text_buffer_get_insert(buffer);
11398 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11400 if (gtk_text_iter_backward_char(&end_iter)) {
11401 gtk_text_buffer_delete(buffer, &end_iter, &ins);
11405 static void textview_delete_forward_word (GtkTextView *text)
11407 GtkTextBuffer *buffer;
11409 GtkTextIter ins, end_iter;
11411 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11413 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11414 mark = gtk_text_buffer_get_insert(buffer);
11415 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11417 if (gtk_text_iter_forward_word_end(&end_iter)) {
11418 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11422 static void textview_delete_backward_word (GtkTextView *text)
11424 GtkTextBuffer *buffer;
11426 GtkTextIter ins, end_iter;
11428 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11430 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11431 mark = gtk_text_buffer_get_insert(buffer);
11432 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11434 if (gtk_text_iter_backward_word_start(&end_iter)) {
11435 gtk_text_buffer_delete(buffer, &end_iter, &ins);
11439 static void textview_delete_line (GtkTextView *text)
11441 GtkTextBuffer *buffer;
11443 GtkTextIter ins, start_iter, end_iter;
11445 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11447 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11448 mark = gtk_text_buffer_get_insert(buffer);
11449 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11452 gtk_text_iter_set_line_offset(&start_iter, 0);
11455 if (gtk_text_iter_ends_line(&end_iter)){
11456 if (!gtk_text_iter_forward_char(&end_iter))
11457 gtk_text_iter_backward_char(&start_iter);
11460 gtk_text_iter_forward_to_line_end(&end_iter);
11461 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
11464 static void textview_delete_to_line_end (GtkTextView *text)
11466 GtkTextBuffer *buffer;
11468 GtkTextIter ins, end_iter;
11470 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11472 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11473 mark = gtk_text_buffer_get_insert(buffer);
11474 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11476 if (gtk_text_iter_ends_line(&end_iter))
11477 gtk_text_iter_forward_char(&end_iter);
11479 gtk_text_iter_forward_to_line_end(&end_iter);
11480 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11483 #define DO_ACTION(name, act) { \
11484 if(!strcmp(name, a_name)) { \
11488 static ComposeCallAdvancedAction compose_call_advanced_action_from_path(GtkAction *action)
11490 const gchar *a_name = gtk_action_get_name(action);
11491 DO_ACTION("Edit/Advanced/BackChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER);
11492 DO_ACTION("Edit/Advanced/ForwChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER);
11493 DO_ACTION("Edit/Advanced/BackWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD);
11494 DO_ACTION("Edit/Advanced/ForwWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD);
11495 DO_ACTION("Edit/Advanced/BegLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE);
11496 DO_ACTION("Edit/Advanced/EndLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE);
11497 DO_ACTION("Edit/Advanced/PrevLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE);
11498 DO_ACTION("Edit/Advanced/NextLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE);
11499 DO_ACTION("Edit/Advanced/DelBackChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER);
11500 DO_ACTION("Edit/Advanced/DelForwChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER);
11501 DO_ACTION("Edit/Advanced/DelBackWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD);
11502 DO_ACTION("Edit/Advanced/DelForwWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD);
11503 DO_ACTION("Edit/Advanced/DelLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE);
11504 DO_ACTION("Edit/Advanced/DelEndLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END);
11505 return COMPOSE_CALL_ADVANCED_ACTION_UNDEFINED;
11508 static void compose_advanced_action_cb(GtkAction *gaction, gpointer data)
11510 Compose *compose = (Compose *)data;
11511 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11512 ComposeCallAdvancedAction action = COMPOSE_CALL_ADVANCED_ACTION_UNDEFINED;
11514 action = compose_call_advanced_action_from_path(gaction);
11517 void (*do_action) (GtkTextView *text);
11518 } action_table[] = {
11519 {textview_move_beginning_of_line},
11520 {textview_move_forward_character},
11521 {textview_move_backward_character},
11522 {textview_move_forward_word},
11523 {textview_move_backward_word},
11524 {textview_move_end_of_line},
11525 {textview_move_next_line},
11526 {textview_move_previous_line},
11527 {textview_delete_forward_character},
11528 {textview_delete_backward_character},
11529 {textview_delete_forward_word},
11530 {textview_delete_backward_word},
11531 {textview_delete_line},
11532 {textview_delete_to_line_end}
11535 if (!gtk_widget_has_focus(GTK_WIDGET(text))) return;
11537 if (action >= COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE &&
11538 action <= COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END) {
11539 if (action_table[action].do_action)
11540 action_table[action].do_action(text);
11542 g_warning("Not implemented yet.");
11546 static void compose_grab_focus_cb(GtkWidget *widget, Compose *compose)
11548 GtkAllocation allocation;
11552 if (GTK_IS_EDITABLE(widget)) {
11553 str = gtk_editable_get_chars(GTK_EDITABLE(widget), 0, -1);
11554 gtk_editable_set_position(GTK_EDITABLE(widget),
11557 if ((parent = gtk_widget_get_parent(widget))
11558 && (parent = gtk_widget_get_parent(parent))
11559 && (parent = gtk_widget_get_parent(parent))) {
11560 if (GTK_IS_SCROLLED_WINDOW(parent)) {
11561 gtk_widget_get_allocation(widget, &allocation);
11562 gint y = allocation.y;
11563 gint height = allocation.height;
11564 GtkAdjustment *shown = gtk_scrolled_window_get_vadjustment
11565 (GTK_SCROLLED_WINDOW(parent));
11567 gfloat value = gtk_adjustment_get_value(shown);
11568 gfloat upper = gtk_adjustment_get_upper(shown);
11569 gfloat page_size = gtk_adjustment_get_page_size(shown);
11570 if (y < (int)value) {
11571 gtk_adjustment_set_value(shown, y - 1);
11573 if ((y + height) > ((int)value + (int)page_size)) {
11574 if ((y - height - 1) < ((int)upper - (int)page_size)) {
11575 gtk_adjustment_set_value(shown,
11576 y + height - (int)page_size - 1);
11578 gtk_adjustment_set_value(shown,
11579 (int)upper - (int)page_size - 1);
11586 if (GTK_IS_EDITABLE(widget) || GTK_IS_TEXT_VIEW(widget))
11587 compose->focused_editable = widget;
11589 #ifdef GENERIC_UMPC
11590 if (GTK_IS_TEXT_VIEW(widget)
11591 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->edit_vbox) {
11592 g_object_ref(compose->notebook);
11593 g_object_ref(compose->edit_vbox);
11594 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
11595 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
11596 gtk_paned_add1(GTK_PANED(compose->paned), compose->edit_vbox);
11597 gtk_paned_add2(GTK_PANED(compose->paned), compose->notebook);
11598 g_object_unref(compose->notebook);
11599 g_object_unref(compose->edit_vbox);
11600 g_signal_handlers_block_by_func(G_OBJECT(widget),
11601 G_CALLBACK(compose_grab_focus_cb),
11603 gtk_widget_grab_focus(widget);
11604 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
11605 G_CALLBACK(compose_grab_focus_cb),
11607 } else if (!GTK_IS_TEXT_VIEW(widget)
11608 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->notebook) {
11609 g_object_ref(compose->notebook);
11610 g_object_ref(compose->edit_vbox);
11611 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
11612 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
11613 gtk_paned_add1(GTK_PANED(compose->paned), compose->notebook);
11614 gtk_paned_add2(GTK_PANED(compose->paned), compose->edit_vbox);
11615 g_object_unref(compose->notebook);
11616 g_object_unref(compose->edit_vbox);
11617 g_signal_handlers_block_by_func(G_OBJECT(widget),
11618 G_CALLBACK(compose_grab_focus_cb),
11620 gtk_widget_grab_focus(widget);
11621 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
11622 G_CALLBACK(compose_grab_focus_cb),
11628 static void compose_changed_cb(GtkTextBuffer *textbuf, Compose *compose)
11630 compose->modified = TRUE;
11631 /* compose_beautify_paragraph(compose, NULL, TRUE); */
11632 #ifndef GENERIC_UMPC
11633 compose_set_title(compose);
11637 static void compose_wrap_cb(GtkAction *action, gpointer data)
11639 Compose *compose = (Compose *)data;
11640 compose_beautify_paragraph(compose, NULL, TRUE);
11643 static void compose_wrap_all_cb(GtkAction *action, gpointer data)
11645 Compose *compose = (Compose *)data;
11646 compose_wrap_all_full(compose, TRUE);
11649 static void compose_find_cb(GtkAction *action, gpointer data)
11651 Compose *compose = (Compose *)data;
11653 message_search_compose(compose);
11656 static void compose_toggle_autowrap_cb(GtkToggleAction *action,
11659 Compose *compose = (Compose *)data;
11660 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11661 if (compose->autowrap)
11662 compose_wrap_all_full(compose, TRUE);
11663 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11666 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
11669 Compose *compose = (Compose *)data;
11670 compose->autoindent = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11673 static void compose_toggle_sign_cb(GtkToggleAction *action, gpointer data)
11675 Compose *compose = (Compose *)data;
11677 compose->use_signing = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11678 gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(compose->toolbar->privacy_sign_btn), compose->use_signing);
11681 static void compose_toggle_encrypt_cb(GtkToggleAction *action, gpointer data)
11683 Compose *compose = (Compose *)data;
11685 compose->use_encryption = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11686 gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(compose->toolbar->privacy_encrypt_btn), compose->use_encryption);
11689 static void compose_activate_privacy_system(Compose *compose, PrefsAccount *account, gboolean warn)
11691 g_free(compose->privacy_system);
11692 g_free(compose->encdata);
11694 compose->privacy_system = g_strdup(account->default_privacy_system);
11695 compose_update_privacy_system_menu_item(compose, warn);
11698 static void compose_apply_folder_privacy_settings(Compose *compose, FolderItem *folder_item)
11700 if (folder_item != NULL) {
11701 if (folder_item->prefs->always_sign != SIGN_OR_ENCRYPT_DEFAULT &&
11702 privacy_system_can_sign(compose->privacy_system)) {
11703 compose_use_signing(compose,
11704 (folder_item->prefs->always_sign == SIGN_OR_ENCRYPT_ALWAYS) ? TRUE : FALSE);
11706 if (folder_item->prefs->always_encrypt != SIGN_OR_ENCRYPT_DEFAULT &&
11707 privacy_system_can_encrypt(compose->privacy_system)) {
11708 compose_use_encryption(compose,
11709 (folder_item->prefs->always_encrypt == SIGN_OR_ENCRYPT_ALWAYS) ? TRUE : FALSE);
11714 static void compose_toggle_ruler_cb(GtkToggleAction *action, gpointer data)
11716 Compose *compose = (Compose *)data;
11718 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action))) {
11719 gtk_widget_show(compose->ruler_hbox);
11720 prefs_common.show_ruler = TRUE;
11722 gtk_widget_hide(compose->ruler_hbox);
11723 gtk_widget_queue_resize(compose->edit_vbox);
11724 prefs_common.show_ruler = FALSE;
11728 static void compose_attach_drag_received_cb (GtkWidget *widget,
11729 GdkDragContext *context,
11732 GtkSelectionData *data,
11735 gpointer user_data)
11737 Compose *compose = (Compose *)user_data;
11740 type = gtk_selection_data_get_data_type(data);
11741 if ((gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list"))
11742 && gtk_drag_get_source_widget(context) !=
11743 summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
11744 attach_uri_list(compose, data);
11745 } else if (gtk_drag_get_source_widget(context)
11746 == summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
11747 /* comes from our summaryview */
11748 SummaryView * summaryview = NULL;
11749 GSList * list = NULL, *cur = NULL;
11751 if (mainwindow_get_mainwindow())
11752 summaryview = mainwindow_get_mainwindow()->summaryview;
11755 list = summary_get_selected_msg_list(summaryview);
11757 for (cur = list; cur; cur = cur->next) {
11758 MsgInfo *msginfo = (MsgInfo *)cur->data;
11759 gchar *file = NULL;
11761 file = procmsg_get_message_file_full(msginfo,
11764 compose_attach_append(compose, (const gchar *)file,
11765 (const gchar *)file, "message/rfc822", NULL);
11769 g_slist_free(list);
11773 static gboolean compose_drag_drop(GtkWidget *widget,
11774 GdkDragContext *drag_context,
11776 guint time, gpointer user_data)
11778 /* not handling this signal makes compose_insert_drag_received_cb
11783 static gboolean completion_set_focus_to_subject
11784 (GtkWidget *widget,
11785 GdkEventKey *event,
11788 cm_return_val_if_fail(compose != NULL, FALSE);
11790 /* make backtab move to subject field */
11791 if(event->keyval == GDK_KEY_ISO_Left_Tab) {
11792 gtk_widget_grab_focus(compose->subject_entry);
11798 static void compose_insert_drag_received_cb (GtkWidget *widget,
11799 GdkDragContext *drag_context,
11802 GtkSelectionData *data,
11805 gpointer user_data)
11807 Compose *compose = (Compose *)user_data;
11813 /* strangely, testing data->type == gdk_atom_intern("text/uri-list", TRUE)
11815 type = gtk_selection_data_get_data_type(data);
11816 if (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list")) {
11817 AlertValue val = G_ALERTDEFAULT;
11818 const gchar* ddata = (const gchar *)gtk_selection_data_get_data(data);
11820 list = uri_list_extract_filenames(ddata);
11821 num_files = g_list_length(list);
11822 if (list == NULL && strstr(ddata, "://")) {
11823 /* Assume a list of no files, and data has ://, is a remote link */
11824 gchar *tmpdata = g_strstrip(g_strdup(ddata));
11825 gchar *tmpfile = get_tmp_file();
11826 str_write_to_file(tmpdata, tmpfile, TRUE);
11828 compose_insert_file(compose, tmpfile);
11829 claws_unlink(tmpfile);
11831 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11832 compose_beautify_paragraph(compose, NULL, TRUE);
11835 switch (prefs_common.compose_dnd_mode) {
11836 case COMPOSE_DND_ASK:
11837 msg = g_strdup_printf(
11839 "Do you want to insert the contents of the file "
11840 "into the message body, or attach it to the email?",
11841 "Do you want to insert the contents of the %d files "
11842 "into the message body, or attach them to the email?",
11845 val = alertpanel_full(_("Insert or attach?"), msg,
11846 GTK_STOCK_CANCEL, _("_Insert"), _("_Attach"),
11848 TRUE, NULL, ALERT_QUESTION);
11851 case COMPOSE_DND_INSERT:
11852 val = G_ALERTALTERNATE;
11854 case COMPOSE_DND_ATTACH:
11855 val = G_ALERTOTHER;
11858 /* unexpected case */
11859 g_warning("error: unexpected compose_dnd_mode option value in compose_insert_drag_received_cb()");
11862 if (val & G_ALERTDISABLE) {
11863 val &= ~G_ALERTDISABLE;
11864 /* remember what action to perform by default, only if we don't click Cancel */
11865 if (val == G_ALERTALTERNATE)
11866 prefs_common.compose_dnd_mode = COMPOSE_DND_INSERT;
11867 else if (val == G_ALERTOTHER)
11868 prefs_common.compose_dnd_mode = COMPOSE_DND_ATTACH;
11871 if (val == G_ALERTDEFAULT || val == G_ALERTCANCEL) {
11872 gtk_drag_finish(drag_context, FALSE, FALSE, time);
11873 list_free_strings_full(list);
11875 } else if (val == G_ALERTOTHER) {
11876 compose_attach_drag_received_cb(widget, drag_context, x, y, data, info, time, user_data);
11877 list_free_strings_full(list);
11881 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11882 compose_insert_file(compose, (const gchar *)tmp->data);
11884 list_free_strings_full(list);
11885 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11890 static void compose_header_drag_received_cb (GtkWidget *widget,
11891 GdkDragContext *drag_context,
11894 GtkSelectionData *data,
11897 gpointer user_data)
11899 GtkEditable *entry = (GtkEditable *)user_data;
11900 const gchar *email = (const gchar *)gtk_selection_data_get_data(data);
11902 /* strangely, testing data->type == gdk_atom_intern("text/plain", TRUE)
11905 if (!strncmp(email, "mailto:", strlen("mailto:"))) {
11906 gchar *decoded=g_new(gchar, strlen(email));
11909 decode_uri(decoded, email + strlen("mailto:")); /* will fit */
11910 gtk_editable_delete_text(entry, 0, -1);
11911 gtk_editable_insert_text(entry, decoded, strlen(decoded), &start);
11912 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11916 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11919 static void compose_toggle_return_receipt_cb(GtkToggleAction *action, gpointer data)
11921 Compose *compose = (Compose *)data;
11923 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11924 compose->return_receipt = TRUE;
11926 compose->return_receipt = FALSE;
11929 static void compose_toggle_remove_refs_cb(GtkToggleAction *action, gpointer data)
11931 Compose *compose = (Compose *)data;
11933 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11934 compose->remove_references = TRUE;
11936 compose->remove_references = FALSE;
11939 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
11940 ComposeHeaderEntry *headerentry)
11942 gtk_entry_set_text(GTK_ENTRY(headerentry->entry), "");
11943 gtk_widget_modify_base(GTK_WIDGET(headerentry->entry), GTK_STATE_NORMAL, NULL);
11944 gtk_widget_modify_text(GTK_WIDGET(headerentry->entry), GTK_STATE_NORMAL, NULL);
11948 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
11949 GdkEventKey *event,
11950 ComposeHeaderEntry *headerentry)
11952 if ((g_slist_length(headerentry->compose->header_list) > 0) &&
11953 ((headerentry->headernum + 1) != headerentry->compose->header_nextrow) &&
11954 !(event->state & GDK_MODIFIER_MASK) &&
11955 (event->keyval == GDK_KEY_BackSpace) &&
11956 (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) == 0)) {
11957 gtk_container_remove
11958 (GTK_CONTAINER(headerentry->compose->header_table),
11959 headerentry->combo);
11960 gtk_container_remove
11961 (GTK_CONTAINER(headerentry->compose->header_table),
11962 headerentry->entry);
11963 headerentry->compose->header_list =
11964 g_slist_remove(headerentry->compose->header_list,
11966 g_free(headerentry);
11967 } else if (event->keyval == GDK_KEY_Tab) {
11968 if (headerentry->compose->header_last == headerentry) {
11969 /* Override default next focus, and give it to subject_entry
11970 * instead of notebook tabs
11972 g_signal_stop_emission_by_name(G_OBJECT(entry), "key-press-event");
11973 gtk_widget_grab_focus(headerentry->compose->subject_entry);
11980 static gboolean scroll_postpone(gpointer data)
11982 Compose *compose = (Compose *)data;
11984 if (compose->batch)
11987 GTK_EVENTS_FLUSH();
11988 compose_show_first_last_header(compose, FALSE);
11992 static void compose_headerentry_changed_cb(GtkWidget *entry,
11993 ComposeHeaderEntry *headerentry)
11995 if (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) != 0) {
11996 compose_create_header_entry(headerentry->compose);
11997 g_signal_handlers_disconnect_matched
11998 (G_OBJECT(entry), G_SIGNAL_MATCH_DATA,
11999 0, 0, NULL, NULL, headerentry);
12001 if (!headerentry->compose->batch)
12002 g_timeout_add(0, scroll_postpone, headerentry->compose);
12006 static gboolean compose_defer_auto_save_draft(Compose *compose)
12008 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
12009 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
12013 static void compose_show_first_last_header(Compose *compose, gboolean show_first)
12015 GtkAdjustment *vadj;
12017 cm_return_if_fail(compose);
12022 cm_return_if_fail(GTK_IS_WIDGET(compose->header_table));
12023 cm_return_if_fail(GTK_IS_VIEWPORT(gtk_widget_get_parent(compose->header_table)));
12024 vadj = gtk_viewport_get_vadjustment(GTK_VIEWPORT(
12025 gtk_widget_get_parent(compose->header_table)));
12026 gtk_adjustment_set_value(vadj, (show_first ?
12027 gtk_adjustment_get_lower(vadj) :
12028 (gtk_adjustment_get_upper(vadj) -
12029 gtk_adjustment_get_page_size(vadj))));
12030 gtk_adjustment_changed(vadj);
12033 static void text_inserted(GtkTextBuffer *buffer, GtkTextIter *iter,
12034 const gchar *text, gint len, Compose *compose)
12036 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data
12037 (G_OBJECT(compose->text), "paste_as_quotation"));
12040 cm_return_if_fail(text != NULL);
12042 g_signal_handlers_block_by_func(G_OBJECT(buffer),
12043 G_CALLBACK(text_inserted),
12045 if (paste_as_quotation) {
12047 const gchar *qmark;
12049 GtkTextIter start_iter;
12052 len = strlen(text);
12054 new_text = g_strndup(text, len);
12056 qmark = compose_quote_char_from_context(compose);
12058 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
12059 gtk_text_buffer_place_cursor(buffer, iter);
12061 pos = gtk_text_iter_get_offset(iter);
12063 compose_quote_fmt(compose, NULL, "%Q", qmark, new_text, TRUE, FALSE,
12064 _("Quote format error at line %d."));
12065 quote_fmt_reset_vartable();
12067 g_object_set_data(G_OBJECT(compose->text), "paste_as_quotation",
12068 GINT_TO_POINTER(paste_as_quotation - 1));
12070 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
12071 gtk_text_buffer_place_cursor(buffer, iter);
12072 gtk_text_buffer_delete_mark(buffer, mark);
12074 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, pos);
12075 mark = gtk_text_buffer_create_mark(buffer, NULL, &start_iter, FALSE);
12076 compose_beautify_paragraph(compose, &start_iter, FALSE);
12077 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark);
12078 gtk_text_buffer_delete_mark(buffer, mark);
12080 if (strcmp(text, "\n") || compose->automatic_break
12081 || gtk_text_iter_starts_line(iter)) {
12082 GtkTextIter before_ins;
12083 gtk_text_buffer_insert(buffer, iter, text, len);
12084 if (!strstr(text, "\n") && gtk_text_iter_has_tag(iter, compose->no_join_tag)) {
12085 before_ins = *iter;
12086 gtk_text_iter_backward_chars(&before_ins, len);
12087 gtk_text_buffer_remove_tag_by_name(buffer, "no_join", &before_ins, iter);
12090 /* check if the preceding is just whitespace or quote */
12091 GtkTextIter start_line;
12092 gchar *tmp = NULL, *quote = NULL;
12093 gint quote_len = 0, is_normal = 0;
12094 start_line = *iter;
12095 gtk_text_iter_set_line_offset(&start_line, 0);
12096 tmp = gtk_text_buffer_get_text(buffer, &start_line, iter, FALSE);
12099 if (*tmp == '\0') {
12102 quote = compose_get_quote_str(buffer, &start_line, "e_len);
12110 gtk_text_buffer_insert(buffer, iter, text, len);
12112 gtk_text_buffer_insert_with_tags_by_name(buffer,
12113 iter, text, len, "no_join", NULL);
12118 if (!paste_as_quotation) {
12119 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
12120 compose_beautify_paragraph(compose, iter, FALSE);
12121 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
12122 gtk_text_buffer_delete_mark(buffer, mark);
12125 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
12126 G_CALLBACK(text_inserted),
12128 g_signal_stop_emission_by_name(G_OBJECT(buffer), "insert-text");
12130 if (compose_can_autosave(compose) &&
12131 gtk_text_buffer_get_char_count(buffer) % prefs_common.autosave_length == 0 &&
12132 compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN /* disabled while loading */)
12133 compose->draft_timeout_tag = g_timeout_add
12134 (500, (GSourceFunc) compose_defer_auto_save_draft, compose);
12138 static void compose_check_all(GtkAction *action, gpointer data)
12140 Compose *compose = (Compose *)data;
12141 if (!compose->gtkaspell)
12144 if (gtk_widget_has_focus(compose->subject_entry))
12145 claws_spell_entry_check_all(
12146 CLAWS_SPELL_ENTRY(compose->subject_entry));
12148 gtkaspell_check_all(compose->gtkaspell);
12151 static void compose_highlight_all(GtkAction *action, gpointer data)
12153 Compose *compose = (Compose *)data;
12154 if (compose->gtkaspell) {
12155 claws_spell_entry_recheck_all(
12156 CLAWS_SPELL_ENTRY(compose->subject_entry));
12157 gtkaspell_highlight_all(compose->gtkaspell);
12161 static void compose_check_backwards(GtkAction *action, gpointer data)
12163 Compose *compose = (Compose *)data;
12164 if (!compose->gtkaspell) {
12165 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
12169 if (gtk_widget_has_focus(compose->subject_entry))
12170 claws_spell_entry_check_backwards(
12171 CLAWS_SPELL_ENTRY(compose->subject_entry));
12173 gtkaspell_check_backwards(compose->gtkaspell);
12176 static void compose_check_forwards_go(GtkAction *action, gpointer data)
12178 Compose *compose = (Compose *)data;
12179 if (!compose->gtkaspell) {
12180 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
12184 if (gtk_widget_has_focus(compose->subject_entry))
12185 claws_spell_entry_check_forwards_go(
12186 CLAWS_SPELL_ENTRY(compose->subject_entry));
12188 gtkaspell_check_forwards_go(compose->gtkaspell);
12193 *\brief Guess originating forward account from MsgInfo and several
12194 * "common preference" settings. Return NULL if no guess.
12196 static PrefsAccount *compose_find_account(MsgInfo *msginfo)
12198 PrefsAccount *account = NULL;
12200 cm_return_val_if_fail(msginfo, NULL);
12201 cm_return_val_if_fail(msginfo->folder, NULL);
12202 cm_return_val_if_fail(msginfo->folder->prefs, NULL);
12204 if (msginfo->folder->prefs->enable_default_account)
12205 account = account_find_from_id(msginfo->folder->prefs->default_account);
12207 if (!account && msginfo->to && prefs_common.forward_account_autosel) {
12209 Xstrdup_a(to, msginfo->to, return NULL);
12210 extract_address(to);
12211 account = account_find_from_address(to, FALSE);
12214 if (!account && prefs_common.forward_account_autosel) {
12216 if (!procheader_get_header_from_msginfo
12217 (msginfo, &cc, "Cc:")) {
12218 gchar *buf = cc + strlen("Cc:");
12219 extract_address(buf);
12220 account = account_find_from_address(buf, FALSE);
12225 if (!account && prefs_common.forward_account_autosel) {
12226 gchar *deliveredto = NULL;
12227 if (!procheader_get_header_from_msginfo
12228 (msginfo, &deliveredto, "Delivered-To:")) {
12229 gchar *buf = deliveredto + strlen("Delivered-To:");
12230 extract_address(buf);
12231 account = account_find_from_address(buf, FALSE);
12232 g_free(deliveredto);
12237 account = msginfo->folder->folder->account;
12242 gboolean compose_close(Compose *compose)
12246 cm_return_val_if_fail(compose, FALSE);
12248 if (!g_mutex_trylock(compose->mutex)) {
12249 /* we have to wait for the (possibly deferred by auto-save)
12250 * drafting to be done, before destroying the compose under
12252 debug_print("waiting for drafting to finish...\n");
12253 compose_allow_user_actions(compose, FALSE);
12254 if (compose->close_timeout_tag == 0) {
12255 compose->close_timeout_tag =
12256 g_timeout_add (500, (GSourceFunc) compose_close,
12262 if (compose->draft_timeout_tag >= 0) {
12263 g_source_remove(compose->draft_timeout_tag);
12264 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN;
12267 gtkut_widget_get_uposition(compose->window, &x, &y);
12268 if (!compose->batch) {
12269 prefs_common.compose_x = x;
12270 prefs_common.compose_y = y;
12272 g_mutex_unlock(compose->mutex);
12273 compose_destroy(compose);
12278 * Add entry field for each address in list.
12279 * \param compose E-Mail composition object.
12280 * \param listAddress List of (formatted) E-Mail addresses.
12282 static void compose_add_field_list( Compose *compose, GList *listAddress ) {
12285 node = listAddress;
12287 addr = ( gchar * ) node->data;
12288 compose_entry_append( compose, addr, COMPOSE_TO, PREF_NONE );
12289 node = g_list_next( node );
12293 static void compose_reply_from_messageview_real(MessageView *msgview, GSList *msginfo_list,
12294 guint action, gboolean opening_multiple)
12296 gchar *body = NULL;
12297 GSList *new_msglist = NULL;
12298 MsgInfo *tmp_msginfo = NULL;
12299 gboolean originally_enc = FALSE;
12300 gboolean originally_sig = FALSE;
12301 Compose *compose = NULL;
12302 gchar *s_system = NULL;
12304 cm_return_if_fail(msginfo_list != NULL);
12306 if (g_slist_length(msginfo_list) == 1 && !opening_multiple && msgview != NULL) {
12307 MimeInfo *mimeinfo = messageview_get_selected_mime_part(msgview);
12308 MsgInfo *orig_msginfo = (MsgInfo *)msginfo_list->data;
12310 if (mimeinfo != NULL && mimeinfo->type == MIMETYPE_MESSAGE &&
12311 !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
12312 tmp_msginfo = procmsg_msginfo_new_from_mimeinfo(
12313 orig_msginfo, mimeinfo);
12314 if (tmp_msginfo != NULL) {
12315 new_msglist = g_slist_append(NULL, tmp_msginfo);
12317 originally_enc = MSG_IS_ENCRYPTED(orig_msginfo->flags);
12318 privacy_msginfo_get_signed_state(orig_msginfo, &s_system);
12319 originally_sig = MSG_IS_SIGNED(orig_msginfo->flags);
12321 tmp_msginfo->folder = orig_msginfo->folder;
12322 tmp_msginfo->msgnum = orig_msginfo->msgnum;
12323 if (orig_msginfo->tags) {
12324 tmp_msginfo->tags = g_slist_copy(orig_msginfo->tags);
12325 tmp_msginfo->folder->tags_dirty = TRUE;
12331 if (!opening_multiple && msgview != NULL)
12332 body = messageview_get_selection(msgview);
12335 compose = compose_reply_mode((ComposeMode)action, new_msglist, body);
12336 procmsg_msginfo_free(&tmp_msginfo);
12337 g_slist_free(new_msglist);
12339 compose = compose_reply_mode((ComposeMode)action, msginfo_list, body);
12341 if (compose && originally_enc) {
12342 compose_force_encryption(compose, compose->account, FALSE, s_system);
12345 if (compose && originally_sig && compose->account->default_sign_reply) {
12346 compose_force_signing(compose, compose->account, s_system);
12350 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
12353 void compose_reply_from_messageview(MessageView *msgview, GSList *msginfo_list,
12356 if ((!prefs_common.forward_as_attachment || action != COMPOSE_FORWARD)
12357 && msginfo_list != NULL
12358 && action != COMPOSE_FORWARD_AS_ATTACH && g_slist_length(msginfo_list) > 1) {
12359 GSList *cur = msginfo_list;
12360 gchar *msg = g_strdup_printf(_("You are about to reply to %d "
12361 "messages. Opening the windows "
12362 "could take some time. Do you "
12363 "want to continue?"),
12364 g_slist_length(msginfo_list));
12365 if (g_slist_length(msginfo_list) > 9
12366 && alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, GTK_STOCK_YES, NULL,
12367 ALERTFOCUS_SECOND) != G_ALERTALTERNATE) {
12372 /* We'll open multiple compose windows */
12373 /* let the WM place the next windows */
12374 compose_force_window_origin = FALSE;
12375 for (; cur; cur = cur->next) {
12377 tmplist.data = cur->data;
12378 tmplist.next = NULL;
12379 compose_reply_from_messageview_real(msgview, &tmplist, action, TRUE);
12381 compose_force_window_origin = TRUE;
12383 /* forwarding multiple mails as attachments is done via a
12384 * single compose window */
12385 if (msginfo_list != NULL) {
12386 compose_reply_from_messageview_real(msgview, msginfo_list, action, FALSE);
12387 } else if (msgview != NULL) {
12389 tmplist.data = msgview->msginfo;
12390 tmplist.next = NULL;
12391 compose_reply_from_messageview_real(msgview, &tmplist, action, FALSE);
12393 debug_print("Nothing to reply to\n");
12398 void compose_check_for_email_account(Compose *compose)
12400 PrefsAccount *ac = NULL, *curr = NULL;
12406 if (compose->account && compose->account->protocol == A_NNTP) {
12407 ac = account_get_cur_account();
12408 if (ac->protocol == A_NNTP) {
12409 list = account_get_list();
12411 for( ; list != NULL ; list = g_list_next(list)) {
12412 curr = (PrefsAccount *) list->data;
12413 if (curr->protocol != A_NNTP) {
12419 combobox_select_by_data(GTK_COMBO_BOX(compose->account_combo),
12424 void compose_reply_to_address(MessageView *msgview, MsgInfo *msginfo,
12425 const gchar *address)
12427 GSList *msginfo_list = NULL;
12428 gchar *body = messageview_get_selection(msgview);
12431 msginfo_list = g_slist_prepend(msginfo_list, msginfo);
12433 compose = compose_reply_mode(COMPOSE_REPLY_TO_ADDRESS, msginfo_list, body);
12434 compose_check_for_email_account(compose);
12435 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
12436 compose_entry_append(compose, address, COMPOSE_TO, PREF_NONE);
12437 compose_reply_set_subject(compose, msginfo);
12440 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
12443 void compose_set_position(Compose *compose, gint pos)
12445 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
12447 gtkut_text_view_set_position(text, pos);
12450 gboolean compose_search_string(Compose *compose,
12451 const gchar *str, gboolean case_sens)
12453 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
12455 return gtkut_text_view_search_string(text, str, case_sens);
12458 gboolean compose_search_string_backward(Compose *compose,
12459 const gchar *str, gboolean case_sens)
12461 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
12463 return gtkut_text_view_search_string_backward(text, str, case_sens);
12466 /* allocate a msginfo structure and populate its data from a compose data structure */
12467 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose)
12469 MsgInfo *newmsginfo;
12471 gchar date[RFC822_DATE_BUFFSIZE];
12473 cm_return_val_if_fail( compose != NULL, NULL );
12475 newmsginfo = procmsg_msginfo_new();
12478 get_rfc822_date(date, sizeof(date));
12479 newmsginfo->date = g_strdup(date);
12482 if (compose->from_name) {
12483 newmsginfo->from = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
12484 newmsginfo->fromname = procheader_get_fromname(newmsginfo->from);
12488 if (compose->subject_entry)
12489 newmsginfo->subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
12491 /* to, cc, reply-to, newsgroups */
12492 for (list = compose->header_list; list; list = list->next) {
12493 gchar *header = gtk_editable_get_chars(
12495 gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
12496 gchar *entry = gtk_editable_get_chars(
12497 GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
12499 if ( strcasecmp(header, prefs_common_translated_header_name("To:")) == 0 ) {
12500 if ( newmsginfo->to == NULL ) {
12501 newmsginfo->to = g_strdup(entry);
12502 } else if (entry && *entry) {
12503 gchar *tmp = g_strconcat(newmsginfo->to, ", ", entry, NULL);
12504 g_free(newmsginfo->to);
12505 newmsginfo->to = tmp;
12508 if ( strcasecmp(header, prefs_common_translated_header_name("Cc:")) == 0 ) {
12509 if ( newmsginfo->cc == NULL ) {
12510 newmsginfo->cc = g_strdup(entry);
12511 } else if (entry && *entry) {
12512 gchar *tmp = g_strconcat(newmsginfo->cc, ", ", entry, NULL);
12513 g_free(newmsginfo->cc);
12514 newmsginfo->cc = tmp;
12517 if ( strcasecmp(header,
12518 prefs_common_translated_header_name("Newsgroups:")) == 0 ) {
12519 if ( newmsginfo->newsgroups == NULL ) {
12520 newmsginfo->newsgroups = g_strdup(entry);
12521 } else if (entry && *entry) {
12522 gchar *tmp = g_strconcat(newmsginfo->newsgroups, ", ", entry, NULL);
12523 g_free(newmsginfo->newsgroups);
12524 newmsginfo->newsgroups = tmp;
12532 /* other data is unset */
12538 /* update compose's dictionaries from folder dict settings */
12539 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
12540 FolderItem *folder_item)
12542 cm_return_if_fail(compose != NULL);
12544 if (compose->gtkaspell && folder_item && folder_item->prefs) {
12545 FolderItemPrefs *prefs = folder_item->prefs;
12547 if (prefs->enable_default_dictionary)
12548 gtkaspell_change_dict(compose->gtkaspell,
12549 prefs->default_dictionary, FALSE);
12550 if (folder_item->prefs->enable_default_alt_dictionary)
12551 gtkaspell_change_alt_dict(compose->gtkaspell,
12552 prefs->default_alt_dictionary);
12553 if (prefs->enable_default_dictionary
12554 || prefs->enable_default_alt_dictionary)
12555 compose_spell_menu_changed(compose);
12560 static void compose_subject_entry_activated(GtkWidget *widget, gpointer data)
12562 Compose *compose = (Compose *)data;
12564 cm_return_if_fail(compose != NULL);
12566 gtk_widget_grab_focus(compose->text);
12569 static void from_name_activate_cb(GtkWidget *widget, gpointer data)
12571 gtk_combo_box_popup(GTK_COMBO_BOX(data));