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)) == 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)) == 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 g_signal_connect(G_OBJECT(window), "delete_event",
9458 G_CALLBACK(attach_property_delete_event),
9460 g_signal_connect(G_OBJECT(window), "key_press_event",
9461 G_CALLBACK(attach_property_key_pressed),
9464 vbox = gtk_vbox_new(FALSE, 8);
9465 gtk_container_add(GTK_CONTAINER(window), vbox);
9467 table = gtk_table_new(4, 2, FALSE);
9468 gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
9469 gtk_table_set_row_spacings(GTK_TABLE(table), 8);
9470 gtk_table_set_col_spacings(GTK_TABLE(table), 8);
9472 label = gtk_label_new(_("MIME type"));
9473 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, (0 + 1),
9475 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
9476 mimetype_entry = gtk_combo_box_text_new_with_entry();
9477 gtk_table_attach(GTK_TABLE(table), mimetype_entry, 1, 2, 0, (0 + 1),
9478 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
9480 /* stuff with list */
9481 mime_type_list = procmime_get_mime_type_list();
9483 for (; mime_type_list != NULL; mime_type_list = mime_type_list->next) {
9484 MimeType *type = (MimeType *) mime_type_list->data;
9487 tmp = g_strdup_printf("%s/%s", type->type, type->sub_type);
9489 if (g_list_find_custom(strlist, tmp, (GCompareFunc)g_strcmp0))
9492 strlist = g_list_insert_sorted(strlist, (gpointer)tmp,
9493 (GCompareFunc)g_strcmp0);
9496 for (mime_type_list = strlist; mime_type_list != NULL;
9497 mime_type_list = mime_type_list->next) {
9498 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(mimetype_entry), mime_type_list->data);
9499 g_free(mime_type_list->data);
9501 g_list_free(strlist);
9502 gtk_combo_box_set_active(GTK_COMBO_BOX(mimetype_entry), 0);
9503 mimetype_entry = gtk_bin_get_child(GTK_BIN((mimetype_entry)));
9505 label = gtk_label_new(_("Encoding"));
9506 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2,
9508 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
9510 hbox = gtk_hbox_new(FALSE, 0);
9511 gtk_table_attach(GTK_TABLE(table), hbox, 1, 2, 1, 2,
9512 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
9514 optmenu = gtkut_sc_combobox_create(NULL, TRUE);
9515 optmenu_menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
9517 COMBOBOX_ADD(optmenu_menu, "7bit", ENC_7BIT);
9518 COMBOBOX_ADD(optmenu_menu, "8bit", ENC_8BIT);
9519 COMBOBOX_ADD(optmenu_menu, "quoted-printable", ENC_QUOTED_PRINTABLE);
9520 COMBOBOX_ADD(optmenu_menu, "base64", ENC_BASE64);
9521 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), 0);
9523 gtk_box_pack_start(GTK_BOX(hbox), optmenu, TRUE, TRUE, 0);
9525 SET_LABEL_AND_ENTRY(_("Path"), path_entry, 2);
9526 SET_LABEL_AND_ENTRY(_("File name"), filename_entry, 3);
9528 gtkut_stock_button_set_create(&hbbox, &cancel_btn, GTK_STOCK_CANCEL,
9529 &ok_btn, GTK_STOCK_OK,
9531 gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
9532 gtk_widget_grab_default(ok_btn);
9534 g_signal_connect(G_OBJECT(ok_btn), "clicked",
9535 G_CALLBACK(attach_property_ok),
9537 g_signal_connect(G_OBJECT(cancel_btn), "clicked",
9538 G_CALLBACK(attach_property_cancel),
9541 gtk_widget_show_all(vbox);
9543 attach_prop.window = window;
9544 attach_prop.mimetype_entry = mimetype_entry;
9545 attach_prop.encoding_optmenu = optmenu;
9546 attach_prop.path_entry = path_entry;
9547 attach_prop.filename_entry = filename_entry;
9548 attach_prop.ok_btn = ok_btn;
9549 attach_prop.cancel_btn = cancel_btn;
9552 #undef SET_LABEL_AND_ENTRY
9554 static void attach_property_ok(GtkWidget *widget, gboolean *cancelled)
9560 static void attach_property_cancel(GtkWidget *widget, gboolean *cancelled)
9566 static gint attach_property_delete_event(GtkWidget *widget, GdkEventAny *event,
9567 gboolean *cancelled)
9575 static gboolean attach_property_key_pressed(GtkWidget *widget,
9577 gboolean *cancelled)
9579 if (event && event->keyval == GDK_KEY_Escape) {
9583 if (event && event->keyval == GDK_KEY_Return) {
9591 static void compose_exec_ext_editor(Compose *compose)
9596 GdkNativeWindow socket_wid = 0;
9600 tmp = g_strdup_printf("%s%ctmpmsg.%p", get_tmp_dir(),
9601 G_DIR_SEPARATOR, compose);
9603 if (compose_get_ext_editor_uses_socket()) {
9604 /* Only allow one socket */
9605 if (compose->exteditor_socket != NULL) {
9606 if (gtk_widget_is_focus(compose->exteditor_socket)) {
9607 /* Move the focus off of the socket */
9608 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9613 /* Create the receiving GtkSocket */
9614 socket = gtk_socket_new ();
9615 g_signal_connect (GTK_OBJECT(socket), "plug-removed",
9616 G_CALLBACK(compose_ext_editor_plug_removed_cb),
9618 gtk_box_pack_start(GTK_BOX(compose->edit_vbox), socket, TRUE, TRUE, 0);
9619 gtk_widget_set_size_request(socket, prefs_common.compose_width, -1);
9620 /* Realize the socket so that we can use its ID */
9621 gtk_widget_realize(socket);
9622 socket_wid = gtk_socket_get_id(GTK_SOCKET (socket));
9623 compose->exteditor_socket = socket;
9626 if (pipe(pipe_fds) < 0) {
9632 if ((pid = fork()) < 0) {
9639 /* close the write side of the pipe */
9642 compose->exteditor_file = g_strdup(tmp);
9643 compose->exteditor_pid = pid;
9645 compose_set_ext_editor_sensitive(compose, FALSE);
9648 compose->exteditor_ch = g_io_channel_unix_new(pipe_fds[0]);
9650 compose->exteditor_ch = g_io_channel_win32_new_fd(pipe_fds[0]);
9652 compose->exteditor_tag = g_io_add_watch(compose->exteditor_ch,
9656 } else { /* process-monitoring process */
9662 /* close the read side of the pipe */
9665 if (compose_write_body_to_file(compose, tmp) < 0) {
9666 fd_write_all(pipe_fds[1], "2\n", 2);
9670 pid_ed = compose_exec_ext_editor_real(tmp, socket_wid);
9672 fd_write_all(pipe_fds[1], "1\n", 2);
9676 /* wait until editor is terminated */
9677 waitpid(pid_ed, NULL, 0);
9679 fd_write_all(pipe_fds[1], "0\n", 2);
9686 #endif /* G_OS_UNIX */
9689 static gboolean compose_can_autosave(Compose *compose)
9691 if (compose->privacy_system && compose->use_encryption)
9692 return prefs_common.autosave && prefs_common.autosave_encrypted;
9694 return prefs_common.autosave;
9698 static gboolean compose_get_ext_editor_cmd_valid()
9700 gboolean has_s = FALSE;
9701 gboolean has_w = FALSE;
9702 const gchar *p = prefs_common_get_ext_editor_cmd();
9705 while ((p = strchr(p, '%'))) {
9711 } else if (*p == 'w') {
9722 static gint compose_exec_ext_editor_real(const gchar *file, GdkNativeWindow socket_wid)
9729 cm_return_val_if_fail(file != NULL, -1);
9731 if ((pid = fork()) < 0) {
9736 if (pid != 0) return pid;
9738 /* grandchild process */
9740 if (setpgid(0, getppid()))
9743 if (compose_get_ext_editor_cmd_valid()) {
9744 if (compose_get_ext_editor_uses_socket()) {
9745 p = g_strdup(prefs_common_get_ext_editor_cmd());
9746 s = strstr(p, "%w");
9748 if (strstr(p, "%s") < s)
9749 buf = g_strdup_printf(p, file, socket_wid);
9751 buf = g_strdup_printf(p, socket_wid, file);
9754 buf = g_strdup_printf(prefs_common_get_ext_editor_cmd(), file);
9757 if (prefs_common_get_ext_editor_cmd())
9758 g_warning("External editor command-line is invalid: '%s'",
9759 prefs_common_get_ext_editor_cmd());
9760 buf = g_strdup_printf(DEFAULT_EDITOR_CMD, file);
9763 cmdline = strsplit_with_quote(buf, " ", 0);
9765 execvp(cmdline[0], cmdline);
9768 g_strfreev(cmdline);
9773 static gboolean compose_ext_editor_kill(Compose *compose)
9775 pid_t pgid = compose->exteditor_pid * -1;
9778 ret = kill(pgid, 0);
9780 if (ret == 0 || (ret == -1 && EPERM == errno)) {
9784 msg = g_strdup_printf
9785 (_("The external editor is still working.\n"
9786 "Force terminating the process?\n"
9787 "process group id: %d"), -pgid);
9788 val = alertpanel_full(_("Notice"), msg, GTK_STOCK_NO, GTK_STOCK_YES,
9789 NULL, ALERTFOCUS_FIRST, FALSE, NULL,
9794 if (val == G_ALERTALTERNATE) {
9795 g_source_remove(compose->exteditor_tag);
9796 g_io_channel_shutdown(compose->exteditor_ch,
9798 g_io_channel_unref(compose->exteditor_ch);
9800 if (kill(pgid, SIGTERM) < 0) perror("kill");
9801 waitpid(compose->exteditor_pid, NULL, 0);
9803 g_warning("Terminated process group id: %d. "
9804 "Temporary file: %s", -pgid, compose->exteditor_file);
9806 compose_set_ext_editor_sensitive(compose, TRUE);
9808 g_free(compose->exteditor_file);
9809 compose->exteditor_file = NULL;
9810 compose->exteditor_pid = -1;
9811 compose->exteditor_ch = NULL;
9812 compose->exteditor_tag = -1;
9820 static gboolean compose_input_cb(GIOChannel *source, GIOCondition condition,
9824 Compose *compose = (Compose *)data;
9827 debug_print("Compose: input from monitoring process\n");
9829 if (g_io_channel_read_chars(source, buf, sizeof(buf), &bytes_read, NULL) != G_IO_STATUS_NORMAL) {
9834 g_io_channel_shutdown(source, FALSE, NULL);
9835 g_io_channel_unref(source);
9837 waitpid(compose->exteditor_pid, NULL, 0);
9839 if (buf[0] == '0') { /* success */
9840 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9841 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
9842 GtkTextIter start, end;
9845 gtk_text_buffer_set_text(buffer, "", -1);
9846 compose_insert_file(compose, compose->exteditor_file);
9847 compose_changed_cb(NULL, compose);
9849 /* Check if we should save the draft or not */
9850 if (compose_can_autosave(compose))
9851 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
9853 if (claws_unlink(compose->exteditor_file) < 0)
9854 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9856 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
9857 gtk_text_buffer_get_start_iter(buffer, &start);
9858 gtk_text_buffer_get_end_iter(buffer, &end);
9859 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
9860 if (chars && strlen(chars) > 0)
9861 compose->modified = TRUE;
9863 } else if (buf[0] == '1') { /* failed */
9864 g_warning("Couldn't exec external editor");
9865 if (claws_unlink(compose->exteditor_file) < 0)
9866 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9867 } else if (buf[0] == '2') {
9868 g_warning("Couldn't write to file");
9869 } else if (buf[0] == '3') {
9870 g_warning("Pipe read failed");
9873 compose_set_ext_editor_sensitive(compose, TRUE);
9875 g_free(compose->exteditor_file);
9876 compose->exteditor_file = NULL;
9877 compose->exteditor_pid = -1;
9878 compose->exteditor_ch = NULL;
9879 compose->exteditor_tag = -1;
9880 if (compose->exteditor_socket) {
9881 gtk_widget_destroy(compose->exteditor_socket);
9882 compose->exteditor_socket = NULL;
9889 static char *ext_editor_menu_entries[] = {
9890 "Menu/Message/Send",
9891 "Menu/Message/SendLater",
9892 "Menu/Message/InsertFile",
9893 "Menu/Message/InsertSig",
9894 "Menu/Message/ReplaceSig",
9895 "Menu/Message/Save",
9896 "Menu/Message/Print",
9901 "Menu/Tools/ShowRuler",
9902 "Menu/Tools/Actions",
9907 static void compose_set_ext_editor_sensitive(Compose *compose,
9912 for (i = 0; ext_editor_menu_entries[i]; ++i) {
9913 cm_menu_set_sensitive_full(compose->ui_manager,
9914 ext_editor_menu_entries[i], sensitive);
9917 if (compose_get_ext_editor_uses_socket()) {
9919 if (compose->exteditor_socket)
9920 gtk_widget_hide(compose->exteditor_socket);
9921 gtk_widget_show(compose->scrolledwin);
9922 if (prefs_common.show_ruler)
9923 gtk_widget_show(compose->ruler_hbox);
9924 /* Fix the focus, as it doesn't go anywhere when the
9925 * socket is hidden or destroyed */
9926 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9928 g_assert (compose->exteditor_socket != NULL);
9929 /* Fix the focus, as it doesn't go anywhere when the
9930 * edit box is hidden */
9931 if (gtk_widget_is_focus(compose->text))
9932 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9933 gtk_widget_hide(compose->scrolledwin);
9934 gtk_widget_hide(compose->ruler_hbox);
9935 gtk_widget_show(compose->exteditor_socket);
9938 gtk_widget_set_sensitive(compose->text, sensitive);
9940 if (compose->toolbar->send_btn)
9941 gtk_widget_set_sensitive(compose->toolbar->send_btn, sensitive);
9942 if (compose->toolbar->sendl_btn)
9943 gtk_widget_set_sensitive(compose->toolbar->sendl_btn, sensitive);
9944 if (compose->toolbar->draft_btn)
9945 gtk_widget_set_sensitive(compose->toolbar->draft_btn, sensitive);
9946 if (compose->toolbar->insert_btn)
9947 gtk_widget_set_sensitive(compose->toolbar->insert_btn, sensitive);
9948 if (compose->toolbar->sig_btn)
9949 gtk_widget_set_sensitive(compose->toolbar->sig_btn, sensitive);
9950 if (compose->toolbar->exteditor_btn)
9951 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, sensitive);
9952 if (compose->toolbar->linewrap_current_btn)
9953 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, sensitive);
9954 if (compose->toolbar->linewrap_all_btn)
9955 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, sensitive);
9958 static gboolean compose_get_ext_editor_uses_socket()
9960 return (prefs_common_get_ext_editor_cmd() &&
9961 strstr(prefs_common_get_ext_editor_cmd(), "%w"));
9964 static gboolean compose_ext_editor_plug_removed_cb(GtkSocket *socket, Compose *compose)
9966 compose->exteditor_socket = NULL;
9967 /* returning FALSE allows destruction of the socket */
9970 #endif /* G_OS_UNIX */
9973 * compose_undo_state_changed:
9975 * Change the sensivity of the menuentries undo and redo
9977 static void compose_undo_state_changed(UndoMain *undostruct, gint undo_state,
9978 gint redo_state, gpointer data)
9980 Compose *compose = (Compose *)data;
9982 switch (undo_state) {
9983 case UNDO_STATE_TRUE:
9984 if (!undostruct->undo_state) {
9985 undostruct->undo_state = TRUE;
9986 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", TRUE);
9989 case UNDO_STATE_FALSE:
9990 if (undostruct->undo_state) {
9991 undostruct->undo_state = FALSE;
9992 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
9995 case UNDO_STATE_UNCHANGED:
9997 case UNDO_STATE_REFRESH:
9998 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", undostruct->undo_state);
10001 g_warning("Undo state not recognized");
10005 switch (redo_state) {
10006 case UNDO_STATE_TRUE:
10007 if (!undostruct->redo_state) {
10008 undostruct->redo_state = TRUE;
10009 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", TRUE);
10012 case UNDO_STATE_FALSE:
10013 if (undostruct->redo_state) {
10014 undostruct->redo_state = FALSE;
10015 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
10018 case UNDO_STATE_UNCHANGED:
10020 case UNDO_STATE_REFRESH:
10021 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", undostruct->redo_state);
10024 g_warning("Redo state not recognized");
10029 /* callback functions */
10031 static void compose_notebook_size_alloc(GtkNotebook *notebook,
10032 GtkAllocation *allocation,
10035 prefs_common.compose_notebook_height = gtk_paned_get_position(paned);
10038 /* compose_edit_size_alloc() - called when resized. don't know whether Gtk
10039 * includes "non-client" (windows-izm) in calculation, so this calculation
10040 * may not be accurate.
10042 static gboolean compose_edit_size_alloc(GtkEditable *widget,
10043 GtkAllocation *allocation,
10044 GtkSHRuler *shruler)
10046 if (prefs_common.show_ruler) {
10047 gint char_width = 0, char_height = 0;
10048 gint line_width_in_chars;
10050 gtkut_get_font_size(GTK_WIDGET(widget),
10051 &char_width, &char_height);
10052 line_width_in_chars =
10053 (allocation->width - allocation->x) / char_width;
10055 /* got the maximum */
10056 gtk_shruler_set_range(GTK_SHRULER(shruler),
10057 0.0, line_width_in_chars, 0);
10066 ComposePrefType type;
10067 gboolean entry_marked;
10068 } HeaderEntryState;
10070 static void account_activated(GtkComboBox *optmenu, gpointer data)
10072 Compose *compose = (Compose *)data;
10075 gchar *folderidentifier;
10076 gint account_id = 0;
10077 GtkTreeModel *menu;
10079 GSList *list, *saved_list = NULL;
10080 HeaderEntryState *state;
10082 /* Get ID of active account in the combo box */
10083 menu = gtk_combo_box_get_model(optmenu);
10084 cm_return_if_fail(gtk_combo_box_get_active_iter(optmenu, &iter));
10085 gtk_tree_model_get(menu, &iter, 1, &account_id, -1);
10087 ac = account_find_from_id(account_id);
10088 cm_return_if_fail(ac != NULL);
10090 if (ac != compose->account) {
10091 compose_select_account(compose, ac, FALSE);
10093 for (list = compose->header_list; list; list = list->next) {
10094 ComposeHeaderEntry *hentry=(ComposeHeaderEntry *)list->data;
10096 if (hentry->type == PREF_ACCOUNT || !list->next) {
10097 compose_destroy_headerentry(compose, hentry);
10100 state = g_malloc0(sizeof(HeaderEntryState));
10101 state->header = gtk_editable_get_chars(GTK_EDITABLE(
10102 gtk_bin_get_child(GTK_BIN(hentry->combo))), 0, -1);
10103 state->entry = gtk_editable_get_chars(
10104 GTK_EDITABLE(hentry->entry), 0, -1);
10105 state->type = hentry->type;
10107 saved_list = g_slist_append(saved_list, state);
10108 compose_destroy_headerentry(compose, hentry);
10111 compose->header_last = NULL;
10112 g_slist_free(compose->header_list);
10113 compose->header_list = NULL;
10114 compose->header_nextrow = 1;
10115 compose_create_header_entry(compose);
10117 if (ac->set_autocc && ac->auto_cc)
10118 compose_entry_append(compose, ac->auto_cc,
10119 COMPOSE_CC, PREF_ACCOUNT);
10120 if (ac->set_autobcc && ac->auto_bcc)
10121 compose_entry_append(compose, ac->auto_bcc,
10122 COMPOSE_BCC, PREF_ACCOUNT);
10123 if (ac->set_autoreplyto && ac->auto_replyto)
10124 compose_entry_append(compose, ac->auto_replyto,
10125 COMPOSE_REPLYTO, PREF_ACCOUNT);
10127 for (list = saved_list; list; list = list->next) {
10128 state = (HeaderEntryState *) list->data;
10130 compose_add_header_entry(compose, state->header,
10131 state->entry, state->type);
10133 g_free(state->header);
10134 g_free(state->entry);
10137 g_slist_free(saved_list);
10139 combobox_select_by_data(GTK_COMBO_BOX(compose->header_last->combo),
10140 (ac->protocol == A_NNTP) ?
10141 COMPOSE_NEWSGROUPS : COMPOSE_TO);
10144 /* Set message save folder */
10145 compose_set_save_to(compose, NULL);
10146 if (compose->folder && compose->folder->prefs && compose->folder->prefs->save_copy_to_folder) {
10147 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
10148 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo), TRUE);
10149 folderidentifier = folder_item_get_identifier(compose->folder);
10150 compose_set_save_to(compose, folderidentifier);
10151 g_free(folderidentifier);
10152 } else if (account_get_special_folder(compose->account, F_OUTBOX)) {
10153 if (compose->account->set_sent_folder || prefs_common.savemsg)
10154 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
10156 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), FALSE);
10157 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo), TRUE);
10158 folderidentifier = folder_item_get_identifier(account_get_special_folder
10159 (compose->account, F_OUTBOX));
10160 compose_set_save_to(compose, folderidentifier);
10161 g_free(folderidentifier);
10165 static void attach_selected(GtkTreeView *tree_view, GtkTreePath *tree_path,
10166 GtkTreeViewColumn *column, Compose *compose)
10168 compose_attach_property(NULL, compose);
10171 static gboolean attach_button_pressed(GtkWidget *widget, GdkEventButton *event,
10174 Compose *compose = (Compose *)data;
10175 GtkTreeSelection *attach_selection;
10176 gint attach_nr_selected;
10179 if (!event) return FALSE;
10181 if (event->button == 3) {
10182 attach_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
10183 attach_nr_selected = gtk_tree_selection_count_selected_rows(attach_selection);
10185 /* If no rows, or just one row is selected, right-click should
10186 * open menu relevant to the row being right-clicked on. We
10187 * achieve that by selecting the clicked row first. If more
10188 * than one row is selected, we shouldn't modify the selection,
10189 * as user may want to remove selected rows (attachments). */
10190 if (attach_nr_selected < 2) {
10191 gtk_tree_selection_unselect_all(attach_selection);
10192 attach_nr_selected = 0;
10193 gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget),
10194 event->x, event->y, &path, NULL, NULL, NULL);
10195 if (path != NULL) {
10196 gtk_tree_selection_select_path(attach_selection, path);
10197 gtk_tree_path_free(path);
10198 attach_nr_selected++;
10202 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", (attach_nr_selected > 0));
10203 /* Properties menu item makes no sense with more than one row
10204 * selected, the properties dialog can only edit one attachment. */
10205 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", (attach_nr_selected == 1));
10207 gtk_menu_popup(GTK_MENU(compose->popupmenu), NULL, NULL,
10208 NULL, NULL, event->button, event->time);
10215 static gboolean attach_key_pressed(GtkWidget *widget, GdkEventKey *event,
10218 Compose *compose = (Compose *)data;
10220 if (!event) return FALSE;
10222 switch (event->keyval) {
10223 case GDK_KEY_Delete:
10224 compose_attach_remove_selected(NULL, compose);
10230 static void compose_allow_user_actions (Compose *compose, gboolean allow)
10232 toolbar_comp_set_sensitive(compose, allow);
10233 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message", allow);
10234 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", allow);
10236 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", allow);
10238 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", allow);
10239 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools", allow);
10240 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Help", allow);
10242 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), allow);
10246 static void compose_send_cb(GtkAction *action, gpointer data)
10248 Compose *compose = (Compose *)data;
10251 if (compose->exteditor_tag != -1) {
10252 debug_print("ignoring send: external editor still open\n");
10256 if (prefs_common.work_offline &&
10257 !inc_offline_should_override(TRUE,
10258 _("Claws Mail needs network access in order "
10259 "to send this email.")))
10262 if (compose->draft_timeout_tag >= 0) { /* CLAWS: disable draft timeout */
10263 g_source_remove(compose->draft_timeout_tag);
10264 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
10267 compose_send(compose);
10270 static void compose_send_later_cb(GtkAction *action, gpointer data)
10272 Compose *compose = (Compose *)data;
10273 ComposeQueueResult val;
10276 compose_allow_user_actions(compose, FALSE);
10277 val = compose_queue_sub(compose, NULL, NULL, NULL, TRUE, TRUE);
10278 compose_allow_user_actions(compose, TRUE);
10281 if (val == COMPOSE_QUEUE_SUCCESS) {
10282 compose_close(compose);
10284 _display_queue_error(val);
10287 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
10290 #define DRAFTED_AT_EXIT "drafted_at_exit"
10291 static void compose_register_draft(MsgInfo *info)
10293 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10294 DRAFTED_AT_EXIT, NULL);
10295 FILE *fp = claws_fopen(filepath, "ab");
10298 fprintf(fp, "%s\t%d\n", folder_item_get_identifier(info->folder),
10306 gboolean compose_draft (gpointer data, guint action)
10308 Compose *compose = (Compose *)data;
10313 MsgFlags flag = {0, 0};
10314 static gboolean lock = FALSE;
10315 MsgInfo *newmsginfo;
10317 gboolean target_locked = FALSE;
10318 gboolean err = FALSE;
10320 if (lock) return FALSE;
10322 if (compose->sending)
10325 draft = account_get_special_folder(compose->account, F_DRAFT);
10326 cm_return_val_if_fail(draft != NULL, FALSE);
10328 if (!g_mutex_trylock(compose->mutex)) {
10329 /* we don't want to lock the mutex once it's available,
10330 * because as the only other part of compose.c locking
10331 * it is compose_close - which means once unlocked,
10332 * the compose struct will be freed */
10333 debug_print("couldn't lock mutex, probably sending\n");
10339 tmp = g_strdup_printf("%s%cdraft.%p", get_tmp_dir(),
10340 G_DIR_SEPARATOR, compose);
10341 if ((fp = claws_fopen(tmp, "wb")) == NULL) {
10342 FILE_OP_ERROR(tmp, "claws_fopen");
10346 /* chmod for security */
10347 if (change_file_mode_rw(fp, tmp) < 0) {
10348 FILE_OP_ERROR(tmp, "chmod");
10349 g_warning("can't change file mode");
10352 /* Save draft infos */
10353 err |= (fprintf(fp, "X-Claws-Account-Id:%d\n", compose->account->account_id) < 0);
10354 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
10356 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
10357 gchar *savefolderid;
10359 savefolderid = compose_get_save_to(compose);
10360 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
10361 g_free(savefolderid);
10363 if (compose->return_receipt) {
10364 err |= (fprintf(fp, "RRCPT:1\n") < 0);
10366 if (compose->privacy_system) {
10367 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
10368 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
10369 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
10372 /* Message-ID of message replying to */
10373 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
10374 gchar *folderid = NULL;
10376 if (compose->replyinfo->folder)
10377 folderid = folder_item_get_identifier(compose->replyinfo->folder);
10378 if (folderid == NULL)
10379 folderid = g_strdup("NULL");
10381 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
10384 /* Message-ID of message forwarding to */
10385 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
10386 gchar *folderid = NULL;
10388 if (compose->fwdinfo->folder)
10389 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
10390 if (folderid == NULL)
10391 folderid = g_strdup("NULL");
10393 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
10397 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
10398 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
10400 sheaders = compose_get_manual_headers_info(compose);
10401 err |= (fprintf(fp, "X-Claws-Manual-Headers:%s\n", sheaders) < 0);
10404 /* end of headers */
10405 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
10412 if (compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_STORE, action != COMPOSE_AUTO_SAVE) < 0) {
10416 if (claws_safe_fclose(fp) == EOF) {
10420 flag.perm_flags = MSG_NEW|MSG_UNREAD;
10421 if (compose->targetinfo) {
10422 target_locked = MSG_IS_LOCKED(compose->targetinfo->flags);
10424 flag.perm_flags |= MSG_LOCKED;
10426 flag.tmp_flags = MSG_DRAFT;
10428 folder_item_scan(draft);
10429 if ((msgnum = folder_item_add_msg(draft, tmp, &flag, TRUE)) < 0) {
10430 MsgInfo *tmpinfo = NULL;
10431 debug_print("didn't get msgnum after adding draft [%s]\n", compose->msgid?compose->msgid:"no msgid");
10432 if (compose->msgid) {
10433 tmpinfo = folder_item_get_msginfo_by_msgid(draft, compose->msgid);
10436 msgnum = tmpinfo->msgnum;
10437 procmsg_msginfo_free(&tmpinfo);
10438 debug_print("got draft msgnum %d from scanning\n", msgnum);
10440 debug_print("didn't get draft msgnum after scanning\n");
10443 debug_print("got draft msgnum %d from adding\n", msgnum);
10449 if (action != COMPOSE_AUTO_SAVE) {
10450 if (action != COMPOSE_DRAFT_FOR_EXIT)
10451 alertpanel_error(_("Could not save draft."));
10454 gtkut_window_popup(compose->window);
10455 val = alertpanel_full(_("Could not save draft"),
10456 _("Could not save draft.\n"
10457 "Do you want to cancel exit or discard this email?"),
10458 _("_Cancel exit"), _("_Discard email"), NULL, ALERTFOCUS_FIRST,
10459 FALSE, NULL, ALERT_QUESTION);
10460 if (val == G_ALERTALTERNATE) {
10462 g_mutex_unlock(compose->mutex); /* must be done before closing */
10463 compose_close(compose);
10467 g_mutex_unlock(compose->mutex); /* must be done before closing */
10476 if (compose->mode == COMPOSE_REEDIT) {
10477 compose_remove_reedit_target(compose, TRUE);
10480 newmsginfo = folder_item_get_msginfo(draft, msgnum);
10483 procmsg_msginfo_unset_flags(newmsginfo, ~0, ~0);
10485 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD|MSG_LOCKED, MSG_DRAFT);
10487 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD, MSG_DRAFT);
10488 if (compose_use_attach(compose) && action != COMPOSE_AUTO_SAVE)
10489 procmsg_msginfo_set_flags(newmsginfo, 0,
10490 MSG_HAS_ATTACHMENT);
10492 if (action == COMPOSE_DRAFT_FOR_EXIT) {
10493 compose_register_draft(newmsginfo);
10495 procmsg_msginfo_free(&newmsginfo);
10498 folder_item_scan(draft);
10500 if (action == COMPOSE_QUIT_EDITING || action == COMPOSE_DRAFT_FOR_EXIT) {
10502 g_mutex_unlock(compose->mutex); /* must be done before closing */
10503 compose_close(compose);
10510 GError *error = NULL;
10515 goffset size, mtime;
10517 path = folder_item_fetch_msg(draft, msgnum);
10518 if (path == NULL) {
10519 debug_print("can't fetch %s:%d\n", draft->path, msgnum);
10523 f = g_file_new_for_path(path);
10524 fi = g_file_query_info(f, "standard::size,time::modified",
10525 G_FILE_QUERY_INFO_NONE, NULL, &error);
10526 if (error != NULL) {
10527 debug_print("couldn't query file info for '%s': %s\n",
10528 path, error->message);
10529 g_error_free(error);
10534 size = g_file_info_get_size(fi);
10535 g_file_info_get_modification_time(fi, &tv);
10537 g_object_unref(fi);
10540 if (g_stat(path, &s) < 0) {
10541 FILE_OP_ERROR(path, "stat");
10546 mtime = s.st_mtime;
10550 procmsg_msginfo_free(&(compose->targetinfo));
10551 compose->targetinfo = procmsg_msginfo_new();
10552 compose->targetinfo->msgnum = msgnum;
10553 compose->targetinfo->size = size;
10554 compose->targetinfo->mtime = mtime;
10555 compose->targetinfo->folder = draft;
10557 procmsg_msginfo_set_flags(compose->targetinfo, MSG_LOCKED, 0);
10558 compose->mode = COMPOSE_REEDIT;
10560 if (action == COMPOSE_AUTO_SAVE) {
10561 compose->autosaved_draft = compose->targetinfo;
10563 compose->modified = FALSE;
10564 compose_set_title(compose);
10568 g_mutex_unlock(compose->mutex);
10572 void compose_clear_exit_drafts(void)
10574 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10575 DRAFTED_AT_EXIT, NULL);
10576 if (is_file_exist(filepath))
10577 claws_unlink(filepath);
10582 void compose_reopen_exit_drafts(void)
10584 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10585 DRAFTED_AT_EXIT, NULL);
10586 FILE *fp = claws_fopen(filepath, "rb");
10590 while (claws_fgets(buf, sizeof(buf), fp)) {
10591 gchar **parts = g_strsplit(buf, "\t", 2);
10592 const gchar *folder = parts[0];
10593 int msgnum = parts[1] ? atoi(parts[1]):-1;
10595 if (folder && *folder && msgnum > -1) {
10596 FolderItem *item = folder_find_item_from_identifier(folder);
10597 MsgInfo *info = folder_item_get_msginfo(item, msgnum);
10599 compose_reedit(info, FALSE);
10606 compose_clear_exit_drafts();
10609 static void compose_save_cb(GtkAction *action, gpointer data)
10611 Compose *compose = (Compose *)data;
10612 compose_draft(compose, COMPOSE_KEEP_EDITING);
10613 compose->rmode = COMPOSE_REEDIT;
10616 void compose_attach_from_list(Compose *compose, GList *file_list, gboolean free_data)
10618 if (compose && file_list) {
10621 for ( tmp = file_list; tmp; tmp = tmp->next) {
10622 gchar *file = (gchar *) tmp->data;
10623 gchar *utf8_filename = conv_filename_to_utf8(file);
10624 compose_attach_append(compose, file, utf8_filename, NULL, NULL);
10625 compose_changed_cb(NULL, compose);
10630 g_free(utf8_filename);
10635 static void compose_attach_cb(GtkAction *action, gpointer data)
10637 Compose *compose = (Compose *)data;
10640 if (compose->redirect_filename != NULL)
10643 /* Set focus_window properly, in case we were called via popup menu,
10644 * which unsets it (via focus_out_event callback on compose window). */
10645 manage_window_focus_in(compose->window, NULL, NULL);
10647 file_list = filesel_select_multiple_files_open(_("Select file"), NULL);
10650 compose_attach_from_list(compose, file_list, TRUE);
10651 g_list_free(file_list);
10655 static void compose_insert_file_cb(GtkAction *action, gpointer data)
10657 Compose *compose = (Compose *)data;
10659 gint files_inserted = 0;
10661 file_list = filesel_select_multiple_files_open(_("Select file"), NULL);
10666 for ( tmp = file_list; tmp; tmp = tmp->next) {
10667 gchar *file = (gchar *) tmp->data;
10668 gchar *filedup = g_strdup(file);
10669 gchar *shortfile = g_path_get_basename(filedup);
10670 ComposeInsertResult res;
10671 /* insert the file if the file is short or if the user confirmed that
10672 he/she wants to insert the large file */
10673 res = compose_insert_file(compose, file);
10674 if (res == COMPOSE_INSERT_READ_ERROR) {
10675 alertpanel_error(_("File '%s' could not be read."), shortfile);
10676 } else if (res == COMPOSE_INSERT_INVALID_CHARACTER) {
10677 alertpanel_error(_("File '%s' contained invalid characters\n"
10678 "for the current encoding, insertion may be incorrect."),
10680 } else if (res == COMPOSE_INSERT_SUCCESS)
10687 g_list_free(file_list);
10691 if (files_inserted > 0 && compose->gtkaspell &&
10692 compose->gtkaspell->check_while_typing)
10693 gtkaspell_highlight_all(compose->gtkaspell);
10697 static void compose_insert_sig_cb(GtkAction *action, gpointer data)
10699 Compose *compose = (Compose *)data;
10701 compose_insert_sig(compose, FALSE);
10704 static void compose_replace_sig_cb(GtkAction *action, gpointer data)
10706 Compose *compose = (Compose *)data;
10708 compose_insert_sig(compose, TRUE);
10711 static gint compose_delete_cb(GtkWidget *widget, GdkEventAny *event,
10715 Compose *compose = (Compose *)data;
10717 gtkut_widget_get_uposition(widget, &x, &y);
10718 if (!compose->batch) {
10719 prefs_common.compose_x = x;
10720 prefs_common.compose_y = y;
10722 if (compose->sending || compose->updating)
10724 compose_close_cb(NULL, compose);
10728 void compose_close_toolbar(Compose *compose)
10730 compose_close_cb(NULL, compose);
10733 static void compose_close_cb(GtkAction *action, gpointer data)
10735 Compose *compose = (Compose *)data;
10739 if (compose->exteditor_tag != -1) {
10740 if (!compose_ext_editor_kill(compose))
10745 if (compose->modified) {
10746 gboolean reedit = (compose->rmode == COMPOSE_REEDIT);
10747 if (!g_mutex_trylock(compose->mutex)) {
10748 /* we don't want to lock the mutex once it's available,
10749 * because as the only other part of compose.c locking
10750 * it is compose_close - which means once unlocked,
10751 * the compose struct will be freed */
10752 debug_print("couldn't lock mutex, probably sending\n");
10755 if (!reedit || compose->folder->stype == F_DRAFT) {
10756 val = alertpanel(_("Discard message"),
10757 _("This message has been modified. Discard it?"),
10758 _("_Discard"), _("_Save to Drafts"), GTK_STOCK_CANCEL,
10761 val = alertpanel(_("Save changes"),
10762 _("This message has been modified. Save the latest changes?"),
10763 _("_Don't save"), _("_Save to Drafts"), GTK_STOCK_CANCEL,
10764 ALERTFOCUS_SECOND);
10766 g_mutex_unlock(compose->mutex);
10768 case G_ALERTDEFAULT:
10769 if (compose_can_autosave(compose) && !reedit)
10770 compose_remove_draft(compose);
10772 case G_ALERTALTERNATE:
10773 compose_draft(data, COMPOSE_QUIT_EDITING);
10780 compose_close(compose);
10783 static void compose_print_cb(GtkAction *action, gpointer data)
10785 Compose *compose = (Compose *) data;
10787 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
10788 if (compose->targetinfo)
10789 messageview_print(compose->targetinfo, FALSE, -1, -1, 0);
10792 static void compose_set_encoding_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
10794 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
10795 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
10796 Compose *compose = (Compose *) data;
10799 compose->out_encoding = (CharSet)value;
10802 static void compose_address_cb(GtkAction *action, gpointer data)
10804 Compose *compose = (Compose *)data;
10806 #ifndef USE_ALT_ADDRBOOK
10807 addressbook_open(compose);
10809 GError* error = NULL;
10810 addressbook_connect_signals(compose);
10811 addressbook_dbus_open(TRUE, &error);
10813 g_warning("%s", error->message);
10814 g_error_free(error);
10819 static void about_show_cb(GtkAction *action, gpointer data)
10824 static void compose_template_activate_cb(GtkWidget *widget, gpointer data)
10826 Compose *compose = (Compose *)data;
10831 tmpl = g_object_get_data(G_OBJECT(widget), "template");
10832 cm_return_if_fail(tmpl != NULL);
10834 msg = g_strdup_printf(_("Do you want to apply the template '%s'?"),
10836 val = alertpanel(_("Apply template"), msg,
10837 _("_Replace"), _("_Insert"), GTK_STOCK_CANCEL, ALERTFOCUS_FIRST);
10840 if (val == G_ALERTDEFAULT)
10841 compose_template_apply(compose, tmpl, TRUE);
10842 else if (val == G_ALERTALTERNATE)
10843 compose_template_apply(compose, tmpl, FALSE);
10846 static void compose_ext_editor_cb(GtkAction *action, gpointer data)
10848 Compose *compose = (Compose *)data;
10851 if (compose->exteditor_tag != -1) {
10852 debug_print("ignoring open external editor: external editor still open\n");
10856 compose_exec_ext_editor(compose);
10859 static void compose_undo_cb(GtkAction *action, gpointer data)
10861 Compose *compose = (Compose *)data;
10862 gboolean prev_autowrap = compose->autowrap;
10864 compose->autowrap = FALSE;
10865 undo_undo(compose->undostruct);
10866 compose->autowrap = prev_autowrap;
10869 static void compose_redo_cb(GtkAction *action, gpointer data)
10871 Compose *compose = (Compose *)data;
10872 gboolean prev_autowrap = compose->autowrap;
10874 compose->autowrap = FALSE;
10875 undo_redo(compose->undostruct);
10876 compose->autowrap = prev_autowrap;
10879 static void entry_cut_clipboard(GtkWidget *entry)
10881 if (GTK_IS_EDITABLE(entry))
10882 gtk_editable_cut_clipboard (GTK_EDITABLE(entry));
10883 else if (GTK_IS_TEXT_VIEW(entry))
10884 gtk_text_buffer_cut_clipboard(
10885 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10886 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
10890 static void entry_copy_clipboard(GtkWidget *entry)
10892 if (GTK_IS_EDITABLE(entry))
10893 gtk_editable_copy_clipboard (GTK_EDITABLE(entry));
10894 else if (GTK_IS_TEXT_VIEW(entry))
10895 gtk_text_buffer_copy_clipboard(
10896 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10897 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
10900 static void paste_text(Compose *compose, GtkWidget *entry,
10901 gboolean wrap, GdkAtom clip, GtkTextIter *insert_place,
10902 const gchar *contents)
10904 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10905 GtkTextMark *mark_start = gtk_text_buffer_get_insert(buffer);
10906 GtkTextIter start_iter, end_iter;
10909 if (contents == NULL)
10912 /* we shouldn't delete the selection when middle-click-pasting, or we
10913 * can't mid-click-paste our own selection */
10914 if (clip != GDK_SELECTION_PRIMARY) {
10915 undo_paste_clipboard(GTK_TEXT_VIEW(compose->text), compose->undostruct);
10916 gtk_text_buffer_delete_selection(buffer, FALSE, TRUE);
10919 if (insert_place == NULL) {
10920 /* if insert_place isn't specified, insert at the cursor.
10921 * used for Ctrl-V pasting */
10922 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10923 start = gtk_text_iter_get_offset(&start_iter);
10924 gtk_text_buffer_insert(buffer, &start_iter, contents, strlen(contents));
10926 /* if insert_place is specified, paste here.
10927 * used for mid-click-pasting */
10928 start = gtk_text_iter_get_offset(insert_place);
10929 gtk_text_buffer_insert(buffer, insert_place, contents, strlen(contents));
10930 if (prefs_common.primary_paste_unselects)
10931 gtk_text_buffer_select_range(buffer, insert_place, insert_place);
10935 /* paste unwrapped: mark the paste so it's not wrapped later */
10936 end = start + strlen(contents);
10937 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, start);
10938 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, end);
10939 gtk_text_buffer_apply_tag_by_name(buffer, "no_wrap", &start_iter, &end_iter);
10940 } else if (wrap && clip == GDK_SELECTION_PRIMARY) {
10941 /* rewrap paragraph now (after a mid-click-paste) */
10942 mark_start = gtk_text_buffer_get_insert(buffer);
10943 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10944 gtk_text_iter_backward_char(&start_iter);
10945 compose_beautify_paragraph(compose, &start_iter, TRUE);
10947 compose->modified = TRUE;
10950 static void attach_uri_list(Compose *compose, GtkSelectionData *data)
10954 list = uri_list_extract_filenames(
10955 (const gchar *)gtk_selection_data_get_data(data));
10956 for (tmp = list; tmp != NULL; tmp = tmp->next) {
10957 gchar *utf8_filename = conv_filename_to_utf8((const gchar *)tmp->data);
10958 compose_attach_append
10959 (compose, (const gchar *)tmp->data,
10960 utf8_filename, NULL, NULL);
10961 g_free(utf8_filename);
10964 compose_changed_cb(NULL, compose);
10966 list_free_strings_full(list);
10969 int attach_image(Compose *compose, GtkSelectionData *data, const gchar *subtype)
10972 const guchar *contents;
10978 cm_return_val_if_fail(data != NULL, -1);
10980 contents = gtk_selection_data_get_data(data);
10981 len = gtk_selection_data_get_length(data);
10983 file = g_strconcat(get_tmp_file(), "-image.", subtype, NULL);
10985 debug_print("writing image to %s\n", file);
10987 if ((fp = claws_fopen(file, "wb")) == NULL) {
10988 FILE_OP_ERROR(file, "claws_fopen");
10992 if (claws_fwrite(contents, 1, len, fp) != len) {
10993 FILE_OP_ERROR(file, "claws_fwrite");
10995 claws_unlink(file);
10999 r = claws_safe_fclose(fp);
11002 FILE_OP_ERROR(file, "claws_fclose");
11003 claws_unlink(file);
11007 type = g_strconcat("image/", subtype, NULL);
11009 compose_attach_append(compose, (const gchar *)file,
11010 (const gchar *)file, type, NULL);
11012 alertpanel_notice(_("The pasted image has been attached as: \n%s"), file);
11020 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry,
11021 gboolean wrap, GdkAtom clip, GtkTextIter *insert_place)
11023 if (GTK_IS_TEXT_VIEW(entry)) {
11024 GdkAtom types = gdk_atom_intern ("TARGETS", FALSE);
11025 GdkAtom *targets = NULL;
11026 int n_targets = 0, i;
11027 gboolean paste_done = FALSE;
11028 GtkClipboard *clipboard = gtk_clipboard_get(clip);
11030 GtkSelectionData *contents = gtk_clipboard_wait_for_contents(
11033 if (contents != NULL) {
11034 gtk_selection_data_get_targets(contents, &targets, &n_targets);
11035 gtk_selection_data_free(contents);
11038 for (i = 0; i < n_targets; i++) {
11039 GdkAtom atom = targets[i];
11040 gchar *atom_type = gdk_atom_name(atom);
11042 if (atom_type != NULL) {
11043 GtkSelectionData *data = gtk_clipboard_wait_for_contents(
11045 debug_print("got contents of type %s\n", atom_type);
11046 if (!strcmp(atom_type, "text/plain")) {
11047 /* let the default text handler handle it */
11049 } else if (!strcmp(atom_type, "text/uri-list")) {
11050 attach_uri_list(compose, data);
11054 } else if (!strncmp(atom_type, "image/", strlen("image/"))) {
11055 gchar *subtype = g_strdup((gchar *)(strstr(atom_type, "/")+1));
11056 debug_print("image of type %s\n", subtype);
11058 attach_image(compose, data, subtype);
11067 gchar *def_text = gtk_clipboard_wait_for_text(clipboard);
11068 paste_text(compose, entry, wrap, clip,
11069 insert_place, def_text);
11074 } else if (GTK_IS_EDITABLE(entry)) {
11075 gtk_editable_paste_clipboard (GTK_EDITABLE(entry));
11076 compose->modified = TRUE;
11080 static void entry_allsel(GtkWidget *entry)
11082 if (GTK_IS_EDITABLE(entry))
11083 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
11084 else if (GTK_IS_TEXT_VIEW(entry)) {
11085 GtkTextIter startiter, enditer;
11086 GtkTextBuffer *textbuf;
11088 textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
11089 gtk_text_buffer_get_start_iter(textbuf, &startiter);
11090 gtk_text_buffer_get_end_iter(textbuf, &enditer);
11092 gtk_text_buffer_move_mark_by_name(textbuf,
11093 "selection_bound", &startiter);
11094 gtk_text_buffer_move_mark_by_name(textbuf,
11095 "insert", &enditer);
11099 static void compose_cut_cb(GtkAction *action, gpointer data)
11101 Compose *compose = (Compose *)data;
11102 if (compose->focused_editable
11103 #ifndef GENERIC_UMPC
11104 && gtk_widget_has_focus(compose->focused_editable)
11107 entry_cut_clipboard(compose->focused_editable);
11110 static void compose_copy_cb(GtkAction *action, gpointer data)
11112 Compose *compose = (Compose *)data;
11113 if (compose->focused_editable
11114 #ifndef GENERIC_UMPC
11115 && gtk_widget_has_focus(compose->focused_editable)
11118 entry_copy_clipboard(compose->focused_editable);
11121 static void compose_paste_cb(GtkAction *action, gpointer data)
11123 Compose *compose = (Compose *)data;
11124 gint prev_autowrap;
11125 GtkTextBuffer *buffer;
11127 if (compose->focused_editable
11128 #ifndef GENERIC_UMPC
11129 && gtk_widget_has_focus(compose->focused_editable)
11132 entry_paste_clipboard(compose, compose->focused_editable,
11133 prefs_common.linewrap_pastes,
11134 GDK_SELECTION_CLIPBOARD, NULL);
11139 #ifndef GENERIC_UMPC
11140 gtk_widget_has_focus(compose->text) &&
11142 compose->gtkaspell &&
11143 compose->gtkaspell->check_while_typing)
11144 gtkaspell_highlight_all(compose->gtkaspell);
11148 static void compose_paste_as_quote_cb(GtkAction *action, gpointer data)
11150 Compose *compose = (Compose *)data;
11151 gint wrap_quote = prefs_common.linewrap_quote;
11152 if (compose->focused_editable
11153 #ifndef GENERIC_UMPC
11154 && gtk_widget_has_focus(compose->focused_editable)
11157 /* let text_insert() (called directly or at a later time
11158 * after the gtk_editable_paste_clipboard) know that
11159 * text is to be inserted as a quotation. implemented
11160 * by using a simple refcount... */
11161 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data(
11162 G_OBJECT(compose->focused_editable),
11163 "paste_as_quotation"));
11164 g_object_set_data(G_OBJECT(compose->focused_editable),
11165 "paste_as_quotation",
11166 GINT_TO_POINTER(paste_as_quotation + 1));
11167 prefs_common.linewrap_quote = prefs_common.linewrap_pastes;
11168 entry_paste_clipboard(compose, compose->focused_editable,
11169 prefs_common.linewrap_pastes,
11170 GDK_SELECTION_CLIPBOARD, NULL);
11171 prefs_common.linewrap_quote = wrap_quote;
11175 static void compose_paste_no_wrap_cb(GtkAction *action, gpointer data)
11177 Compose *compose = (Compose *)data;
11178 gint prev_autowrap;
11179 GtkTextBuffer *buffer;
11181 if (compose->focused_editable
11182 #ifndef GENERIC_UMPC
11183 && gtk_widget_has_focus(compose->focused_editable)
11186 entry_paste_clipboard(compose, compose->focused_editable, FALSE,
11187 GDK_SELECTION_CLIPBOARD, NULL);
11192 #ifndef GENERIC_UMPC
11193 gtk_widget_has_focus(compose->text) &&
11195 compose->gtkaspell &&
11196 compose->gtkaspell->check_while_typing)
11197 gtkaspell_highlight_all(compose->gtkaspell);
11201 static void compose_paste_wrap_cb(GtkAction *action, gpointer data)
11203 Compose *compose = (Compose *)data;
11204 gint prev_autowrap;
11205 GtkTextBuffer *buffer;
11207 if (compose->focused_editable
11208 #ifndef GENERIC_UMPC
11209 && gtk_widget_has_focus(compose->focused_editable)
11212 entry_paste_clipboard(compose, compose->focused_editable, TRUE,
11213 GDK_SELECTION_CLIPBOARD, NULL);
11218 #ifndef GENERIC_UMPC
11219 gtk_widget_has_focus(compose->text) &&
11221 compose->gtkaspell &&
11222 compose->gtkaspell->check_while_typing)
11223 gtkaspell_highlight_all(compose->gtkaspell);
11227 static void compose_allsel_cb(GtkAction *action, gpointer data)
11229 Compose *compose = (Compose *)data;
11230 if (compose->focused_editable
11231 #ifndef GENERIC_UMPC
11232 && gtk_widget_has_focus(compose->focused_editable)
11235 entry_allsel(compose->focused_editable);
11238 static void textview_move_beginning_of_line (GtkTextView *text)
11240 GtkTextBuffer *buffer;
11244 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11246 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11247 mark = gtk_text_buffer_get_insert(buffer);
11248 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11249 gtk_text_iter_set_line_offset(&ins, 0);
11250 gtk_text_buffer_place_cursor(buffer, &ins);
11253 static void textview_move_forward_character (GtkTextView *text)
11255 GtkTextBuffer *buffer;
11259 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11261 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11262 mark = gtk_text_buffer_get_insert(buffer);
11263 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11264 if (gtk_text_iter_forward_cursor_position(&ins))
11265 gtk_text_buffer_place_cursor(buffer, &ins);
11268 static void textview_move_backward_character (GtkTextView *text)
11270 GtkTextBuffer *buffer;
11274 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11276 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11277 mark = gtk_text_buffer_get_insert(buffer);
11278 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11279 if (gtk_text_iter_backward_cursor_position(&ins))
11280 gtk_text_buffer_place_cursor(buffer, &ins);
11283 static void textview_move_forward_word (GtkTextView *text)
11285 GtkTextBuffer *buffer;
11290 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11292 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11293 mark = gtk_text_buffer_get_insert(buffer);
11294 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11295 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
11296 if (gtk_text_iter_forward_word_ends(&ins, count)) {
11297 gtk_text_iter_backward_word_start(&ins);
11298 gtk_text_buffer_place_cursor(buffer, &ins);
11302 static void textview_move_backward_word (GtkTextView *text)
11304 GtkTextBuffer *buffer;
11308 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11310 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11311 mark = gtk_text_buffer_get_insert(buffer);
11312 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11313 if (gtk_text_iter_backward_word_starts(&ins, 1))
11314 gtk_text_buffer_place_cursor(buffer, &ins);
11317 static void textview_move_end_of_line (GtkTextView *text)
11319 GtkTextBuffer *buffer;
11323 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11325 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11326 mark = gtk_text_buffer_get_insert(buffer);
11327 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11328 if (gtk_text_iter_forward_to_line_end(&ins))
11329 gtk_text_buffer_place_cursor(buffer, &ins);
11332 static void textview_move_next_line (GtkTextView *text)
11334 GtkTextBuffer *buffer;
11339 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11341 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11342 mark = gtk_text_buffer_get_insert(buffer);
11343 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11344 offset = gtk_text_iter_get_line_offset(&ins);
11345 if (gtk_text_iter_forward_line(&ins)) {
11346 gtk_text_iter_set_line_offset(&ins, offset);
11347 gtk_text_buffer_place_cursor(buffer, &ins);
11351 static void textview_move_previous_line (GtkTextView *text)
11353 GtkTextBuffer *buffer;
11358 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11360 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11361 mark = gtk_text_buffer_get_insert(buffer);
11362 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11363 offset = gtk_text_iter_get_line_offset(&ins);
11364 if (gtk_text_iter_backward_line(&ins)) {
11365 gtk_text_iter_set_line_offset(&ins, offset);
11366 gtk_text_buffer_place_cursor(buffer, &ins);
11370 static void textview_delete_forward_character (GtkTextView *text)
11372 GtkTextBuffer *buffer;
11374 GtkTextIter ins, end_iter;
11376 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11378 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11379 mark = gtk_text_buffer_get_insert(buffer);
11380 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11382 if (gtk_text_iter_forward_char(&end_iter)) {
11383 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11387 static void textview_delete_backward_character (GtkTextView *text)
11389 GtkTextBuffer *buffer;
11391 GtkTextIter ins, end_iter;
11393 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11395 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11396 mark = gtk_text_buffer_get_insert(buffer);
11397 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11399 if (gtk_text_iter_backward_char(&end_iter)) {
11400 gtk_text_buffer_delete(buffer, &end_iter, &ins);
11404 static void textview_delete_forward_word (GtkTextView *text)
11406 GtkTextBuffer *buffer;
11408 GtkTextIter ins, end_iter;
11410 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11412 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11413 mark = gtk_text_buffer_get_insert(buffer);
11414 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11416 if (gtk_text_iter_forward_word_end(&end_iter)) {
11417 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11421 static void textview_delete_backward_word (GtkTextView *text)
11423 GtkTextBuffer *buffer;
11425 GtkTextIter ins, end_iter;
11427 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11429 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11430 mark = gtk_text_buffer_get_insert(buffer);
11431 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11433 if (gtk_text_iter_backward_word_start(&end_iter)) {
11434 gtk_text_buffer_delete(buffer, &end_iter, &ins);
11438 static void textview_delete_line (GtkTextView *text)
11440 GtkTextBuffer *buffer;
11442 GtkTextIter ins, start_iter, end_iter;
11444 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11446 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11447 mark = gtk_text_buffer_get_insert(buffer);
11448 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11451 gtk_text_iter_set_line_offset(&start_iter, 0);
11454 if (gtk_text_iter_ends_line(&end_iter)){
11455 if (!gtk_text_iter_forward_char(&end_iter))
11456 gtk_text_iter_backward_char(&start_iter);
11459 gtk_text_iter_forward_to_line_end(&end_iter);
11460 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
11463 static void textview_delete_to_line_end (GtkTextView *text)
11465 GtkTextBuffer *buffer;
11467 GtkTextIter ins, end_iter;
11469 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11471 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11472 mark = gtk_text_buffer_get_insert(buffer);
11473 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11475 if (gtk_text_iter_ends_line(&end_iter))
11476 gtk_text_iter_forward_char(&end_iter);
11478 gtk_text_iter_forward_to_line_end(&end_iter);
11479 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11482 #define DO_ACTION(name, act) { \
11483 if(!strcmp(name, a_name)) { \
11487 static ComposeCallAdvancedAction compose_call_advanced_action_from_path(GtkAction *action)
11489 const gchar *a_name = gtk_action_get_name(action);
11490 DO_ACTION("Edit/Advanced/BackChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER);
11491 DO_ACTION("Edit/Advanced/ForwChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER);
11492 DO_ACTION("Edit/Advanced/BackWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD);
11493 DO_ACTION("Edit/Advanced/ForwWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD);
11494 DO_ACTION("Edit/Advanced/BegLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE);
11495 DO_ACTION("Edit/Advanced/EndLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE);
11496 DO_ACTION("Edit/Advanced/PrevLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE);
11497 DO_ACTION("Edit/Advanced/NextLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE);
11498 DO_ACTION("Edit/Advanced/DelBackChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER);
11499 DO_ACTION("Edit/Advanced/DelForwChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER);
11500 DO_ACTION("Edit/Advanced/DelBackWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD);
11501 DO_ACTION("Edit/Advanced/DelForwWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD);
11502 DO_ACTION("Edit/Advanced/DelLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE);
11503 DO_ACTION("Edit/Advanced/DelEndLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END);
11504 return COMPOSE_CALL_ADVANCED_ACTION_UNDEFINED;
11507 static void compose_advanced_action_cb(GtkAction *gaction, gpointer data)
11509 Compose *compose = (Compose *)data;
11510 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11511 ComposeCallAdvancedAction action = COMPOSE_CALL_ADVANCED_ACTION_UNDEFINED;
11513 action = compose_call_advanced_action_from_path(gaction);
11516 void (*do_action) (GtkTextView *text);
11517 } action_table[] = {
11518 {textview_move_beginning_of_line},
11519 {textview_move_forward_character},
11520 {textview_move_backward_character},
11521 {textview_move_forward_word},
11522 {textview_move_backward_word},
11523 {textview_move_end_of_line},
11524 {textview_move_next_line},
11525 {textview_move_previous_line},
11526 {textview_delete_forward_character},
11527 {textview_delete_backward_character},
11528 {textview_delete_forward_word},
11529 {textview_delete_backward_word},
11530 {textview_delete_line},
11531 {textview_delete_to_line_end}
11534 if (!gtk_widget_has_focus(GTK_WIDGET(text))) return;
11536 if (action >= COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE &&
11537 action <= COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END) {
11538 if (action_table[action].do_action)
11539 action_table[action].do_action(text);
11541 g_warning("Not implemented yet.");
11545 static void compose_grab_focus_cb(GtkWidget *widget, Compose *compose)
11547 GtkAllocation allocation;
11551 if (GTK_IS_EDITABLE(widget)) {
11552 str = gtk_editable_get_chars(GTK_EDITABLE(widget), 0, -1);
11553 gtk_editable_set_position(GTK_EDITABLE(widget),
11556 if ((parent = gtk_widget_get_parent(widget))
11557 && (parent = gtk_widget_get_parent(parent))
11558 && (parent = gtk_widget_get_parent(parent))) {
11559 if (GTK_IS_SCROLLED_WINDOW(parent)) {
11560 gtk_widget_get_allocation(widget, &allocation);
11561 gint y = allocation.y;
11562 gint height = allocation.height;
11563 GtkAdjustment *shown = gtk_scrolled_window_get_vadjustment
11564 (GTK_SCROLLED_WINDOW(parent));
11566 gfloat value = gtk_adjustment_get_value(shown);
11567 gfloat upper = gtk_adjustment_get_upper(shown);
11568 gfloat page_size = gtk_adjustment_get_page_size(shown);
11569 if (y < (int)value) {
11570 gtk_adjustment_set_value(shown, y - 1);
11572 if ((y + height) > ((int)value + (int)page_size)) {
11573 if ((y - height - 1) < ((int)upper - (int)page_size)) {
11574 gtk_adjustment_set_value(shown,
11575 y + height - (int)page_size - 1);
11577 gtk_adjustment_set_value(shown,
11578 (int)upper - (int)page_size - 1);
11585 if (GTK_IS_EDITABLE(widget) || GTK_IS_TEXT_VIEW(widget))
11586 compose->focused_editable = widget;
11588 #ifdef GENERIC_UMPC
11589 if (GTK_IS_TEXT_VIEW(widget)
11590 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->edit_vbox) {
11591 g_object_ref(compose->notebook);
11592 g_object_ref(compose->edit_vbox);
11593 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
11594 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
11595 gtk_paned_add1(GTK_PANED(compose->paned), compose->edit_vbox);
11596 gtk_paned_add2(GTK_PANED(compose->paned), compose->notebook);
11597 g_object_unref(compose->notebook);
11598 g_object_unref(compose->edit_vbox);
11599 g_signal_handlers_block_by_func(G_OBJECT(widget),
11600 G_CALLBACK(compose_grab_focus_cb),
11602 gtk_widget_grab_focus(widget);
11603 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
11604 G_CALLBACK(compose_grab_focus_cb),
11606 } else if (!GTK_IS_TEXT_VIEW(widget)
11607 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->notebook) {
11608 g_object_ref(compose->notebook);
11609 g_object_ref(compose->edit_vbox);
11610 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
11611 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
11612 gtk_paned_add1(GTK_PANED(compose->paned), compose->notebook);
11613 gtk_paned_add2(GTK_PANED(compose->paned), compose->edit_vbox);
11614 g_object_unref(compose->notebook);
11615 g_object_unref(compose->edit_vbox);
11616 g_signal_handlers_block_by_func(G_OBJECT(widget),
11617 G_CALLBACK(compose_grab_focus_cb),
11619 gtk_widget_grab_focus(widget);
11620 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
11621 G_CALLBACK(compose_grab_focus_cb),
11627 static void compose_changed_cb(GtkTextBuffer *textbuf, Compose *compose)
11629 compose->modified = TRUE;
11630 /* compose_beautify_paragraph(compose, NULL, TRUE); */
11631 #ifndef GENERIC_UMPC
11632 compose_set_title(compose);
11636 static void compose_wrap_cb(GtkAction *action, gpointer data)
11638 Compose *compose = (Compose *)data;
11639 compose_beautify_paragraph(compose, NULL, TRUE);
11642 static void compose_wrap_all_cb(GtkAction *action, gpointer data)
11644 Compose *compose = (Compose *)data;
11645 compose_wrap_all_full(compose, TRUE);
11648 static void compose_find_cb(GtkAction *action, gpointer data)
11650 Compose *compose = (Compose *)data;
11652 message_search_compose(compose);
11655 static void compose_toggle_autowrap_cb(GtkToggleAction *action,
11658 Compose *compose = (Compose *)data;
11659 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11660 if (compose->autowrap)
11661 compose_wrap_all_full(compose, TRUE);
11662 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11665 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
11668 Compose *compose = (Compose *)data;
11669 compose->autoindent = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11672 static void compose_toggle_sign_cb(GtkToggleAction *action, gpointer data)
11674 Compose *compose = (Compose *)data;
11676 compose->use_signing = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11677 gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(compose->toolbar->privacy_sign_btn), compose->use_signing);
11680 static void compose_toggle_encrypt_cb(GtkToggleAction *action, gpointer data)
11682 Compose *compose = (Compose *)data;
11684 compose->use_encryption = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11685 gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(compose->toolbar->privacy_encrypt_btn), compose->use_encryption);
11688 static void compose_activate_privacy_system(Compose *compose, PrefsAccount *account, gboolean warn)
11690 g_free(compose->privacy_system);
11691 g_free(compose->encdata);
11693 compose->privacy_system = g_strdup(account->default_privacy_system);
11694 compose_update_privacy_system_menu_item(compose, warn);
11697 static void compose_apply_folder_privacy_settings(Compose *compose, FolderItem *folder_item)
11699 if (folder_item != NULL) {
11700 if (folder_item->prefs->always_sign != SIGN_OR_ENCRYPT_DEFAULT &&
11701 privacy_system_can_sign(compose->privacy_system)) {
11702 compose_use_signing(compose,
11703 (folder_item->prefs->always_sign == SIGN_OR_ENCRYPT_ALWAYS) ? TRUE : FALSE);
11705 if (folder_item->prefs->always_encrypt != SIGN_OR_ENCRYPT_DEFAULT &&
11706 privacy_system_can_encrypt(compose->privacy_system)) {
11707 compose_use_encryption(compose,
11708 (folder_item->prefs->always_encrypt == SIGN_OR_ENCRYPT_ALWAYS) ? TRUE : FALSE);
11713 static void compose_toggle_ruler_cb(GtkToggleAction *action, gpointer data)
11715 Compose *compose = (Compose *)data;
11717 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action))) {
11718 gtk_widget_show(compose->ruler_hbox);
11719 prefs_common.show_ruler = TRUE;
11721 gtk_widget_hide(compose->ruler_hbox);
11722 gtk_widget_queue_resize(compose->edit_vbox);
11723 prefs_common.show_ruler = FALSE;
11727 static void compose_attach_drag_received_cb (GtkWidget *widget,
11728 GdkDragContext *context,
11731 GtkSelectionData *data,
11734 gpointer user_data)
11736 Compose *compose = (Compose *)user_data;
11739 type = gtk_selection_data_get_data_type(data);
11740 if ((gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list"))
11741 && gtk_drag_get_source_widget(context) !=
11742 summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
11743 attach_uri_list(compose, data);
11744 } else if (gtk_drag_get_source_widget(context)
11745 == summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
11746 /* comes from our summaryview */
11747 SummaryView * summaryview = NULL;
11748 GSList * list = NULL, *cur = NULL;
11750 if (mainwindow_get_mainwindow())
11751 summaryview = mainwindow_get_mainwindow()->summaryview;
11754 list = summary_get_selected_msg_list(summaryview);
11756 for (cur = list; cur; cur = cur->next) {
11757 MsgInfo *msginfo = (MsgInfo *)cur->data;
11758 gchar *file = NULL;
11760 file = procmsg_get_message_file_full(msginfo,
11763 compose_attach_append(compose, (const gchar *)file,
11764 (const gchar *)file, "message/rfc822", NULL);
11768 g_slist_free(list);
11772 static gboolean compose_drag_drop(GtkWidget *widget,
11773 GdkDragContext *drag_context,
11775 guint time, gpointer user_data)
11777 /* not handling this signal makes compose_insert_drag_received_cb
11782 static gboolean completion_set_focus_to_subject
11783 (GtkWidget *widget,
11784 GdkEventKey *event,
11787 cm_return_val_if_fail(compose != NULL, FALSE);
11789 /* make backtab move to subject field */
11790 if(event->keyval == GDK_KEY_ISO_Left_Tab) {
11791 gtk_widget_grab_focus(compose->subject_entry);
11797 static void compose_insert_drag_received_cb (GtkWidget *widget,
11798 GdkDragContext *drag_context,
11801 GtkSelectionData *data,
11804 gpointer user_data)
11806 Compose *compose = (Compose *)user_data;
11812 /* strangely, testing data->type == gdk_atom_intern("text/uri-list", TRUE)
11814 type = gtk_selection_data_get_data_type(data);
11815 if (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list")) {
11816 AlertValue val = G_ALERTDEFAULT;
11817 const gchar* ddata = (const gchar *)gtk_selection_data_get_data(data);
11819 list = uri_list_extract_filenames(ddata);
11820 num_files = g_list_length(list);
11821 if (list == NULL && strstr(ddata, "://")) {
11822 /* Assume a list of no files, and data has ://, is a remote link */
11823 gchar *tmpdata = g_strstrip(g_strdup(ddata));
11824 gchar *tmpfile = get_tmp_file();
11825 str_write_to_file(tmpdata, tmpfile, TRUE);
11827 compose_insert_file(compose, tmpfile);
11828 claws_unlink(tmpfile);
11830 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11831 compose_beautify_paragraph(compose, NULL, TRUE);
11834 switch (prefs_common.compose_dnd_mode) {
11835 case COMPOSE_DND_ASK:
11836 msg = g_strdup_printf(
11838 "Do you want to insert the contents of the file "
11839 "into the message body, or attach it to the email?",
11840 "Do you want to insert the contents of the %d files "
11841 "into the message body, or attach them to the email?",
11844 val = alertpanel_full(_("Insert or attach?"), msg,
11845 GTK_STOCK_CANCEL, _("_Insert"), _("_Attach"),
11847 TRUE, NULL, ALERT_QUESTION);
11850 case COMPOSE_DND_INSERT:
11851 val = G_ALERTALTERNATE;
11853 case COMPOSE_DND_ATTACH:
11854 val = G_ALERTOTHER;
11857 /* unexpected case */
11858 g_warning("error: unexpected compose_dnd_mode option value in compose_insert_drag_received_cb()");
11861 if (val & G_ALERTDISABLE) {
11862 val &= ~G_ALERTDISABLE;
11863 /* remember what action to perform by default, only if we don't click Cancel */
11864 if (val == G_ALERTALTERNATE)
11865 prefs_common.compose_dnd_mode = COMPOSE_DND_INSERT;
11866 else if (val == G_ALERTOTHER)
11867 prefs_common.compose_dnd_mode = COMPOSE_DND_ATTACH;
11870 if (val == G_ALERTDEFAULT || val == G_ALERTCANCEL) {
11871 gtk_drag_finish(drag_context, FALSE, FALSE, time);
11872 list_free_strings_full(list);
11874 } else if (val == G_ALERTOTHER) {
11875 compose_attach_drag_received_cb(widget, drag_context, x, y, data, info, time, user_data);
11876 list_free_strings_full(list);
11880 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11881 compose_insert_file(compose, (const gchar *)tmp->data);
11883 list_free_strings_full(list);
11884 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11889 static void compose_header_drag_received_cb (GtkWidget *widget,
11890 GdkDragContext *drag_context,
11893 GtkSelectionData *data,
11896 gpointer user_data)
11898 GtkEditable *entry = (GtkEditable *)user_data;
11899 const gchar *email = (const gchar *)gtk_selection_data_get_data(data);
11901 /* strangely, testing data->type == gdk_atom_intern("text/plain", TRUE)
11904 if (!strncmp(email, "mailto:", strlen("mailto:"))) {
11905 gchar *decoded=g_new(gchar, strlen(email));
11908 decode_uri(decoded, email + strlen("mailto:")); /* will fit */
11909 gtk_editable_delete_text(entry, 0, -1);
11910 gtk_editable_insert_text(entry, decoded, strlen(decoded), &start);
11911 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11915 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11918 static void compose_toggle_return_receipt_cb(GtkToggleAction *action, gpointer data)
11920 Compose *compose = (Compose *)data;
11922 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11923 compose->return_receipt = TRUE;
11925 compose->return_receipt = FALSE;
11928 static void compose_toggle_remove_refs_cb(GtkToggleAction *action, gpointer data)
11930 Compose *compose = (Compose *)data;
11932 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11933 compose->remove_references = TRUE;
11935 compose->remove_references = FALSE;
11938 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
11939 ComposeHeaderEntry *headerentry)
11941 gtk_entry_set_text(GTK_ENTRY(headerentry->entry), "");
11942 gtk_widget_modify_base(GTK_WIDGET(headerentry->entry), GTK_STATE_NORMAL, NULL);
11943 gtk_widget_modify_text(GTK_WIDGET(headerentry->entry), GTK_STATE_NORMAL, NULL);
11947 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
11948 GdkEventKey *event,
11949 ComposeHeaderEntry *headerentry)
11951 if ((g_slist_length(headerentry->compose->header_list) > 0) &&
11952 ((headerentry->headernum + 1) != headerentry->compose->header_nextrow) &&
11953 !(event->state & GDK_MODIFIER_MASK) &&
11954 (event->keyval == GDK_KEY_BackSpace) &&
11955 (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) == 0)) {
11956 gtk_container_remove
11957 (GTK_CONTAINER(headerentry->compose->header_table),
11958 headerentry->combo);
11959 gtk_container_remove
11960 (GTK_CONTAINER(headerentry->compose->header_table),
11961 headerentry->entry);
11962 headerentry->compose->header_list =
11963 g_slist_remove(headerentry->compose->header_list,
11965 g_free(headerentry);
11966 } else if (event->keyval == GDK_KEY_Tab) {
11967 if (headerentry->compose->header_last == headerentry) {
11968 /* Override default next focus, and give it to subject_entry
11969 * instead of notebook tabs
11971 g_signal_stop_emission_by_name(G_OBJECT(entry), "key-press-event");
11972 gtk_widget_grab_focus(headerentry->compose->subject_entry);
11979 static gboolean scroll_postpone(gpointer data)
11981 Compose *compose = (Compose *)data;
11983 if (compose->batch)
11986 GTK_EVENTS_FLUSH();
11987 compose_show_first_last_header(compose, FALSE);
11991 static void compose_headerentry_changed_cb(GtkWidget *entry,
11992 ComposeHeaderEntry *headerentry)
11994 if (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) != 0) {
11995 compose_create_header_entry(headerentry->compose);
11996 g_signal_handlers_disconnect_matched
11997 (G_OBJECT(entry), G_SIGNAL_MATCH_DATA,
11998 0, 0, NULL, NULL, headerentry);
12000 if (!headerentry->compose->batch)
12001 g_timeout_add(0, scroll_postpone, headerentry->compose);
12005 static gboolean compose_defer_auto_save_draft(Compose *compose)
12007 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
12008 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
12012 static void compose_show_first_last_header(Compose *compose, gboolean show_first)
12014 GtkAdjustment *vadj;
12016 cm_return_if_fail(compose);
12021 cm_return_if_fail(GTK_IS_WIDGET(compose->header_table));
12022 cm_return_if_fail(GTK_IS_VIEWPORT(gtk_widget_get_parent(compose->header_table)));
12023 vadj = gtk_viewport_get_vadjustment(GTK_VIEWPORT(
12024 gtk_widget_get_parent(compose->header_table)));
12025 gtk_adjustment_set_value(vadj, (show_first ?
12026 gtk_adjustment_get_lower(vadj) :
12027 (gtk_adjustment_get_upper(vadj) -
12028 gtk_adjustment_get_page_size(vadj))));
12029 gtk_adjustment_changed(vadj);
12032 static void text_inserted(GtkTextBuffer *buffer, GtkTextIter *iter,
12033 const gchar *text, gint len, Compose *compose)
12035 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data
12036 (G_OBJECT(compose->text), "paste_as_quotation"));
12039 cm_return_if_fail(text != NULL);
12041 g_signal_handlers_block_by_func(G_OBJECT(buffer),
12042 G_CALLBACK(text_inserted),
12044 if (paste_as_quotation) {
12046 const gchar *qmark;
12048 GtkTextIter start_iter;
12051 len = strlen(text);
12053 new_text = g_strndup(text, len);
12055 qmark = compose_quote_char_from_context(compose);
12057 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
12058 gtk_text_buffer_place_cursor(buffer, iter);
12060 pos = gtk_text_iter_get_offset(iter);
12062 compose_quote_fmt(compose, NULL, "%Q", qmark, new_text, TRUE, FALSE,
12063 _("Quote format error at line %d."));
12064 quote_fmt_reset_vartable();
12066 g_object_set_data(G_OBJECT(compose->text), "paste_as_quotation",
12067 GINT_TO_POINTER(paste_as_quotation - 1));
12069 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
12070 gtk_text_buffer_place_cursor(buffer, iter);
12071 gtk_text_buffer_delete_mark(buffer, mark);
12073 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, pos);
12074 mark = gtk_text_buffer_create_mark(buffer, NULL, &start_iter, FALSE);
12075 compose_beautify_paragraph(compose, &start_iter, FALSE);
12076 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark);
12077 gtk_text_buffer_delete_mark(buffer, mark);
12079 if (strcmp(text, "\n") || compose->automatic_break
12080 || gtk_text_iter_starts_line(iter)) {
12081 GtkTextIter before_ins;
12082 gtk_text_buffer_insert(buffer, iter, text, len);
12083 if (!strstr(text, "\n") && gtk_text_iter_has_tag(iter, compose->no_join_tag)) {
12084 before_ins = *iter;
12085 gtk_text_iter_backward_chars(&before_ins, len);
12086 gtk_text_buffer_remove_tag_by_name(buffer, "no_join", &before_ins, iter);
12089 /* check if the preceding is just whitespace or quote */
12090 GtkTextIter start_line;
12091 gchar *tmp = NULL, *quote = NULL;
12092 gint quote_len = 0, is_normal = 0;
12093 start_line = *iter;
12094 gtk_text_iter_set_line_offset(&start_line, 0);
12095 tmp = gtk_text_buffer_get_text(buffer, &start_line, iter, FALSE);
12098 if (*tmp == '\0') {
12101 quote = compose_get_quote_str(buffer, &start_line, "e_len);
12109 gtk_text_buffer_insert(buffer, iter, text, len);
12111 gtk_text_buffer_insert_with_tags_by_name(buffer,
12112 iter, text, len, "no_join", NULL);
12117 if (!paste_as_quotation) {
12118 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
12119 compose_beautify_paragraph(compose, iter, FALSE);
12120 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
12121 gtk_text_buffer_delete_mark(buffer, mark);
12124 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
12125 G_CALLBACK(text_inserted),
12127 g_signal_stop_emission_by_name(G_OBJECT(buffer), "insert-text");
12129 if (compose_can_autosave(compose) &&
12130 gtk_text_buffer_get_char_count(buffer) % prefs_common.autosave_length == 0 &&
12131 compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN /* disabled while loading */)
12132 compose->draft_timeout_tag = g_timeout_add
12133 (500, (GSourceFunc) compose_defer_auto_save_draft, compose);
12137 static void compose_check_all(GtkAction *action, gpointer data)
12139 Compose *compose = (Compose *)data;
12140 if (!compose->gtkaspell)
12143 if (gtk_widget_has_focus(compose->subject_entry))
12144 claws_spell_entry_check_all(
12145 CLAWS_SPELL_ENTRY(compose->subject_entry));
12147 gtkaspell_check_all(compose->gtkaspell);
12150 static void compose_highlight_all(GtkAction *action, gpointer data)
12152 Compose *compose = (Compose *)data;
12153 if (compose->gtkaspell) {
12154 claws_spell_entry_recheck_all(
12155 CLAWS_SPELL_ENTRY(compose->subject_entry));
12156 gtkaspell_highlight_all(compose->gtkaspell);
12160 static void compose_check_backwards(GtkAction *action, gpointer data)
12162 Compose *compose = (Compose *)data;
12163 if (!compose->gtkaspell) {
12164 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
12168 if (gtk_widget_has_focus(compose->subject_entry))
12169 claws_spell_entry_check_backwards(
12170 CLAWS_SPELL_ENTRY(compose->subject_entry));
12172 gtkaspell_check_backwards(compose->gtkaspell);
12175 static void compose_check_forwards_go(GtkAction *action, gpointer data)
12177 Compose *compose = (Compose *)data;
12178 if (!compose->gtkaspell) {
12179 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
12183 if (gtk_widget_has_focus(compose->subject_entry))
12184 claws_spell_entry_check_forwards_go(
12185 CLAWS_SPELL_ENTRY(compose->subject_entry));
12187 gtkaspell_check_forwards_go(compose->gtkaspell);
12192 *\brief Guess originating forward account from MsgInfo and several
12193 * "common preference" settings. Return NULL if no guess.
12195 static PrefsAccount *compose_find_account(MsgInfo *msginfo)
12197 PrefsAccount *account = NULL;
12199 cm_return_val_if_fail(msginfo, NULL);
12200 cm_return_val_if_fail(msginfo->folder, NULL);
12201 cm_return_val_if_fail(msginfo->folder->prefs, NULL);
12203 if (msginfo->folder->prefs->enable_default_account)
12204 account = account_find_from_id(msginfo->folder->prefs->default_account);
12206 if (!account && msginfo->to && prefs_common.forward_account_autosel) {
12208 Xstrdup_a(to, msginfo->to, return NULL);
12209 extract_address(to);
12210 account = account_find_from_address(to, FALSE);
12213 if (!account && prefs_common.forward_account_autosel) {
12215 if (!procheader_get_header_from_msginfo
12216 (msginfo, &cc, "Cc:")) {
12217 gchar *buf = cc + strlen("Cc:");
12218 extract_address(buf);
12219 account = account_find_from_address(buf, FALSE);
12224 if (!account && prefs_common.forward_account_autosel) {
12225 gchar *deliveredto = NULL;
12226 if (!procheader_get_header_from_msginfo
12227 (msginfo, &deliveredto, "Delivered-To:")) {
12228 gchar *buf = deliveredto + strlen("Delivered-To:");
12229 extract_address(buf);
12230 account = account_find_from_address(buf, FALSE);
12231 g_free(deliveredto);
12236 account = msginfo->folder->folder->account;
12241 gboolean compose_close(Compose *compose)
12245 cm_return_val_if_fail(compose, FALSE);
12247 if (!g_mutex_trylock(compose->mutex)) {
12248 /* we have to wait for the (possibly deferred by auto-save)
12249 * drafting to be done, before destroying the compose under
12251 debug_print("waiting for drafting to finish...\n");
12252 compose_allow_user_actions(compose, FALSE);
12253 if (compose->close_timeout_tag == 0) {
12254 compose->close_timeout_tag =
12255 g_timeout_add (500, (GSourceFunc) compose_close,
12261 if (compose->draft_timeout_tag >= 0) {
12262 g_source_remove(compose->draft_timeout_tag);
12263 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN;
12266 gtkut_widget_get_uposition(compose->window, &x, &y);
12267 if (!compose->batch) {
12268 prefs_common.compose_x = x;
12269 prefs_common.compose_y = y;
12271 g_mutex_unlock(compose->mutex);
12272 compose_destroy(compose);
12277 * Add entry field for each address in list.
12278 * \param compose E-Mail composition object.
12279 * \param listAddress List of (formatted) E-Mail addresses.
12281 static void compose_add_field_list( Compose *compose, GList *listAddress ) {
12284 node = listAddress;
12286 addr = ( gchar * ) node->data;
12287 compose_entry_append( compose, addr, COMPOSE_TO, PREF_NONE );
12288 node = g_list_next( node );
12292 static void compose_reply_from_messageview_real(MessageView *msgview, GSList *msginfo_list,
12293 guint action, gboolean opening_multiple)
12295 gchar *body = NULL;
12296 GSList *new_msglist = NULL;
12297 MsgInfo *tmp_msginfo = NULL;
12298 gboolean originally_enc = FALSE;
12299 gboolean originally_sig = FALSE;
12300 Compose *compose = NULL;
12301 gchar *s_system = NULL;
12303 cm_return_if_fail(msginfo_list != NULL);
12305 if (g_slist_length(msginfo_list) == 1 && !opening_multiple && msgview != NULL) {
12306 MimeInfo *mimeinfo = messageview_get_selected_mime_part(msgview);
12307 MsgInfo *orig_msginfo = (MsgInfo *)msginfo_list->data;
12309 if (mimeinfo != NULL && mimeinfo->type == MIMETYPE_MESSAGE &&
12310 !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
12311 tmp_msginfo = procmsg_msginfo_new_from_mimeinfo(
12312 orig_msginfo, mimeinfo);
12313 if (tmp_msginfo != NULL) {
12314 new_msglist = g_slist_append(NULL, tmp_msginfo);
12316 originally_enc = MSG_IS_ENCRYPTED(orig_msginfo->flags);
12317 privacy_msginfo_get_signed_state(orig_msginfo, &s_system);
12318 originally_sig = MSG_IS_SIGNED(orig_msginfo->flags);
12320 tmp_msginfo->folder = orig_msginfo->folder;
12321 tmp_msginfo->msgnum = orig_msginfo->msgnum;
12322 if (orig_msginfo->tags) {
12323 tmp_msginfo->tags = g_slist_copy(orig_msginfo->tags);
12324 tmp_msginfo->folder->tags_dirty = TRUE;
12330 if (!opening_multiple && msgview != NULL)
12331 body = messageview_get_selection(msgview);
12334 compose = compose_reply_mode((ComposeMode)action, new_msglist, body);
12335 procmsg_msginfo_free(&tmp_msginfo);
12336 g_slist_free(new_msglist);
12338 compose = compose_reply_mode((ComposeMode)action, msginfo_list, body);
12340 if (compose && originally_enc) {
12341 compose_force_encryption(compose, compose->account, FALSE, s_system);
12344 if (compose && originally_sig && compose->account->default_sign_reply) {
12345 compose_force_signing(compose, compose->account, s_system);
12349 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
12352 void compose_reply_from_messageview(MessageView *msgview, GSList *msginfo_list,
12355 if ((!prefs_common.forward_as_attachment || action != COMPOSE_FORWARD)
12356 && msginfo_list != NULL
12357 && action != COMPOSE_FORWARD_AS_ATTACH && g_slist_length(msginfo_list) > 1) {
12358 GSList *cur = msginfo_list;
12359 gchar *msg = g_strdup_printf(_("You are about to reply to %d "
12360 "messages. Opening the windows "
12361 "could take some time. Do you "
12362 "want to continue?"),
12363 g_slist_length(msginfo_list));
12364 if (g_slist_length(msginfo_list) > 9
12365 && alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, GTK_STOCK_YES, NULL,
12366 ALERTFOCUS_SECOND) != G_ALERTALTERNATE) {
12371 /* We'll open multiple compose windows */
12372 /* let the WM place the next windows */
12373 compose_force_window_origin = FALSE;
12374 for (; cur; cur = cur->next) {
12376 tmplist.data = cur->data;
12377 tmplist.next = NULL;
12378 compose_reply_from_messageview_real(msgview, &tmplist, action, TRUE);
12380 compose_force_window_origin = TRUE;
12382 /* forwarding multiple mails as attachments is done via a
12383 * single compose window */
12384 if (msginfo_list != NULL) {
12385 compose_reply_from_messageview_real(msgview, msginfo_list, action, FALSE);
12386 } else if (msgview != NULL) {
12388 tmplist.data = msgview->msginfo;
12389 tmplist.next = NULL;
12390 compose_reply_from_messageview_real(msgview, &tmplist, action, FALSE);
12392 debug_print("Nothing to reply to\n");
12397 void compose_check_for_email_account(Compose *compose)
12399 PrefsAccount *ac = NULL, *curr = NULL;
12405 if (compose->account && compose->account->protocol == A_NNTP) {
12406 ac = account_get_cur_account();
12407 if (ac->protocol == A_NNTP) {
12408 list = account_get_list();
12410 for( ; list != NULL ; list = g_list_next(list)) {
12411 curr = (PrefsAccount *) list->data;
12412 if (curr->protocol != A_NNTP) {
12418 combobox_select_by_data(GTK_COMBO_BOX(compose->account_combo),
12423 void compose_reply_to_address(MessageView *msgview, MsgInfo *msginfo,
12424 const gchar *address)
12426 GSList *msginfo_list = NULL;
12427 gchar *body = messageview_get_selection(msgview);
12430 msginfo_list = g_slist_prepend(msginfo_list, msginfo);
12432 compose = compose_reply_mode(COMPOSE_REPLY_TO_ADDRESS, msginfo_list, body);
12433 compose_check_for_email_account(compose);
12434 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
12435 compose_entry_append(compose, address, COMPOSE_TO, PREF_NONE);
12436 compose_reply_set_subject(compose, msginfo);
12439 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
12442 void compose_set_position(Compose *compose, gint pos)
12444 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
12446 gtkut_text_view_set_position(text, pos);
12449 gboolean compose_search_string(Compose *compose,
12450 const gchar *str, gboolean case_sens)
12452 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
12454 return gtkut_text_view_search_string(text, str, case_sens);
12457 gboolean compose_search_string_backward(Compose *compose,
12458 const gchar *str, gboolean case_sens)
12460 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
12462 return gtkut_text_view_search_string_backward(text, str, case_sens);
12465 /* allocate a msginfo structure and populate its data from a compose data structure */
12466 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose)
12468 MsgInfo *newmsginfo;
12470 gchar date[RFC822_DATE_BUFFSIZE];
12472 cm_return_val_if_fail( compose != NULL, NULL );
12474 newmsginfo = procmsg_msginfo_new();
12477 get_rfc822_date(date, sizeof(date));
12478 newmsginfo->date = g_strdup(date);
12481 if (compose->from_name) {
12482 newmsginfo->from = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
12483 newmsginfo->fromname = procheader_get_fromname(newmsginfo->from);
12487 if (compose->subject_entry)
12488 newmsginfo->subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
12490 /* to, cc, reply-to, newsgroups */
12491 for (list = compose->header_list; list; list = list->next) {
12492 gchar *header = gtk_editable_get_chars(
12494 gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
12495 gchar *entry = gtk_editable_get_chars(
12496 GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
12498 if ( strcasecmp(header, prefs_common_translated_header_name("To:")) == 0 ) {
12499 if ( newmsginfo->to == NULL ) {
12500 newmsginfo->to = g_strdup(entry);
12501 } else if (entry && *entry) {
12502 gchar *tmp = g_strconcat(newmsginfo->to, ", ", entry, NULL);
12503 g_free(newmsginfo->to);
12504 newmsginfo->to = tmp;
12507 if ( strcasecmp(header, prefs_common_translated_header_name("Cc:")) == 0 ) {
12508 if ( newmsginfo->cc == NULL ) {
12509 newmsginfo->cc = g_strdup(entry);
12510 } else if (entry && *entry) {
12511 gchar *tmp = g_strconcat(newmsginfo->cc, ", ", entry, NULL);
12512 g_free(newmsginfo->cc);
12513 newmsginfo->cc = tmp;
12516 if ( strcasecmp(header,
12517 prefs_common_translated_header_name("Newsgroups:")) == 0 ) {
12518 if ( newmsginfo->newsgroups == NULL ) {
12519 newmsginfo->newsgroups = g_strdup(entry);
12520 } else if (entry && *entry) {
12521 gchar *tmp = g_strconcat(newmsginfo->newsgroups, ", ", entry, NULL);
12522 g_free(newmsginfo->newsgroups);
12523 newmsginfo->newsgroups = tmp;
12531 /* other data is unset */
12537 /* update compose's dictionaries from folder dict settings */
12538 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
12539 FolderItem *folder_item)
12541 cm_return_if_fail(compose != NULL);
12543 if (compose->gtkaspell && folder_item && folder_item->prefs) {
12544 FolderItemPrefs *prefs = folder_item->prefs;
12546 if (prefs->enable_default_dictionary)
12547 gtkaspell_change_dict(compose->gtkaspell,
12548 prefs->default_dictionary, FALSE);
12549 if (folder_item->prefs->enable_default_alt_dictionary)
12550 gtkaspell_change_alt_dict(compose->gtkaspell,
12551 prefs->default_alt_dictionary);
12552 if (prefs->enable_default_dictionary
12553 || prefs->enable_default_alt_dictionary)
12554 compose_spell_menu_changed(compose);
12559 static void compose_subject_entry_activated(GtkWidget *widget, gpointer data)
12561 Compose *compose = (Compose *)data;
12563 cm_return_if_fail(compose != NULL);
12565 gtk_widget_grab_focus(compose->text);
12568 static void from_name_activate_cb(GtkWidget *widget, gpointer data)
12570 gtk_combo_box_popup(GTK_COMBO_BOX(data));