2 * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2018 the Claws Mail team and Hiroyuki Yamamoto
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 3 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
21 #include "claws-features.h"
26 #ifndef PANGO_ENABLE_ENGINE
27 # define PANGO_ENABLE_ENGINE
31 #include <glib/gi18n.h>
32 #include <gdk/gdkkeysyms.h>
35 #include <pango/pango-break.h>
40 #include <sys/types.h>
46 # include <sys/wait.h>
50 #ifndef G_OS_WIN32 /* fixme we should have a configure test. */
54 #if (HAVE_WCTYPE_H && HAVE_WCHAR_H)
61 #include "mainwindow.h"
63 #ifndef USE_ALT_ADDRBOOK
64 #include "addressbook.h"
66 #include "addressbook-dbus.h"
67 #include "addressadd.h"
69 #include "folderview.h"
72 #include "stock_pixmap.h"
73 #include "send_message.h"
76 #include "customheader.h"
77 #include "prefs_common.h"
78 #include "prefs_account.h"
82 #include "procheader.h"
84 #include "statusbar.h"
86 #include "quoted-printable.h"
90 #include "gtkshruler.h"
92 #include "alertpanel.h"
93 #include "manage_window.h"
95 #include "folder_item_prefs.h"
96 #include "addr_compl.h"
97 #include "quote_fmt.h"
99 #include "foldersel.h"
102 #include "message_search.h"
103 #include "combobox.h"
107 #include "autofaces.h"
108 #include "spell_entry.h"
110 #include "file-utils.h"
113 #include "password.h"
114 #include "ldapserver.h"
128 #define N_ATTACH_COLS (N_COL_COLUMNS)
132 COMPOSE_CALL_ADVANCED_ACTION_UNDEFINED = -1,
133 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE = 0,
134 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER,
135 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER,
136 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD,
137 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD,
138 COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE,
139 COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE,
140 COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE,
141 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER,
142 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER,
143 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD,
144 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD,
145 COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE,
146 COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END
147 } ComposeCallAdvancedAction;
151 PRIORITY_HIGHEST = 1,
160 COMPOSE_INSERT_SUCCESS,
161 COMPOSE_INSERT_READ_ERROR,
162 COMPOSE_INSERT_INVALID_CHARACTER,
163 COMPOSE_INSERT_NO_FILE
164 } ComposeInsertResult;
168 COMPOSE_WRITE_FOR_SEND,
169 COMPOSE_WRITE_FOR_STORE
174 COMPOSE_QUOTE_FORCED,
181 SUBJECT_FIELD_PRESENT,
186 #define B64_LINE_SIZE 57
187 #define B64_BUFFSIZE 77
189 #define MAX_REFERENCES_LEN 999
191 #define COMPOSE_DRAFT_TIMEOUT_UNSET -1
192 #define COMPOSE_DRAFT_TIMEOUT_FORBIDDEN -2
194 static GdkColor default_header_bgcolor = {
201 static GdkColor default_header_color = {
208 static GList *compose_list = NULL;
209 static GSList *extra_headers = NULL;
211 static Compose *compose_generic_new (PrefsAccount *account,
215 GList *listAddress );
217 static Compose *compose_create (PrefsAccount *account,
222 static void compose_entry_indicate (Compose *compose,
223 const gchar *address);
224 static Compose *compose_followup_and_reply_to (MsgInfo *msginfo,
225 ComposeQuoteMode quote_mode,
229 static Compose *compose_forward_multiple (PrefsAccount *account,
230 GSList *msginfo_list);
231 static Compose *compose_reply (MsgInfo *msginfo,
232 ComposeQuoteMode quote_mode,
237 static Compose *compose_reply_mode (ComposeMode mode,
238 GSList *msginfo_list,
240 static void compose_template_apply_fields(Compose *compose, Template *tmpl);
241 static void compose_update_privacy_systems_menu(Compose *compose);
243 static GtkWidget *compose_account_option_menu_create
245 static void compose_set_out_encoding (Compose *compose);
246 static void compose_set_template_menu (Compose *compose);
247 static void compose_destroy (Compose *compose);
249 static MailField compose_entries_set (Compose *compose,
251 ComposeEntryType to_type);
252 static gint compose_parse_header (Compose *compose,
254 static gint compose_parse_manual_headers (Compose *compose,
256 HeaderEntry *entries);
257 static gchar *compose_parse_references (const gchar *ref,
260 static gchar *compose_quote_fmt (Compose *compose,
266 gboolean need_unescape,
267 const gchar *err_msg);
269 static void compose_reply_set_entry (Compose *compose,
275 followup_and_reply_to);
276 static void compose_reedit_set_entry (Compose *compose,
279 static void compose_insert_sig (Compose *compose,
281 static ComposeInsertResult compose_insert_file (Compose *compose,
284 static gboolean compose_attach_append (Compose *compose,
287 const gchar *content_type,
288 const gchar *charset);
289 static void compose_attach_parts (Compose *compose,
292 static gboolean compose_beautify_paragraph (Compose *compose,
293 GtkTextIter *par_iter,
295 static void compose_wrap_all (Compose *compose);
296 static void compose_wrap_all_full (Compose *compose,
299 static void compose_set_title (Compose *compose);
300 static void compose_select_account (Compose *compose,
301 PrefsAccount *account,
304 static PrefsAccount *compose_current_mail_account(void);
305 /* static gint compose_send (Compose *compose); */
306 static gboolean compose_check_for_valid_recipient
308 static gboolean compose_check_entries (Compose *compose,
309 gboolean check_everything);
310 static gint compose_write_to_file (Compose *compose,
313 gboolean attach_parts);
314 static gint compose_write_body_to_file (Compose *compose,
316 static gint compose_remove_reedit_target (Compose *compose,
318 static void compose_remove_draft (Compose *compose);
319 static ComposeQueueResult compose_queue_sub (Compose *compose,
323 gboolean perform_checks,
324 gboolean remove_reedit_target);
325 static int compose_add_attachments (Compose *compose,
327 static gchar *compose_get_header (Compose *compose);
328 static gchar *compose_get_manual_headers_info (Compose *compose);
330 static void compose_convert_header (Compose *compose,
335 gboolean addr_field);
337 static void compose_attach_info_free (AttachInfo *ainfo);
338 static void compose_attach_remove_selected (GtkAction *action,
341 static void compose_template_apply (Compose *compose,
344 static void compose_attach_property (GtkAction *action,
346 static void compose_attach_property_create (gboolean *cancelled);
347 static void attach_property_ok (GtkWidget *widget,
348 gboolean *cancelled);
349 static void attach_property_cancel (GtkWidget *widget,
350 gboolean *cancelled);
351 static gint attach_property_delete_event (GtkWidget *widget,
353 gboolean *cancelled);
354 static gboolean attach_property_key_pressed (GtkWidget *widget,
356 gboolean *cancelled);
358 static void compose_exec_ext_editor (Compose *compose);
360 static gint compose_exec_ext_editor_real (const gchar *file,
361 GdkNativeWindow socket_wid);
362 static gboolean compose_ext_editor_kill (Compose *compose);
363 static gboolean compose_input_cb (GIOChannel *source,
364 GIOCondition condition,
366 static void compose_set_ext_editor_sensitive (Compose *compose,
368 static gboolean compose_get_ext_editor_cmd_valid();
369 static gboolean compose_get_ext_editor_uses_socket();
370 static gboolean compose_ext_editor_plug_removed_cb
373 #endif /* G_OS_UNIX */
375 static void compose_undo_state_changed (UndoMain *undostruct,
380 static void compose_create_header_entry (Compose *compose);
381 static void compose_add_header_entry (Compose *compose, const gchar *header,
382 gchar *text, ComposePrefType pref_type);
383 static void compose_remove_header_entries(Compose *compose);
385 static void compose_update_priority_menu_item(Compose * compose);
387 static void compose_spell_menu_changed (void *data);
388 static void compose_dict_changed (void *data);
390 static void compose_add_field_list ( Compose *compose,
391 GList *listAddress );
393 /* callback functions */
395 static void compose_notebook_size_alloc (GtkNotebook *notebook,
396 GtkAllocation *allocation,
398 static gboolean compose_edit_size_alloc (GtkEditable *widget,
399 GtkAllocation *allocation,
400 GtkSHRuler *shruler);
401 static void account_activated (GtkComboBox *optmenu,
403 static void attach_selected (GtkTreeView *tree_view,
404 GtkTreePath *tree_path,
405 GtkTreeViewColumn *column,
407 static gboolean attach_button_pressed (GtkWidget *widget,
408 GdkEventButton *event,
410 static gboolean attach_key_pressed (GtkWidget *widget,
413 static void compose_send_cb (GtkAction *action, gpointer data);
414 static void compose_send_later_cb (GtkAction *action, gpointer data);
416 static void compose_save_cb (GtkAction *action,
419 static void compose_attach_cb (GtkAction *action,
421 static void compose_insert_file_cb (GtkAction *action,
423 static void compose_insert_sig_cb (GtkAction *action,
425 static void compose_replace_sig_cb (GtkAction *action,
428 static void compose_close_cb (GtkAction *action,
430 static void compose_print_cb (GtkAction *action,
433 static void compose_set_encoding_cb (GtkAction *action, GtkRadioAction *current, gpointer data);
435 static void compose_address_cb (GtkAction *action,
437 static void about_show_cb (GtkAction *action,
439 static void compose_template_activate_cb(GtkWidget *widget,
442 static void compose_ext_editor_cb (GtkAction *action,
445 static gint compose_delete_cb (GtkWidget *widget,
449 static void compose_undo_cb (GtkAction *action,
451 static void compose_redo_cb (GtkAction *action,
453 static void compose_cut_cb (GtkAction *action,
455 static void compose_copy_cb (GtkAction *action,
457 static void compose_paste_cb (GtkAction *action,
459 static void compose_paste_as_quote_cb (GtkAction *action,
461 static void compose_paste_no_wrap_cb (GtkAction *action,
463 static void compose_paste_wrap_cb (GtkAction *action,
465 static void compose_allsel_cb (GtkAction *action,
468 static void compose_advanced_action_cb (GtkAction *action,
471 static void compose_grab_focus_cb (GtkWidget *widget,
474 static void compose_changed_cb (GtkTextBuffer *textbuf,
477 static void compose_wrap_cb (GtkAction *action,
479 static void compose_wrap_all_cb (GtkAction *action,
481 static void compose_find_cb (GtkAction *action,
483 static void compose_toggle_autowrap_cb (GtkToggleAction *action,
485 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
488 static void compose_toggle_ruler_cb (GtkToggleAction *action,
490 static void compose_toggle_sign_cb (GtkToggleAction *action,
492 static void compose_toggle_encrypt_cb (GtkToggleAction *action,
494 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data);
495 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn);
496 static void compose_activate_privacy_system (Compose *compose,
497 PrefsAccount *account,
499 static void compose_apply_folder_privacy_settings(Compose *compose, FolderItem *folder_item);
500 static void compose_toggle_return_receipt_cb(GtkToggleAction *action,
502 static void compose_toggle_remove_refs_cb(GtkToggleAction *action,
504 static void compose_set_priority_cb (GtkAction *action, GtkRadioAction *current, gpointer data);
505 static void compose_reply_change_mode (Compose *compose, ComposeMode action);
506 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data);
508 static void compose_attach_drag_received_cb (GtkWidget *widget,
509 GdkDragContext *drag_context,
512 GtkSelectionData *data,
516 static void compose_insert_drag_received_cb (GtkWidget *widget,
517 GdkDragContext *drag_context,
520 GtkSelectionData *data,
524 static void compose_header_drag_received_cb (GtkWidget *widget,
525 GdkDragContext *drag_context,
528 GtkSelectionData *data,
533 static gboolean compose_drag_drop (GtkWidget *widget,
534 GdkDragContext *drag_context,
536 guint time, gpointer user_data);
537 static gboolean completion_set_focus_to_subject
542 static void text_inserted (GtkTextBuffer *buffer,
547 static Compose *compose_generic_reply(MsgInfo *msginfo,
548 ComposeQuoteMode quote_mode,
552 gboolean followup_and_reply_to,
555 static void compose_headerentry_changed_cb (GtkWidget *entry,
556 ComposeHeaderEntry *headerentry);
557 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
559 ComposeHeaderEntry *headerentry);
560 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
561 ComposeHeaderEntry *headerentry);
563 static void compose_show_first_last_header (Compose *compose, gboolean show_first);
565 static void compose_allow_user_actions (Compose *compose, gboolean allow);
567 static void compose_nothing_cb (GtkAction *action, gpointer data)
573 static void compose_check_all (GtkAction *action, gpointer data);
574 static void compose_highlight_all (GtkAction *action, gpointer data);
575 static void compose_check_backwards (GtkAction *action, gpointer data);
576 static void compose_check_forwards_go (GtkAction *action, gpointer data);
579 static PrefsAccount *compose_find_account (MsgInfo *msginfo);
581 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose);
584 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
585 FolderItem *folder_item);
587 static void compose_attach_update_label(Compose *compose);
588 static void compose_set_folder_prefs(Compose *compose, FolderItem *folder,
589 gboolean respect_default_to);
590 static void compose_subject_entry_activated(GtkWidget *widget, gpointer data);
591 static void from_name_activate_cb(GtkWidget *widget, gpointer data);
593 static GtkActionEntry compose_popup_entries[] =
595 {"Compose", NULL, "Compose", NULL, NULL, NULL },
596 {"Compose/Add", NULL, N_("_Add..."), NULL, NULL, G_CALLBACK(compose_attach_cb) },
597 {"Compose/Remove", NULL, N_("_Remove"), NULL, NULL, G_CALLBACK(compose_attach_remove_selected) },
598 {"Compose/---", NULL, "---", NULL, NULL, NULL },
599 {"Compose/Properties", NULL, N_("_Properties..."), NULL, NULL, G_CALLBACK(compose_attach_property) },
602 static GtkActionEntry compose_entries[] =
604 {"Menu", NULL, "Menu", NULL, NULL, NULL },
606 {"Message", NULL, N_("_Message"), NULL, NULL, NULL },
607 {"Edit", NULL, N_("_Edit"), NULL, NULL, NULL },
609 {"Spelling", NULL, N_("_Spelling"), NULL, NULL, NULL },
611 {"Options", NULL, N_("_Options"), NULL, NULL, NULL },
612 {"Tools", NULL, N_("_Tools"), NULL, NULL, NULL },
613 {"Help", NULL, N_("_Help"), NULL, NULL, NULL },
615 {"Message/Send", NULL, N_("S_end"), "<control>Return", NULL, G_CALLBACK(compose_send_cb) },
616 {"Message/SendLater", NULL, N_("Send _later"), "<shift><control>S", NULL, G_CALLBACK(compose_send_later_cb) },
617 {"Message/---", NULL, "---", NULL, NULL, NULL },
619 {"Message/AttachFile", NULL, N_("_Attach file"), "<control>M", NULL, G_CALLBACK(compose_attach_cb) },
620 {"Message/InsertFile", NULL, N_("_Insert file"), "<control>I", NULL, G_CALLBACK(compose_insert_file_cb) },
621 {"Message/InsertSig", NULL, N_("Insert si_gnature"), "<control>G", NULL, G_CALLBACK(compose_insert_sig_cb) },
622 {"Message/ReplaceSig", NULL, N_("_Replace signature"), NULL, NULL, G_CALLBACK(compose_replace_sig_cb) },
623 /* {"Message/---", NULL, "---", NULL, NULL, NULL }, */
624 {"Message/Save", NULL, N_("_Save"), "<control>S", NULL, G_CALLBACK(compose_save_cb) }, /*COMPOSE_KEEP_EDITING*/
625 /* {"Message/---", NULL, "---", NULL, NULL, NULL }, */
626 {"Message/Print", NULL, N_("_Print"), NULL, NULL, G_CALLBACK(compose_print_cb) },
627 /* {"Message/---", NULL, "---", NULL, NULL, NULL }, */
628 {"Message/Close", NULL, N_("_Close"), "<control>W", NULL, G_CALLBACK(compose_close_cb) },
631 {"Edit/Undo", NULL, N_("_Undo"), "<control>Z", NULL, G_CALLBACK(compose_undo_cb) },
632 {"Edit/Redo", NULL, N_("_Redo"), "<control>Y", NULL, G_CALLBACK(compose_redo_cb) },
633 {"Edit/---", NULL, "---", NULL, NULL, NULL },
635 {"Edit/Cut", NULL, N_("Cu_t"), "<control>X", NULL, G_CALLBACK(compose_cut_cb) },
636 {"Edit/Copy", NULL, N_("_Copy"), "<control>C", NULL, G_CALLBACK(compose_copy_cb) },
637 {"Edit/Paste", NULL, N_("_Paste"), "<control>V", NULL, G_CALLBACK(compose_paste_cb) },
639 {"Edit/SpecialPaste", NULL, N_("_Special paste"), NULL, NULL, NULL },
640 {"Edit/SpecialPaste/AsQuotation", NULL, N_("As _quotation"), NULL, NULL, G_CALLBACK(compose_paste_as_quote_cb) },
641 {"Edit/SpecialPaste/Wrapped", NULL, N_("_Wrapped"), NULL, NULL, G_CALLBACK(compose_paste_wrap_cb) },
642 {"Edit/SpecialPaste/Unwrapped", NULL, N_("_Unwrapped"), NULL, NULL, G_CALLBACK(compose_paste_no_wrap_cb) },
644 {"Edit/SelectAll", NULL, N_("Select _all"), "<control>A", NULL, G_CALLBACK(compose_allsel_cb) },
646 {"Edit/Advanced", NULL, N_("A_dvanced"), NULL, NULL, NULL },
647 {"Edit/Advanced/BackChar", NULL, N_("Move a character backward"), "<shift><control>B", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER*/
648 {"Edit/Advanced/ForwChar", NULL, N_("Move a character forward"), "<shift><control>F", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER*/
649 {"Edit/Advanced/BackWord", NULL, N_("Move a word backward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD*/
650 {"Edit/Advanced/ForwWord", NULL, N_("Move a word forward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD*/
651 {"Edit/Advanced/BegLine", NULL, N_("Move to beginning of line"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE*/
652 {"Edit/Advanced/EndLine", NULL, N_("Move to end of line"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE*/
653 {"Edit/Advanced/PrevLine", NULL, N_("Move to previous line"), "<control>P", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE*/
654 {"Edit/Advanced/NextLine", NULL, N_("Move to next line"), "<control>N", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE*/
655 {"Edit/Advanced/DelBackChar", NULL, N_("Delete a character backward"), "<control>H", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER*/
656 {"Edit/Advanced/DelForwChar", NULL, N_("Delete a character forward"), "<control>D", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER*/
657 {"Edit/Advanced/DelBackWord", NULL, N_("Delete a word backward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD*/
658 {"Edit/Advanced/DelForwWord", NULL, N_("Delete a word forward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD*/
659 {"Edit/Advanced/DelLine", NULL, N_("Delete line"), "<control>U", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE*/
660 {"Edit/Advanced/DelEndLine", NULL, N_("Delete to end of line"), "<control>K", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END*/
662 /* {"Edit/---", NULL, "---", NULL, NULL, NULL }, */
663 {"Edit/Find", NULL, N_("_Find"), "<control>F", NULL, G_CALLBACK(compose_find_cb) },
665 /* {"Edit/---", NULL, "---", NULL, NULL, NULL }, */
666 {"Edit/WrapPara", NULL, N_("_Wrap current paragraph"), "<control>L", NULL, G_CALLBACK(compose_wrap_cb) }, /* 0 */
667 {"Edit/WrapAllLines", NULL, N_("Wrap all long _lines"), "<control><alt>L", NULL, G_CALLBACK(compose_wrap_all_cb) }, /* 1 */
668 /* {"Edit/---", NULL, "---", NULL, NULL, NULL }, */
669 {"Edit/ExtEditor", NULL, N_("Edit with e_xternal editor"), "<shift><control>X", NULL, G_CALLBACK(compose_ext_editor_cb) },
672 {"Spelling/CheckAllSel", NULL, N_("_Check all or check selection"), NULL, NULL, G_CALLBACK(compose_check_all) },
673 {"Spelling/HighlightAll", NULL, N_("_Highlight all misspelled words"), NULL, NULL, G_CALLBACK(compose_highlight_all) },
674 {"Spelling/CheckBackwards", NULL, N_("Check _backwards misspelled word"), NULL, NULL, G_CALLBACK(compose_check_backwards) },
675 {"Spelling/ForwardNext", NULL, N_("_Forward to next misspelled word"), NULL, NULL, G_CALLBACK(compose_check_forwards_go) },
677 {"Spelling/---", NULL, "---", NULL, NULL, NULL },
678 {"Spelling/Options", NULL, N_("_Options"), NULL, NULL, NULL },
682 {"Options/ReplyMode", NULL, N_("Reply _mode"), NULL, NULL, NULL },
683 {"Options/---", NULL, "---", NULL, NULL, NULL },
684 {"Options/PrivacySystem", NULL, N_("Privacy _System"), NULL, NULL, NULL },
685 {"Options/PrivacySystem/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
687 /* {"Options/---", NULL, "---", NULL, NULL, NULL }, */
688 {"Options/Priority", NULL, N_("_Priority"), NULL, NULL, NULL },
690 {"Options/Encoding", NULL, N_("Character _encoding"), NULL, NULL, NULL },
691 {"Options/Encoding/---", NULL, "---", NULL, NULL, NULL },
692 #define ENC_ACTION(cs_char,c_char,string) \
693 {"Options/Encoding/" cs_char, NULL, N_(string), NULL, NULL, c_char }
695 {"Options/Encoding/Western", NULL, N_("Western European"), NULL, NULL, NULL },
696 {"Options/Encoding/Baltic", NULL, N_("Baltic"), NULL, NULL, NULL },
697 {"Options/Encoding/Hebrew", NULL, N_("Hebrew"), NULL, NULL, NULL },
698 {"Options/Encoding/Arabic", NULL, N_("Arabic"), NULL, NULL, NULL },
699 {"Options/Encoding/Cyrillic", NULL, N_("Cyrillic"), NULL, NULL, NULL },
700 {"Options/Encoding/Japanese", NULL, N_("Japanese"), NULL, NULL, NULL },
701 {"Options/Encoding/Chinese", NULL, N_("Chinese"), NULL, NULL, NULL },
702 {"Options/Encoding/Korean", NULL, N_("Korean"), NULL, NULL, NULL },
703 {"Options/Encoding/Thai", NULL, N_("Thai"), NULL, NULL, NULL },
706 {"Tools/AddressBook", NULL, N_("_Address book"), NULL, NULL, G_CALLBACK(compose_address_cb) },
708 {"Tools/Template", NULL, N_("_Template"), NULL, NULL, NULL },
709 {"Tools/Template/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
710 {"Tools/Actions", NULL, N_("Actio_ns"), NULL, NULL, NULL },
711 {"Tools/Actions/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
714 {"Help/About", NULL, N_("_About"), NULL, NULL, G_CALLBACK(about_show_cb) },
717 static GtkToggleActionEntry compose_toggle_entries[] =
719 {"Edit/AutoWrap", NULL, N_("Aut_o wrapping"), "<shift><control>L", NULL, G_CALLBACK(compose_toggle_autowrap_cb), FALSE }, /* Toggle */
720 {"Edit/AutoIndent", NULL, N_("Auto _indent"), NULL, NULL, G_CALLBACK(compose_toggle_autoindent_cb), FALSE }, /* Toggle */
721 {"Options/Sign", NULL, N_("Si_gn"), NULL, NULL, G_CALLBACK(compose_toggle_sign_cb), FALSE }, /* Toggle */
722 {"Options/Encrypt", NULL, N_("_Encrypt"), NULL, NULL, G_CALLBACK(compose_toggle_encrypt_cb), FALSE }, /* Toggle */
723 {"Options/RequestRetRcpt", NULL, N_("_Request Return Receipt"), NULL, NULL, G_CALLBACK(compose_toggle_return_receipt_cb), FALSE }, /* Toggle */
724 {"Options/RemoveReferences", NULL, N_("Remo_ve references"), NULL, NULL, G_CALLBACK(compose_toggle_remove_refs_cb), FALSE }, /* Toggle */
725 {"Tools/ShowRuler", NULL, N_("Show _ruler"), NULL, NULL, G_CALLBACK(compose_toggle_ruler_cb), FALSE }, /* Toggle */
728 static GtkRadioActionEntry compose_radio_rm_entries[] =
730 {"Options/ReplyMode/Normal", NULL, N_("_Normal"), NULL, NULL, COMPOSE_REPLY }, /* RADIO compose_reply_change_mode_cb */
731 {"Options/ReplyMode/All", NULL, N_("_All"), NULL, NULL, COMPOSE_REPLY_TO_ALL }, /* RADIO compose_reply_change_mode_cb */
732 {"Options/ReplyMode/Sender", NULL, N_("_Sender"), NULL, NULL, COMPOSE_REPLY_TO_SENDER }, /* RADIO compose_reply_change_mode_cb */
733 {"Options/ReplyMode/List", NULL, N_("_Mailing-list"), NULL, NULL, COMPOSE_REPLY_TO_LIST }, /* RADIO compose_reply_change_mode_cb */
736 static GtkRadioActionEntry compose_radio_prio_entries[] =
738 {"Options/Priority/Highest", NULL, N_("_Highest"), NULL, NULL, PRIORITY_HIGHEST }, /* RADIO compose_set_priority_cb */
739 {"Options/Priority/High", NULL, N_("Hi_gh"), NULL, NULL, PRIORITY_HIGH }, /* RADIO compose_set_priority_cb */
740 {"Options/Priority/Normal", NULL, N_("_Normal"), NULL, NULL, PRIORITY_NORMAL }, /* RADIO compose_set_priority_cb */
741 {"Options/Priority/Low", NULL, N_("Lo_w"), NULL, NULL, PRIORITY_LOW }, /* RADIO compose_set_priority_cb */
742 {"Options/Priority/Lowest", NULL, N_("_Lowest"), NULL, NULL, PRIORITY_LOWEST }, /* RADIO compose_set_priority_cb */
745 static GtkRadioActionEntry compose_radio_enc_entries[] =
747 ENC_ACTION(CS_AUTO, C_AUTO, N_("_Automatic")), /* RADIO compose_set_encoding_cb */
748 ENC_ACTION(CS_US_ASCII, C_US_ASCII, N_("7bit ASCII (US-ASC_II)")), /* RADIO compose_set_encoding_cb */
749 ENC_ACTION(CS_UTF_8, C_UTF_8, N_("Unicode (_UTF-8)")), /* RADIO compose_set_encoding_cb */
750 ENC_ACTION("Western/"CS_ISO_8859_1, C_ISO_8859_1, "ISO-8859-_1"), /* RADIO compose_set_encoding_cb */
751 ENC_ACTION("Western/"CS_ISO_8859_15, C_ISO_8859_15, "ISO-8859-15"), /* RADIO compose_set_encoding_cb */
752 ENC_ACTION("Western/"CS_WINDOWS_1252, C_WINDOWS_1252, "Windows-1252"), /* RADIO compose_set_encoding_cb */
753 ENC_ACTION(CS_ISO_8859_2, C_ISO_8859_2, N_("Central European (ISO-8859-_2)")), /* RADIO compose_set_encoding_cb */
754 ENC_ACTION("Baltic/"CS_ISO_8859_13, C_ISO_8859_13, "ISO-8859-13"), /* RADIO compose_set_encoding_cb */
755 ENC_ACTION("Baltic/"CS_ISO_8859_4, C_ISO_8859_14, "ISO-8859-_4"), /* RADIO compose_set_encoding_cb */
756 ENC_ACTION(CS_ISO_8859_7, C_ISO_8859_7, N_("Greek (ISO-8859-_7)")), /* RADIO compose_set_encoding_cb */
757 ENC_ACTION("Hebrew/"CS_ISO_8859_8, C_ISO_8859_8, "ISO-8859-_8"), /* RADIO compose_set_encoding_cb */
758 ENC_ACTION("Hebrew/"CS_WINDOWS_1255, C_WINDOWS_1255, "Windows-1255"), /* RADIO compose_set_encoding_cb */
759 ENC_ACTION("Arabic/"CS_ISO_8859_6, C_ISO_8859_6, "ISO-8859-_6"), /* RADIO compose_set_encoding_cb */
760 ENC_ACTION("Arabic/"CS_WINDOWS_1256, C_WINDOWS_1256, "Windows-1256"), /* RADIO compose_set_encoding_cb */
761 ENC_ACTION(CS_ISO_8859_9, C_ISO_8859_9, N_("Turkish (ISO-8859-_9)")), /* RADIO compose_set_encoding_cb */
762 ENC_ACTION("Cyrillic/"CS_ISO_8859_5, C_ISO_8859_5, "ISO-8859-_5"), /* RADIO compose_set_encoding_cb */
763 ENC_ACTION("Cyrillic/"CS_KOI8_R, C_KOI8_R, "KOI8-_R"), /* RADIO compose_set_encoding_cb */
764 ENC_ACTION("Cyrillic/"CS_MACCYR, C_MACCYR, "_Mac-Cyrillic"), /* RADIO compose_set_encoding_cb */
765 ENC_ACTION("Cyrillic/"CS_KOI8_U, C_KOI8_U, "KOI8-_U"), /* RADIO compose_set_encoding_cb */
766 ENC_ACTION("Cyrillic/"CS_WINDOWS_1251, C_WINDOWS_1251, "Windows-1251"), /* RADIO compose_set_encoding_cb */
767 ENC_ACTION("Japanese/"CS_ISO_2022_JP, C_ISO_2022_JP, "ISO-2022-_JP"), /* RADIO compose_set_encoding_cb */
768 ENC_ACTION("Japanese/"CS_ISO_2022_JP_2, C_ISO_2022_JP_2, "ISO-2022-JP-_2"), /* RADIO compose_set_encoding_cb */
769 ENC_ACTION("Japanese/"CS_EUC_JP, C_EUC_JP, "_EUC-JP"), /* RADIO compose_set_encoding_cb */
770 ENC_ACTION("Japanese/"CS_SHIFT_JIS, C_SHIFT_JIS, "_Shift-JIS"), /* RADIO compose_set_encoding_cb */
771 ENC_ACTION("Chinese/"CS_GB18030, C_GB18030, "_GB18030"), /* RADIO compose_set_encoding_cb */
772 ENC_ACTION("Chinese/"CS_GB2312, C_GB2312, "_GB2312"), /* RADIO compose_set_encoding_cb */
773 ENC_ACTION("Chinese/"CS_GBK, C_GBK, "GB_K"), /* RADIO compose_set_encoding_cb */
774 ENC_ACTION("Chinese/"CS_BIG5, C_BIG5, "_Big5-JP"), /* RADIO compose_set_encoding_cb */
775 ENC_ACTION("Chinese/"CS_EUC_TW, C_EUC_TW, "EUC-_TW"), /* RADIO compose_set_encoding_cb */
776 ENC_ACTION("Korean/"CS_EUC_KR, C_EUC_KR, "_EUC-KR"), /* RADIO compose_set_encoding_cb */
777 ENC_ACTION("Korean/"CS_ISO_2022_KR, C_ISO_2022_KR, "_ISO-2022-KR"), /* RADIO compose_set_encoding_cb */
778 ENC_ACTION("Thai/"CS_TIS_620, C_TIS_620, "_TIS-620-KR"), /* RADIO compose_set_encoding_cb */
779 ENC_ACTION("Thai/"CS_WINDOWS_874, C_WINDOWS_874, "_Windows-874"), /* RADIO compose_set_encoding_cb */
782 static GtkTargetEntry compose_mime_types[] =
784 {"text/uri-list", 0, 0},
785 {"UTF8_STRING", 0, 0},
789 static gboolean compose_put_existing_to_front(MsgInfo *info)
791 const GList *compose_list = compose_get_compose_list();
792 const GList *elem = NULL;
795 for (elem = compose_list; elem != NULL && elem->data != NULL;
797 Compose *c = (Compose*)elem->data;
799 if (!c->targetinfo || !c->targetinfo->msgid ||
803 if (!strcmp(c->targetinfo->msgid, info->msgid)) {
804 gtkut_window_popup(c->window);
812 static GdkColor quote_color1 =
813 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
814 static GdkColor quote_color2 =
815 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
816 static GdkColor quote_color3 =
817 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
819 static GdkColor quote_bgcolor1 =
820 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
821 static GdkColor quote_bgcolor2 =
822 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
823 static GdkColor quote_bgcolor3 =
824 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
826 static GdkColor signature_color = {
833 static GdkColor uri_color = {
840 static void compose_create_tags(GtkTextView *text, Compose *compose)
842 GtkTextBuffer *buffer;
843 GdkColor black = {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
845 buffer = gtk_text_view_get_buffer(text);
847 if (prefs_common.enable_color) {
848 /* grab the quote colors, converting from an int to a GdkColor */
849 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_QUOTE_LEVEL1],
851 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_QUOTE_LEVEL2],
853 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_QUOTE_LEVEL3],
855 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_QUOTE_LEVEL1_BG],
857 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_QUOTE_LEVEL2_BG],
859 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_QUOTE_LEVEL3_BG],
861 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_SIGNATURE],
863 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_URI],
866 signature_color = quote_color1 = quote_color2 = quote_color3 =
867 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 = uri_color = black;
870 if (prefs_common.enable_color && prefs_common.enable_bgcolor) {
871 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
872 "foreground-gdk", "e_color1,
873 "paragraph-background-gdk", "e_bgcolor1,
875 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
876 "foreground-gdk", "e_color2,
877 "paragraph-background-gdk", "e_bgcolor2,
879 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
880 "foreground-gdk", "e_color3,
881 "paragraph-background-gdk", "e_bgcolor3,
884 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
885 "foreground-gdk", "e_color1,
887 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
888 "foreground-gdk", "e_color2,
890 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
891 "foreground-gdk", "e_color3,
895 compose->signature_tag = gtk_text_buffer_create_tag(buffer, "signature",
896 "foreground-gdk", &signature_color,
899 compose->uri_tag = gtk_text_buffer_create_tag(buffer, "link",
900 "foreground-gdk", &uri_color,
902 compose->no_wrap_tag = gtk_text_buffer_create_tag(buffer, "no_wrap", NULL);
903 compose->no_join_tag = gtk_text_buffer_create_tag(buffer, "no_join", NULL);
906 Compose *compose_new(PrefsAccount *account, const gchar *mailto,
909 return compose_generic_new(account, mailto, NULL, attach_files, NULL);
912 Compose *compose_new_with_folderitem(PrefsAccount *account, FolderItem *item, const gchar *mailto)
914 return compose_generic_new(account, mailto, item, NULL, NULL);
917 Compose *compose_new_with_list( PrefsAccount *account, GList *listAddress )
919 return compose_generic_new( account, NULL, NULL, NULL, listAddress );
922 #define SCROLL_TO_CURSOR(compose) { \
923 GtkTextMark *cmark = gtk_text_buffer_get_insert( \
924 gtk_text_view_get_buffer( \
925 GTK_TEXT_VIEW(compose->text))); \
926 gtk_text_view_scroll_mark_onscreen( \
927 GTK_TEXT_VIEW(compose->text), \
931 static void compose_set_save_to(Compose *compose, const gchar *folderidentifier)
934 if (folderidentifier) {
935 combobox_unset_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo));
936 prefs_common.compose_save_to_history = add_history(
937 prefs_common.compose_save_to_history, folderidentifier);
938 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo),
939 prefs_common.compose_save_to_history);
942 entry = GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(compose->savemsg_combo)));
943 if (folderidentifier)
944 gtk_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;
4262 if (attr->is_line_break && can_break && was_white && !prev_dont_break)
4265 was_white = attr->is_white;
4267 /* don't wrap URI */
4268 if ((uri_len = get_uri_len(p)) > 0) {
4270 if (pos > 0 && col > max_col) {
4280 wc = g_utf8_get_char(p);
4281 if (g_unichar_iswide(wc)) {
4283 if (prev_dont_break && can_break && attr->is_line_break)
4285 } else if (*p == '\t')
4289 if (pos > 0 && col > max_col) {
4294 if (*p == '-' || *p == '/')
4295 prev_dont_break = TRUE;
4297 prev_dont_break = FALSE;
4299 p = g_utf8_next_char(p);
4303 /* debug_print("compose_get_line_break_pos(): do_break = %d, pos = %d, col = %d\n", do_break, pos, col); */
4308 *break_pos = *start;
4309 gtk_text_iter_set_line_offset(break_pos, pos);
4314 static gboolean compose_join_next_line(Compose *compose,
4315 GtkTextBuffer *buffer,
4317 const gchar *quote_str)
4319 GtkTextIter iter_ = *iter, cur, prev, next, end;
4320 PangoLogAttr attrs[3];
4322 gchar *next_quote_str;
4325 gboolean keep_cursor = FALSE;
4327 if (!gtk_text_iter_forward_line(&iter_) ||
4328 gtk_text_iter_ends_line(&iter_)) {
4331 next_quote_str = compose_get_quote_str(buffer, &iter_, "e_len);
4333 if ((quote_str || next_quote_str) &&
4334 strcmp2(quote_str, next_quote_str) != 0) {
4335 g_free(next_quote_str);
4338 g_free(next_quote_str);
4341 if (quote_len > 0) {
4342 gtk_text_iter_forward_chars(&end, quote_len);
4343 if (gtk_text_iter_ends_line(&end)) {
4348 /* don't join itemized lines */
4349 if (compose_itemized_length(buffer, &end) > 0) {
4353 /* don't join signature separator */
4354 if (compose_is_sig_separator(compose, buffer, &iter_)) {
4357 /* delete quote str */
4359 gtk_text_buffer_delete(buffer, &iter_, &end);
4361 /* don't join line breaks put by the user */
4363 gtk_text_iter_backward_char(&cur);
4364 if (gtk_text_iter_has_tag(&cur, compose->no_join_tag)) {
4365 gtk_text_iter_forward_char(&cur);
4369 gtk_text_iter_forward_char(&cur);
4370 /* delete linebreak and extra spaces */
4371 while (gtk_text_iter_backward_char(&cur)) {
4372 wc1 = gtk_text_iter_get_char(&cur);
4373 if (!g_unichar_isspace(wc1))
4378 while (!gtk_text_iter_ends_line(&cur)) {
4379 wc1 = gtk_text_iter_get_char(&cur);
4380 if (!g_unichar_isspace(wc1))
4382 gtk_text_iter_forward_char(&cur);
4385 if (!gtk_text_iter_equal(&prev, &next)) {
4388 mark = gtk_text_buffer_get_insert(buffer);
4389 gtk_text_buffer_get_iter_at_mark(buffer, &cur, mark);
4390 if (gtk_text_iter_equal(&prev, &cur))
4392 gtk_text_buffer_delete(buffer, &prev, &next);
4396 /* insert space if required */
4397 gtk_text_iter_backward_char(&prev);
4398 wc1 = gtk_text_iter_get_char(&prev);
4399 wc2 = gtk_text_iter_get_char(&next);
4400 gtk_text_iter_forward_char(&next);
4401 str = gtk_text_buffer_get_text(buffer, &prev, &next, FALSE);
4402 pango_default_break(str, -1, NULL, attrs, 3);
4403 if (!attrs[1].is_line_break ||
4404 (!g_unichar_iswide(wc1) || !g_unichar_iswide(wc2))) {
4405 gtk_text_buffer_insert(buffer, &iter_, " ", 1);
4407 gtk_text_iter_backward_char(&iter_);
4408 gtk_text_buffer_place_cursor(buffer, &iter_);
4417 #define ADD_TXT_POS(bp_, ep_, pti_) \
4418 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
4419 last = last->next; \
4420 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
4421 last->next = NULL; \
4423 g_warning("alloc error scanning URIs"); \
4426 static gboolean compose_beautify_paragraph(Compose *compose, GtkTextIter *par_iter, gboolean force)
4428 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4429 GtkTextBuffer *buffer;
4430 GtkTextIter iter, break_pos, end_of_line;
4431 gchar *quote_str = NULL;
4433 gboolean wrap_quote = force || prefs_common.linewrap_quote;
4434 gboolean prev_autowrap = compose->autowrap;
4435 gint startq_offset = -1, noq_offset = -1;
4436 gint uri_start = -1, uri_stop = -1;
4437 gint nouri_start = -1, nouri_stop = -1;
4438 gint num_blocks = 0;
4439 gint quotelevel = -1;
4440 gboolean modified = force;
4441 gboolean removed = FALSE;
4442 gboolean modified_before_remove = FALSE;
4444 gboolean start = TRUE;
4445 gint itemized_len = 0, rem_item_len = 0;
4446 gchar *itemized_chars = NULL;
4447 gboolean item_continuation = FALSE;
4452 if (compose->draft_timeout_tag == COMPOSE_DRAFT_TIMEOUT_FORBIDDEN) {
4456 compose->autowrap = FALSE;
4458 buffer = gtk_text_view_get_buffer(text);
4459 undo_wrapping(compose->undostruct, TRUE);
4464 mark = gtk_text_buffer_get_insert(buffer);
4465 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
4469 if (compose->draft_timeout_tag == COMPOSE_DRAFT_TIMEOUT_FORBIDDEN) {
4470 if (gtk_text_iter_ends_line(&iter)) {
4471 while (gtk_text_iter_ends_line(&iter) &&
4472 gtk_text_iter_forward_line(&iter))
4475 while (gtk_text_iter_backward_line(&iter)) {
4476 if (gtk_text_iter_ends_line(&iter)) {
4477 gtk_text_iter_forward_line(&iter);
4483 /* move to line start */
4484 gtk_text_iter_set_line_offset(&iter, 0);
4487 itemized_len = compose_itemized_length(buffer, &iter);
4489 if (!itemized_len) {
4490 itemized_len = compose_left_offset_length(buffer, &iter);
4491 item_continuation = TRUE;
4495 itemized_chars = compose_get_itemized_chars(buffer, &iter);
4497 /* go until paragraph end (empty line) */
4498 while (start || !gtk_text_iter_ends_line(&iter)) {
4499 gchar *scanpos = NULL;
4500 /* parse table - in order of priority */
4502 const gchar *needle; /* token */
4504 /* token search function */
4505 gchar *(*search) (const gchar *haystack,
4506 const gchar *needle);
4507 /* part parsing function */
4508 gboolean (*parse) (const gchar *start,
4509 const gchar *scanpos,
4513 /* part to URI function */
4514 gchar *(*build_uri) (const gchar *bp,
4518 static struct table parser[] = {
4519 {"http://", strcasestr, get_uri_part, make_uri_string},
4520 {"https://", strcasestr, get_uri_part, make_uri_string},
4521 {"ftp://", strcasestr, get_uri_part, make_uri_string},
4522 {"sftp://", strcasestr, get_uri_part, make_uri_string},
4523 {"gopher://",strcasestr, get_uri_part, make_uri_string},
4524 {"www.", strcasestr, get_uri_part, make_http_string},
4525 {"mailto:", strcasestr, get_uri_part, make_uri_string},
4526 {"@", strcasestr, get_email_part, make_email_string}
4528 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
4529 gint last_index = PARSE_ELEMS;
4531 gchar *o_walk = NULL, *walk = NULL, *bp = NULL, *ep = NULL;
4535 if (!prev_autowrap && num_blocks == 0) {
4537 g_signal_handlers_block_by_func(G_OBJECT(buffer),
4538 G_CALLBACK(text_inserted),
4541 if (gtk_text_iter_has_tag(&iter, compose->no_wrap_tag) && !force)
4544 uri_start = uri_stop = -1;
4546 quote_str = compose_get_quote_str(buffer, &iter, "e_len);
4549 /* debug_print("compose_beautify_paragraph(): quote_str = '%s'\n", quote_str); */
4550 if (startq_offset == -1)
4551 startq_offset = gtk_text_iter_get_offset(&iter);
4552 quotelevel = get_quote_level(quote_str, prefs_common.quote_chars);
4553 if (quotelevel > 2) {
4554 /* recycle colors */
4555 if (prefs_common.recycle_quote_colors)
4564 if (startq_offset == -1)
4565 noq_offset = gtk_text_iter_get_offset(&iter);
4569 if (prev_autowrap == FALSE && !force && !wrap_quote) {
4572 if (gtk_text_iter_ends_line(&iter)) {
4574 } else if (compose_get_line_break_pos(buffer, &iter, &break_pos,
4575 prefs_common.linewrap_len,
4577 GtkTextIter prev, next, cur;
4578 if (prev_autowrap != FALSE || force) {
4579 compose->automatic_break = TRUE;
4581 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4582 compose->automatic_break = FALSE;
4583 if (itemized_len && compose->autoindent) {
4584 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4585 if (!item_continuation)
4586 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4588 } else if (quote_str && wrap_quote) {
4589 compose->automatic_break = TRUE;
4591 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4592 compose->automatic_break = FALSE;
4593 if (itemized_len && compose->autoindent) {
4594 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4595 if (!item_continuation)
4596 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4600 /* remove trailing spaces */
4602 rem_item_len = itemized_len;
4603 while (compose->autoindent && rem_item_len-- > 0)
4604 gtk_text_iter_backward_char(&cur);
4605 gtk_text_iter_backward_char(&cur);
4608 while (!gtk_text_iter_starts_line(&cur)) {
4611 gtk_text_iter_backward_char(&cur);
4612 wc = gtk_text_iter_get_char(&cur);
4613 if (!g_unichar_isspace(wc))
4617 if (!gtk_text_iter_equal(&prev, &next)) {
4618 gtk_text_buffer_delete(buffer, &prev, &next);
4620 gtk_text_iter_forward_char(&break_pos);
4624 gtk_text_buffer_insert(buffer, &break_pos,
4628 modified |= compose_join_next_line(compose, buffer, &iter, quote_str);
4630 /* move iter to current line start */
4631 gtk_text_iter_set_line_offset(&iter, 0);
4638 /* move iter to next line start */
4644 if (!prev_autowrap && num_blocks > 0) {
4646 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
4647 G_CALLBACK(text_inserted),
4651 while (!gtk_text_iter_ends_line(&end_of_line)) {
4652 gtk_text_iter_forward_char(&end_of_line);
4654 o_walk = walk = gtk_text_buffer_get_text(buffer, &iter, &end_of_line, FALSE);
4656 nouri_start = gtk_text_iter_get_offset(&iter);
4657 nouri_stop = gtk_text_iter_get_offset(&end_of_line);
4659 walk_pos = gtk_text_iter_get_offset(&iter);
4660 /* FIXME: this looks phony. scanning for anything in the parse table */
4661 for (n = 0; n < PARSE_ELEMS; n++) {
4664 tmp = parser[n].search(walk, parser[n].needle);
4666 if (scanpos == NULL || tmp < scanpos) {
4675 /* check if URI can be parsed */
4676 if (parser[last_index].parse(walk, scanpos, (const gchar **)&bp,
4677 (const gchar **)&ep, FALSE)
4678 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
4682 strlen(parser[last_index].needle);
4685 uri_start = walk_pos + (bp - o_walk);
4686 uri_stop = walk_pos + (ep - o_walk);
4690 gtk_text_iter_forward_line(&iter);
4693 if (startq_offset != -1) {
4694 GtkTextIter startquote, endquote;
4695 gtk_text_buffer_get_iter_at_offset(
4696 buffer, &startquote, startq_offset);
4699 switch (quotelevel) {
4701 if (!gtk_text_iter_has_tag(&startquote, compose->quote0_tag) ||
4702 !gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) {
4703 gtk_text_buffer_apply_tag_by_name(
4704 buffer, "quote0", &startquote, &endquote);
4705 gtk_text_buffer_remove_tag_by_name(
4706 buffer, "quote1", &startquote, &endquote);
4707 gtk_text_buffer_remove_tag_by_name(
4708 buffer, "quote2", &startquote, &endquote);
4713 if (!gtk_text_iter_has_tag(&startquote, compose->quote1_tag) ||
4714 !gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) {
4715 gtk_text_buffer_apply_tag_by_name(
4716 buffer, "quote1", &startquote, &endquote);
4717 gtk_text_buffer_remove_tag_by_name(
4718 buffer, "quote0", &startquote, &endquote);
4719 gtk_text_buffer_remove_tag_by_name(
4720 buffer, "quote2", &startquote, &endquote);
4725 if (!gtk_text_iter_has_tag(&startquote, compose->quote2_tag) ||
4726 !gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag)) {
4727 gtk_text_buffer_apply_tag_by_name(
4728 buffer, "quote2", &startquote, &endquote);
4729 gtk_text_buffer_remove_tag_by_name(
4730 buffer, "quote0", &startquote, &endquote);
4731 gtk_text_buffer_remove_tag_by_name(
4732 buffer, "quote1", &startquote, &endquote);
4738 } else if (noq_offset != -1) {
4739 GtkTextIter startnoquote, endnoquote;
4740 gtk_text_buffer_get_iter_at_offset(
4741 buffer, &startnoquote, noq_offset);
4744 if ((gtk_text_iter_has_tag(&startnoquote, compose->quote0_tag)
4745 && gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) ||
4746 (gtk_text_iter_has_tag(&startnoquote, compose->quote1_tag)
4747 && gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) ||
4748 (gtk_text_iter_has_tag(&startnoquote, compose->quote2_tag)
4749 && gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag))) {
4750 gtk_text_buffer_remove_tag_by_name(
4751 buffer, "quote0", &startnoquote, &endnoquote);
4752 gtk_text_buffer_remove_tag_by_name(
4753 buffer, "quote1", &startnoquote, &endnoquote);
4754 gtk_text_buffer_remove_tag_by_name(
4755 buffer, "quote2", &startnoquote, &endnoquote);
4761 if (uri_start != nouri_start && uri_stop != nouri_stop) {
4762 GtkTextIter nouri_start_iter, nouri_end_iter;
4763 gtk_text_buffer_get_iter_at_offset(
4764 buffer, &nouri_start_iter, nouri_start);
4765 gtk_text_buffer_get_iter_at_offset(
4766 buffer, &nouri_end_iter, nouri_stop);
4767 if (gtk_text_iter_has_tag(&nouri_start_iter, compose->uri_tag) &&
4768 gtk_text_iter_has_tag(&nouri_end_iter, compose->uri_tag)) {
4769 gtk_text_buffer_remove_tag_by_name(
4770 buffer, "link", &nouri_start_iter, &nouri_end_iter);
4771 modified_before_remove = modified;
4776 if (uri_start >= 0 && uri_stop > 0) {
4777 GtkTextIter uri_start_iter, uri_end_iter, back;
4778 gtk_text_buffer_get_iter_at_offset(
4779 buffer, &uri_start_iter, uri_start);
4780 gtk_text_buffer_get_iter_at_offset(
4781 buffer, &uri_end_iter, uri_stop);
4782 back = uri_end_iter;
4783 gtk_text_iter_backward_char(&back);
4784 if (!gtk_text_iter_has_tag(&uri_start_iter, compose->uri_tag) ||
4785 !gtk_text_iter_has_tag(&back, compose->uri_tag)) {
4786 gtk_text_buffer_apply_tag_by_name(
4787 buffer, "link", &uri_start_iter, &uri_end_iter);
4789 if (removed && !modified_before_remove) {
4795 /* debug_print("not modified, out after %d lines\n", lines); */
4799 /* debug_print("modified, out after %d lines\n", lines); */
4801 g_free(itemized_chars);
4804 undo_wrapping(compose->undostruct, FALSE);
4805 compose->autowrap = prev_autowrap;
4810 void compose_action_cb(void *data)
4812 Compose *compose = (Compose *)data;
4813 compose_wrap_all(compose);
4816 static void compose_wrap_all(Compose *compose)
4818 compose_wrap_all_full(compose, FALSE);
4821 static void compose_wrap_all_full(Compose *compose, gboolean force)
4823 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4824 GtkTextBuffer *buffer;
4826 gboolean modified = TRUE;
4828 buffer = gtk_text_view_get_buffer(text);
4830 gtk_text_buffer_get_start_iter(buffer, &iter);
4832 undo_wrapping(compose->undostruct, TRUE);
4834 while (!gtk_text_iter_is_end(&iter) && modified)
4835 modified = compose_beautify_paragraph(compose, &iter, force);
4837 undo_wrapping(compose->undostruct, FALSE);
4841 static void compose_set_title(Compose *compose)
4847 edited = compose->modified ? _(" [Edited]") : "";
4849 subject = gtk_editable_get_chars(
4850 GTK_EDITABLE(compose->subject_entry), 0, -1);
4852 #ifndef GENERIC_UMPC
4853 if (subject && strlen(subject))
4854 str = g_strdup_printf(_("%s - Compose message%s"),
4857 str = g_strdup_printf(_("[no subject] - Compose message%s"), edited);
4859 str = g_strdup(_("Compose message"));
4862 gtk_window_set_title(GTK_WINDOW(compose->window), str);
4868 * compose_current_mail_account:
4870 * Find a current mail account (the currently selected account, or the
4871 * default account, if a news account is currently selected). If a
4872 * mail account cannot be found, display an error message.
4874 * Return value: Mail account, or NULL if not found.
4876 static PrefsAccount *
4877 compose_current_mail_account(void)
4881 if (cur_account && cur_account->protocol != A_NNTP)
4884 ac = account_get_default();
4885 if (!ac || ac->protocol == A_NNTP) {
4886 alertpanel_error(_("Account for sending mail is not specified.\n"
4887 "Please select a mail account before sending."));
4894 #define QUOTE_IF_REQUIRED(out, str) \
4896 if (*str != '"' && strpbrk(str, ",.:;[]<>()@\\\"")) { \
4900 len = strlen(str) + 3; \
4901 if ((__tmp = alloca(len)) == NULL) { \
4902 g_warning("can't allocate memory"); \
4903 g_string_free(header, TRUE); \
4906 g_snprintf(__tmp, len, "\"%s\"", str); \
4911 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4912 g_warning("can't allocate memory"); \
4913 g_string_free(header, TRUE); \
4916 strcpy(__tmp, str); \
4922 #define QUOTE_IF_REQUIRED_NORMAL(out, str, errret) \
4924 if (*str != '"' && strpbrk(str, ",.:;[]<>()@\\\"")) { \
4928 len = strlen(str) + 3; \
4929 if ((__tmp = alloca(len)) == NULL) { \
4930 g_warning("can't allocate memory"); \
4933 g_snprintf(__tmp, len, "\"%s\"", str); \
4938 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4939 g_warning("can't allocate memory"); \
4942 strcpy(__tmp, str); \
4948 static void compose_select_account(Compose *compose, PrefsAccount *account,
4951 gchar *from = NULL, *header = NULL;
4952 ComposeHeaderEntry *header_entry;
4955 cm_return_if_fail(account != NULL);
4957 compose->account = account;
4958 if (account->name && *account->name) {
4960 QUOTE_IF_REQUIRED_NORMAL(buf, account->name, return);
4961 qbuf = escape_internal_quotes(buf, '"');
4962 from = g_strdup_printf("%s <%s>",
4963 qbuf, account->address);
4966 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4968 from = g_strdup_printf("<%s>",
4970 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4975 compose_set_title(compose);
4977 compose_activate_privacy_system(compose, account, FALSE);
4979 if (account->default_sign && privacy_system_can_sign(compose->privacy_system) &&
4980 compose->mode != COMPOSE_REDIRECT)
4981 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", TRUE);
4983 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", FALSE);
4984 if (account->default_encrypt && privacy_system_can_encrypt(compose->privacy_system) &&
4985 compose->mode != COMPOSE_REDIRECT)
4986 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", TRUE);
4988 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", FALSE);
4990 if (!init && compose->mode != COMPOSE_REDIRECT) {
4991 undo_block(compose->undostruct);
4992 compose_insert_sig(compose, TRUE);
4993 undo_unblock(compose->undostruct);
4996 header_entry = (ComposeHeaderEntry *) compose->header_list->data;
4997 if (gtk_combo_box_get_active_iter(GTK_COMBO_BOX(header_entry->combo), &iter))
4998 gtk_tree_model_get(gtk_combo_box_get_model(GTK_COMBO_BOX(
4999 header_entry->combo)), &iter, COMBOBOX_TEXT, &header, -1);
5001 if (header && !strlen(gtk_entry_get_text(GTK_ENTRY(header_entry->entry)))) {
5002 if (account->protocol == A_NNTP) {
5003 if (!strcmp(header, _("To:")))
5004 combobox_select_by_text(
5005 GTK_COMBO_BOX(header_entry->combo),
5008 if (!strcmp(header, _("Newsgroups:")))
5009 combobox_select_by_text(
5010 GTK_COMBO_BOX(header_entry->combo),
5018 /* use account's dict info if set */
5019 if (compose->gtkaspell) {
5020 if (account->enable_default_dictionary)
5021 gtkaspell_change_dict(compose->gtkaspell,
5022 account->default_dictionary, FALSE);
5023 if (account->enable_default_alt_dictionary)
5024 gtkaspell_change_alt_dict(compose->gtkaspell,
5025 account->default_alt_dictionary);
5026 if (account->enable_default_dictionary
5027 || account->enable_default_alt_dictionary)
5028 compose_spell_menu_changed(compose);
5033 gboolean compose_check_for_valid_recipient(Compose *compose) {
5034 gchar *recipient_headers_mail[] = {"To:", "Cc:", "Bcc:", NULL};
5035 gchar *recipient_headers_news[] = {"Newsgroups:", NULL};
5036 gboolean recipient_found = FALSE;
5040 /* free to and newsgroup list */
5041 slist_free_strings_full(compose->to_list);
5042 compose->to_list = NULL;
5044 slist_free_strings_full(compose->newsgroup_list);
5045 compose->newsgroup_list = NULL;
5047 /* search header entries for to and newsgroup entries */
5048 for (list = compose->header_list; list; list = list->next) {
5051 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5052 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5055 if (entry[0] != '\0') {
5056 for (strptr = recipient_headers_mail; *strptr != NULL; strptr++) {
5057 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
5058 compose->to_list = address_list_append(compose->to_list, entry);
5059 recipient_found = TRUE;
5062 for (strptr = recipient_headers_news; *strptr != NULL; strptr++) {
5063 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
5064 compose->newsgroup_list = newsgroup_list_append(compose->newsgroup_list, entry);
5065 recipient_found = TRUE;
5072 return recipient_found;
5075 static gboolean compose_check_for_set_recipients(Compose *compose)
5077 if (compose->account->set_autocc && compose->account->auto_cc) {
5078 gboolean found_other = FALSE;
5080 /* search header entries for to and newsgroup entries */
5081 for (list = compose->header_list; list; list = list->next) {
5084 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5085 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5088 if (strcmp(entry, compose->account->auto_cc)
5089 || strcmp(header, prefs_common_translated_header_name("Cc:"))) {
5100 if (compose->batch) {
5101 gtk_widget_show_all(compose->window);
5103 text = g_strdup_printf(_("The only recipient is the default '%s' address. Send anyway?"),
5104 prefs_common_translated_header_name("Cc"));
5105 aval = alertpanel(_("Send"),
5107 GTK_STOCK_CANCEL, _("_Send"), NULL, ALERTFOCUS_SECOND);
5109 if (aval != G_ALERTALTERNATE)
5113 if (compose->account->set_autobcc && compose->account->auto_bcc) {
5114 gboolean found_other = FALSE;
5116 /* search header entries for to and newsgroup entries */
5117 for (list = compose->header_list; list; list = list->next) {
5120 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5121 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5124 if (strcmp(entry, compose->account->auto_bcc)
5125 || strcmp(header, prefs_common_translated_header_name("Bcc:"))) {
5137 if (compose->batch) {
5138 gtk_widget_show_all(compose->window);
5140 text = g_strdup_printf(_("The only recipient is the default '%s' address. Send anyway?"),
5141 prefs_common_translated_header_name("Bcc"));
5142 aval = alertpanel(_("Send"),
5144 GTK_STOCK_CANCEL, _("_Send"), NULL, ALERTFOCUS_SECOND);
5146 if (aval != G_ALERTALTERNATE)
5153 static gboolean compose_check_entries(Compose *compose, gboolean check_everything)
5157 if (compose_check_for_valid_recipient(compose) == FALSE) {
5158 if (compose->batch) {
5159 gtk_widget_show_all(compose->window);
5161 alertpanel_error(_("Recipient is not specified."));
5165 if (compose_check_for_set_recipients(compose) == FALSE) {
5169 if (!compose->batch && prefs_common.warn_empty_subj == TRUE) {
5170 str = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5171 if (*str == '\0' && check_everything == TRUE &&
5172 compose->mode != COMPOSE_REDIRECT) {
5176 message = g_strdup_printf(_("Subject is empty. %s"),
5177 compose->sending?_("Send it anyway?"):
5178 _("Queue it anyway?"));
5180 aval = alertpanel_full(compose->sending?_("Send"):_("Send later"), message,
5181 GTK_STOCK_CANCEL, compose->sending?_("_Send"):_("_Queue"), NULL,
5182 ALERTFOCUS_FIRST, TRUE, NULL, ALERT_QUESTION);
5184 if (aval & G_ALERTDISABLE) {
5185 aval &= ~G_ALERTDISABLE;
5186 prefs_common.warn_empty_subj = FALSE;
5188 if (aval != G_ALERTALTERNATE)
5193 if (!compose->batch && prefs_common.warn_sending_many_recipients_num > 0
5194 && check_everything == TRUE) {
5198 /* count To and Cc recipients */
5199 for (list = compose->header_list; list; list = list->next) {
5203 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5204 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5207 if ((entry[0] != '\0') &&
5208 (!strcmp(header, prefs_common_translated_header_name("To:")) ||
5209 !strcmp(header, prefs_common_translated_header_name("Cc:")))) {
5215 if (cnt > prefs_common.warn_sending_many_recipients_num) {
5219 message = g_strdup_printf(_("Sending to %d recipients. %s"), cnt,
5220 compose->sending?_("Send it anyway?"):
5221 _("Queue it anyway?"));
5223 aval = alertpanel_full(compose->sending?_("Send"):_("Send later"), message,
5224 GTK_STOCK_CANCEL, compose->sending?_("_Send"):_("_Queue"), NULL,
5225 ALERTFOCUS_FIRST, TRUE, NULL, ALERT_QUESTION);
5227 if (aval & G_ALERTDISABLE) {
5228 aval &= ~G_ALERTDISABLE;
5229 prefs_common.warn_sending_many_recipients_num = 0;
5231 if (aval != G_ALERTALTERNATE)
5236 if (check_everything && hooks_invoke(COMPOSE_CHECK_BEFORE_SEND_HOOKLIST, compose))
5242 static void _display_queue_error(ComposeQueueResult val)
5245 case COMPOSE_QUEUE_SUCCESS:
5247 case COMPOSE_QUEUE_ERROR_NO_MSG:
5248 alertpanel_error(_("Could not queue message."));
5250 case COMPOSE_QUEUE_ERROR_WITH_ERRNO:
5251 alertpanel_error(_("Could not queue message:\n\n%s."),
5254 case COMPOSE_QUEUE_ERROR_SIGNING_FAILED:
5255 alertpanel_error(_("Could not queue message for sending:\n\n"
5256 "Signature failed: %s"),
5257 privacy_peek_error() ? privacy_get_error() : _("Unknown error"));
5259 case COMPOSE_QUEUE_ERROR_ENCRYPT_FAILED:
5260 alertpanel_error(_("Could not queue message for sending:\n\n"
5261 "Encryption failed: %s"),
5262 privacy_peek_error() ? privacy_get_error() : _("Unknown error"));
5264 case COMPOSE_QUEUE_ERROR_CHAR_CONVERSION:
5265 alertpanel_error(_("Could not queue message for sending:\n\n"
5266 "Charset conversion failed."));
5268 case COMPOSE_QUEUE_ERROR_NO_ENCRYPTION_KEY:
5269 alertpanel_error(_("Could not queue message for sending:\n\n"
5270 "Couldn't get recipient encryption key."));
5273 /* unhandled error */
5274 debug_print("oops, unhandled compose_queue() return value %d\n",
5280 gint compose_send(Compose *compose)
5283 FolderItem *folder = NULL;
5284 ComposeQueueResult val = COMPOSE_QUEUE_ERROR_NO_MSG;
5285 gchar *msgpath = NULL;
5286 gboolean discard_window = FALSE;
5287 gchar *errstr = NULL;
5288 gchar *tmsgid = NULL;
5289 MainWindow *mainwin = mainwindow_get_mainwindow();
5290 gboolean queued_removed = FALSE;
5292 if (prefs_common.send_dialog_invisible
5293 || compose->batch == TRUE)
5294 discard_window = TRUE;
5296 compose_allow_user_actions (compose, FALSE);
5297 compose->sending = TRUE;
5299 if (compose_check_entries(compose, TRUE) == FALSE) {
5300 if (compose->batch) {
5301 gtk_widget_show_all(compose->window);
5307 val = compose_queue(compose, &msgnum, &folder, &msgpath, TRUE);
5309 if (val != COMPOSE_QUEUE_SUCCESS) {
5310 if (compose->batch) {
5311 gtk_widget_show_all(compose->window);
5314 _display_queue_error(val);
5319 tmsgid = compose->msgid ? g_strdup(compose->msgid) : NULL;
5320 if (discard_window) {
5321 compose->sending = FALSE;
5322 compose_close(compose);
5323 /* No more compose access in the normal codepath
5324 * after this point! */
5329 alertpanel_error(_("The message was queued but could not be "
5330 "sent.\nUse \"Send queued messages\" from "
5331 "the main window to retry."));
5332 if (!discard_window) {
5339 if (msgpath == NULL) {
5340 msgpath = folder_item_fetch_msg(folder, msgnum);
5341 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5344 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5345 claws_unlink(msgpath);
5348 if (!discard_window) {
5350 if (!queued_removed)
5351 folder_item_remove_msg(folder, msgnum);
5352 folder_item_scan(folder);
5354 /* make sure we delete that */
5355 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5357 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5358 folder_item_remove_msg(folder, tmp->msgnum);
5359 procmsg_msginfo_free(&tmp);
5366 if (!queued_removed)
5367 folder_item_remove_msg(folder, msgnum);
5368 folder_item_scan(folder);
5370 /* make sure we delete that */
5371 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5373 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5374 folder_item_remove_msg(folder, tmp->msgnum);
5375 procmsg_msginfo_free(&tmp);
5378 if (!discard_window) {
5379 compose->sending = FALSE;
5380 compose_allow_user_actions (compose, TRUE);
5381 compose_close(compose);
5385 alertpanel_error_log(_("%s\nUse \"Send queued messages\" from "
5386 "the main window to retry."), errstr);
5389 alertpanel_error_log(_("The message was queued but could not be "
5390 "sent.\nUse \"Send queued messages\" from "
5391 "the main window to retry."));
5393 if (!discard_window) {
5402 toolbar_main_set_sensitive(mainwin);
5403 main_window_set_menu_sensitive(mainwin);
5409 compose_allow_user_actions (compose, TRUE);
5410 compose->sending = FALSE;
5411 compose->modified = TRUE;
5412 toolbar_main_set_sensitive(mainwin);
5413 main_window_set_menu_sensitive(mainwin);
5418 static gboolean compose_use_attach(Compose *compose)
5420 GtkTreeModel *model = gtk_tree_view_get_model
5421 (GTK_TREE_VIEW(compose->attach_clist));
5422 return gtk_tree_model_iter_n_children(model, NULL) > 0;
5425 static gint compose_redirect_write_headers_from_headerlist(Compose *compose,
5428 gchar buf[BUFFSIZE];
5430 gboolean first_to_address;
5431 gboolean first_cc_address;
5433 ComposeHeaderEntry *headerentry;
5434 const gchar *headerentryname;
5435 const gchar *cc_hdr;
5436 const gchar *to_hdr;
5437 gboolean err = FALSE;
5439 debug_print("Writing redirect header\n");
5441 cc_hdr = prefs_common_translated_header_name("Cc:");
5442 to_hdr = prefs_common_translated_header_name("To:");
5444 first_to_address = TRUE;
5445 for (list = compose->header_list; list; list = list->next) {
5446 headerentry = ((ComposeHeaderEntry *)list->data);
5447 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5449 if (g_utf8_collate(headerentryname, to_hdr) == 0) {
5450 const gchar *entstr = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5451 Xstrdup_a(str, entstr, return -1);
5453 if (str[0] != '\0') {
5454 compose_convert_header
5455 (compose, buf, sizeof(buf), str,
5456 strlen("Resent-To") + 2, TRUE);
5458 if (first_to_address) {
5459 err |= (fprintf(fp, "Resent-To: ") < 0);
5460 first_to_address = FALSE;
5462 err |= (fprintf(fp, ",") < 0);
5464 err |= (fprintf(fp, "%s", buf) < 0);
5468 if (!first_to_address) {
5469 err |= (fprintf(fp, "\n") < 0);
5472 first_cc_address = TRUE;
5473 for (list = compose->header_list; list; list = list->next) {
5474 headerentry = ((ComposeHeaderEntry *)list->data);
5475 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5477 if (g_utf8_collate(headerentryname, cc_hdr) == 0) {
5478 const gchar *strg = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5479 Xstrdup_a(str, strg, return -1);
5481 if (str[0] != '\0') {
5482 compose_convert_header
5483 (compose, buf, sizeof(buf), str,
5484 strlen("Resent-Cc") + 2, TRUE);
5486 if (first_cc_address) {
5487 err |= (fprintf(fp, "Resent-Cc: ") < 0);
5488 first_cc_address = FALSE;
5490 err |= (fprintf(fp, ",") < 0);
5492 err |= (fprintf(fp, "%s", buf) < 0);
5496 if (!first_cc_address) {
5497 err |= (fprintf(fp, "\n") < 0);
5500 return (err ? -1:0);
5503 static gint compose_redirect_write_headers(Compose *compose, FILE *fp)
5505 gchar date[RFC822_DATE_BUFFSIZE];
5506 gchar buf[BUFFSIZE];
5508 const gchar *entstr;
5509 /* struct utsname utsbuf; */
5510 gboolean err = FALSE;
5512 cm_return_val_if_fail(fp != NULL, -1);
5513 cm_return_val_if_fail(compose->account != NULL, -1);
5514 cm_return_val_if_fail(compose->account->address != NULL, -1);
5517 if (prefs_common.hide_timezone)
5518 get_rfc822_date_hide_tz(date, sizeof(date));
5520 get_rfc822_date(date, sizeof(date));
5521 err |= (fprintf(fp, "Resent-Date: %s\n", date) < 0);
5524 if (compose->account->name && *compose->account->name) {
5525 compose_convert_header
5526 (compose, buf, sizeof(buf), compose->account->name,
5527 strlen("From: "), TRUE);
5528 err |= (fprintf(fp, "Resent-From: %s <%s>\n",
5529 buf, compose->account->address) < 0);
5531 err |= (fprintf(fp, "Resent-From: %s\n", compose->account->address) < 0);
5534 entstr = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5535 if (*entstr != '\0') {
5536 Xstrdup_a(str, entstr, return -1);
5539 compose_convert_header(compose, buf, sizeof(buf), str,
5540 strlen("Subject: "), FALSE);
5541 err |= (fprintf(fp, "Subject: %s\n", buf) < 0);
5545 /* Resent-Message-ID */
5546 if (compose->account->gen_msgid) {
5547 gchar *addr = prefs_account_generate_msgid(compose->account);
5548 err |= (fprintf(fp, "Resent-Message-ID: <%s>\n", addr) < 0);
5550 g_free(compose->msgid);
5551 compose->msgid = addr;
5553 compose->msgid = NULL;
5556 if (compose_redirect_write_headers_from_headerlist(compose, fp))
5559 /* separator between header and body */
5560 err |= (claws_fputs("\n", fp) == EOF);
5562 return (err ? -1:0);
5565 static gint compose_redirect_write_to_file(Compose *compose, FILE *fdest)
5570 gchar rewrite_buf[BUFFSIZE];
5572 gboolean skip = FALSE;
5573 gboolean err = FALSE;
5574 gchar *not_included[]={
5575 "Return-Path:", "Delivered-To:", "Received:",
5576 "Subject:", "X-UIDL:", "AF:",
5577 "NF:", "PS:", "SRH:",
5578 "SFN:", "DSR:", "MID:",
5579 "CFG:", "PT:", "S:",
5580 "RQ:", "SSV:", "NSV:",
5581 "SSH:", "R:", "MAID:",
5582 "NAID:", "RMID:", "FMID:",
5583 "SCF:", "RRCPT:", "NG:",
5584 "X-Claws-Privacy", "X-Claws-Sign:", "X-Claws-Encrypt",
5585 "X-Claws-End-Special-Headers:", "X-Claws-Account-Id:",
5586 "X-Sylpheed-Privacy", "X-Sylpheed-Sign:", "X-Sylpheed-Encrypt",
5587 "X-Sylpheed-End-Special-Headers:", "X-Sylpheed-Account-Id:",
5588 "X-Claws-Auto-Wrapping:", "X-Claws-Auto-Indent:",
5593 if ((fp = claws_fopen(compose->redirect_filename, "rb")) == NULL) {
5594 FILE_OP_ERROR(compose->redirect_filename, "claws_fopen");
5598 while ((ret = procheader_get_one_field_asis(&buf, fp)) != -1) {
5600 for (i = 0; not_included[i] != NULL; i++) {
5601 if (g_ascii_strncasecmp(buf, not_included[i],
5602 strlen(not_included[i])) == 0) {
5612 if (claws_fputs(buf, fdest) == -1) {
5618 if (!prefs_common.redirect_keep_from) {
5619 if (g_ascii_strncasecmp(buf, "From:",
5620 strlen("From:")) == 0) {
5621 err |= (claws_fputs(" (by way of ", fdest) == EOF);
5622 if (compose->account->name
5623 && *compose->account->name) {
5624 gchar buffer[BUFFSIZE];
5626 compose_convert_header
5627 (compose, buffer, sizeof(buffer),
5628 compose->account->name,
5631 err |= (fprintf(fdest, "%s <%s>",
5633 compose->account->address) < 0);
5635 err |= (fprintf(fdest, "%s",
5636 compose->account->address) < 0);
5637 err |= (claws_fputs(")", fdest) == EOF);
5643 if (claws_fputs("\n", fdest) == -1)
5650 if (compose_redirect_write_headers(compose, fdest))
5653 while ((len = claws_fread(rewrite_buf, sizeof(gchar), sizeof(rewrite_buf), fp)) > 0) {
5654 if (claws_fwrite(rewrite_buf, sizeof(gchar), len, fdest) != len)
5668 static gint compose_write_to_file(Compose *compose, FILE *fp, gint action, gboolean attach_parts)
5670 GtkTextBuffer *buffer;
5671 GtkTextIter start, end, tmp;
5672 gchar *chars, *tmp_enc_file, *content;
5674 const gchar *out_codeset;
5675 EncodingType encoding = ENC_UNKNOWN;
5676 MimeInfo *mimemsg, *mimetext;
5678 const gchar *src_codeset = CS_INTERNAL;
5679 gchar *from_addr = NULL;
5680 gchar *from_name = NULL;
5683 if (action == COMPOSE_WRITE_FOR_SEND) {
5684 attach_parts = TRUE;
5686 /* We're sending the message, generate a Message-ID
5688 if (compose->msgid == NULL &&
5689 compose->account->gen_msgid) {
5690 compose->msgid = prefs_account_generate_msgid(compose->account);
5694 /* create message MimeInfo */
5695 mimemsg = procmime_mimeinfo_new();
5696 mimemsg->type = MIMETYPE_MESSAGE;
5697 mimemsg->subtype = g_strdup("rfc822");
5698 mimemsg->content = MIMECONTENT_MEM;
5699 mimemsg->tmp = TRUE; /* must free content later */
5700 mimemsg->data.mem = compose_get_header(compose);
5702 /* Create text part MimeInfo */
5703 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5704 gtk_text_buffer_get_end_iter(buffer, &end);
5707 /* We make sure that there is a newline at the end. */
5708 if (action == COMPOSE_WRITE_FOR_SEND && gtk_text_iter_backward_char(&tmp)) {
5709 chars = gtk_text_buffer_get_text(buffer, &tmp, &end, FALSE);
5710 if (*chars != '\n') {
5711 gtk_text_buffer_insert(buffer, &end, "\n", 1);
5716 /* get all composed text */
5717 gtk_text_buffer_get_start_iter(buffer, &start);
5718 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5720 out_codeset = conv_get_charset_str(compose->out_encoding);
5722 if (!out_codeset && is_ascii_str(chars)) {
5723 out_codeset = CS_US_ASCII;
5724 } else if (prefs_common.outgoing_fallback_to_ascii &&
5725 is_ascii_str(chars)) {
5726 out_codeset = CS_US_ASCII;
5727 encoding = ENC_7BIT;
5731 gchar *test_conv_global_out = NULL;
5732 gchar *test_conv_reply = NULL;
5734 /* automatic mode. be automatic. */
5735 codeconv_set_strict(TRUE);
5737 out_codeset = conv_get_outgoing_charset_str();
5739 debug_print("trying to convert to %s\n", out_codeset);
5740 test_conv_global_out = conv_codeset_strdup(chars, src_codeset, out_codeset);
5743 if (!test_conv_global_out && compose->orig_charset
5744 && strcmp(compose->orig_charset, CS_US_ASCII)) {
5745 out_codeset = compose->orig_charset;
5746 debug_print("failure; trying to convert to %s\n", out_codeset);
5747 test_conv_reply = conv_codeset_strdup(chars, src_codeset, out_codeset);
5750 if (!test_conv_global_out && !test_conv_reply) {
5752 out_codeset = CS_INTERNAL;
5753 debug_print("failure; finally using %s\n", out_codeset);
5755 g_free(test_conv_global_out);
5756 g_free(test_conv_reply);
5757 codeconv_set_strict(FALSE);
5760 if (encoding == ENC_UNKNOWN) {
5761 if (prefs_common.encoding_method == CTE_BASE64)
5762 encoding = ENC_BASE64;
5763 else if (prefs_common.encoding_method == CTE_QUOTED_PRINTABLE)
5764 encoding = ENC_QUOTED_PRINTABLE;
5765 else if (prefs_common.encoding_method == CTE_8BIT)
5766 encoding = ENC_8BIT;
5768 encoding = procmime_get_encoding_for_charset(out_codeset);
5771 debug_print("src encoding = %s, out encoding = %s, transfer encoding = %s\n",
5772 src_codeset, out_codeset, procmime_get_encoding_str(encoding));
5774 if (action == COMPOSE_WRITE_FOR_SEND) {
5775 codeconv_set_strict(TRUE);
5776 buf = conv_codeset_strdup(chars, src_codeset, out_codeset);
5777 codeconv_set_strict(FALSE);
5782 msg = g_strdup_printf(_("Can't convert the character encoding of the message \n"
5783 "to the specified %s charset.\n"
5784 "Send it as %s?"), out_codeset, src_codeset);
5785 aval = alertpanel_full(_("Error"), msg, GTK_STOCK_CANCEL,
5786 _("_Send"), NULL, ALERTFOCUS_SECOND, FALSE,
5790 if (aval != G_ALERTALTERNATE) {
5792 return COMPOSE_QUEUE_ERROR_CHAR_CONVERSION;
5795 out_codeset = src_codeset;
5801 out_codeset = src_codeset;
5806 if (prefs_common.rewrite_first_from && (encoding == ENC_8BIT || encoding == ENC_7BIT)) {
5807 if (!strncmp(buf, "From ", sizeof("From ")-1) ||
5808 strstr(buf, "\nFrom ") != NULL) {
5809 encoding = ENC_QUOTED_PRINTABLE;
5813 mimetext = procmime_mimeinfo_new();
5814 mimetext->content = MIMECONTENT_MEM;
5815 mimetext->tmp = TRUE; /* must free content later */
5816 /* dup'ed because procmime_encode_content can turn it into a tmpfile
5817 * and free the data, which we need later. */
5818 mimetext->data.mem = g_strdup(buf);
5819 mimetext->type = MIMETYPE_TEXT;
5820 mimetext->subtype = g_strdup("plain");
5821 g_hash_table_insert(mimetext->typeparameters, g_strdup("charset"),
5822 g_strdup(out_codeset));
5824 /* protect trailing spaces when signing message */
5825 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5826 privacy_system_can_sign(compose->privacy_system)) {
5827 encoding = ENC_QUOTED_PRINTABLE;
5831 debug_print("main text: %Id bytes encoded as %s in %d\n",
5833 debug_print("main text: %zd bytes encoded as %s in %d\n",
5835 strlen(buf), out_codeset, encoding);
5837 /* check for line length limit */
5838 if (action == COMPOSE_WRITE_FOR_SEND &&
5839 encoding != ENC_QUOTED_PRINTABLE && encoding != ENC_BASE64 &&
5840 check_line_length(buf, 1000, &line) < 0) {
5843 msg = g_strdup_printf
5844 (_("Line %d exceeds the line length limit (998 bytes).\n"
5845 "The contents of the message might be broken on the way to the delivery.\n"
5847 "Send it anyway?"), line + 1);
5848 aval = alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, GTK_STOCK_OK, NULL,
5851 if (aval != G_ALERTALTERNATE) {
5853 return COMPOSE_QUEUE_ERROR_NO_MSG;
5857 if (encoding != ENC_UNKNOWN)
5858 procmime_encode_content(mimetext, encoding);
5860 /* append attachment parts */
5861 if (compose_use_attach(compose) && attach_parts) {
5862 MimeInfo *mimempart;
5863 gchar *boundary = NULL;
5864 mimempart = procmime_mimeinfo_new();
5865 mimempart->content = MIMECONTENT_EMPTY;
5866 mimempart->type = MIMETYPE_MULTIPART;
5867 mimempart->subtype = g_strdup("mixed");
5871 boundary = generate_mime_boundary(NULL);
5872 } while (strstr(buf, boundary) != NULL);
5874 g_hash_table_insert(mimempart->typeparameters, g_strdup("boundary"),
5877 mimetext->disposition = DISPOSITIONTYPE_INLINE;
5879 g_node_append(mimempart->node, mimetext->node);
5880 g_node_append(mimemsg->node, mimempart->node);
5882 if (compose_add_attachments(compose, mimempart) < 0)
5883 return COMPOSE_QUEUE_ERROR_NO_MSG;
5885 g_node_append(mimemsg->node, mimetext->node);
5889 if (strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) != 0) {
5890 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
5891 /* extract name and address */
5892 if (strstr(spec, " <") && strstr(spec, ">")) {
5893 from_addr = g_strdup(strrchr(spec, '<')+1);
5894 *(strrchr(from_addr, '>')) = '\0';
5895 from_name = g_strdup(spec);
5896 *(strrchr(from_name, '<')) = '\0';
5903 /* sign message if sending */
5904 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5905 privacy_system_can_sign(compose->privacy_system))
5906 if (!privacy_sign(compose->privacy_system, mimemsg,
5907 compose->account, from_addr)) {
5910 return COMPOSE_QUEUE_ERROR_SIGNING_FAILED;
5915 if (compose->use_encryption) {
5916 if (compose->encdata != NULL &&
5917 strcmp(compose->encdata, "_DONT_ENCRYPT_")) {
5919 /* First, write an unencrypted copy and save it to outbox, if
5920 * user wants that. */
5921 if (compose->account->save_encrypted_as_clear_text) {
5922 debug_print("saving sent message unencrypted...\n");
5923 FILE *tmpfp = get_tmpfile_in_dir(get_mime_tmp_dir(), &tmp_enc_file);
5925 claws_fclose(tmpfp);
5927 /* fp now points to a file with headers written,
5928 * let's make a copy. */
5930 content = file_read_stream_to_str(fp);
5932 str_write_to_file(content, tmp_enc_file, TRUE);
5935 /* Now write the unencrypted body. */
5936 if ((tmpfp = claws_fopen(tmp_enc_file, "a")) != NULL) {
5937 procmime_write_mimeinfo(mimemsg, tmpfp);
5938 claws_fclose(tmpfp);
5940 outbox = folder_find_item_from_identifier(compose_get_save_to(compose));
5942 outbox = folder_get_default_outbox();
5944 procmsg_save_to_outbox(outbox, tmp_enc_file, TRUE);
5945 claws_unlink(tmp_enc_file);
5947 g_warning("Can't open file '%s'", tmp_enc_file);
5950 g_warning("couldn't get tempfile");
5953 if (!privacy_encrypt(compose->privacy_system, mimemsg, compose->encdata)) {
5954 debug_print("Couldn't encrypt mime structure: %s.\n",
5955 privacy_get_error());
5956 return COMPOSE_QUEUE_ERROR_ENCRYPT_FAILED;
5961 procmime_write_mimeinfo(mimemsg, fp);
5963 procmime_mimeinfo_free_all(&mimemsg);
5968 static gint compose_write_body_to_file(Compose *compose, const gchar *file)
5970 GtkTextBuffer *buffer;
5971 GtkTextIter start, end;
5976 if ((fp = claws_fopen(file, "wb")) == NULL) {
5977 FILE_OP_ERROR(file, "claws_fopen");
5981 /* chmod for security */
5982 if (change_file_mode_rw(fp, file) < 0) {
5983 FILE_OP_ERROR(file, "chmod");
5984 g_warning("can't change file mode");
5987 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5988 gtk_text_buffer_get_start_iter(buffer, &start);
5989 gtk_text_buffer_get_end_iter(buffer, &end);
5990 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5992 chars = conv_codeset_strdup
5993 (tmp, CS_INTERNAL, conv_get_locale_charset_str());
6002 len = strlen(chars);
6003 if (claws_fwrite(chars, sizeof(gchar), len, fp) != len) {
6004 FILE_OP_ERROR(file, "claws_fwrite");
6013 if (claws_safe_fclose(fp) == EOF) {
6014 FILE_OP_ERROR(file, "claws_fclose");
6021 static gint compose_remove_reedit_target(Compose *compose, gboolean force)
6024 MsgInfo *msginfo = compose->targetinfo;
6026 cm_return_val_if_fail(compose->mode == COMPOSE_REEDIT, -1);
6027 if (!msginfo) return -1;
6029 if (!force && MSG_IS_LOCKED(msginfo->flags))
6032 item = msginfo->folder;
6033 cm_return_val_if_fail(item != NULL, -1);
6035 if (procmsg_msg_exist(msginfo) &&
6036 (folder_has_parent_of_type(item, F_QUEUE) ||
6037 folder_has_parent_of_type(item, F_DRAFT)
6038 || msginfo == compose->autosaved_draft)) {
6039 if (folder_item_remove_msg(item, msginfo->msgnum) < 0) {
6040 g_warning("can't remove the old message");
6043 debug_print("removed reedit target %d\n", msginfo->msgnum);
6050 static void compose_remove_draft(Compose *compose)
6053 MsgInfo *msginfo = compose->targetinfo;
6054 drafts = account_get_special_folder(compose->account, F_DRAFT);
6056 if (procmsg_msg_exist(msginfo)) {
6057 folder_item_remove_msg(drafts, msginfo->msgnum);
6062 ComposeQueueResult compose_queue(Compose *compose, gint *msgnum, FolderItem **item, gchar **msgpath,
6063 gboolean remove_reedit_target)
6065 return compose_queue_sub (compose, msgnum, item, msgpath, FALSE, remove_reedit_target);
6068 static gboolean compose_warn_encryption(Compose *compose)
6070 const gchar *warning = privacy_get_encrypt_warning(compose->privacy_system);
6071 AlertValue val = G_ALERTALTERNATE;
6073 if (warning == NULL)
6076 val = alertpanel_full(_("Encryption warning"), warning,
6077 GTK_STOCK_CANCEL, _("C_ontinue"), NULL, ALERTFOCUS_SECOND,
6078 TRUE, NULL, ALERT_WARNING);
6079 if (val & G_ALERTDISABLE) {
6080 val &= ~G_ALERTDISABLE;
6081 if (val == G_ALERTALTERNATE)
6082 privacy_inhibit_encrypt_warning(compose->privacy_system,
6086 if (val == G_ALERTALTERNATE) {
6093 static ComposeQueueResult compose_queue_sub(Compose *compose, gint *msgnum, FolderItem **item,
6094 gchar **msgpath, gboolean perform_checks,
6095 gboolean remove_reedit_target)
6102 PrefsAccount *mailac = NULL, *newsac = NULL;
6103 gboolean err = FALSE;
6105 debug_print("queueing message...\n");
6106 cm_return_val_if_fail(compose->account != NULL, -1);
6108 if (compose_check_entries(compose, perform_checks) == FALSE) {
6109 if (compose->batch) {
6110 gtk_widget_show_all(compose->window);
6112 return COMPOSE_QUEUE_ERROR_NO_MSG;
6115 if (!compose->to_list && !compose->newsgroup_list) {
6116 g_warning("can't get recipient list.");
6117 return COMPOSE_QUEUE_ERROR_NO_MSG;
6120 if (compose->to_list) {
6121 if (compose->account->protocol != A_NNTP)
6122 mailac = compose->account;
6123 else if (cur_account && cur_account->protocol != A_NNTP)
6124 mailac = cur_account;
6125 else if (!(mailac = compose_current_mail_account())) {
6126 alertpanel_error(_("No account for sending mails available!"));
6127 return COMPOSE_QUEUE_ERROR_NO_MSG;
6131 if (compose->newsgroup_list) {
6132 if (compose->account->protocol == A_NNTP)
6133 newsac = compose->account;
6135 alertpanel_error(_("Selected account isn't NNTP: Posting is impossible."));
6136 return COMPOSE_QUEUE_ERROR_NO_MSG;
6140 /* write queue header */
6141 tmp = g_strdup_printf("%s%cqueue.%p%08x", get_tmp_dir(),
6142 G_DIR_SEPARATOR, compose, (guint) rand());
6143 debug_print("queuing to %s\n", tmp);
6144 if ((fp = claws_fopen(tmp, "w+b")) == NULL) {
6145 FILE_OP_ERROR(tmp, "claws_fopen");
6147 return COMPOSE_QUEUE_ERROR_WITH_ERRNO;
6150 if (change_file_mode_rw(fp, tmp) < 0) {
6151 FILE_OP_ERROR(tmp, "chmod");
6152 g_warning("can't change file mode");
6155 /* queueing variables */
6156 err |= (fprintf(fp, "AF:\n") < 0);
6157 err |= (fprintf(fp, "NF:0\n") < 0);
6158 err |= (fprintf(fp, "PS:10\n") < 0);
6159 err |= (fprintf(fp, "SRH:1\n") < 0);
6160 err |= (fprintf(fp, "SFN:\n") < 0);
6161 err |= (fprintf(fp, "DSR:\n") < 0);
6163 err |= (fprintf(fp, "MID:<%s>\n", compose->msgid) < 0);
6165 err |= (fprintf(fp, "MID:\n") < 0);
6166 err |= (fprintf(fp, "CFG:\n") < 0);
6167 err |= (fprintf(fp, "PT:0\n") < 0);
6168 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
6169 err |= (fprintf(fp, "RQ:\n") < 0);
6171 err |= (fprintf(fp, "SSV:%s\n", mailac->smtp_server) < 0);
6173 err |= (fprintf(fp, "SSV:\n") < 0);
6175 err |= (fprintf(fp, "NSV:%s\n", newsac->nntp_server) < 0);
6177 err |= (fprintf(fp, "NSV:\n") < 0);
6178 err |= (fprintf(fp, "SSH:\n") < 0);
6179 /* write recipient list */
6180 if (compose->to_list) {
6181 err |= (fprintf(fp, "R:<%s>", (gchar *)compose->to_list->data) < 0);
6182 for (cur = compose->to_list->next; cur != NULL;
6184 err |= (fprintf(fp, ",<%s>", (gchar *)cur->data) < 0);
6185 err |= (fprintf(fp, "\n") < 0);
6187 /* write newsgroup list */
6188 if (compose->newsgroup_list) {
6189 err |= (fprintf(fp, "NG:") < 0);
6190 err |= (fprintf(fp, "%s", (gchar *)compose->newsgroup_list->data) < 0);
6191 for (cur = compose->newsgroup_list->next; cur != NULL; cur = cur->next)
6192 err |= (fprintf(fp, ",%s", (gchar *)cur->data) < 0);
6193 err |= (fprintf(fp, "\n") < 0);
6197 err |= (fprintf(fp, "MAID:%d\n", mailac->account_id) < 0);
6199 err |= (fprintf(fp, "NAID:%d\n", newsac->account_id) < 0);
6202 if (compose->privacy_system != NULL) {
6203 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
6204 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
6205 if (compose->use_encryption) {
6206 if (!compose_warn_encryption(compose)) {
6210 return COMPOSE_QUEUE_ERROR_NO_MSG;
6212 if (mailac && mailac->encrypt_to_self) {
6213 GSList *tmp_list = g_slist_copy(compose->to_list);
6214 tmp_list = g_slist_append(tmp_list, compose->account->address);
6215 compose->encdata = privacy_get_encrypt_data(compose->privacy_system, tmp_list);
6216 g_slist_free(tmp_list);
6218 compose->encdata = privacy_get_encrypt_data(compose->privacy_system, compose->to_list);
6220 if (compose->encdata != NULL) {
6221 if (strcmp(compose->encdata, "_DONT_ENCRYPT_")) {
6222 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
6223 err |= (fprintf(fp, "X-Claws-Encrypt-Data:%s\n",
6224 compose->encdata) < 0);
6225 } /* else we finally dont want to encrypt */
6227 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
6228 /* and if encdata was null, it means there's been a problem in
6231 g_warning("failed to write queue message");
6235 return COMPOSE_QUEUE_ERROR_NO_ENCRYPTION_KEY;
6240 /* Save copy folder */
6241 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
6242 gchar *savefolderid;
6244 savefolderid = compose_get_save_to(compose);
6245 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
6246 g_free(savefolderid);
6248 /* Save copy folder */
6249 if (compose->return_receipt) {
6250 err |= (fprintf(fp, "RRCPT:1\n") < 0);
6252 /* Message-ID of message replying to */
6253 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
6254 gchar *folderid = NULL;
6256 if (compose->replyinfo->folder)
6257 folderid = folder_item_get_identifier(compose->replyinfo->folder);
6258 if (folderid == NULL)
6259 folderid = g_strdup("NULL");
6261 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
6264 /* Message-ID of message forwarding to */
6265 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
6266 gchar *folderid = NULL;
6268 if (compose->fwdinfo->folder)
6269 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
6270 if (folderid == NULL)
6271 folderid = g_strdup("NULL");
6273 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
6277 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
6278 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
6280 /* end of headers */
6281 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
6283 if (compose->redirect_filename != NULL) {
6284 if (compose_redirect_write_to_file(compose, fp) < 0) {
6288 return COMPOSE_QUEUE_ERROR_WITH_ERRNO;
6292 if ((result = compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_SEND, TRUE)) < 0) {
6300 g_warning("failed to write queue message");
6304 return COMPOSE_QUEUE_ERROR_WITH_ERRNO;
6306 if (claws_safe_fclose(fp) == EOF) {
6307 FILE_OP_ERROR(tmp, "claws_fclose");
6310 return COMPOSE_QUEUE_ERROR_WITH_ERRNO;
6313 if (item && *item) {
6316 queue = account_get_special_folder(compose->account, F_QUEUE);
6319 g_warning("can't find queue folder");
6322 return COMPOSE_QUEUE_ERROR_NO_MSG;
6324 folder_item_scan(queue);
6325 if ((num = folder_item_add_msg(queue, tmp, NULL, FALSE)) < 0) {
6326 g_warning("can't queue the message");
6329 return COMPOSE_QUEUE_ERROR_NO_MSG;
6332 if (msgpath == NULL) {
6338 if (compose->mode == COMPOSE_REEDIT && compose->targetinfo) {
6339 MsgInfo *mi = folder_item_get_msginfo(queue, num);
6341 procmsg_msginfo_change_flags(mi,
6342 compose->targetinfo->flags.perm_flags,
6343 compose->targetinfo->flags.tmp_flags & ~(MSG_COPY | MSG_MOVE | MSG_MOVE_DONE),
6346 g_slist_free(mi->tags);
6347 mi->tags = g_slist_copy(compose->targetinfo->tags);
6348 procmsg_msginfo_free(&mi);
6352 if (compose->mode == COMPOSE_REEDIT && remove_reedit_target) {
6353 compose_remove_reedit_target(compose, FALSE);
6356 if ((msgnum != NULL) && (item != NULL)) {
6361 return COMPOSE_QUEUE_SUCCESS;
6364 static int compose_add_attachments(Compose *compose, MimeInfo *parent)
6367 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
6372 GError *error = NULL;
6377 gchar *type, *subtype;
6378 GtkTreeModel *model;
6381 model = gtk_tree_view_get_model(tree_view);
6383 if (!gtk_tree_model_get_iter_first(model, &iter))
6386 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
6388 if (!is_file_exist(ainfo->file)) {
6389 gchar *msg = g_strdup_printf(_("Attachment %s doesn't exist anymore. Ignore?"), ainfo->file);
6390 AlertValue val = alertpanel_full(_("Warning"), msg,
6391 _("Cancel sending"), _("Ignore attachment"), NULL,
6392 ALERTFOCUS_FIRST, FALSE, NULL, ALERT_WARNING);
6394 if (val == G_ALERTDEFAULT) {
6400 f = g_file_new_for_path(ainfo->file);
6401 fi = g_file_query_info(f, "standard::size",
6402 G_FILE_QUERY_INFO_NONE, NULL, &error);
6403 if (error != NULL) {
6404 g_warning(error->message);
6405 g_error_free(error);
6409 size = g_file_info_get_size(fi);
6413 if (g_stat(ainfo->file, &statbuf) < 0)
6415 size = statbuf.st_size;
6418 mimepart = procmime_mimeinfo_new();
6419 mimepart->content = MIMECONTENT_FILE;
6420 mimepart->data.filename = g_strdup(ainfo->file);
6421 mimepart->tmp = FALSE; /* or we destroy our attachment */
6422 mimepart->offset = 0;
6423 mimepart->length = size;
6425 type = g_strdup(ainfo->content_type);
6427 if (!strchr(type, '/')) {
6429 type = g_strdup("application/octet-stream");
6432 subtype = strchr(type, '/') + 1;
6433 *(subtype - 1) = '\0';
6434 mimepart->type = procmime_get_media_type(type);
6435 mimepart->subtype = g_strdup(subtype);
6438 if (mimepart->type == MIMETYPE_MESSAGE &&
6439 !g_ascii_strcasecmp(mimepart->subtype, "rfc822")) {
6440 mimepart->disposition = DISPOSITIONTYPE_INLINE;
6441 } else if (mimepart->type == MIMETYPE_TEXT) {
6442 if (!ainfo->name && g_ascii_strcasecmp(mimepart->subtype, "plain")) {
6443 /* Text parts with no name come from multipart/alternative
6444 * forwards. Make sure the recipient won't look at the
6445 * original HTML part by mistake. */
6446 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6447 ainfo->name = g_strdup_printf(_("Original %s part"),
6451 g_hash_table_insert(mimepart->typeparameters,
6452 g_strdup("charset"), g_strdup(ainfo->charset));
6454 if (ainfo->name && mimepart->type != MIMETYPE_MESSAGE) {
6455 if (mimepart->type == MIMETYPE_APPLICATION &&
6456 !strcmp2(mimepart->subtype, "octet-stream"))
6457 g_hash_table_insert(mimepart->typeparameters,
6458 g_strdup("name"), g_strdup(ainfo->name));
6459 g_hash_table_insert(mimepart->dispositionparameters,
6460 g_strdup("filename"), g_strdup(ainfo->name));
6461 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6464 if (mimepart->type == MIMETYPE_MESSAGE
6465 || mimepart->type == MIMETYPE_MULTIPART)
6466 ainfo->encoding = ENC_BINARY;
6467 else if (compose->use_signing) {
6468 if (ainfo->encoding == ENC_7BIT)
6469 ainfo->encoding = ENC_QUOTED_PRINTABLE;
6470 else if (ainfo->encoding == ENC_8BIT)
6471 ainfo->encoding = ENC_BASE64;
6474 procmime_encode_content(mimepart, ainfo->encoding);
6476 g_node_append(parent->node, mimepart->node);
6477 } while (gtk_tree_model_iter_next(model, &iter));
6482 static gchar *compose_quote_list_of_addresses(gchar *str)
6484 GSList *list = NULL, *item = NULL;
6485 gchar *qname = NULL, *faddr = NULL, *result = NULL;
6487 list = address_list_append_with_comments(list, str);
6488 for (item = list; item != NULL; item = item->next) {
6489 gchar *spec = item->data;
6490 gchar *endofname = strstr(spec, " <");
6491 if (endofname != NULL) {
6494 QUOTE_IF_REQUIRED_NORMAL(qname, spec, return NULL);
6495 qqname = escape_internal_quotes(qname, '"');
6497 if (*qname != *spec || qqname != qname) { /* has been quoted, compute new */
6498 gchar *addr = g_strdup(endofname);
6499 gchar *name = (qqname != qname)? qqname: g_strdup(qname);
6500 faddr = g_strconcat(name, addr, NULL);
6503 debug_print("new auto-quoted address: '%s'\n", faddr);
6507 result = g_strdup((faddr != NULL)? faddr: spec);
6509 result = g_strconcat(result,
6511 (faddr != NULL)? faddr: spec,
6514 if (faddr != NULL) {
6519 slist_free_strings_full(list);
6524 #define IS_IN_CUSTOM_HEADER(header) \
6525 (compose->account->add_customhdr && \
6526 custom_header_find(compose->account->customhdr_list, header) != NULL)
6528 static const gchar *compose_untranslated_header_name(gchar *header_name)
6530 /* return the untranslated header name, if header_name is a known
6531 header name, in either its translated or untranslated form, with
6532 or without trailing colon. otherwise, returns header_name. */
6533 gchar *translated_header_name;
6534 gchar *translated_header_name_wcolon;
6535 const gchar *untranslated_header_name;
6536 const gchar *untranslated_header_name_wcolon;
6539 cm_return_val_if_fail(header_name != NULL, NULL);
6541 for (i = 0; HEADERS[i].header_name != NULL; i++) {
6542 untranslated_header_name = HEADERS[i].header_name;
6543 untranslated_header_name_wcolon = HEADERS[i].header_name_w_colon;
6545 translated_header_name = gettext(untranslated_header_name);
6546 translated_header_name_wcolon = gettext(untranslated_header_name_wcolon);
6548 if (!strcmp(header_name, untranslated_header_name) ||
6549 !strcmp(header_name, translated_header_name)) {
6550 return untranslated_header_name;
6552 if (!strcmp(header_name, untranslated_header_name_wcolon) ||
6553 !strcmp(header_name, translated_header_name_wcolon)) {
6554 return untranslated_header_name_wcolon;
6558 debug_print("compose_untranslated_header_name: unknown header '%s'\n", header_name);
6562 static void compose_add_headerfield_from_headerlist(Compose *compose,
6564 const gchar *fieldname,
6565 const gchar *seperator)
6567 gchar *str, *fieldname_w_colon;
6568 gboolean add_field = FALSE;
6570 ComposeHeaderEntry *headerentry;
6571 const gchar *headerentryname;
6572 const gchar *trans_fieldname;
6575 if (IS_IN_CUSTOM_HEADER(fieldname))
6578 debug_print("Adding %s-fields\n", fieldname);
6580 fieldstr = g_string_sized_new(64);
6582 fieldname_w_colon = g_strconcat(fieldname, ":", NULL);
6583 trans_fieldname = prefs_common_translated_header_name(fieldname_w_colon);
6585 for (list = compose->header_list; list; list = list->next) {
6586 headerentry = ((ComposeHeaderEntry *)list->data);
6587 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
6589 if (!g_utf8_collate(trans_fieldname, headerentryname)) {
6590 gchar * ustr = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
6592 str = compose_quote_list_of_addresses(ustr);
6594 if (str != NULL && str[0] != '\0') {
6596 g_string_append(fieldstr, seperator);
6597 g_string_append(fieldstr, str);
6606 buf = g_new0(gchar, fieldstr->len * 4 + 256);
6607 compose_convert_header
6608 (compose, buf, fieldstr->len * 4 + 256, fieldstr->str,
6609 strlen(fieldname) + 2, TRUE);
6610 g_string_append_printf(header, "%s: %s\n", fieldname, buf);
6614 g_free(fieldname_w_colon);
6615 g_string_free(fieldstr, TRUE);
6620 static gchar *compose_get_manual_headers_info(Compose *compose)
6622 GString *sh_header = g_string_new(" ");
6624 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6626 for (list = compose->header_list; list; list = list->next) {
6627 ComposeHeaderEntry *headerentry;
6630 gchar *headername_wcolon;
6631 const gchar *headername_trans;
6633 gboolean standard_header = FALSE;
6635 headerentry = ((ComposeHeaderEntry *)list->data);
6637 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6639 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6644 if (!strstr(tmp, ":")) {
6645 headername_wcolon = g_strconcat(tmp, ":", NULL);
6646 headername = g_strdup(tmp);
6648 headername_wcolon = g_strdup(tmp);
6649 headername = g_strdup(strtok(tmp, ":"));
6653 string = std_headers;
6654 while (*string != NULL) {
6655 headername_trans = prefs_common_translated_header_name(*string);
6656 if (!strcmp(headername_trans, headername_wcolon))
6657 standard_header = TRUE;
6660 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
6661 g_string_append_printf(sh_header, "%s ", headername);
6663 g_free(headername_wcolon);
6665 g_string_truncate(sh_header, strlen(sh_header->str) - 1); /* remove last space */
6666 return g_string_free(sh_header, FALSE);
6669 static gchar *compose_get_header(Compose *compose)
6671 gchar date[RFC822_DATE_BUFFSIZE];
6672 gchar buf[BUFFSIZE];
6673 const gchar *entry_str;
6677 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6679 gchar *from_name = NULL, *from_address = NULL;
6682 cm_return_val_if_fail(compose->account != NULL, NULL);
6683 cm_return_val_if_fail(compose->account->address != NULL, NULL);
6685 header = g_string_sized_new(64);
6688 if (prefs_common.hide_timezone)
6689 get_rfc822_date_hide_tz(date, sizeof(date));
6691 get_rfc822_date(date, sizeof(date));
6692 g_string_append_printf(header, "Date: %s\n", date);
6696 if (compose->account->name && *compose->account->name) {
6698 QUOTE_IF_REQUIRED(buf, compose->account->name);
6699 tmp = g_strdup_printf("%s <%s>",
6700 buf, compose->account->address);
6702 tmp = g_strdup_printf("%s",
6703 compose->account->address);
6705 if (!strcmp(gtk_entry_get_text(GTK_ENTRY(compose->from_name)), tmp)
6706 || strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) == 0) {
6708 from_name = compose->account->name ? g_strdup(compose->account->name):NULL;
6709 from_address = g_strdup(compose->account->address);
6711 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
6712 /* extract name and address */
6713 if (strstr(spec, " <") && strstr(spec, ">")) {
6714 from_address = g_strdup(strrchr(spec, '<')+1);
6715 *(strrchr(from_address, '>')) = '\0';
6716 from_name = g_strdup(spec);
6717 *(strrchr(from_name, '<')) = '\0';
6720 from_address = g_strdup(spec);
6727 if (from_name && *from_name) {
6729 compose_convert_header
6730 (compose, buf, sizeof(buf), from_name,
6731 strlen("From: "), TRUE);
6732 QUOTE_IF_REQUIRED(name, buf);
6733 qname = escape_internal_quotes(name, '"');
6735 g_string_append_printf(header, "From: %s <%s>\n",
6736 qname, from_address);
6737 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To") &&
6738 compose->return_receipt) {
6739 compose_convert_header(compose, buf, sizeof(buf), from_name,
6740 strlen("Disposition-Notification-To: "),
6742 g_string_append_printf(header, "Disposition-Notification-To: %s <%s>\n", buf, from_address);
6747 g_string_append_printf(header, "From: %s\n", from_address);
6748 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To") &&
6749 compose->return_receipt)
6750 g_string_append_printf(header, "Disposition-Notification-To: %s\n", from_address);
6754 g_free(from_address);
6757 compose_add_headerfield_from_headerlist(compose, header, "To", ", ");
6760 compose_add_headerfield_from_headerlist(compose, header, "Newsgroups", ",");
6763 compose_add_headerfield_from_headerlist(compose, header, "Cc", ", ");
6767 * If this account is a NNTP account remove Bcc header from
6768 * message body since it otherwise will be publicly shown
6770 if (compose->account->protocol != A_NNTP)
6771 compose_add_headerfield_from_headerlist(compose, header, "Bcc", ", ");
6774 str = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
6776 if (*str != '\0' && !IS_IN_CUSTOM_HEADER("Subject")) {
6779 compose_convert_header(compose, buf, sizeof(buf), str,
6780 strlen("Subject: "), FALSE);
6781 g_string_append_printf(header, "Subject: %s\n", buf);
6787 if (compose->msgid != NULL && strlen(compose->msgid) > 0) {
6788 g_string_append_printf(header, "Message-ID: <%s>\n",
6792 if (compose->remove_references == FALSE) {
6794 if (compose->inreplyto && compose->to_list)
6795 g_string_append_printf(header, "In-Reply-To: <%s>\n", compose->inreplyto);
6798 if (compose->references)
6799 g_string_append_printf(header, "References: %s\n", compose->references);
6803 compose_add_headerfield_from_headerlist(compose, header, "Followup-To", ",");
6806 compose_add_headerfield_from_headerlist(compose, header, "Reply-To", ", ");
6809 if (compose->account->organization &&
6810 strlen(compose->account->organization) &&
6811 !IS_IN_CUSTOM_HEADER("Organization")) {
6812 compose_convert_header(compose, buf, sizeof(buf),
6813 compose->account->organization,
6814 strlen("Organization: "), FALSE);
6815 g_string_append_printf(header, "Organization: %s\n", buf);
6818 /* Program version and system info */
6819 if (compose->account->gen_xmailer &&
6820 g_slist_length(compose->to_list) && !IS_IN_CUSTOM_HEADER("X-Mailer") &&
6821 !compose->newsgroup_list) {
6822 g_string_append_printf(header, "X-Mailer: %s (GTK+ %d.%d.%d; %s)\n",
6824 gtk_major_version, gtk_minor_version, gtk_micro_version,
6827 if (compose->account->gen_xmailer &&
6828 g_slist_length(compose->newsgroup_list) && !IS_IN_CUSTOM_HEADER("X-Newsreader")) {
6829 g_string_append_printf(header, "X-Newsreader: %s (GTK+ %d.%d.%d; %s)\n",
6831 gtk_major_version, gtk_minor_version, gtk_micro_version,
6835 /* custom headers */
6836 if (compose->account->add_customhdr) {
6839 for (cur = compose->account->customhdr_list; cur != NULL;
6841 CustomHeader *chdr = (CustomHeader *)cur->data;
6843 if (custom_header_is_allowed(chdr->name)
6844 && chdr->value != NULL
6845 && *(chdr->value) != '\0') {
6846 compose_convert_header
6847 (compose, buf, sizeof(buf),
6849 strlen(chdr->name) + 2, FALSE);
6850 g_string_append_printf(header, "%s: %s\n", chdr->name, buf);
6855 /* Automatic Faces and X-Faces */
6856 if (get_account_xface (buf, sizeof(buf), compose->account->account_name) == 0) {
6857 g_string_append_printf(header, "X-Face: %s\n", buf);
6859 else if (get_default_xface (buf, sizeof(buf)) == 0) {
6860 g_string_append_printf(header, "X-Face: %s\n", buf);
6862 if (get_account_face (buf, sizeof(buf), compose->account->account_name) == 0) {
6863 g_string_append_printf(header, "Face: %s\n", buf);
6865 else if (get_default_face (buf, sizeof(buf)) == 0) {
6866 g_string_append_printf(header, "Face: %s\n", buf);
6870 switch (compose->priority) {
6871 case PRIORITY_HIGHEST: g_string_append_printf(header, "Importance: high\n"
6872 "X-Priority: 1 (Highest)\n");
6874 case PRIORITY_HIGH: g_string_append_printf(header, "Importance: high\n"
6875 "X-Priority: 2 (High)\n");
6877 case PRIORITY_NORMAL: break;
6878 case PRIORITY_LOW: g_string_append_printf(header, "Importance: low\n"
6879 "X-Priority: 4 (Low)\n");
6881 case PRIORITY_LOWEST: g_string_append_printf(header, "Importance: low\n"
6882 "X-Priority: 5 (Lowest)\n");
6884 default: debug_print("compose: priority unknown : %d\n",
6888 /* get special headers */
6889 for (list = compose->header_list; list; list = list->next) {
6890 ComposeHeaderEntry *headerentry;
6893 gchar *headername_wcolon;
6894 const gchar *headername_trans;
6897 gboolean standard_header = FALSE;
6899 headerentry = ((ComposeHeaderEntry *)list->data);
6901 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6903 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6908 if (!strstr(tmp, ":")) {
6909 headername_wcolon = g_strconcat(tmp, ":", NULL);
6910 headername = g_strdup(tmp);
6912 headername_wcolon = g_strdup(tmp);
6913 headername = g_strdup(strtok(tmp, ":"));
6917 entry_str = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
6918 Xstrdup_a(headervalue, entry_str, return NULL);
6919 subst_char(headervalue, '\r', ' ');
6920 subst_char(headervalue, '\n', ' ');
6921 g_strstrip(headervalue);
6922 if (*headervalue != '\0') {
6923 string = std_headers;
6924 while (*string != NULL && !standard_header) {
6925 headername_trans = prefs_common_translated_header_name(*string);
6926 /* support mixed translated and untranslated headers */
6927 if (!strcmp(headername_trans, headername_wcolon) || !strcmp(*string, headername_wcolon))
6928 standard_header = TRUE;
6931 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername)) {
6932 /* store untranslated header name */
6933 g_string_append_printf(header, "%s %s\n",
6934 compose_untranslated_header_name(headername_wcolon), headervalue);
6938 g_free(headername_wcolon);
6942 g_string_free(header, FALSE);
6947 #undef IS_IN_CUSTOM_HEADER
6949 static void compose_convert_header(Compose *compose, gchar *dest, gint len, gchar *src,
6950 gint header_len, gboolean addr_field)
6952 gchar *tmpstr = NULL;
6953 const gchar *out_codeset = NULL;
6955 cm_return_if_fail(src != NULL);
6956 cm_return_if_fail(dest != NULL);
6958 if (len < 1) return;
6960 tmpstr = g_strdup(src);
6962 subst_char(tmpstr, '\n', ' ');
6963 subst_char(tmpstr, '\r', ' ');
6966 if (!g_utf8_validate(tmpstr, -1, NULL)) {
6967 gchar *mybuf = g_malloc(strlen(tmpstr)*2 +1);
6968 conv_localetodisp(mybuf, strlen(tmpstr)*2 +1, tmpstr);
6973 codeconv_set_strict(TRUE);
6974 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6975 conv_get_charset_str(compose->out_encoding));
6976 codeconv_set_strict(FALSE);
6978 if (!dest || *dest == '\0') {
6979 gchar *test_conv_global_out = NULL;
6980 gchar *test_conv_reply = NULL;
6982 /* automatic mode. be automatic. */
6983 codeconv_set_strict(TRUE);
6985 out_codeset = conv_get_outgoing_charset_str();
6987 debug_print("trying to convert to %s\n", out_codeset);
6988 test_conv_global_out = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6991 if (!test_conv_global_out && compose->orig_charset
6992 && strcmp(compose->orig_charset, CS_US_ASCII)) {
6993 out_codeset = compose->orig_charset;
6994 debug_print("failure; trying to convert to %s\n", out_codeset);
6995 test_conv_reply = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6998 if (!test_conv_global_out && !test_conv_reply) {
7000 out_codeset = CS_INTERNAL;
7001 debug_print("finally using %s\n", out_codeset);
7003 g_free(test_conv_global_out);
7004 g_free(test_conv_reply);
7005 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
7007 codeconv_set_strict(FALSE);
7012 static void compose_add_to_addressbook_cb(GtkMenuItem *menuitem, gpointer user_data)
7016 cm_return_if_fail(user_data != NULL);
7018 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(user_data)));
7019 g_strstrip(address);
7020 if (*address != '\0') {
7021 gchar *name = procheader_get_fromname(address);
7022 extract_address(address);
7023 #ifndef USE_ALT_ADDRBOOK
7024 addressbook_add_contact(name, address, NULL, NULL);
7026 debug_print("%s: %s\n", name, address);
7027 if (addressadd_selection(name, address, NULL, NULL)) {
7028 debug_print( "addressbook_add_contact - added\n" );
7035 static void compose_entry_popup_extend(GtkEntry *entry, GtkMenu *menu, gpointer user_data)
7037 GtkWidget *menuitem;
7040 cm_return_if_fail(menu != NULL);
7041 cm_return_if_fail(GTK_IS_MENU_SHELL(menu));
7043 menuitem = gtk_separator_menu_item_new();
7044 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
7045 gtk_widget_show(menuitem);
7047 menuitem = gtk_menu_item_new_with_mnemonic(_("Add to address _book"));
7048 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
7050 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
7051 g_strstrip(address);
7052 if (*address == '\0') {
7053 gtk_widget_set_sensitive(GTK_WIDGET(menuitem), FALSE);
7056 g_signal_connect(G_OBJECT(menuitem), "activate",
7057 G_CALLBACK(compose_add_to_addressbook_cb), entry);
7058 gtk_widget_show(menuitem);
7061 void compose_add_extra_header(gchar *header, GtkListStore *model)
7064 if (strcmp(header, "")) {
7065 COMBOBOX_ADD(model, header, COMPOSE_TO);
7069 void compose_add_extra_header_entries(GtkListStore *model)
7073 gchar buf[BUFFSIZE];
7076 if (extra_headers == NULL) {
7077 exhrc = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, "extraheaderrc", NULL);
7078 if ((exh = claws_fopen(exhrc, "rb")) == NULL) {
7079 debug_print("extra headers file not found\n");
7080 goto extra_headers_done;
7082 while (claws_fgets(buf, BUFFSIZE, exh) != NULL) {
7083 lastc = strlen(buf) - 1; /* remove trailing control chars */
7084 while (lastc >= 0 && buf[lastc] != ':')
7085 buf[lastc--] = '\0';
7086 if (lastc > 0 && buf[0] != '#' && buf[lastc] == ':') {
7087 buf[lastc] = '\0'; /* remove trailing : for comparison */
7088 if (custom_header_is_allowed(buf)) {
7090 extra_headers = g_slist_prepend(extra_headers, g_strdup(buf));
7093 g_message("disallowed extra header line: %s\n", buf);
7097 g_message("invalid extra header line: %s\n", buf);
7103 extra_headers = g_slist_prepend(extra_headers, g_strdup("")); /* end of list */
7104 extra_headers = g_slist_reverse(extra_headers);
7106 g_slist_foreach(extra_headers, (GFunc)compose_add_extra_header, (gpointer)model);
7110 static void _ldap_srv_func(gpointer data, gpointer user_data)
7112 LdapServer *server = (LdapServer *)data;
7113 gboolean *enable = (gboolean *)user_data;
7115 debug_print("%s server '%s'\n", (*enable == TRUE ? "enabling" : "disabling"), server->control->hostName);
7116 server->searchFlag = *enable;
7120 static void compose_create_header_entry(Compose *compose)
7122 gchar *headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
7129 const gchar *header = NULL;
7130 ComposeHeaderEntry *headerentry;
7131 gboolean standard_header = FALSE;
7132 GtkListStore *model;
7135 headerentry = g_new0(ComposeHeaderEntry, 1);
7137 /* Combo box model */
7138 model = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_BOOLEAN);
7139 COMBOBOX_ADD(model, prefs_common_translated_header_name("To:"),
7141 COMBOBOX_ADD(model, prefs_common_translated_header_name("Cc:"),
7143 COMBOBOX_ADD(model, prefs_common_translated_header_name("Bcc:"),
7145 COMBOBOX_ADD(model, prefs_common_translated_header_name("Newsgroups:"),
7146 COMPOSE_NEWSGROUPS);
7147 COMBOBOX_ADD(model, prefs_common_translated_header_name("Reply-To:"),
7149 COMBOBOX_ADD(model, prefs_common_translated_header_name("Followup-To:"),
7150 COMPOSE_FOLLOWUPTO);
7151 compose_add_extra_header_entries(model);
7154 combo = gtk_combo_box_new_with_model_and_entry(GTK_TREE_MODEL(model));
7155 GtkCellRenderer *cell = gtk_cell_renderer_text_new();
7156 gtk_cell_renderer_set_alignment(cell, 0.0, 0.5);
7157 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), cell, TRUE);
7158 gtk_combo_box_set_entry_text_column(GTK_COMBO_BOX(combo), 0);
7159 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
7160 g_signal_connect(G_OBJECT(gtk_bin_get_child(GTK_BIN(combo))), "grab_focus",
7161 G_CALLBACK(compose_grab_focus_cb), compose);
7162 gtk_widget_show(combo);
7164 /* Putting only the combobox child into focus chain of its parent causes
7165 * the parent to be skipped when changing focus via Tab or Shift+Tab.
7166 * This eliminates need to pres Tab twice in order to really get from the
7167 * combobox to next widget. */
7169 l = g_list_prepend(l, gtk_bin_get_child(GTK_BIN(combo)));
7170 gtk_container_set_focus_chain(GTK_CONTAINER(combo), l);
7173 gtk_table_attach(GTK_TABLE(compose->header_table), combo, 0, 1,
7174 compose->header_nextrow, compose->header_nextrow+1,
7175 GTK_SHRINK, GTK_FILL, 0, 0);
7176 if (compose->header_last && (compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN)) {
7177 const gchar *last_header_entry = gtk_entry_get_text(
7178 GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
7180 while (*string != NULL) {
7181 if (!strcmp(prefs_common_translated_header_name(*string), last_header_entry))
7182 standard_header = TRUE;
7185 if (standard_header)
7186 header = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
7188 if (!compose->header_last || !standard_header) {
7189 switch(compose->account->protocol) {
7191 header = prefs_common_translated_header_name("Newsgroups:");
7194 header = prefs_common_translated_header_name("To:");
7199 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((combo)))), header);
7201 gtk_editable_set_editable(
7202 GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((combo)))),
7203 prefs_common.type_any_header);
7205 g_signal_connect_after(G_OBJECT(gtk_bin_get_child(GTK_BIN((combo)))), "grab_focus",
7206 G_CALLBACK(compose_grab_focus_cb), compose);
7208 /* Entry field with cleanup button */
7209 button = gtk_button_new();
7210 gtk_button_set_image(GTK_BUTTON(button),
7211 gtk_image_new_from_stock(GTK_STOCK_CLEAR, GTK_ICON_SIZE_MENU));
7212 gtk_widget_show(button);
7213 CLAWS_SET_TIP(button,
7214 _("Delete entry contents"));
7215 entry = gtk_entry_new();
7216 gtk_widget_show(entry);
7217 CLAWS_SET_TIP(entry,
7218 _("Use <tab> to autocomplete from addressbook"));
7219 hbox = gtk_hbox_new (FALSE, 0);
7220 gtk_widget_show(hbox);
7221 gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, TRUE, 0);
7222 gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
7223 gtk_table_attach(GTK_TABLE(compose->header_table), hbox, 1, 2,
7224 compose->header_nextrow, compose->header_nextrow+1,
7225 GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
7227 g_signal_connect(G_OBJECT(entry), "key-press-event",
7228 G_CALLBACK(compose_headerentry_key_press_event_cb),
7230 g_signal_connect(G_OBJECT(entry), "changed",
7231 G_CALLBACK(compose_headerentry_changed_cb),
7233 g_signal_connect_after(G_OBJECT(entry), "grab_focus",
7234 G_CALLBACK(compose_grab_focus_cb), compose);
7236 g_signal_connect(G_OBJECT(button), "clicked",
7237 G_CALLBACK(compose_headerentry_button_clicked_cb),
7241 gtk_drag_dest_set(entry, GTK_DEST_DEFAULT_ALL, compose_mime_types,
7242 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7243 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7244 g_signal_connect(G_OBJECT(entry), "drag_data_received",
7245 G_CALLBACK(compose_header_drag_received_cb),
7247 g_signal_connect(G_OBJECT(entry), "drag-drop",
7248 G_CALLBACK(compose_drag_drop),
7250 g_signal_connect(G_OBJECT(entry), "populate-popup",
7251 G_CALLBACK(compose_entry_popup_extend),
7255 #ifndef PASSWORD_CRYPTO_OLD
7256 GSList *pwd_servers = addrindex_get_password_protected_ldap_servers();
7257 if (pwd_servers != NULL && master_passphrase() == NULL) {
7258 gboolean enable = FALSE;
7259 debug_print("Master passphrase not available, disabling password-protected LDAP servers for this compose window.\n");
7260 /* Temporarily disable password-protected LDAP servers,
7261 * because user did not provide a master passphrase.
7262 * We can safely enable searchFlag on all servers in this list
7263 * later, since addrindex_get_password_protected_ldap_servers()
7264 * includes servers which have it enabled initially. */
7265 g_slist_foreach(pwd_servers, _ldap_srv_func, &enable);
7266 compose->passworded_ldap_servers = pwd_servers;
7268 #endif /* PASSWORD_CRYPTO_OLD */
7269 #endif /* USE_LDAP */
7271 address_completion_register_entry(GTK_ENTRY(entry), TRUE);
7273 headerentry->compose = compose;
7274 headerentry->combo = combo;
7275 headerentry->entry = entry;
7276 headerentry->button = button;
7277 headerentry->hbox = hbox;
7278 headerentry->headernum = compose->header_nextrow;
7279 headerentry->type = PREF_NONE;
7281 compose->header_nextrow++;
7282 compose->header_last = headerentry;
7283 compose->header_list =
7284 g_slist_append(compose->header_list,
7288 static void compose_add_header_entry(Compose *compose, const gchar *header,
7289 gchar *text, ComposePrefType pref_type)
7291 ComposeHeaderEntry *last_header = compose->header_last;
7292 gchar *tmp = g_strdup(text), *email;
7293 gboolean replyto_hdr;
7295 replyto_hdr = (!strcasecmp(header,
7296 prefs_common_translated_header_name("Reply-To:")) ||
7298 prefs_common_translated_header_name("Followup-To:")) ||
7300 prefs_common_translated_header_name("In-Reply-To:")));
7302 extract_address(tmp);
7303 email = g_utf8_strdown(tmp, -1);
7305 if (replyto_hdr == FALSE &&
7306 g_hash_table_lookup(compose->email_hashtable, email) != NULL)
7308 debug_print("Ignoring duplicate address - %s %s, pref_type: %d\n",
7309 header, text, (gint) pref_type);
7315 if (!strcasecmp(header, prefs_common_translated_header_name("In-Reply-To:")))
7316 gtk_entry_set_text(GTK_ENTRY(
7317 gtk_bin_get_child(GTK_BIN(last_header->combo))), header);
7319 combobox_select_by_text(GTK_COMBO_BOX(last_header->combo), header);
7320 gtk_entry_set_text(GTK_ENTRY(last_header->entry), text);
7321 last_header->type = pref_type;
7323 if (replyto_hdr == FALSE)
7324 g_hash_table_insert(compose->email_hashtable, email,
7325 GUINT_TO_POINTER(1));
7332 static void compose_destroy_headerentry(Compose *compose,
7333 ComposeHeaderEntry *headerentry)
7335 gchar *text = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
7338 extract_address(text);
7339 email = g_utf8_strdown(text, -1);
7340 g_hash_table_remove(compose->email_hashtable, email);
7344 gtk_widget_destroy(headerentry->combo);
7345 gtk_widget_destroy(headerentry->entry);
7346 gtk_widget_destroy(headerentry->button);
7347 gtk_widget_destroy(headerentry->hbox);
7348 g_free(headerentry);
7351 static void compose_remove_header_entries(Compose *compose)
7354 for (list = compose->header_list; list; list = list->next)
7355 compose_destroy_headerentry(compose, (ComposeHeaderEntry *)list->data);
7357 compose->header_last = NULL;
7358 g_slist_free(compose->header_list);
7359 compose->header_list = NULL;
7360 compose->header_nextrow = 1;
7361 compose_create_header_entry(compose);
7364 static GtkWidget *compose_create_header(Compose *compose)
7366 GtkWidget *from_optmenu_hbox;
7367 GtkWidget *header_table_main;
7368 GtkWidget *header_scrolledwin;
7369 GtkWidget *header_table;
7371 /* parent with account selection and from header */
7372 header_table_main = gtk_table_new(2, 2, FALSE);
7373 gtk_widget_show(header_table_main);
7374 gtk_container_set_border_width(GTK_CONTAINER(header_table_main), BORDER_WIDTH);
7376 from_optmenu_hbox = compose_account_option_menu_create(compose);
7377 gtk_table_attach(GTK_TABLE(header_table_main), from_optmenu_hbox,
7378 0, 2, 0, 1, GTK_EXPAND | GTK_FILL, GTK_SHRINK, 0, 0);
7380 /* child with header labels and entries */
7381 header_scrolledwin = gtk_scrolled_window_new(NULL, NULL);
7382 gtk_widget_show(header_scrolledwin);
7383 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
7385 header_table = gtk_table_new(2, 2, FALSE);
7386 gtk_widget_show(header_table);
7387 gtk_container_set_border_width(GTK_CONTAINER(header_table), 0);
7388 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin), header_table);
7389 gtk_container_set_focus_vadjustment(GTK_CONTAINER(header_table),
7390 gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(header_scrolledwin)));
7391 gtk_viewport_set_shadow_type(GTK_VIEWPORT(gtk_bin_get_child(GTK_BIN(header_scrolledwin))), GTK_SHADOW_NONE);
7393 gtk_table_attach(GTK_TABLE(header_table_main), header_scrolledwin,
7394 0, 2, 1, 2, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 2);
7396 compose->header_table = header_table;
7397 compose->header_list = NULL;
7398 compose->header_nextrow = 0;
7400 compose_create_header_entry(compose);
7402 compose->table = NULL;
7404 return header_table_main;
7407 static gboolean popup_attach_button_pressed(GtkWidget *widget, gpointer data)
7409 Compose *compose = (Compose *)data;
7410 GdkEventButton event;
7413 event.time = gtk_get_current_event_time();
7415 return attach_button_pressed(compose->attach_clist, &event, compose);
7418 static GtkWidget *compose_create_attach(Compose *compose)
7420 GtkWidget *attach_scrwin;
7421 GtkWidget *attach_clist;
7423 GtkListStore *store;
7424 GtkCellRenderer *renderer;
7425 GtkTreeViewColumn *column;
7426 GtkTreeSelection *selection;
7428 /* attachment list */
7429 attach_scrwin = gtk_scrolled_window_new(NULL, NULL);
7430 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(attach_scrwin),
7431 GTK_POLICY_AUTOMATIC,
7432 GTK_POLICY_AUTOMATIC);
7433 gtk_widget_set_size_request(attach_scrwin, -1, 80);
7435 store = gtk_list_store_new(N_ATTACH_COLS,
7441 G_TYPE_AUTO_POINTER,
7443 attach_clist = GTK_WIDGET(gtk_tree_view_new_with_model
7444 (GTK_TREE_MODEL(store)));
7445 gtk_container_add(GTK_CONTAINER(attach_scrwin), attach_clist);
7446 g_object_unref(store);
7448 renderer = gtk_cell_renderer_text_new();
7449 column = gtk_tree_view_column_new_with_attributes
7450 (_("Mime type"), renderer, "text",
7451 COL_MIMETYPE, NULL);
7452 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7454 renderer = gtk_cell_renderer_text_new();
7455 column = gtk_tree_view_column_new_with_attributes
7456 (_("Size"), renderer, "text",
7458 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7460 renderer = gtk_cell_renderer_text_new();
7461 column = gtk_tree_view_column_new_with_attributes
7462 (_("Name"), renderer, "text",
7464 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7466 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(attach_clist),
7467 prefs_common.use_stripes_everywhere);
7468 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(attach_clist));
7469 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
7471 g_signal_connect(G_OBJECT(attach_clist), "row_activated",
7472 G_CALLBACK(attach_selected), compose);
7473 g_signal_connect(G_OBJECT(attach_clist), "button_press_event",
7474 G_CALLBACK(attach_button_pressed), compose);
7475 g_signal_connect(G_OBJECT(attach_clist), "popup-menu",
7476 G_CALLBACK(popup_attach_button_pressed), compose);
7477 g_signal_connect(G_OBJECT(attach_clist), "key_press_event",
7478 G_CALLBACK(attach_key_pressed), compose);
7481 gtk_drag_dest_set(attach_clist,
7482 GTK_DEST_DEFAULT_ALL, compose_mime_types,
7483 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7484 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7485 g_signal_connect(G_OBJECT(attach_clist), "drag_data_received",
7486 G_CALLBACK(compose_attach_drag_received_cb),
7488 g_signal_connect(G_OBJECT(attach_clist), "drag-drop",
7489 G_CALLBACK(compose_drag_drop),
7492 compose->attach_scrwin = attach_scrwin;
7493 compose->attach_clist = attach_clist;
7495 return attach_scrwin;
7498 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose);
7500 static GtkWidget *compose_create_others(Compose *compose)
7503 GtkWidget *savemsg_checkbtn;
7504 GtkWidget *savemsg_combo;
7505 GtkWidget *savemsg_select;
7508 gchar *folderidentifier;
7510 /* Table for settings */
7511 table = gtk_table_new(3, 1, FALSE);
7512 gtk_container_set_border_width(GTK_CONTAINER(table), BORDER_WIDTH);
7513 gtk_widget_show(table);
7514 gtk_table_set_row_spacings(GTK_TABLE(table), VSPACING_NARROW);
7517 /* Save Message to folder */
7518 savemsg_checkbtn = gtk_check_button_new_with_label(_("Save Message to "));
7519 gtk_widget_show(savemsg_checkbtn);
7520 gtk_table_attach(GTK_TABLE(table), savemsg_checkbtn, 0, 1, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7521 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7522 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), prefs_common.savemsg);
7525 savemsg_combo = gtk_combo_box_text_new_with_entry();
7526 compose->savemsg_checkbtn = savemsg_checkbtn;
7527 compose->savemsg_combo = savemsg_combo;
7528 gtk_widget_show(savemsg_combo);
7530 if (prefs_common.compose_save_to_history)
7531 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(savemsg_combo),
7532 prefs_common.compose_save_to_history);
7533 gtk_table_attach(GTK_TABLE(table), savemsg_combo, 1, 2, rowcount, rowcount + 1, GTK_FILL|GTK_EXPAND, GTK_SHRINK, 0, 0);
7534 gtk_widget_set_sensitive(GTK_WIDGET(savemsg_combo), prefs_common.savemsg);
7535 g_signal_connect_after(G_OBJECT(savemsg_combo), "grab_focus",
7536 G_CALLBACK(compose_grab_focus_cb), compose);
7537 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7538 if (compose->account->set_sent_folder)
7539 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), TRUE);
7541 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), FALSE);
7542 gtk_widget_set_sensitive(GTK_WIDGET(savemsg_combo), TRUE);
7543 folderidentifier = folder_item_get_identifier(account_get_special_folder
7544 (compose->account, F_OUTBOX));
7545 compose_set_save_to(compose, folderidentifier);
7546 g_free(folderidentifier);
7549 savemsg_select = gtkut_get_browse_file_btn(_("_Browse"));
7550 gtk_widget_show(savemsg_select);
7551 gtk_table_attach(GTK_TABLE(table), savemsg_select, 2, 3, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7552 g_signal_connect(G_OBJECT(savemsg_select), "clicked",
7553 G_CALLBACK(compose_savemsg_select_cb),
7559 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose)
7564 dest = foldersel_folder_sel(NULL, FOLDER_SEL_COPY, NULL, FALSE,
7565 _("Select folder to save message to"));
7568 path = folder_item_get_identifier(dest);
7570 compose_set_save_to(compose, path);
7574 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry, gboolean wrap,
7575 GdkAtom clip, GtkTextIter *insert_place);
7578 static gboolean text_clicked(GtkWidget *text, GdkEventButton *event,
7582 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
7584 if (event->button == 3) {
7586 GtkTextIter sel_start, sel_end;
7587 gboolean stuff_selected;
7589 /* move the cursor to allow GtkAspell to check the word
7590 * under the mouse */
7591 if (event->x && event->y) {
7592 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7593 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7595 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7598 GtkTextMark *mark = gtk_text_buffer_get_insert(buffer);
7599 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7602 stuff_selected = gtk_text_buffer_get_selection_bounds(
7604 &sel_start, &sel_end);
7606 gtk_text_buffer_place_cursor (buffer, &iter);
7607 /* reselect stuff */
7609 && gtk_text_iter_in_range(&iter, &sel_start, &sel_end)) {
7610 gtk_text_buffer_select_range(buffer,
7611 &sel_start, &sel_end);
7613 return FALSE; /* pass the event so that the right-click goes through */
7616 if (event->button == 2) {
7621 /* get the middle-click position to paste at the correct place */
7622 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7623 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7625 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7628 entry_paste_clipboard(compose, text,
7629 prefs_common.linewrap_pastes,
7630 GDK_SELECTION_PRIMARY, &iter);
7638 static void compose_spell_menu_changed(void *data)
7640 Compose *compose = (Compose *)data;
7642 GtkWidget *menuitem;
7643 GtkWidget *parent_item;
7644 GtkMenu *menu = GTK_MENU(gtk_menu_new());
7647 if (compose->gtkaspell == NULL)
7650 parent_item = gtk_ui_manager_get_widget(compose->ui_manager,
7651 "/Menu/Spelling/Options");
7653 /* setting the submenu removes /Spelling/Options from the factory
7654 * so we need to save it */
7656 if (parent_item == NULL) {
7657 parent_item = compose->aspell_options_menu;
7658 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), NULL);
7660 compose->aspell_options_menu = parent_item;
7662 spell_menu = gtkaspell_make_config_menu(compose->gtkaspell);
7664 spell_menu = g_slist_reverse(spell_menu);
7665 for (items = spell_menu;
7666 items; items = items->next) {
7667 menuitem = GTK_WIDGET(GTK_MENU_ITEM(items->data));
7668 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), GTK_WIDGET(menuitem));
7669 gtk_widget_show(GTK_WIDGET(menuitem));
7671 g_slist_free(spell_menu);
7673 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), GTK_WIDGET(menu));
7674 gtk_widget_show(parent_item);
7677 static void compose_dict_changed(void *data)
7679 Compose *compose = (Compose *) data;
7681 if(!compose->gtkaspell)
7683 if(compose->gtkaspell->recheck_when_changing_dict == FALSE)
7686 gtkaspell_highlight_all(compose->gtkaspell);
7687 claws_spell_entry_recheck_all(CLAWS_SPELL_ENTRY(compose->subject_entry));
7691 static gboolean compose_popup_menu(GtkWidget *widget, gpointer data)
7693 Compose *compose = (Compose *)data;
7694 GdkEventButton event;
7697 event.time = gtk_get_current_event_time();
7701 return text_clicked(compose->text, &event, compose);
7704 static gboolean compose_force_window_origin = TRUE;
7705 static Compose *compose_create(PrefsAccount *account,
7714 GtkWidget *handlebox;
7716 GtkWidget *notebook;
7718 GtkWidget *attach_hbox;
7719 GtkWidget *attach_lab1;
7720 GtkWidget *attach_lab2;
7725 GtkWidget *subject_hbox;
7726 GtkWidget *subject_frame;
7727 GtkWidget *subject_entry;
7731 GtkWidget *edit_vbox;
7732 GtkWidget *ruler_hbox;
7734 GtkWidget *scrolledwin;
7736 GtkTextBuffer *buffer;
7737 GtkClipboard *clipboard;
7739 UndoMain *undostruct;
7741 GtkWidget *popupmenu;
7742 GtkWidget *tmpl_menu;
7743 GtkActionGroup *action_group = NULL;
7746 GtkAspell * gtkaspell = NULL;
7749 static GdkGeometry geometry;
7751 cm_return_val_if_fail(account != NULL, NULL);
7753 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_DEFAULT_HEADER_BG],
7754 &default_header_bgcolor);
7755 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_DEFAULT_HEADER],
7756 &default_header_color);
7758 debug_print("Creating compose window...\n");
7759 compose = g_new0(Compose, 1);
7761 compose->batch = batch;
7762 compose->account = account;
7763 compose->folder = folder;
7765 compose->mutex = cm_mutex_new();
7766 compose->set_cursor_pos = -1;
7768 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose");
7770 gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
7771 gtk_widget_set_size_request(window, prefs_common.compose_width,
7772 prefs_common.compose_height);
7774 if (!geometry.max_width) {
7775 geometry.max_width = gdk_screen_width();
7776 geometry.max_height = gdk_screen_height();
7779 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7780 &geometry, GDK_HINT_MAX_SIZE);
7781 if (!geometry.min_width) {
7782 geometry.min_width = 600;
7783 geometry.min_height = 440;
7785 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7786 &geometry, GDK_HINT_MIN_SIZE);
7788 #ifndef GENERIC_UMPC
7789 if (compose_force_window_origin)
7790 gtk_window_move(GTK_WINDOW(window), prefs_common.compose_x,
7791 prefs_common.compose_y);
7793 g_signal_connect(G_OBJECT(window), "delete_event",
7794 G_CALLBACK(compose_delete_cb), compose);
7795 MANAGE_WINDOW_SIGNALS_CONNECT(window);
7796 gtk_widget_realize(window);
7798 gtkut_widget_set_composer_icon(window);
7800 vbox = gtk_vbox_new(FALSE, 0);
7801 gtk_container_add(GTK_CONTAINER(window), vbox);
7803 compose->ui_manager = gtk_ui_manager_new();
7804 action_group = cm_menu_create_action_group_full(compose->ui_manager,"Menu", compose_entries,
7805 G_N_ELEMENTS(compose_entries), (gpointer)compose);
7806 gtk_action_group_add_toggle_actions(action_group, compose_toggle_entries,
7807 G_N_ELEMENTS(compose_toggle_entries), (gpointer)compose);
7808 gtk_action_group_add_radio_actions(action_group, compose_radio_rm_entries,
7809 G_N_ELEMENTS(compose_radio_rm_entries), COMPOSE_REPLY, G_CALLBACK(compose_reply_change_mode_cb), (gpointer)compose);
7810 gtk_action_group_add_radio_actions(action_group, compose_radio_prio_entries,
7811 G_N_ELEMENTS(compose_radio_prio_entries), PRIORITY_NORMAL, G_CALLBACK(compose_set_priority_cb), (gpointer)compose);
7812 gtk_action_group_add_radio_actions(action_group, compose_radio_enc_entries,
7813 G_N_ELEMENTS(compose_radio_enc_entries), C_AUTO, G_CALLBACK(compose_set_encoding_cb), (gpointer)compose);
7815 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Menu", NULL, GTK_UI_MANAGER_MENUBAR)
7817 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Message", "Message", GTK_UI_MANAGER_MENU)
7818 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Edit", "Edit", GTK_UI_MANAGER_MENU)
7820 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Spelling", "Spelling", GTK_UI_MANAGER_MENU)
7822 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Options", "Options", GTK_UI_MANAGER_MENU)
7823 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Tools", "Tools", GTK_UI_MANAGER_MENU)
7824 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Help", "Help", GTK_UI_MANAGER_MENU)
7827 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Send", "Message/Send", GTK_UI_MANAGER_MENUITEM)
7828 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "SendLater", "Message/SendLater", GTK_UI_MANAGER_MENUITEM)
7829 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator1", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7830 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "AttachFile", "Message/AttachFile", GTK_UI_MANAGER_MENUITEM)
7831 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertFile", "Message/InsertFile", GTK_UI_MANAGER_MENUITEM)
7832 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertSig", "Message/InsertSig", GTK_UI_MANAGER_MENUITEM)
7833 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "ReplaceSig", "Message/ReplaceSig", GTK_UI_MANAGER_MENUITEM)
7834 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator2", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7835 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Save", "Message/Save", GTK_UI_MANAGER_MENUITEM)
7836 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator3", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7837 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Print", "Message/Print", GTK_UI_MANAGER_MENUITEM)
7838 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator4", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7839 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Close", "Message/Close", GTK_UI_MANAGER_MENUITEM)
7842 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Undo", "Edit/Undo", GTK_UI_MANAGER_MENUITEM)
7843 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Redo", "Edit/Redo", GTK_UI_MANAGER_MENUITEM)
7844 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator1", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7846 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Cut", "Edit/Cut", GTK_UI_MANAGER_MENUITEM)
7847 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Copy", "Edit/Copy", GTK_UI_MANAGER_MENUITEM)
7848 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Paste", "Edit/Paste", GTK_UI_MANAGER_MENUITEM)
7850 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SpecialPaste", "Edit/SpecialPaste", GTK_UI_MANAGER_MENU)
7851 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "AsQuotation", "Edit/SpecialPaste/AsQuotation", GTK_UI_MANAGER_MENUITEM)
7852 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Wrapped", "Edit/SpecialPaste/Wrapped", GTK_UI_MANAGER_MENUITEM)
7853 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Unwrapped", "Edit/SpecialPaste/Unwrapped", GTK_UI_MANAGER_MENUITEM)
7855 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SelectAll", "Edit/SelectAll", GTK_UI_MANAGER_MENUITEM)
7857 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Advanced", "Edit/Advanced", GTK_UI_MANAGER_MENU)
7858 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackChar", "Edit/Advanced/BackChar", GTK_UI_MANAGER_MENUITEM)
7859 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwChar", "Edit/Advanced/ForwChar", GTK_UI_MANAGER_MENUITEM)
7860 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackWord", "Edit/Advanced/BackWord", GTK_UI_MANAGER_MENUITEM)
7861 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwWord", "Edit/Advanced/ForwWord", GTK_UI_MANAGER_MENUITEM)
7862 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BegLine", "Edit/Advanced/BegLine", GTK_UI_MANAGER_MENUITEM)
7863 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "EndLine", "Edit/Advanced/EndLine", GTK_UI_MANAGER_MENUITEM)
7864 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "PrevLine", "Edit/Advanced/PrevLine", GTK_UI_MANAGER_MENUITEM)
7865 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "NextLine", "Edit/Advanced/NextLine", GTK_UI_MANAGER_MENUITEM)
7866 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackChar", "Edit/Advanced/DelBackChar", GTK_UI_MANAGER_MENUITEM)
7867 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwChar", "Edit/Advanced/DelForwChar", GTK_UI_MANAGER_MENUITEM)
7868 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackWord", "Edit/Advanced/DelBackWord", GTK_UI_MANAGER_MENUITEM)
7869 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwWord", "Edit/Advanced/DelForwWord", GTK_UI_MANAGER_MENUITEM)
7870 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelLine", "Edit/Advanced/DelLine", GTK_UI_MANAGER_MENUITEM)
7871 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelEndLine", "Edit/Advanced/DelEndLine", GTK_UI_MANAGER_MENUITEM)
7873 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator2", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7875 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Find", "Edit/Find", GTK_UI_MANAGER_MENUITEM)
7876 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapPara", "Edit/WrapPara", GTK_UI_MANAGER_MENUITEM)
7877 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapAllLines", "Edit/WrapAllLines", GTK_UI_MANAGER_MENUITEM)
7878 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoWrap", "Edit/AutoWrap", GTK_UI_MANAGER_MENUITEM)
7879 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoIndent", "Edit/AutoIndent", GTK_UI_MANAGER_MENUITEM)
7881 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator3", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7883 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "ExtEditor", "Edit/ExtEditor", GTK_UI_MANAGER_MENUITEM)
7887 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckAllSel", "Spelling/CheckAllSel", GTK_UI_MANAGER_MENUITEM)
7888 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "HighlightAll", "Spelling/HighlightAll", GTK_UI_MANAGER_MENUITEM)
7889 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckBackwards", "Spelling/CheckBackwards", GTK_UI_MANAGER_MENUITEM)
7890 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "ForwardNext", "Spelling/ForwardNext", GTK_UI_MANAGER_MENUITEM)
7891 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Separator1", "Spelling/---", GTK_UI_MANAGER_SEPARATOR)
7892 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Options", "Spelling/Options", GTK_UI_MANAGER_MENU)
7896 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "ReplyMode", "Options/ReplyMode", GTK_UI_MANAGER_MENU)
7897 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Normal", "Options/ReplyMode/Normal", GTK_UI_MANAGER_MENUITEM)
7898 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "All", "Options/ReplyMode/All", GTK_UI_MANAGER_MENUITEM)
7899 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Sender", "Options/ReplyMode/Sender", GTK_UI_MANAGER_MENUITEM)
7900 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "List", "Options/ReplyMode/List", GTK_UI_MANAGER_MENUITEM)
7902 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator1", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7903 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "PrivacySystem", "Options/PrivacySystem", GTK_UI_MANAGER_MENU)
7904 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/PrivacySystem", "PlaceHolder", "Options/PrivacySystem/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7905 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Sign", "Options/Sign", GTK_UI_MANAGER_MENUITEM)
7906 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encrypt", "Options/Encrypt", GTK_UI_MANAGER_MENUITEM)
7909 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator2", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7910 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Priority", "Options/Priority", GTK_UI_MANAGER_MENU)
7911 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Highest", "Options/Priority/Highest", GTK_UI_MANAGER_MENUITEM)
7912 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "High", "Options/Priority/High", GTK_UI_MANAGER_MENUITEM)
7913 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Normal", "Options/Priority/Normal", GTK_UI_MANAGER_MENUITEM)
7914 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Low", "Options/Priority/Low", GTK_UI_MANAGER_MENUITEM)
7915 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Lowest", "Options/Priority/Lowest", GTK_UI_MANAGER_MENUITEM)
7917 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator3", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7918 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RequestRetRcpt", "Options/RequestRetRcpt", GTK_UI_MANAGER_MENUITEM)
7919 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator4", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7920 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RemoveReferences", "Options/RemoveReferences", GTK_UI_MANAGER_MENUITEM)
7921 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator5", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7923 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encoding", "Options/Encoding", GTK_UI_MANAGER_MENU)
7925 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_AUTO, "Options/Encoding/"CS_AUTO, GTK_UI_MANAGER_MENUITEM)
7926 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator1", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7927 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_US_ASCII, "Options/Encoding/"CS_US_ASCII, GTK_UI_MANAGER_MENUITEM)
7928 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_UTF_8, "Options/Encoding/"CS_UTF_8, GTK_UI_MANAGER_MENUITEM)
7929 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator2", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7931 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Western", "Options/Encoding/Western", GTK_UI_MANAGER_MENU)
7932 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)
7933 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)
7934 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Western", CS_WINDOWS_1252, "Options/Encoding/Western/"CS_WINDOWS_1252, GTK_UI_MANAGER_MENUITEM)
7936 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_2, "Options/Encoding/"CS_ISO_8859_2, GTK_UI_MANAGER_MENUITEM)
7938 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Baltic", "Options/Encoding/Baltic", GTK_UI_MANAGER_MENU)
7939 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)
7940 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)
7942 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_7, "Options/Encoding/"CS_ISO_8859_7, GTK_UI_MANAGER_MENUITEM)
7944 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Hebrew", "Options/Encoding/Hebrew", GTK_UI_MANAGER_MENU)
7945 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)
7946 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Hebrew", CS_WINDOWS_1255, "Options/Encoding/Hebrew/"CS_WINDOWS_1255, GTK_UI_MANAGER_MENUITEM)
7948 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Arabic", "Options/Encoding/Arabic", GTK_UI_MANAGER_MENU)
7949 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)
7950 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Arabic", CS_WINDOWS_1256, "Options/Encoding/Arabic/"CS_WINDOWS_1256, GTK_UI_MANAGER_MENUITEM)
7952 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_9, "Options/Encoding/"CS_ISO_8859_9, GTK_UI_MANAGER_MENUITEM)
7954 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Cyrillic", "Options/Encoding/Cyrillic", GTK_UI_MANAGER_MENU)
7955 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)
7956 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_R, "Options/Encoding/Cyrillic/"CS_KOI8_R, GTK_UI_MANAGER_MENUITEM)
7957 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_MACCYR, "Options/Encoding/Cyrillic/"CS_MACCYR, GTK_UI_MANAGER_MENUITEM)
7958 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_U, "Options/Encoding/Cyrillic/"CS_KOI8_U, GTK_UI_MANAGER_MENUITEM)
7959 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_WINDOWS_1251, "Options/Encoding/Cyrillic/"CS_WINDOWS_1251, GTK_UI_MANAGER_MENUITEM)
7961 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Japanese", "Options/Encoding/Japanese", GTK_UI_MANAGER_MENU)
7962 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)
7963 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)
7964 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_EUC_JP, "Options/Encoding/Japanese/"CS_EUC_JP, GTK_UI_MANAGER_MENUITEM)
7965 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_SHIFT_JIS, "Options/Encoding/Japanese/"CS_SHIFT_JIS, GTK_UI_MANAGER_MENUITEM)
7967 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Chinese", "Options/Encoding/Chinese", GTK_UI_MANAGER_MENU)
7968 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB18030, "Options/Encoding/Chinese/"CS_GB18030, GTK_UI_MANAGER_MENUITEM)
7969 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB2312, "Options/Encoding/Chinese/"CS_GB2312, GTK_UI_MANAGER_MENUITEM)
7970 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GBK, "Options/Encoding/Chinese/"CS_GBK, GTK_UI_MANAGER_MENUITEM)
7971 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_BIG5, "Options/Encoding/Chinese/"CS_BIG5, GTK_UI_MANAGER_MENUITEM)
7972 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_EUC_TW, "Options/Encoding/Chinese/"CS_EUC_TW, GTK_UI_MANAGER_MENUITEM)
7974 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Korean", "Options/Encoding/Korean", GTK_UI_MANAGER_MENU)
7975 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Korean", CS_EUC_KR, "Options/Encoding/Korean/"CS_EUC_KR, GTK_UI_MANAGER_MENUITEM)
7976 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)
7978 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Thai", "Options/Encoding/Thai", GTK_UI_MANAGER_MENU)
7979 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_TIS_620, "Options/Encoding/Thai/"CS_TIS_620, GTK_UI_MANAGER_MENUITEM)
7980 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_WINDOWS_874, "Options/Encoding/Thai/"CS_WINDOWS_874, GTK_UI_MANAGER_MENUITEM)
7984 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "ShowRuler", "Tools/ShowRuler", GTK_UI_MANAGER_MENUITEM)
7985 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "AddressBook", "Tools/AddressBook", GTK_UI_MANAGER_MENUITEM)
7986 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Template", "Tools/Template", GTK_UI_MANAGER_MENU)
7987 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Template", "PlaceHolder", "Tools/Template/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7988 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Actions", "Tools/Actions", GTK_UI_MANAGER_MENU)
7989 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Actions", "PlaceHolder", "Tools/Actions/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7992 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Help", "About", "Help/About", GTK_UI_MANAGER_MENUITEM)
7994 menubar = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu");
7995 gtk_widget_show_all(menubar);
7997 gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(compose->ui_manager));
7998 gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, TRUE, 0);
8000 if (prefs_common.toolbar_detachable) {
8001 handlebox = gtk_handle_box_new();
8003 handlebox = gtk_hbox_new(FALSE, 0);
8005 gtk_box_pack_start(GTK_BOX(vbox), handlebox, FALSE, FALSE, 0);
8007 gtk_widget_realize(handlebox);
8008 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, handlebox,
8011 vbox2 = gtk_vbox_new(FALSE, 2);
8012 gtk_box_pack_start(GTK_BOX(vbox), vbox2, TRUE, TRUE, 0);
8013 gtk_container_set_border_width(GTK_CONTAINER(vbox2), 0);
8016 notebook = gtk_notebook_new();
8017 gtk_widget_show(notebook);
8019 /* header labels and entries */
8020 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
8021 compose_create_header(compose),
8022 gtk_label_new_with_mnemonic(_("Hea_der")));
8023 /* attachment list */
8024 attach_hbox = gtk_hbox_new(FALSE, 0);
8025 gtk_widget_show(attach_hbox);
8027 attach_lab1 = gtk_label_new_with_mnemonic(_("_Attachments"));
8028 gtk_widget_show(attach_lab1);
8029 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab1, TRUE, TRUE, 0);
8031 attach_lab2 = gtk_label_new("");
8032 gtk_widget_show(attach_lab2);
8033 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab2, FALSE, FALSE, 0);
8035 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
8036 compose_create_attach(compose),
8039 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
8040 compose_create_others(compose),
8041 gtk_label_new_with_mnemonic(_("Othe_rs")));
8044 subject_hbox = gtk_hbox_new(FALSE, 0);
8045 gtk_widget_show(subject_hbox);
8047 subject_frame = gtk_frame_new(NULL);
8048 gtk_frame_set_shadow_type(GTK_FRAME(subject_frame), GTK_SHADOW_NONE);
8049 gtk_box_pack_start(GTK_BOX(subject_hbox), subject_frame, TRUE, TRUE, 0);
8050 gtk_widget_show(subject_frame);
8052 subject = gtk_hbox_new(FALSE, HSPACING_NARROW);
8053 gtk_container_set_border_width(GTK_CONTAINER(subject), 0);
8054 gtk_widget_show(subject);
8056 label = gtk_label_new_with_mnemonic(_("S_ubject:"));
8057 gtk_box_pack_start(GTK_BOX(subject), label, FALSE, FALSE, 0);
8058 gtk_widget_show(label);
8061 subject_entry = claws_spell_entry_new();
8063 subject_entry = gtk_entry_new();
8065 gtk_box_pack_start(GTK_BOX(subject), subject_entry, TRUE, TRUE, 0);
8066 g_signal_connect_after(G_OBJECT(subject_entry), "grab_focus",
8067 G_CALLBACK(compose_grab_focus_cb), compose);
8068 gtk_label_set_mnemonic_widget(GTK_LABEL(label), subject_entry);
8069 gtk_widget_show(subject_entry);
8070 compose->subject_entry = subject_entry;
8071 gtk_container_add(GTK_CONTAINER(subject_frame), subject);
8073 edit_vbox = gtk_vbox_new(FALSE, 0);
8075 gtk_box_pack_start(GTK_BOX(edit_vbox), subject_hbox, FALSE, FALSE, 0);
8078 ruler_hbox = gtk_hbox_new(FALSE, 0);
8079 gtk_box_pack_start(GTK_BOX(edit_vbox), ruler_hbox, FALSE, FALSE, 0);
8081 ruler = gtk_shruler_new(GTK_ORIENTATION_HORIZONTAL);
8082 gtk_shruler_set_range(GTK_SHRULER(ruler), 0.0, 100.0, 1.0);
8083 gtk_box_pack_start(GTK_BOX(ruler_hbox), ruler, TRUE, TRUE,
8087 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
8088 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
8089 GTK_POLICY_AUTOMATIC,
8090 GTK_POLICY_AUTOMATIC);
8091 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
8093 gtk_box_pack_start(GTK_BOX(edit_vbox), scrolledwin, TRUE, TRUE, 0);
8095 text = gtk_text_view_new();
8096 if (prefs_common.show_compose_margin) {
8097 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text), 6);
8098 gtk_text_view_set_right_margin(GTK_TEXT_VIEW(text), 6);
8100 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
8101 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
8102 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), TRUE);
8103 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
8104 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
8106 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
8107 g_signal_connect_after(G_OBJECT(text), "size_allocate",
8108 G_CALLBACK(compose_edit_size_alloc),
8110 g_signal_connect(G_OBJECT(buffer), "changed",
8111 G_CALLBACK(compose_changed_cb), compose);
8112 g_signal_connect(G_OBJECT(text), "grab_focus",
8113 G_CALLBACK(compose_grab_focus_cb), compose);
8114 g_signal_connect(G_OBJECT(buffer), "insert_text",
8115 G_CALLBACK(text_inserted), compose);
8116 g_signal_connect(G_OBJECT(text), "button_press_event",
8117 G_CALLBACK(text_clicked), compose);
8118 g_signal_connect(G_OBJECT(text), "popup-menu",
8119 G_CALLBACK(compose_popup_menu), compose);
8120 g_signal_connect(G_OBJECT(subject_entry), "changed",
8121 G_CALLBACK(compose_changed_cb), compose);
8122 g_signal_connect(G_OBJECT(subject_entry), "activate",
8123 G_CALLBACK(compose_subject_entry_activated), compose);
8126 gtk_drag_dest_set(text, GTK_DEST_DEFAULT_ALL, compose_mime_types,
8127 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
8128 GDK_ACTION_COPY | GDK_ACTION_MOVE);
8129 g_signal_connect(G_OBJECT(text), "drag_data_received",
8130 G_CALLBACK(compose_insert_drag_received_cb),
8132 g_signal_connect(G_OBJECT(text), "drag-drop",
8133 G_CALLBACK(compose_drag_drop),
8135 g_signal_connect(G_OBJECT(text), "key-press-event",
8136 G_CALLBACK(completion_set_focus_to_subject),
8138 gtk_widget_show_all(vbox);
8140 /* pane between attach clist and text */
8141 paned = gtk_vpaned_new();
8142 gtk_container_add(GTK_CONTAINER(vbox2), paned);
8143 gtk_paned_pack1(GTK_PANED(paned), notebook, FALSE, FALSE);
8144 gtk_paned_pack2(GTK_PANED(paned), edit_vbox, TRUE, FALSE);
8145 gtk_paned_set_position(GTK_PANED(paned), prefs_common.compose_notebook_height);
8146 g_signal_connect(G_OBJECT(notebook), "size_allocate",
8147 G_CALLBACK(compose_notebook_size_alloc), paned);
8149 gtk_widget_show_all(paned);
8152 if (prefs_common.textfont) {
8153 PangoFontDescription *font_desc;
8155 font_desc = pango_font_description_from_string
8156 (prefs_common.textfont);
8158 gtk_widget_modify_font(text, font_desc);
8159 pango_font_description_free(font_desc);
8163 gtk_action_group_add_actions(action_group, compose_popup_entries,
8164 G_N_ELEMENTS(compose_popup_entries), (gpointer)compose);
8165 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Popup", NULL, GTK_UI_MANAGER_MENUBAR)
8166 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup", "Compose", "Compose", GTK_UI_MANAGER_MENU)
8167 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Add", "Compose/Add", GTK_UI_MANAGER_MENUITEM)
8168 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Remove", "Compose/Remove", GTK_UI_MANAGER_MENUITEM)
8169 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Separator1", "Compose/---", GTK_UI_MANAGER_SEPARATOR)
8170 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Properties", "Compose/Properties", GTK_UI_MANAGER_MENUITEM)
8172 popupmenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(gtk_ui_manager_get_widget(compose->ui_manager, "/Popup/Compose")));
8174 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
8175 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
8176 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
8178 tmpl_menu = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu/Tools/Template");
8180 undostruct = undo_init(text);
8181 undo_set_change_state_func(undostruct, &compose_undo_state_changed,
8184 address_completion_start(window);
8186 compose->window = window;
8187 compose->vbox = vbox;
8188 compose->menubar = menubar;
8189 compose->handlebox = handlebox;
8191 compose->vbox2 = vbox2;
8193 compose->paned = paned;
8195 compose->attach_label = attach_lab2;
8197 compose->notebook = notebook;
8198 compose->edit_vbox = edit_vbox;
8199 compose->ruler_hbox = ruler_hbox;
8200 compose->ruler = ruler;
8201 compose->scrolledwin = scrolledwin;
8202 compose->text = text;
8204 compose->focused_editable = NULL;
8206 compose->popupmenu = popupmenu;
8208 compose->tmpl_menu = tmpl_menu;
8210 compose->mode = mode;
8211 compose->rmode = mode;
8213 compose->targetinfo = NULL;
8214 compose->replyinfo = NULL;
8215 compose->fwdinfo = NULL;
8217 compose->email_hashtable = g_hash_table_new_full(g_str_hash,
8218 g_str_equal, (GDestroyNotify) g_free, NULL);
8220 compose->replyto = NULL;
8222 compose->bcc = NULL;
8223 compose->followup_to = NULL;
8225 compose->ml_post = NULL;
8227 compose->inreplyto = NULL;
8228 compose->references = NULL;
8229 compose->msgid = NULL;
8230 compose->boundary = NULL;
8232 compose->autowrap = prefs_common.autowrap;
8233 compose->autoindent = prefs_common.auto_indent;
8234 compose->use_signing = FALSE;
8235 compose->use_encryption = FALSE;
8236 compose->privacy_system = NULL;
8237 compose->encdata = NULL;
8239 compose->modified = FALSE;
8241 compose->return_receipt = FALSE;
8243 compose->to_list = NULL;
8244 compose->newsgroup_list = NULL;
8246 compose->undostruct = undostruct;
8248 compose->sig_str = NULL;
8250 compose->exteditor_file = NULL;
8251 compose->exteditor_pid = -1;
8252 compose->exteditor_tag = -1;
8253 compose->exteditor_socket = NULL;
8254 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN; /* inhibit auto-drafting while loading */
8256 compose->folder_update_callback_id =
8257 hooks_register_hook(FOLDER_UPDATE_HOOKLIST,
8258 compose_update_folder_hook,
8259 (gpointer) compose);
8262 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
8263 if (mode != COMPOSE_REDIRECT) {
8264 if (prefs_common.enable_aspell && prefs_common.dictionary &&
8265 strcmp(prefs_common.dictionary, "")) {
8266 gtkaspell = gtkaspell_new(prefs_common.dictionary,
8267 prefs_common.alt_dictionary,
8268 conv_get_locale_charset_str(),
8269 prefs_common.color[COL_MISSPELLED],
8270 prefs_common.check_while_typing,
8271 prefs_common.recheck_when_changing_dict,
8272 prefs_common.use_alternate,
8273 prefs_common.use_both_dicts,
8274 GTK_TEXT_VIEW(text),
8275 GTK_WINDOW(compose->window),
8276 compose_dict_changed,
8277 compose_spell_menu_changed,
8280 alertpanel_error(_("Spell checker could not "
8282 gtkaspell_checkers_strerror());
8283 gtkaspell_checkers_reset_error();
8285 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", TRUE);
8289 compose->gtkaspell = gtkaspell;
8290 compose_spell_menu_changed(compose);
8291 claws_spell_entry_set_gtkaspell(CLAWS_SPELL_ENTRY(subject_entry), gtkaspell);
8294 compose_select_account(compose, account, TRUE);
8296 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", prefs_common.autowrap);
8297 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", prefs_common.auto_indent);
8299 if (account->set_autocc && account->auto_cc && mode != COMPOSE_REEDIT)
8300 compose_entry_append(compose, account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8302 if (account->set_autobcc && account->auto_bcc && mode != COMPOSE_REEDIT)
8303 compose_entry_append(compose, account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8305 if (account->set_autoreplyto && account->auto_replyto && mode != COMPOSE_REEDIT)
8306 compose_entry_append(compose, account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8308 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", compose->mode == COMPOSE_REPLY);
8309 if (account->protocol != A_NNTP)
8310 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
8311 prefs_common_translated_header_name("To:"));
8313 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
8314 prefs_common_translated_header_name("Newsgroups:"));
8316 #ifndef USE_ALT_ADDRBOOK
8317 addressbook_set_target_compose(compose);
8319 if (mode != COMPOSE_REDIRECT)
8320 compose_set_template_menu(compose);
8322 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Template", FALSE);
8325 compose_list = g_list_append(compose_list, compose);
8327 if (!prefs_common.show_ruler)
8328 gtk_widget_hide(ruler_hbox);
8330 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Tools/ShowRuler", prefs_common.show_ruler);
8333 compose->priority = PRIORITY_NORMAL;
8334 compose_update_priority_menu_item(compose);
8336 compose_set_out_encoding(compose);
8339 compose_update_actions_menu(compose);
8341 /* Privacy Systems menu */
8342 compose_update_privacy_systems_menu(compose);
8343 compose_activate_privacy_system(compose, account, TRUE);
8345 toolbar_set_style(compose->toolbar->toolbar, compose->handlebox, prefs_common.toolbar_style);
8347 gtk_widget_realize(window);
8349 gtk_widget_show(window);
8355 static GtkWidget *compose_account_option_menu_create(Compose *compose)
8360 GtkWidget *optmenubox;
8361 GtkWidget *fromlabel;
8364 GtkWidget *from_name = NULL;
8366 gint num = 0, def_menu = 0;
8368 accounts = account_get_list();
8369 cm_return_val_if_fail(accounts != NULL, NULL);
8371 optmenubox = gtk_event_box_new();
8372 optmenu = gtkut_sc_combobox_create(optmenubox, FALSE);
8373 menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
8375 hbox = gtk_hbox_new(FALSE, 4);
8376 from_name = gtk_entry_new();
8378 g_signal_connect_after(G_OBJECT(from_name), "grab_focus",
8379 G_CALLBACK(compose_grab_focus_cb), compose);
8380 g_signal_connect_after(G_OBJECT(from_name), "activate",
8381 G_CALLBACK(from_name_activate_cb), optmenu);
8383 for (; accounts != NULL; accounts = accounts->next, num++) {
8384 PrefsAccount *ac = (PrefsAccount *)accounts->data;
8385 gchar *name, *from = NULL;
8387 if (ac == compose->account) def_menu = num;
8389 name = g_markup_printf_escaped("<i>%s</i>",
8392 if (ac == compose->account) {
8393 if (ac->name && *ac->name) {
8395 QUOTE_IF_REQUIRED_NORMAL(buf, ac->name, return NULL);
8396 from = g_strdup_printf("%s <%s>",
8398 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8400 from = g_strdup_printf("%s",
8402 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8404 if (cur_account != compose->account) {
8405 gtk_widget_modify_base(
8406 GTK_WIDGET(from_name),
8407 GTK_STATE_NORMAL, &default_header_bgcolor);
8408 gtk_widget_modify_text(
8409 GTK_WIDGET(from_name),
8410 GTK_STATE_NORMAL, &default_header_color);
8413 COMBOBOX_ADD(menu, name, ac->account_id);
8418 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), def_menu);
8420 g_signal_connect(G_OBJECT(optmenu), "changed",
8421 G_CALLBACK(account_activated),
8423 g_signal_connect(G_OBJECT(from_name), "populate-popup",
8424 G_CALLBACK(compose_entry_popup_extend),
8427 fromlabel = gtk_label_new_with_mnemonic(_("_From:"));
8428 gtk_label_set_mnemonic_widget(GTK_LABEL(fromlabel), from_name);
8430 gtk_box_pack_start(GTK_BOX(hbox), fromlabel, FALSE, FALSE, 4);
8431 gtk_box_pack_start(GTK_BOX(hbox), optmenubox, FALSE, FALSE, 0);
8432 gtk_box_pack_start(GTK_BOX(hbox), from_name, TRUE, TRUE, 0);
8434 /* Putting only the GtkEntry into focus chain of parent hbox causes
8435 * the account selector combobox next to it to be unreachable when
8436 * navigating widgets in GtkTable with up/down arrow keys.
8437 * Note: gtk_widget_set_can_focus() was not enough. */
8439 l = g_list_prepend(l, from_name);
8440 gtk_container_set_focus_chain(GTK_CONTAINER(hbox), l);
8443 CLAWS_SET_TIP(optmenubox,
8444 _("Account to use for this email"));
8445 CLAWS_SET_TIP(from_name,
8446 _("Sender address to be used"));
8448 compose->account_combo = optmenu;
8449 compose->from_name = from_name;
8454 static void compose_set_priority_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8456 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8457 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8458 Compose *compose = (Compose *) data;
8460 compose->priority = value;
8464 static void compose_reply_change_mode(Compose *compose,
8467 gboolean was_modified = compose->modified;
8469 gboolean all = FALSE, ml = FALSE, sender = FALSE, followup = FALSE;
8471 cm_return_if_fail(compose->replyinfo != NULL);
8473 if (action == COMPOSE_REPLY && prefs_common.default_reply_list)
8475 if (action == COMPOSE_REPLY && compose->rmode == COMPOSE_FOLLOWUP_AND_REPLY_TO)
8477 if (action == COMPOSE_REPLY_TO_ALL)
8479 if (action == COMPOSE_REPLY_TO_SENDER)
8481 if (action == COMPOSE_REPLY_TO_LIST)
8484 compose_remove_header_entries(compose);
8485 compose_reply_set_entry(compose, compose->replyinfo, all, ml, sender, followup);
8486 if (compose->account->set_autocc && compose->account->auto_cc)
8487 compose_entry_append(compose, compose->account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8489 if (compose->account->set_autobcc && compose->account->auto_bcc)
8490 compose_entry_append(compose, compose->account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8492 if (compose->account->set_autoreplyto && compose->account->auto_replyto)
8493 compose_entry_append(compose, compose->account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8494 compose_show_first_last_header(compose, TRUE);
8495 compose->modified = was_modified;
8496 compose_set_title(compose);
8499 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8501 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8502 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8503 Compose *compose = (Compose *) data;
8506 compose_reply_change_mode(compose, value);
8509 static void compose_update_priority_menu_item(Compose * compose)
8511 GtkWidget *menuitem = NULL;
8512 switch (compose->priority) {
8513 case PRIORITY_HIGHEST:
8514 menuitem = gtk_ui_manager_get_widget
8515 (compose->ui_manager, "/Menu/Options/Priority/Highest");
8518 menuitem = gtk_ui_manager_get_widget
8519 (compose->ui_manager, "/Menu/Options/Priority/High");
8521 case PRIORITY_NORMAL:
8522 menuitem = gtk_ui_manager_get_widget
8523 (compose->ui_manager, "/Menu/Options/Priority/Normal");
8526 menuitem = gtk_ui_manager_get_widget
8527 (compose->ui_manager, "/Menu/Options/Priority/Low");
8529 case PRIORITY_LOWEST:
8530 menuitem = gtk_ui_manager_get_widget
8531 (compose->ui_manager, "/Menu/Options/Priority/Lowest");
8534 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8537 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data)
8539 Compose *compose = (Compose *) data;
8541 gboolean can_sign = FALSE, can_encrypt = FALSE;
8543 cm_return_if_fail(GTK_IS_CHECK_MENU_ITEM(widget));
8545 if (!gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget)))
8548 systemid = g_object_get_data(G_OBJECT(widget), "privacy_system");
8549 g_free(compose->privacy_system);
8550 compose->privacy_system = NULL;
8551 g_free(compose->encdata);
8552 compose->encdata = NULL;
8553 if (systemid != NULL) {
8554 compose->privacy_system = g_strdup(systemid);
8556 can_sign = privacy_system_can_sign(systemid);
8557 can_encrypt = privacy_system_can_encrypt(systemid);
8560 debug_print("activated privacy system: %s\n", systemid != NULL ? systemid : "None");
8562 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8563 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8564 if (compose->toolbar->privacy_sign_btn != NULL) {
8565 gtk_widget_set_sensitive(
8566 GTK_WIDGET(compose->toolbar->privacy_sign_btn),
8568 gtk_toggle_tool_button_set_active(
8569 GTK_TOGGLE_TOOL_BUTTON(compose->toolbar->privacy_sign_btn),
8570 can_sign ? compose->use_signing : FALSE);
8572 if (compose->toolbar->privacy_encrypt_btn != NULL) {
8573 gtk_widget_set_sensitive(
8574 GTK_WIDGET(compose->toolbar->privacy_encrypt_btn),
8576 gtk_toggle_tool_button_set_active(
8577 GTK_TOGGLE_TOOL_BUTTON(compose->toolbar->privacy_encrypt_btn),
8578 can_encrypt ? compose->use_encryption : FALSE);
8582 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn)
8584 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8585 GtkWidget *menuitem = NULL;
8586 GList *children, *amenu;
8587 gboolean can_sign = FALSE, can_encrypt = FALSE;
8588 gboolean found = FALSE;
8590 if (compose->privacy_system != NULL) {
8592 menuitem = gtk_menu_item_get_submenu(GTK_MENU_ITEM(
8593 gtk_ui_manager_get_widget(compose->ui_manager, branch_path)));
8594 cm_return_if_fail(menuitem != NULL);
8596 children = gtk_container_get_children(GTK_CONTAINER(GTK_MENU_SHELL(menuitem)));
8599 while (amenu != NULL) {
8600 systemid = g_object_get_data(G_OBJECT(amenu->data), "privacy_system");
8601 if (systemid != NULL) {
8602 if (strcmp(systemid, compose->privacy_system) == 0 &&
8603 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8604 menuitem = GTK_WIDGET(amenu->data);
8606 can_sign = privacy_system_can_sign(systemid);
8607 can_encrypt = privacy_system_can_encrypt(systemid);
8611 } else if (strlen(compose->privacy_system) == 0 &&
8612 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8613 menuitem = GTK_WIDGET(amenu->data);
8616 can_encrypt = FALSE;
8621 amenu = amenu->next;
8623 g_list_free(children);
8624 if (menuitem != NULL)
8625 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8627 if (warn && !found && strlen(compose->privacy_system)) {
8628 alertpanel_warning(_("The privacy system '%s' cannot be loaded. You "
8629 "will not be able to sign or encrypt this message."),
8630 compose->privacy_system);
8634 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8635 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8636 if (compose->toolbar->privacy_sign_btn != NULL) {
8637 gtk_widget_set_sensitive(
8638 GTK_WIDGET(compose->toolbar->privacy_sign_btn),
8641 if (compose->toolbar->privacy_encrypt_btn != NULL) {
8642 gtk_widget_set_sensitive(
8643 GTK_WIDGET(compose->toolbar->privacy_encrypt_btn),
8648 static void compose_set_out_encoding(Compose *compose)
8650 CharSet out_encoding;
8651 const gchar *branch = NULL;
8652 out_encoding = conv_get_charset_from_str(prefs_common.outgoing_charset);
8654 switch(out_encoding) {
8655 case C_AUTO: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8656 case C_US_ASCII: branch = "Menu/Options/Encoding/" CS_US_ASCII; break;
8657 case C_UTF_8: branch = "Menu/Options/Encoding/" CS_UTF_8; break;
8658 case C_ISO_8859_2: branch = "Menu/Options/Encoding/" CS_ISO_8859_2; break;
8659 case C_ISO_8859_7: branch = "Menu/Options/Encoding/" CS_ISO_8859_7; break;
8660 case C_ISO_8859_9: branch = "Menu/Options/Encoding/" CS_ISO_8859_9; break;
8661 case C_ISO_8859_1: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_1; break;
8662 case C_ISO_8859_15: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_15; break;
8663 case C_WINDOWS_1252: branch = "Menu/Options/Encoding/Western/" CS_WINDOWS_1252; break;
8664 case C_ISO_8859_13: branch = "Menu/Options/Encoding/Baltic/" CS_ISO_8859_13; break;
8665 case C_ISO_8859_4: branch = "Menu/Options/Encoding/Baltic" CS_ISO_8859_4; break;
8666 case C_ISO_8859_8: branch = "Menu/Options/Encoding/Hebrew/" CS_ISO_8859_8; break;
8667 case C_WINDOWS_1255: branch = "Menu/Options/Encoding/Hebrew/" CS_WINDOWS_1255; break;
8668 case C_ISO_8859_6: branch = "Menu/Options/Encoding/Arabic/" CS_ISO_8859_6; break;
8669 case C_WINDOWS_1256: branch = "Menu/Options/Encoding/Arabic/" CS_WINDOWS_1256; break;
8670 case C_ISO_8859_5: branch = "Menu/Options/Encoding/Cyrillic/" CS_ISO_8859_5; break;
8671 case C_KOI8_R: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_R; break;
8672 case C_MACCYR: branch = "Menu/Options/Encoding/Cyrillic/" CS_MACCYR; break;
8673 case C_KOI8_U: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_U; break;
8674 case C_WINDOWS_1251: branch = "Menu/Options/Encoding/Cyrillic/" CS_WINDOWS_1251; break;
8675 case C_ISO_2022_JP: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP; break;
8676 case C_ISO_2022_JP_2: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP_2; break;
8677 case C_EUC_JP: branch = "Menu/Options/Encoding/Japanese/" CS_EUC_JP; break;
8678 case C_SHIFT_JIS: branch = "Menu/Options/Encoding/Japanese/" CS_SHIFT_JIS; break;
8679 case C_GB18030: branch = "Menu/Options/Encoding/Chinese/" CS_GB18030; break;
8680 case C_GB2312: branch = "Menu/Options/Encoding/Chinese/" CS_GB2312; break;
8681 case C_GBK: branch = "Menu/Options/Encoding/Chinese/" CS_GBK; break;
8682 case C_BIG5: branch = "Menu/Options/Encoding/Chinese/" CS_BIG5; break;
8683 case C_EUC_TW: branch = "Menu/Options/Encoding/Chinese/" CS_EUC_TW; break;
8684 case C_EUC_KR: branch = "Menu/Options/Encoding/Korean/" CS_EUC_KR; break;
8685 case C_ISO_2022_KR: branch = "Menu/Options/Encoding/Korean/" CS_ISO_2022_KR; break;
8686 case C_TIS_620: branch = "Menu/Options/Encoding/Thai/" CS_TIS_620; break;
8687 case C_WINDOWS_874: branch = "Menu/Options/Encoding/Thai/" CS_WINDOWS_874; break;
8688 default: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8690 cm_toggle_menu_set_active_full(compose->ui_manager, (gchar *)branch, TRUE);
8693 static void compose_set_template_menu(Compose *compose)
8695 GSList *tmpl_list, *cur;
8699 tmpl_list = template_get_config();
8701 menu = gtk_menu_new();
8703 gtk_menu_set_accel_group (GTK_MENU (menu),
8704 gtk_ui_manager_get_accel_group(compose->ui_manager));
8705 for (cur = tmpl_list; cur != NULL; cur = cur->next) {
8706 Template *tmpl = (Template *)cur->data;
8707 gchar *accel_path = NULL;
8708 item = gtk_menu_item_new_with_label(tmpl->name);
8709 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
8710 g_signal_connect(G_OBJECT(item), "activate",
8711 G_CALLBACK(compose_template_activate_cb),
8713 g_object_set_data(G_OBJECT(item), "template", tmpl);
8714 gtk_widget_show(item);
8715 accel_path = g_strconcat("<ComposeTemplates>" , "/", tmpl->name, NULL);
8716 gtk_menu_item_set_accel_path(GTK_MENU_ITEM(item), accel_path);
8720 gtk_widget_show(menu);
8721 gtk_menu_item_set_submenu(GTK_MENU_ITEM(compose->tmpl_menu), menu);
8724 void compose_update_actions_menu(Compose *compose)
8726 action_update_compose_menu(compose->ui_manager, "/Menu/Tools/Actions", compose);
8729 static void compose_update_privacy_systems_menu(Compose *compose)
8731 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8732 GSList *systems, *cur;
8734 GtkWidget *system_none;
8736 GtkWidget *privacy_menuitem = gtk_ui_manager_get_widget(compose->ui_manager, branch_path);
8737 GtkWidget *privacy_menu = gtk_menu_new();
8739 system_none = gtk_radio_menu_item_new_with_mnemonic(NULL, _("_None"));
8740 g_object_set_data_full(G_OBJECT(system_none), "privacy_system", NULL, NULL);
8742 g_signal_connect(G_OBJECT(system_none), "activate",
8743 G_CALLBACK(compose_set_privacy_system_cb), compose);
8745 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), system_none);
8746 gtk_widget_show(system_none);
8748 systems = privacy_get_system_ids();
8749 for (cur = systems; cur != NULL; cur = g_slist_next(cur)) {
8750 gchar *systemid = cur->data;
8752 group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(system_none));
8753 widget = gtk_radio_menu_item_new_with_label(group,
8754 privacy_system_get_name(systemid));
8755 g_object_set_data_full(G_OBJECT(widget), "privacy_system",
8756 g_strdup(systemid), g_free);
8757 g_signal_connect(G_OBJECT(widget), "activate",
8758 G_CALLBACK(compose_set_privacy_system_cb), compose);
8760 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), widget);
8761 gtk_widget_show(widget);
8764 g_slist_free(systems);
8765 gtk_menu_item_set_submenu(GTK_MENU_ITEM(privacy_menuitem), privacy_menu);
8766 gtk_widget_show_all(privacy_menu);
8767 gtk_widget_show_all(privacy_menuitem);
8770 void compose_reflect_prefs_all(void)
8775 for (cur = compose_list; cur != NULL; cur = cur->next) {
8776 compose = (Compose *)cur->data;
8777 compose_set_template_menu(compose);
8781 void compose_reflect_prefs_pixmap_theme(void)
8786 for (cur = compose_list; cur != NULL; cur = cur->next) {
8787 compose = (Compose *)cur->data;
8788 toolbar_update(TOOLBAR_COMPOSE, compose);
8792 static const gchar *compose_quote_char_from_context(Compose *compose)
8794 const gchar *qmark = NULL;
8796 cm_return_val_if_fail(compose != NULL, NULL);
8798 switch (compose->mode) {
8799 /* use forward-specific quote char */
8800 case COMPOSE_FORWARD:
8801 case COMPOSE_FORWARD_AS_ATTACH:
8802 case COMPOSE_FORWARD_INLINE:
8803 if (compose->folder && compose->folder->prefs &&
8804 compose->folder->prefs->forward_with_format)
8805 qmark = compose->folder->prefs->forward_quotemark;
8806 else if (compose->account->forward_with_format)
8807 qmark = compose->account->forward_quotemark;
8809 qmark = prefs_common.fw_quotemark;
8812 /* use reply-specific quote char in all other modes */
8814 if (compose->folder && compose->folder->prefs &&
8815 compose->folder->prefs->reply_with_format)
8816 qmark = compose->folder->prefs->reply_quotemark;
8817 else if (compose->account->reply_with_format)
8818 qmark = compose->account->reply_quotemark;
8820 qmark = prefs_common.quotemark;
8824 if (qmark == NULL || *qmark == '\0')
8830 static void compose_template_apply(Compose *compose, Template *tmpl,
8834 GtkTextBuffer *buffer;
8838 gchar *parsed_str = NULL;
8839 gint cursor_pos = 0;
8840 const gchar *err_msg = _("The body of the template has an error at line %d.");
8843 /* process the body */
8845 text = GTK_TEXT_VIEW(compose->text);
8846 buffer = gtk_text_view_get_buffer(text);
8849 qmark = compose_quote_char_from_context(compose);
8851 if (compose->replyinfo != NULL) {
8854 gtk_text_buffer_set_text(buffer, "", -1);
8855 mark = gtk_text_buffer_get_insert(buffer);
8856 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8858 parsed_str = compose_quote_fmt(compose, compose->replyinfo,
8859 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8861 } else if (compose->fwdinfo != NULL) {
8864 gtk_text_buffer_set_text(buffer, "", -1);
8865 mark = gtk_text_buffer_get_insert(buffer);
8866 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8868 parsed_str = compose_quote_fmt(compose, compose->fwdinfo,
8869 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8872 MsgInfo* dummyinfo = compose_msginfo_new_from_compose(compose);
8874 GtkTextIter start, end;
8877 gtk_text_buffer_get_start_iter(buffer, &start);
8878 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
8879 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
8881 /* clear the buffer now */
8883 gtk_text_buffer_set_text(buffer, "", -1);
8885 parsed_str = compose_quote_fmt(compose, dummyinfo,
8886 tmpl->value, qmark, tmp, FALSE, FALSE, err_msg);
8887 procmsg_msginfo_free( &dummyinfo );
8893 gtk_text_buffer_set_text(buffer, "", -1);
8894 mark = gtk_text_buffer_get_insert(buffer);
8895 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8898 if (replace && parsed_str && compose->account->auto_sig)
8899 compose_insert_sig(compose, FALSE);
8901 if (replace && parsed_str) {
8902 gtk_text_buffer_get_start_iter(buffer, &iter);
8903 gtk_text_buffer_place_cursor(buffer, &iter);
8907 cursor_pos = quote_fmt_get_cursor_pos();
8908 compose->set_cursor_pos = cursor_pos;
8909 if (cursor_pos == -1)
8911 gtk_text_buffer_get_start_iter(buffer, &iter);
8912 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
8913 gtk_text_buffer_place_cursor(buffer, &iter);
8916 /* process the other fields */
8918 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
8919 compose_template_apply_fields(compose, tmpl);
8920 quote_fmt_reset_vartable();
8921 quote_fmtlex_destroy();
8923 compose_changed_cb(NULL, compose);
8926 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
8927 gtkaspell_highlight_all(compose->gtkaspell);
8931 static void compose_template_apply_fields_error(const gchar *header)
8936 tr = g_strdup(C_("'%s' stands for a header name",
8937 "Template '%s' format error."));
8938 text = g_strdup_printf(tr, prefs_common_translated_header_name(header));
8939 alertpanel_error("%s", text);
8945 static void compose_template_apply_fields(Compose *compose, Template *tmpl)
8947 MsgInfo* dummyinfo = NULL;
8948 MsgInfo *msginfo = NULL;
8951 if (compose->replyinfo != NULL)
8952 msginfo = compose->replyinfo;
8953 else if (compose->fwdinfo != NULL)
8954 msginfo = compose->fwdinfo;
8956 dummyinfo = compose_msginfo_new_from_compose(compose);
8957 msginfo = dummyinfo;
8960 if (tmpl->from && *tmpl->from != '\0') {
8962 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8963 compose->gtkaspell);
8965 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8967 quote_fmt_scan_string(tmpl->from);
8970 buf = quote_fmt_get_buffer();
8972 compose_template_apply_fields_error("From");
8974 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
8977 quote_fmt_reset_vartable();
8978 quote_fmtlex_destroy();
8981 if (tmpl->to && *tmpl->to != '\0') {
8983 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8984 compose->gtkaspell);
8986 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8988 quote_fmt_scan_string(tmpl->to);
8991 buf = quote_fmt_get_buffer();
8993 compose_template_apply_fields_error("To");
8995 compose_entry_append(compose, buf, COMPOSE_TO, PREF_TEMPLATE);
8998 quote_fmt_reset_vartable();
8999 quote_fmtlex_destroy();
9002 if (tmpl->cc && *tmpl->cc != '\0') {
9004 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
9005 compose->gtkaspell);
9007 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
9009 quote_fmt_scan_string(tmpl->cc);
9012 buf = quote_fmt_get_buffer();
9014 compose_template_apply_fields_error("Cc");
9016 compose_entry_append(compose, buf, COMPOSE_CC, PREF_TEMPLATE);
9019 quote_fmt_reset_vartable();
9020 quote_fmtlex_destroy();
9023 if (tmpl->bcc && *tmpl->bcc != '\0') {
9025 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
9026 compose->gtkaspell);
9028 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
9030 quote_fmt_scan_string(tmpl->bcc);
9033 buf = quote_fmt_get_buffer();
9035 compose_template_apply_fields_error("Bcc");
9037 compose_entry_append(compose, buf, COMPOSE_BCC, PREF_TEMPLATE);
9040 quote_fmt_reset_vartable();
9041 quote_fmtlex_destroy();
9044 if (tmpl->replyto && *tmpl->replyto != '\0') {
9046 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
9047 compose->gtkaspell);
9049 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
9051 quote_fmt_scan_string(tmpl->replyto);
9054 buf = quote_fmt_get_buffer();
9056 compose_template_apply_fields_error("Reply-To");
9058 compose_entry_append(compose, buf, COMPOSE_REPLYTO, PREF_TEMPLATE);
9061 quote_fmt_reset_vartable();
9062 quote_fmtlex_destroy();
9065 /* process the subject */
9066 if (tmpl->subject && *tmpl->subject != '\0') {
9068 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
9069 compose->gtkaspell);
9071 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
9073 quote_fmt_scan_string(tmpl->subject);
9076 buf = quote_fmt_get_buffer();
9078 compose_template_apply_fields_error("Subject");
9080 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
9083 quote_fmt_reset_vartable();
9084 quote_fmtlex_destroy();
9087 procmsg_msginfo_free( &dummyinfo );
9090 static void compose_destroy(Compose *compose)
9092 GtkAllocation allocation;
9093 GtkTextBuffer *buffer;
9094 GtkClipboard *clipboard;
9096 compose_list = g_list_remove(compose_list, compose);
9099 gboolean enable = TRUE;
9100 g_slist_foreach(compose->passworded_ldap_servers,
9101 _ldap_srv_func, &enable);
9102 g_slist_free(compose->passworded_ldap_servers);
9105 if (compose->updating) {
9106 debug_print("danger, not destroying anything now\n");
9107 compose->deferred_destroy = TRUE;
9111 /* NOTE: address_completion_end() does nothing with the window
9112 * however this may change. */
9113 address_completion_end(compose->window);
9115 slist_free_strings_full(compose->to_list);
9116 slist_free_strings_full(compose->newsgroup_list);
9117 slist_free_strings_full(compose->header_list);
9119 slist_free_strings_full(extra_headers);
9120 extra_headers = NULL;
9122 compose->header_list = compose->newsgroup_list = compose->to_list = NULL;
9124 g_hash_table_destroy(compose->email_hashtable);
9126 hooks_unregister_hook(FOLDER_UPDATE_HOOKLIST,
9127 compose->folder_update_callback_id);
9129 procmsg_msginfo_free(&(compose->targetinfo));
9130 procmsg_msginfo_free(&(compose->replyinfo));
9131 procmsg_msginfo_free(&(compose->fwdinfo));
9133 g_free(compose->replyto);
9134 g_free(compose->cc);
9135 g_free(compose->bcc);
9136 g_free(compose->newsgroups);
9137 g_free(compose->followup_to);
9139 g_free(compose->ml_post);
9141 g_free(compose->inreplyto);
9142 g_free(compose->references);
9143 g_free(compose->msgid);
9144 g_free(compose->boundary);
9146 g_free(compose->redirect_filename);
9147 if (compose->undostruct)
9148 undo_destroy(compose->undostruct);
9150 g_free(compose->sig_str);
9152 g_free(compose->exteditor_file);
9154 g_free(compose->orig_charset);
9156 g_free(compose->privacy_system);
9157 g_free(compose->encdata);
9159 #ifndef USE_ALT_ADDRBOOK
9160 if (addressbook_get_target_compose() == compose)
9161 addressbook_set_target_compose(NULL);
9164 if (compose->gtkaspell) {
9165 gtkaspell_delete(compose->gtkaspell);
9166 compose->gtkaspell = NULL;
9170 if (!compose->batch) {
9171 gtk_widget_get_allocation(compose->window, &allocation);
9172 prefs_common.compose_width = allocation.width;
9173 prefs_common.compose_height = allocation.height;
9176 if (!gtk_widget_get_parent(compose->paned))
9177 gtk_widget_destroy(compose->paned);
9178 gtk_widget_destroy(compose->popupmenu);
9180 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
9181 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
9182 gtk_text_buffer_remove_selection_clipboard(buffer, clipboard);
9184 message_search_close(compose);
9185 gtk_widget_destroy(compose->window);
9186 toolbar_destroy(compose->toolbar);
9187 g_free(compose->toolbar);
9188 cm_mutex_free(compose->mutex);
9192 static void compose_attach_info_free(AttachInfo *ainfo)
9194 g_free(ainfo->file);
9195 g_free(ainfo->content_type);
9196 g_free(ainfo->name);
9197 g_free(ainfo->charset);
9201 static void compose_attach_update_label(Compose *compose)
9206 GtkTreeModel *model;
9210 if (compose == NULL)
9213 model = gtk_tree_view_get_model(GTK_TREE_VIEW(compose->attach_clist));
9214 if (!gtk_tree_model_get_iter_first(model, &iter)) {
9215 gtk_label_set_text(GTK_LABEL(compose->attach_label), "");
9219 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
9220 total_size = ainfo->size;
9221 while(gtk_tree_model_iter_next(model, &iter)) {
9222 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
9223 total_size += ainfo->size;
9226 text = g_strdup_printf(" (%d/%s)", i, to_human_readable(total_size));
9227 gtk_label_set_text(GTK_LABEL(compose->attach_label), text);
9231 static void compose_attach_remove_selected(GtkAction *action, gpointer data)
9233 Compose *compose = (Compose *)data;
9234 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
9235 GtkTreeSelection *selection;
9237 GtkTreeModel *model;
9239 selection = gtk_tree_view_get_selection(tree_view);
9240 sel = gtk_tree_selection_get_selected_rows(selection, &model);
9241 cm_return_if_fail(sel);
9243 for (cur = sel; cur != NULL; cur = cur->next) {
9244 GtkTreePath *path = cur->data;
9245 GtkTreeRowReference *ref = gtk_tree_row_reference_new
9248 gtk_tree_path_free(path);
9251 for (cur = sel; cur != NULL; cur = cur->next) {
9252 GtkTreeRowReference *ref = cur->data;
9253 GtkTreePath *path = gtk_tree_row_reference_get_path(ref);
9256 if (gtk_tree_model_get_iter(model, &iter, path))
9257 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
9259 gtk_tree_path_free(path);
9260 gtk_tree_row_reference_free(ref);
9264 compose_attach_update_label(compose);
9267 static struct _AttachProperty
9270 GtkWidget *mimetype_entry;
9271 GtkWidget *encoding_optmenu;
9272 GtkWidget *path_entry;
9273 GtkWidget *filename_entry;
9275 GtkWidget *cancel_btn;
9278 static void gtk_tree_path_free_(gpointer ptr, gpointer data)
9280 gtk_tree_path_free((GtkTreePath *)ptr);
9283 static void compose_attach_property(GtkAction *action, gpointer data)
9285 Compose *compose = (Compose *)data;
9286 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
9288 GtkComboBox *optmenu;
9289 GtkTreeSelection *selection;
9291 GtkTreeModel *model;
9294 static gboolean cancelled;
9296 /* only if one selected */
9297 selection = gtk_tree_view_get_selection(tree_view);
9298 if (gtk_tree_selection_count_selected_rows(selection) != 1)
9301 sel = gtk_tree_selection_get_selected_rows(selection, &model);
9302 cm_return_if_fail(sel);
9304 path = (GtkTreePath *) sel->data;
9305 gtk_tree_model_get_iter(model, &iter, path);
9306 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
9309 g_list_foreach(sel, gtk_tree_path_free_, NULL);
9315 if (!attach_prop.window)
9316 compose_attach_property_create(&cancelled);
9317 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), TRUE);
9318 gtk_widget_grab_focus(attach_prop.ok_btn);
9319 gtk_widget_show(attach_prop.window);
9320 gtk_window_set_transient_for(GTK_WINDOW(attach_prop.window),
9321 GTK_WINDOW(compose->window));
9323 optmenu = GTK_COMBO_BOX(attach_prop.encoding_optmenu);
9324 if (ainfo->encoding == ENC_UNKNOWN)
9325 combobox_select_by_data(optmenu, ENC_BASE64);
9327 combobox_select_by_data(optmenu, ainfo->encoding);
9329 gtk_entry_set_text(GTK_ENTRY(attach_prop.mimetype_entry),
9330 ainfo->content_type ? ainfo->content_type : "");
9331 gtk_entry_set_text(GTK_ENTRY(attach_prop.path_entry),
9332 ainfo->file ? ainfo->file : "");
9333 gtk_entry_set_text(GTK_ENTRY(attach_prop.filename_entry),
9334 ainfo->name ? ainfo->name : "");
9337 const gchar *entry_text;
9339 gchar *cnttype = NULL;
9346 gtk_widget_hide(attach_prop.window);
9347 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), FALSE);
9352 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.mimetype_entry));
9353 if (*entry_text != '\0') {
9356 text = g_strstrip(g_strdup(entry_text));
9357 if ((p = strchr(text, '/')) && !strchr(p + 1, '/')) {
9358 cnttype = g_strdup(text);
9361 alertpanel_error(_("Invalid MIME type."));
9367 ainfo->encoding = combobox_get_active_data(optmenu);
9369 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.path_entry));
9370 if (*entry_text != '\0') {
9371 if (is_file_exist(entry_text) &&
9372 (size = get_file_size(entry_text)) > 0)
9373 file = g_strdup(entry_text);
9376 (_("File doesn't exist or is empty."));
9382 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.filename_entry));
9383 if (*entry_text != '\0') {
9384 g_free(ainfo->name);
9385 ainfo->name = g_strdup(entry_text);
9389 g_free(ainfo->content_type);
9390 ainfo->content_type = cnttype;
9393 g_free(ainfo->file);
9397 ainfo->size = (goffset)size;
9399 /* update tree store */
9400 text = to_human_readable(ainfo->size);
9401 gtk_tree_model_get_iter(model, &iter, path);
9402 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
9403 COL_MIMETYPE, ainfo->content_type,
9405 COL_NAME, ainfo->name,
9406 COL_CHARSET, ainfo->charset,
9412 gtk_tree_path_free(path);
9415 #define SET_LABEL_AND_ENTRY(str, entry, top) \
9417 label = gtk_label_new(str); \
9418 gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), \
9419 GTK_FILL, 0, 0, 0); \
9420 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); \
9422 entry = gtk_entry_new(); \
9423 gtk_table_attach(GTK_TABLE(table), entry, 1, 2, top, (top + 1), \
9424 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); \
9427 static void compose_attach_property_create(gboolean *cancelled)
9433 GtkWidget *mimetype_entry;
9436 GtkListStore *optmenu_menu;
9437 GtkWidget *path_entry;
9438 GtkWidget *filename_entry;
9441 GtkWidget *cancel_btn;
9442 GList *mime_type_list, *strlist;
9445 debug_print("Creating attach_property window...\n");
9447 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose_attach_property");
9448 gtk_widget_set_size_request(window, 480, -1);
9449 gtk_container_set_border_width(GTK_CONTAINER(window), 8);
9450 gtk_window_set_title(GTK_WINDOW(window), _("Properties"));
9451 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
9452 g_signal_connect(G_OBJECT(window), "delete_event",
9453 G_CALLBACK(attach_property_delete_event),
9455 g_signal_connect(G_OBJECT(window), "key_press_event",
9456 G_CALLBACK(attach_property_key_pressed),
9459 vbox = gtk_vbox_new(FALSE, 8);
9460 gtk_container_add(GTK_CONTAINER(window), vbox);
9462 table = gtk_table_new(4, 2, FALSE);
9463 gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
9464 gtk_table_set_row_spacings(GTK_TABLE(table), 8);
9465 gtk_table_set_col_spacings(GTK_TABLE(table), 8);
9467 label = gtk_label_new(_("MIME type"));
9468 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, (0 + 1),
9470 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
9471 mimetype_entry = gtk_combo_box_text_new_with_entry();
9472 gtk_table_attach(GTK_TABLE(table), mimetype_entry, 1, 2, 0, (0 + 1),
9473 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
9475 /* stuff with list */
9476 mime_type_list = procmime_get_mime_type_list();
9478 for (; mime_type_list != NULL; mime_type_list = mime_type_list->next) {
9479 MimeType *type = (MimeType *) mime_type_list->data;
9482 tmp = g_strdup_printf("%s/%s", type->type, type->sub_type);
9484 if (g_list_find_custom(strlist, tmp, (GCompareFunc)strcmp2))
9487 strlist = g_list_insert_sorted(strlist, (gpointer)tmp,
9488 (GCompareFunc)strcmp2);
9491 for (mime_type_list = strlist; mime_type_list != NULL;
9492 mime_type_list = mime_type_list->next) {
9493 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(mimetype_entry), mime_type_list->data);
9494 g_free(mime_type_list->data);
9496 g_list_free(strlist);
9497 gtk_combo_box_set_active(GTK_COMBO_BOX(mimetype_entry), 0);
9498 mimetype_entry = gtk_bin_get_child(GTK_BIN((mimetype_entry)));
9500 label = gtk_label_new(_("Encoding"));
9501 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2,
9503 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
9505 hbox = gtk_hbox_new(FALSE, 0);
9506 gtk_table_attach(GTK_TABLE(table), hbox, 1, 2, 1, 2,
9507 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
9509 optmenu = gtkut_sc_combobox_create(NULL, TRUE);
9510 optmenu_menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
9512 COMBOBOX_ADD(optmenu_menu, "7bit", ENC_7BIT);
9513 COMBOBOX_ADD(optmenu_menu, "8bit", ENC_8BIT);
9514 COMBOBOX_ADD(optmenu_menu, "quoted-printable", ENC_QUOTED_PRINTABLE);
9515 COMBOBOX_ADD(optmenu_menu, "base64", ENC_BASE64);
9516 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), 0);
9518 gtk_box_pack_start(GTK_BOX(hbox), optmenu, TRUE, TRUE, 0);
9520 SET_LABEL_AND_ENTRY(_("Path"), path_entry, 2);
9521 SET_LABEL_AND_ENTRY(_("File name"), filename_entry, 3);
9523 gtkut_stock_button_set_create(&hbbox, &cancel_btn, GTK_STOCK_CANCEL,
9524 &ok_btn, GTK_STOCK_OK,
9526 gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
9527 gtk_widget_grab_default(ok_btn);
9529 g_signal_connect(G_OBJECT(ok_btn), "clicked",
9530 G_CALLBACK(attach_property_ok),
9532 g_signal_connect(G_OBJECT(cancel_btn), "clicked",
9533 G_CALLBACK(attach_property_cancel),
9536 gtk_widget_show_all(vbox);
9538 attach_prop.window = window;
9539 attach_prop.mimetype_entry = mimetype_entry;
9540 attach_prop.encoding_optmenu = optmenu;
9541 attach_prop.path_entry = path_entry;
9542 attach_prop.filename_entry = filename_entry;
9543 attach_prop.ok_btn = ok_btn;
9544 attach_prop.cancel_btn = cancel_btn;
9547 #undef SET_LABEL_AND_ENTRY
9549 static void attach_property_ok(GtkWidget *widget, gboolean *cancelled)
9555 static void attach_property_cancel(GtkWidget *widget, gboolean *cancelled)
9561 static gint attach_property_delete_event(GtkWidget *widget, GdkEventAny *event,
9562 gboolean *cancelled)
9570 static gboolean attach_property_key_pressed(GtkWidget *widget,
9572 gboolean *cancelled)
9574 if (event && event->keyval == GDK_KEY_Escape) {
9578 if (event && event->keyval == GDK_KEY_Return) {
9586 static void compose_exec_ext_editor(Compose *compose)
9591 GdkNativeWindow socket_wid = 0;
9595 tmp = g_strdup_printf("%s%ctmpmsg.%p", get_tmp_dir(),
9596 G_DIR_SEPARATOR, compose);
9598 if (compose_get_ext_editor_uses_socket()) {
9599 /* Only allow one socket */
9600 if (compose->exteditor_socket != NULL) {
9601 if (gtk_widget_is_focus(compose->exteditor_socket)) {
9602 /* Move the focus off of the socket */
9603 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9608 /* Create the receiving GtkSocket */
9609 socket = gtk_socket_new ();
9610 g_signal_connect (GTK_OBJECT(socket), "plug-removed",
9611 G_CALLBACK(compose_ext_editor_plug_removed_cb),
9613 gtk_box_pack_start(GTK_BOX(compose->edit_vbox), socket, TRUE, TRUE, 0);
9614 gtk_widget_set_size_request(socket, prefs_common.compose_width, -1);
9615 /* Realize the socket so that we can use its ID */
9616 gtk_widget_realize(socket);
9617 socket_wid = gtk_socket_get_id(GTK_SOCKET (socket));
9618 compose->exteditor_socket = socket;
9621 if (pipe(pipe_fds) < 0) {
9627 if ((pid = fork()) < 0) {
9634 /* close the write side of the pipe */
9637 compose->exteditor_file = g_strdup(tmp);
9638 compose->exteditor_pid = pid;
9640 compose_set_ext_editor_sensitive(compose, FALSE);
9643 compose->exteditor_ch = g_io_channel_unix_new(pipe_fds[0]);
9645 compose->exteditor_ch = g_io_channel_win32_new_fd(pipe_fds[0]);
9647 compose->exteditor_tag = g_io_add_watch(compose->exteditor_ch,
9651 } else { /* process-monitoring process */
9657 /* close the read side of the pipe */
9660 if (compose_write_body_to_file(compose, tmp) < 0) {
9661 fd_write_all(pipe_fds[1], "2\n", 2);
9665 pid_ed = compose_exec_ext_editor_real(tmp, socket_wid);
9667 fd_write_all(pipe_fds[1], "1\n", 2);
9671 /* wait until editor is terminated */
9672 waitpid(pid_ed, NULL, 0);
9674 fd_write_all(pipe_fds[1], "0\n", 2);
9681 #endif /* G_OS_UNIX */
9684 static gboolean compose_can_autosave(Compose *compose)
9686 if (compose->privacy_system && compose->use_encryption)
9687 return prefs_common.autosave && prefs_common.autosave_encrypted;
9689 return prefs_common.autosave;
9693 static gboolean compose_get_ext_editor_cmd_valid()
9695 gboolean has_s = FALSE;
9696 gboolean has_w = FALSE;
9697 const gchar *p = prefs_common_get_ext_editor_cmd();
9700 while ((p = strchr(p, '%'))) {
9706 } else if (*p == 'w') {
9717 static gint compose_exec_ext_editor_real(const gchar *file, GdkNativeWindow socket_wid)
9724 cm_return_val_if_fail(file != NULL, -1);
9726 if ((pid = fork()) < 0) {
9731 if (pid != 0) return pid;
9733 /* grandchild process */
9735 if (setpgid(0, getppid()))
9738 if (compose_get_ext_editor_cmd_valid()) {
9739 if (compose_get_ext_editor_uses_socket()) {
9740 p = g_strdup(prefs_common_get_ext_editor_cmd());
9741 s = strstr(p, "%w");
9743 if (strstr(p, "%s") < s)
9744 buf = g_strdup_printf(p, file, socket_wid);
9746 buf = g_strdup_printf(p, socket_wid, file);
9749 buf = g_strdup_printf(prefs_common_get_ext_editor_cmd(), file);
9752 if (prefs_common_get_ext_editor_cmd())
9753 g_warning("External editor command-line is invalid: '%s'",
9754 prefs_common_get_ext_editor_cmd());
9755 buf = g_strdup_printf(DEFAULT_EDITOR_CMD, file);
9758 cmdline = strsplit_with_quote(buf, " ", 0);
9760 execvp(cmdline[0], cmdline);
9763 g_strfreev(cmdline);
9768 static gboolean compose_ext_editor_kill(Compose *compose)
9770 pid_t pgid = compose->exteditor_pid * -1;
9773 ret = kill(pgid, 0);
9775 if (ret == 0 || (ret == -1 && EPERM == errno)) {
9779 msg = g_strdup_printf
9780 (_("The external editor is still working.\n"
9781 "Force terminating the process?\n"
9782 "process group id: %d"), -pgid);
9783 val = alertpanel_full(_("Notice"), msg, GTK_STOCK_NO, GTK_STOCK_YES,
9784 NULL, ALERTFOCUS_FIRST, FALSE, NULL,
9789 if (val == G_ALERTALTERNATE) {
9790 g_source_remove(compose->exteditor_tag);
9791 g_io_channel_shutdown(compose->exteditor_ch,
9793 g_io_channel_unref(compose->exteditor_ch);
9795 if (kill(pgid, SIGTERM) < 0) perror("kill");
9796 waitpid(compose->exteditor_pid, NULL, 0);
9798 g_warning("Terminated process group id: %d. "
9799 "Temporary file: %s", -pgid, compose->exteditor_file);
9801 compose_set_ext_editor_sensitive(compose, TRUE);
9803 g_free(compose->exteditor_file);
9804 compose->exteditor_file = NULL;
9805 compose->exteditor_pid = -1;
9806 compose->exteditor_ch = NULL;
9807 compose->exteditor_tag = -1;
9815 static gboolean compose_input_cb(GIOChannel *source, GIOCondition condition,
9819 Compose *compose = (Compose *)data;
9822 debug_print("Compose: input from monitoring process\n");
9824 if (g_io_channel_read_chars(source, buf, sizeof(buf), &bytes_read, NULL) != G_IO_STATUS_NORMAL) {
9829 g_io_channel_shutdown(source, FALSE, NULL);
9830 g_io_channel_unref(source);
9832 waitpid(compose->exteditor_pid, NULL, 0);
9834 if (buf[0] == '0') { /* success */
9835 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9836 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
9837 GtkTextIter start, end;
9840 gtk_text_buffer_set_text(buffer, "", -1);
9841 compose_insert_file(compose, compose->exteditor_file);
9842 compose_changed_cb(NULL, compose);
9844 /* Check if we should save the draft or not */
9845 if (compose_can_autosave(compose))
9846 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
9848 if (claws_unlink(compose->exteditor_file) < 0)
9849 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9851 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
9852 gtk_text_buffer_get_start_iter(buffer, &start);
9853 gtk_text_buffer_get_end_iter(buffer, &end);
9854 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
9855 if (chars && strlen(chars) > 0)
9856 compose->modified = TRUE;
9858 } else if (buf[0] == '1') { /* failed */
9859 g_warning("Couldn't exec external editor");
9860 if (claws_unlink(compose->exteditor_file) < 0)
9861 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9862 } else if (buf[0] == '2') {
9863 g_warning("Couldn't write to file");
9864 } else if (buf[0] == '3') {
9865 g_warning("Pipe read failed");
9868 compose_set_ext_editor_sensitive(compose, TRUE);
9870 g_free(compose->exteditor_file);
9871 compose->exteditor_file = NULL;
9872 compose->exteditor_pid = -1;
9873 compose->exteditor_ch = NULL;
9874 compose->exteditor_tag = -1;
9875 if (compose->exteditor_socket) {
9876 gtk_widget_destroy(compose->exteditor_socket);
9877 compose->exteditor_socket = NULL;
9884 static char *ext_editor_menu_entries[] = {
9885 "Menu/Message/Send",
9886 "Menu/Message/SendLater",
9887 "Menu/Message/InsertFile",
9888 "Menu/Message/InsertSig",
9889 "Menu/Message/ReplaceSig",
9890 "Menu/Message/Save",
9891 "Menu/Message/Print",
9896 "Menu/Tools/ShowRuler",
9897 "Menu/Tools/Actions",
9902 static void compose_set_ext_editor_sensitive(Compose *compose,
9907 for (i = 0; ext_editor_menu_entries[i]; ++i) {
9908 cm_menu_set_sensitive_full(compose->ui_manager,
9909 ext_editor_menu_entries[i], sensitive);
9912 if (compose_get_ext_editor_uses_socket()) {
9914 if (compose->exteditor_socket)
9915 gtk_widget_hide(compose->exteditor_socket);
9916 gtk_widget_show(compose->scrolledwin);
9917 if (prefs_common.show_ruler)
9918 gtk_widget_show(compose->ruler_hbox);
9919 /* Fix the focus, as it doesn't go anywhere when the
9920 * socket is hidden or destroyed */
9921 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9923 g_assert (compose->exteditor_socket != NULL);
9924 /* Fix the focus, as it doesn't go anywhere when the
9925 * edit box is hidden */
9926 if (gtk_widget_is_focus(compose->text))
9927 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9928 gtk_widget_hide(compose->scrolledwin);
9929 gtk_widget_hide(compose->ruler_hbox);
9930 gtk_widget_show(compose->exteditor_socket);
9933 gtk_widget_set_sensitive(compose->text, sensitive);
9935 if (compose->toolbar->send_btn)
9936 gtk_widget_set_sensitive(compose->toolbar->send_btn, sensitive);
9937 if (compose->toolbar->sendl_btn)
9938 gtk_widget_set_sensitive(compose->toolbar->sendl_btn, sensitive);
9939 if (compose->toolbar->draft_btn)
9940 gtk_widget_set_sensitive(compose->toolbar->draft_btn, sensitive);
9941 if (compose->toolbar->insert_btn)
9942 gtk_widget_set_sensitive(compose->toolbar->insert_btn, sensitive);
9943 if (compose->toolbar->sig_btn)
9944 gtk_widget_set_sensitive(compose->toolbar->sig_btn, sensitive);
9945 if (compose->toolbar->exteditor_btn)
9946 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, sensitive);
9947 if (compose->toolbar->linewrap_current_btn)
9948 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, sensitive);
9949 if (compose->toolbar->linewrap_all_btn)
9950 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, sensitive);
9953 static gboolean compose_get_ext_editor_uses_socket()
9955 return (prefs_common_get_ext_editor_cmd() &&
9956 strstr(prefs_common_get_ext_editor_cmd(), "%w"));
9959 static gboolean compose_ext_editor_plug_removed_cb(GtkSocket *socket, Compose *compose)
9961 compose->exteditor_socket = NULL;
9962 /* returning FALSE allows destruction of the socket */
9965 #endif /* G_OS_UNIX */
9968 * compose_undo_state_changed:
9970 * Change the sensivity of the menuentries undo and redo
9972 static void compose_undo_state_changed(UndoMain *undostruct, gint undo_state,
9973 gint redo_state, gpointer data)
9975 Compose *compose = (Compose *)data;
9977 switch (undo_state) {
9978 case UNDO_STATE_TRUE:
9979 if (!undostruct->undo_state) {
9980 undostruct->undo_state = TRUE;
9981 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", TRUE);
9984 case UNDO_STATE_FALSE:
9985 if (undostruct->undo_state) {
9986 undostruct->undo_state = FALSE;
9987 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
9990 case UNDO_STATE_UNCHANGED:
9992 case UNDO_STATE_REFRESH:
9993 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", undostruct->undo_state);
9996 g_warning("Undo state not recognized");
10000 switch (redo_state) {
10001 case UNDO_STATE_TRUE:
10002 if (!undostruct->redo_state) {
10003 undostruct->redo_state = TRUE;
10004 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", TRUE);
10007 case UNDO_STATE_FALSE:
10008 if (undostruct->redo_state) {
10009 undostruct->redo_state = FALSE;
10010 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
10013 case UNDO_STATE_UNCHANGED:
10015 case UNDO_STATE_REFRESH:
10016 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", undostruct->redo_state);
10019 g_warning("Redo state not recognized");
10024 /* callback functions */
10026 static void compose_notebook_size_alloc(GtkNotebook *notebook,
10027 GtkAllocation *allocation,
10030 prefs_common.compose_notebook_height = gtk_paned_get_position(paned);
10033 /* compose_edit_size_alloc() - called when resized. don't know whether Gtk
10034 * includes "non-client" (windows-izm) in calculation, so this calculation
10035 * may not be accurate.
10037 static gboolean compose_edit_size_alloc(GtkEditable *widget,
10038 GtkAllocation *allocation,
10039 GtkSHRuler *shruler)
10041 if (prefs_common.show_ruler) {
10042 gint char_width = 0, char_height = 0;
10043 gint line_width_in_chars;
10045 gtkut_get_font_size(GTK_WIDGET(widget),
10046 &char_width, &char_height);
10047 line_width_in_chars =
10048 (allocation->width - allocation->x) / char_width;
10050 /* got the maximum */
10051 gtk_shruler_set_range(GTK_SHRULER(shruler),
10052 0.0, line_width_in_chars, 0);
10061 ComposePrefType type;
10062 gboolean entry_marked;
10063 } HeaderEntryState;
10065 static void account_activated(GtkComboBox *optmenu, gpointer data)
10067 Compose *compose = (Compose *)data;
10070 gchar *folderidentifier;
10071 gint account_id = 0;
10072 GtkTreeModel *menu;
10074 GSList *list, *saved_list = NULL;
10075 HeaderEntryState *state;
10077 /* Get ID of active account in the combo box */
10078 menu = gtk_combo_box_get_model(optmenu);
10079 cm_return_if_fail(gtk_combo_box_get_active_iter(optmenu, &iter));
10080 gtk_tree_model_get(menu, &iter, 1, &account_id, -1);
10082 ac = account_find_from_id(account_id);
10083 cm_return_if_fail(ac != NULL);
10085 if (ac != compose->account) {
10086 compose_select_account(compose, ac, FALSE);
10088 for (list = compose->header_list; list; list = list->next) {
10089 ComposeHeaderEntry *hentry=(ComposeHeaderEntry *)list->data;
10091 if (hentry->type == PREF_ACCOUNT || !list->next) {
10092 compose_destroy_headerentry(compose, hentry);
10095 state = g_malloc0(sizeof(HeaderEntryState));
10096 state->header = gtk_editable_get_chars(GTK_EDITABLE(
10097 gtk_bin_get_child(GTK_BIN(hentry->combo))), 0, -1);
10098 state->entry = gtk_editable_get_chars(
10099 GTK_EDITABLE(hentry->entry), 0, -1);
10100 state->type = hentry->type;
10102 saved_list = g_slist_append(saved_list, state);
10103 compose_destroy_headerentry(compose, hentry);
10106 compose->header_last = NULL;
10107 g_slist_free(compose->header_list);
10108 compose->header_list = NULL;
10109 compose->header_nextrow = 1;
10110 compose_create_header_entry(compose);
10112 if (ac->set_autocc && ac->auto_cc)
10113 compose_entry_append(compose, ac->auto_cc,
10114 COMPOSE_CC, PREF_ACCOUNT);
10115 if (ac->set_autobcc && ac->auto_bcc)
10116 compose_entry_append(compose, ac->auto_bcc,
10117 COMPOSE_BCC, PREF_ACCOUNT);
10118 if (ac->set_autoreplyto && ac->auto_replyto)
10119 compose_entry_append(compose, ac->auto_replyto,
10120 COMPOSE_REPLYTO, PREF_ACCOUNT);
10122 for (list = saved_list; list; list = list->next) {
10123 state = (HeaderEntryState *) list->data;
10125 compose_add_header_entry(compose, state->header,
10126 state->entry, state->type);
10128 g_free(state->header);
10129 g_free(state->entry);
10132 g_slist_free(saved_list);
10134 combobox_select_by_data(GTK_COMBO_BOX(compose->header_last->combo),
10135 (ac->protocol == A_NNTP) ?
10136 COMPOSE_NEWSGROUPS : COMPOSE_TO);
10139 /* Set message save folder */
10140 compose_set_save_to(compose, NULL);
10141 if (compose->folder && compose->folder->prefs && compose->folder->prefs->save_copy_to_folder) {
10142 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
10143 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo), TRUE);
10144 folderidentifier = folder_item_get_identifier(compose->folder);
10145 compose_set_save_to(compose, folderidentifier);
10146 g_free(folderidentifier);
10147 } else if (account_get_special_folder(compose->account, F_OUTBOX)) {
10148 if (compose->account->set_sent_folder)
10149 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
10151 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), FALSE);
10152 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo), TRUE);
10153 folderidentifier = folder_item_get_identifier(account_get_special_folder
10154 (compose->account, F_OUTBOX));
10155 compose_set_save_to(compose, folderidentifier);
10156 g_free(folderidentifier);
10160 static void attach_selected(GtkTreeView *tree_view, GtkTreePath *tree_path,
10161 GtkTreeViewColumn *column, Compose *compose)
10163 compose_attach_property(NULL, compose);
10166 static gboolean attach_button_pressed(GtkWidget *widget, GdkEventButton *event,
10169 Compose *compose = (Compose *)data;
10170 GtkTreeSelection *attach_selection;
10171 gint attach_nr_selected;
10174 if (!event) return FALSE;
10176 if (event->button == 3) {
10177 attach_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
10178 attach_nr_selected = gtk_tree_selection_count_selected_rows(attach_selection);
10180 /* If no rows, or just one row is selected, right-click should
10181 * open menu relevant to the row being right-clicked on. We
10182 * achieve that by selecting the clicked row first. If more
10183 * than one row is selected, we shouldn't modify the selection,
10184 * as user may want to remove selected rows (attachments). */
10185 if (attach_nr_selected < 2) {
10186 gtk_tree_selection_unselect_all(attach_selection);
10187 attach_nr_selected = 0;
10188 gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget),
10189 event->x, event->y, &path, NULL, NULL, NULL);
10190 if (path != NULL) {
10191 gtk_tree_selection_select_path(attach_selection, path);
10192 gtk_tree_path_free(path);
10193 attach_nr_selected++;
10197 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", (attach_nr_selected > 0));
10198 /* Properties menu item makes no sense with more than one row
10199 * selected, the properties dialog can only edit one attachment. */
10200 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", (attach_nr_selected == 1));
10202 gtk_menu_popup(GTK_MENU(compose->popupmenu), NULL, NULL,
10203 NULL, NULL, event->button, event->time);
10210 static gboolean attach_key_pressed(GtkWidget *widget, GdkEventKey *event,
10213 Compose *compose = (Compose *)data;
10215 if (!event) return FALSE;
10217 switch (event->keyval) {
10218 case GDK_KEY_Delete:
10219 compose_attach_remove_selected(NULL, compose);
10225 static void compose_allow_user_actions (Compose *compose, gboolean allow)
10227 toolbar_comp_set_sensitive(compose, allow);
10228 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message", allow);
10229 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", allow);
10231 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", allow);
10233 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", allow);
10234 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools", allow);
10235 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Help", allow);
10237 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), allow);
10241 static void compose_send_cb(GtkAction *action, gpointer data)
10243 Compose *compose = (Compose *)data;
10246 if (compose->exteditor_tag != -1) {
10247 debug_print("ignoring send: external editor still open\n");
10251 if (prefs_common.work_offline &&
10252 !inc_offline_should_override(TRUE,
10253 _("Claws Mail needs network access in order "
10254 "to send this email.")))
10257 if (compose->draft_timeout_tag >= 0) { /* CLAWS: disable draft timeout */
10258 g_source_remove(compose->draft_timeout_tag);
10259 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
10262 compose_send(compose);
10265 static void compose_send_later_cb(GtkAction *action, gpointer data)
10267 Compose *compose = (Compose *)data;
10268 ComposeQueueResult val;
10271 compose_allow_user_actions(compose, FALSE);
10272 val = compose_queue_sub(compose, NULL, NULL, NULL, TRUE, TRUE);
10273 compose_allow_user_actions(compose, TRUE);
10276 if (val == COMPOSE_QUEUE_SUCCESS) {
10277 compose_close(compose);
10279 _display_queue_error(val);
10282 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
10285 #define DRAFTED_AT_EXIT "drafted_at_exit"
10286 static void compose_register_draft(MsgInfo *info)
10288 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10289 DRAFTED_AT_EXIT, NULL);
10290 FILE *fp = claws_fopen(filepath, "ab");
10293 fprintf(fp, "%s\t%d\n", folder_item_get_identifier(info->folder),
10301 gboolean compose_draft (gpointer data, guint action)
10303 Compose *compose = (Compose *)data;
10308 MsgFlags flag = {0, 0};
10309 static gboolean lock = FALSE;
10310 MsgInfo *newmsginfo;
10312 gboolean target_locked = FALSE;
10313 gboolean err = FALSE;
10315 if (lock) return FALSE;
10317 if (compose->sending)
10320 draft = account_get_special_folder(compose->account, F_DRAFT);
10321 cm_return_val_if_fail(draft != NULL, FALSE);
10323 if (!g_mutex_trylock(compose->mutex)) {
10324 /* we don't want to lock the mutex once it's available,
10325 * because as the only other part of compose.c locking
10326 * it is compose_close - which means once unlocked,
10327 * the compose struct will be freed */
10328 debug_print("couldn't lock mutex, probably sending\n");
10334 tmp = g_strdup_printf("%s%cdraft.%p", get_tmp_dir(),
10335 G_DIR_SEPARATOR, compose);
10336 if ((fp = claws_fopen(tmp, "wb")) == NULL) {
10337 FILE_OP_ERROR(tmp, "claws_fopen");
10341 /* chmod for security */
10342 if (change_file_mode_rw(fp, tmp) < 0) {
10343 FILE_OP_ERROR(tmp, "chmod");
10344 g_warning("can't change file mode");
10347 /* Save draft infos */
10348 err |= (fprintf(fp, "X-Claws-Account-Id:%d\n", compose->account->account_id) < 0);
10349 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
10351 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
10352 gchar *savefolderid;
10354 savefolderid = compose_get_save_to(compose);
10355 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
10356 g_free(savefolderid);
10358 if (compose->return_receipt) {
10359 err |= (fprintf(fp, "RRCPT:1\n") < 0);
10361 if (compose->privacy_system) {
10362 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
10363 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
10364 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
10367 /* Message-ID of message replying to */
10368 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
10369 gchar *folderid = NULL;
10371 if (compose->replyinfo->folder)
10372 folderid = folder_item_get_identifier(compose->replyinfo->folder);
10373 if (folderid == NULL)
10374 folderid = g_strdup("NULL");
10376 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
10379 /* Message-ID of message forwarding to */
10380 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
10381 gchar *folderid = NULL;
10383 if (compose->fwdinfo->folder)
10384 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
10385 if (folderid == NULL)
10386 folderid = g_strdup("NULL");
10388 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
10392 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
10393 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
10395 sheaders = compose_get_manual_headers_info(compose);
10396 err |= (fprintf(fp, "X-Claws-Manual-Headers:%s\n", sheaders) < 0);
10399 /* end of headers */
10400 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
10407 if (compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_STORE, action != COMPOSE_AUTO_SAVE) < 0) {
10411 if (claws_safe_fclose(fp) == EOF) {
10415 flag.perm_flags = MSG_NEW|MSG_UNREAD;
10416 if (compose->targetinfo) {
10417 target_locked = MSG_IS_LOCKED(compose->targetinfo->flags);
10419 flag.perm_flags |= MSG_LOCKED;
10421 flag.tmp_flags = MSG_DRAFT;
10423 folder_item_scan(draft);
10424 if ((msgnum = folder_item_add_msg(draft, tmp, &flag, TRUE)) < 0) {
10425 MsgInfo *tmpinfo = NULL;
10426 debug_print("didn't get msgnum after adding draft [%s]\n", compose->msgid?compose->msgid:"no msgid");
10427 if (compose->msgid) {
10428 tmpinfo = folder_item_get_msginfo_by_msgid(draft, compose->msgid);
10431 msgnum = tmpinfo->msgnum;
10432 procmsg_msginfo_free(&tmpinfo);
10433 debug_print("got draft msgnum %d from scanning\n", msgnum);
10435 debug_print("didn't get draft msgnum after scanning\n");
10438 debug_print("got draft msgnum %d from adding\n", msgnum);
10444 if (action != COMPOSE_AUTO_SAVE) {
10445 if (action != COMPOSE_DRAFT_FOR_EXIT)
10446 alertpanel_error(_("Could not save draft."));
10449 gtkut_window_popup(compose->window);
10450 val = alertpanel_full(_("Could not save draft"),
10451 _("Could not save draft.\n"
10452 "Do you want to cancel exit or discard this email?"),
10453 _("_Cancel exit"), _("_Discard email"), NULL, ALERTFOCUS_FIRST,
10454 FALSE, NULL, ALERT_QUESTION);
10455 if (val == G_ALERTALTERNATE) {
10457 g_mutex_unlock(compose->mutex); /* must be done before closing */
10458 compose_close(compose);
10462 g_mutex_unlock(compose->mutex); /* must be done before closing */
10471 if (compose->mode == COMPOSE_REEDIT) {
10472 compose_remove_reedit_target(compose, TRUE);
10475 newmsginfo = folder_item_get_msginfo(draft, msgnum);
10478 procmsg_msginfo_unset_flags(newmsginfo, ~0, ~0);
10480 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD|MSG_LOCKED, MSG_DRAFT);
10482 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD, MSG_DRAFT);
10483 if (compose_use_attach(compose) && action != COMPOSE_AUTO_SAVE)
10484 procmsg_msginfo_set_flags(newmsginfo, 0,
10485 MSG_HAS_ATTACHMENT);
10487 if (action == COMPOSE_DRAFT_FOR_EXIT) {
10488 compose_register_draft(newmsginfo);
10490 procmsg_msginfo_free(&newmsginfo);
10493 folder_item_scan(draft);
10495 if (action == COMPOSE_QUIT_EDITING || action == COMPOSE_DRAFT_FOR_EXIT) {
10497 g_mutex_unlock(compose->mutex); /* must be done before closing */
10498 compose_close(compose);
10505 GError *error = NULL;
10510 goffset size, mtime;
10512 path = folder_item_fetch_msg(draft, msgnum);
10513 if (path == NULL) {
10514 debug_print("can't fetch %s:%d\n", draft->path, msgnum);
10518 f = g_file_new_for_path(path);
10519 fi = g_file_query_info(f, "standard::size,time::modified",
10520 G_FILE_QUERY_INFO_NONE, NULL, &error);
10521 if (error != NULL) {
10522 debug_print("couldn't query file info for '%s': %s\n",
10523 path, error->message);
10524 g_error_free(error);
10529 size = g_file_info_get_size(fi);
10530 g_file_info_get_modification_time(fi, &tv);
10532 g_object_unref(fi);
10535 if (g_stat(path, &s) < 0) {
10536 FILE_OP_ERROR(path, "stat");
10541 mtime = s.st_mtime;
10545 procmsg_msginfo_free(&(compose->targetinfo));
10546 compose->targetinfo = procmsg_msginfo_new();
10547 compose->targetinfo->msgnum = msgnum;
10548 compose->targetinfo->size = size;
10549 compose->targetinfo->mtime = mtime;
10550 compose->targetinfo->folder = draft;
10552 procmsg_msginfo_set_flags(compose->targetinfo, MSG_LOCKED, 0);
10553 compose->mode = COMPOSE_REEDIT;
10555 if (action == COMPOSE_AUTO_SAVE) {
10556 compose->autosaved_draft = compose->targetinfo;
10558 compose->modified = FALSE;
10559 compose_set_title(compose);
10563 g_mutex_unlock(compose->mutex);
10567 void compose_clear_exit_drafts(void)
10569 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10570 DRAFTED_AT_EXIT, NULL);
10571 if (is_file_exist(filepath))
10572 claws_unlink(filepath);
10577 void compose_reopen_exit_drafts(void)
10579 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10580 DRAFTED_AT_EXIT, NULL);
10581 FILE *fp = claws_fopen(filepath, "rb");
10585 while (claws_fgets(buf, sizeof(buf), fp)) {
10586 gchar **parts = g_strsplit(buf, "\t", 2);
10587 const gchar *folder = parts[0];
10588 int msgnum = parts[1] ? atoi(parts[1]):-1;
10590 if (folder && *folder && msgnum > -1) {
10591 FolderItem *item = folder_find_item_from_identifier(folder);
10592 MsgInfo *info = folder_item_get_msginfo(item, msgnum);
10594 compose_reedit(info, FALSE);
10601 compose_clear_exit_drafts();
10604 static void compose_save_cb(GtkAction *action, gpointer data)
10606 Compose *compose = (Compose *)data;
10607 compose_draft(compose, COMPOSE_KEEP_EDITING);
10608 compose->rmode = COMPOSE_REEDIT;
10611 void compose_attach_from_list(Compose *compose, GList *file_list, gboolean free_data)
10613 if (compose && file_list) {
10616 for ( tmp = file_list; tmp; tmp = tmp->next) {
10617 gchar *file = (gchar *) tmp->data;
10618 gchar *utf8_filename = conv_filename_to_utf8(file);
10619 compose_attach_append(compose, file, utf8_filename, NULL, NULL);
10620 compose_changed_cb(NULL, compose);
10625 g_free(utf8_filename);
10630 static void compose_attach_cb(GtkAction *action, gpointer data)
10632 Compose *compose = (Compose *)data;
10635 if (compose->redirect_filename != NULL)
10638 /* Set focus_window properly, in case we were called via popup menu,
10639 * which unsets it (via focus_out_event callback on compose window). */
10640 manage_window_focus_in(compose->window, NULL, NULL);
10642 file_list = filesel_select_multiple_files_open(_("Select file"), NULL);
10645 compose_attach_from_list(compose, file_list, TRUE);
10646 g_list_free(file_list);
10650 static void compose_insert_file_cb(GtkAction *action, gpointer data)
10652 Compose *compose = (Compose *)data;
10654 gint files_inserted = 0;
10656 file_list = filesel_select_multiple_files_open(_("Select file"), NULL);
10661 for ( tmp = file_list; tmp; tmp = tmp->next) {
10662 gchar *file = (gchar *) tmp->data;
10663 gchar *filedup = g_strdup(file);
10664 gchar *shortfile = g_path_get_basename(filedup);
10665 ComposeInsertResult res;
10666 /* insert the file if the file is short or if the user confirmed that
10667 he/she wants to insert the large file */
10668 res = compose_insert_file(compose, file);
10669 if (res == COMPOSE_INSERT_READ_ERROR) {
10670 alertpanel_error(_("File '%s' could not be read."), shortfile);
10671 } else if (res == COMPOSE_INSERT_INVALID_CHARACTER) {
10672 alertpanel_error(_("File '%s' contained invalid characters\n"
10673 "for the current encoding, insertion may be incorrect."),
10675 } else if (res == COMPOSE_INSERT_SUCCESS)
10682 g_list_free(file_list);
10686 if (files_inserted > 0 && compose->gtkaspell &&
10687 compose->gtkaspell->check_while_typing)
10688 gtkaspell_highlight_all(compose->gtkaspell);
10692 static void compose_insert_sig_cb(GtkAction *action, gpointer data)
10694 Compose *compose = (Compose *)data;
10696 compose_insert_sig(compose, FALSE);
10699 static void compose_replace_sig_cb(GtkAction *action, gpointer data)
10701 Compose *compose = (Compose *)data;
10703 compose_insert_sig(compose, TRUE);
10706 static gint compose_delete_cb(GtkWidget *widget, GdkEventAny *event,
10710 Compose *compose = (Compose *)data;
10712 gtkut_widget_get_uposition(widget, &x, &y);
10713 if (!compose->batch) {
10714 prefs_common.compose_x = x;
10715 prefs_common.compose_y = y;
10717 if (compose->sending || compose->updating)
10719 compose_close_cb(NULL, compose);
10723 void compose_close_toolbar(Compose *compose)
10725 compose_close_cb(NULL, compose);
10728 static void compose_close_cb(GtkAction *action, gpointer data)
10730 Compose *compose = (Compose *)data;
10734 if (compose->exteditor_tag != -1) {
10735 if (!compose_ext_editor_kill(compose))
10740 if (compose->modified) {
10741 gboolean reedit = (compose->rmode == COMPOSE_REEDIT);
10742 if (!g_mutex_trylock(compose->mutex)) {
10743 /* we don't want to lock the mutex once it's available,
10744 * because as the only other part of compose.c locking
10745 * it is compose_close - which means once unlocked,
10746 * the compose struct will be freed */
10747 debug_print("couldn't lock mutex, probably sending\n");
10750 if (!reedit || compose->folder->stype == F_DRAFT) {
10751 val = alertpanel(_("Discard message"),
10752 _("This message has been modified. Discard it?"),
10753 _("_Discard"), _("_Save to Drafts"), GTK_STOCK_CANCEL,
10756 val = alertpanel(_("Save changes"),
10757 _("This message has been modified. Save the latest changes?"),
10758 _("_Don't save"), _("_Save to Drafts"), GTK_STOCK_CANCEL,
10759 ALERTFOCUS_SECOND);
10761 g_mutex_unlock(compose->mutex);
10763 case G_ALERTDEFAULT:
10764 if (compose_can_autosave(compose) && !reedit)
10765 compose_remove_draft(compose);
10767 case G_ALERTALTERNATE:
10768 compose_draft(data, COMPOSE_QUIT_EDITING);
10775 compose_close(compose);
10778 static void compose_print_cb(GtkAction *action, gpointer data)
10780 Compose *compose = (Compose *) data;
10782 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
10783 if (compose->targetinfo)
10784 messageview_print(compose->targetinfo, FALSE, -1, -1, 0);
10787 static void compose_set_encoding_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
10789 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
10790 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
10791 Compose *compose = (Compose *) data;
10794 compose->out_encoding = (CharSet)value;
10797 static void compose_address_cb(GtkAction *action, gpointer data)
10799 Compose *compose = (Compose *)data;
10801 #ifndef USE_ALT_ADDRBOOK
10802 addressbook_open(compose);
10804 GError* error = NULL;
10805 addressbook_connect_signals(compose);
10806 addressbook_dbus_open(TRUE, &error);
10808 g_warning("%s", error->message);
10809 g_error_free(error);
10814 static void about_show_cb(GtkAction *action, gpointer data)
10819 static void compose_template_activate_cb(GtkWidget *widget, gpointer data)
10821 Compose *compose = (Compose *)data;
10826 tmpl = g_object_get_data(G_OBJECT(widget), "template");
10827 cm_return_if_fail(tmpl != NULL);
10829 msg = g_strdup_printf(_("Do you want to apply the template '%s'?"),
10831 val = alertpanel(_("Apply template"), msg,
10832 _("_Replace"), _("_Insert"), GTK_STOCK_CANCEL, ALERTFOCUS_FIRST);
10835 if (val == G_ALERTDEFAULT)
10836 compose_template_apply(compose, tmpl, TRUE);
10837 else if (val == G_ALERTALTERNATE)
10838 compose_template_apply(compose, tmpl, FALSE);
10841 static void compose_ext_editor_cb(GtkAction *action, gpointer data)
10843 Compose *compose = (Compose *)data;
10846 if (compose->exteditor_tag != -1) {
10847 debug_print("ignoring open external editor: external editor still open\n");
10851 compose_exec_ext_editor(compose);
10854 static void compose_undo_cb(GtkAction *action, gpointer data)
10856 Compose *compose = (Compose *)data;
10857 gboolean prev_autowrap = compose->autowrap;
10859 compose->autowrap = FALSE;
10860 undo_undo(compose->undostruct);
10861 compose->autowrap = prev_autowrap;
10864 static void compose_redo_cb(GtkAction *action, gpointer data)
10866 Compose *compose = (Compose *)data;
10867 gboolean prev_autowrap = compose->autowrap;
10869 compose->autowrap = FALSE;
10870 undo_redo(compose->undostruct);
10871 compose->autowrap = prev_autowrap;
10874 static void entry_cut_clipboard(GtkWidget *entry)
10876 if (GTK_IS_EDITABLE(entry))
10877 gtk_editable_cut_clipboard (GTK_EDITABLE(entry));
10878 else if (GTK_IS_TEXT_VIEW(entry))
10879 gtk_text_buffer_cut_clipboard(
10880 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10881 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
10885 static void entry_copy_clipboard(GtkWidget *entry)
10887 if (GTK_IS_EDITABLE(entry))
10888 gtk_editable_copy_clipboard (GTK_EDITABLE(entry));
10889 else if (GTK_IS_TEXT_VIEW(entry))
10890 gtk_text_buffer_copy_clipboard(
10891 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10892 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
10895 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry,
10896 gboolean wrap, GdkAtom clip, GtkTextIter *insert_place)
10898 if (GTK_IS_TEXT_VIEW(entry)) {
10899 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10900 GtkTextMark *mark_start = gtk_text_buffer_get_insert(buffer);
10901 GtkTextIter start_iter, end_iter;
10903 gchar *contents = gtk_clipboard_wait_for_text(gtk_clipboard_get(clip));
10905 if (contents == NULL)
10908 /* we shouldn't delete the selection when middle-click-pasting, or we
10909 * can't mid-click-paste our own selection */
10910 if (clip != GDK_SELECTION_PRIMARY) {
10911 undo_paste_clipboard(GTK_TEXT_VIEW(compose->text), compose->undostruct);
10912 gtk_text_buffer_delete_selection(buffer, FALSE, TRUE);
10915 if (insert_place == NULL) {
10916 /* if insert_place isn't specified, insert at the cursor.
10917 * used for Ctrl-V pasting */
10918 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10919 start = gtk_text_iter_get_offset(&start_iter);
10920 gtk_text_buffer_insert(buffer, &start_iter, contents, strlen(contents));
10922 /* if insert_place is specified, paste here.
10923 * used for mid-click-pasting */
10924 start = gtk_text_iter_get_offset(insert_place);
10925 gtk_text_buffer_insert(buffer, insert_place, contents, strlen(contents));
10926 if (prefs_common.primary_paste_unselects)
10927 gtk_text_buffer_select_range(buffer, insert_place, insert_place);
10931 /* paste unwrapped: mark the paste so it's not wrapped later */
10932 end = start + strlen(contents);
10933 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, start);
10934 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, end);
10935 gtk_text_buffer_apply_tag_by_name(buffer, "no_wrap", &start_iter, &end_iter);
10936 } else if (wrap && clip == GDK_SELECTION_PRIMARY) {
10937 /* rewrap paragraph now (after a mid-click-paste) */
10938 mark_start = gtk_text_buffer_get_insert(buffer);
10939 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10940 gtk_text_iter_backward_char(&start_iter);
10941 compose_beautify_paragraph(compose, &start_iter, TRUE);
10943 } else if (GTK_IS_EDITABLE(entry))
10944 gtk_editable_paste_clipboard (GTK_EDITABLE(entry));
10946 compose->modified = TRUE;
10949 static void entry_allsel(GtkWidget *entry)
10951 if (GTK_IS_EDITABLE(entry))
10952 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
10953 else if (GTK_IS_TEXT_VIEW(entry)) {
10954 GtkTextIter startiter, enditer;
10955 GtkTextBuffer *textbuf;
10957 textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10958 gtk_text_buffer_get_start_iter(textbuf, &startiter);
10959 gtk_text_buffer_get_end_iter(textbuf, &enditer);
10961 gtk_text_buffer_move_mark_by_name(textbuf,
10962 "selection_bound", &startiter);
10963 gtk_text_buffer_move_mark_by_name(textbuf,
10964 "insert", &enditer);
10968 static void compose_cut_cb(GtkAction *action, gpointer data)
10970 Compose *compose = (Compose *)data;
10971 if (compose->focused_editable
10972 #ifndef GENERIC_UMPC
10973 && gtk_widget_has_focus(compose->focused_editable)
10976 entry_cut_clipboard(compose->focused_editable);
10979 static void compose_copy_cb(GtkAction *action, gpointer data)
10981 Compose *compose = (Compose *)data;
10982 if (compose->focused_editable
10983 #ifndef GENERIC_UMPC
10984 && gtk_widget_has_focus(compose->focused_editable)
10987 entry_copy_clipboard(compose->focused_editable);
10990 static void compose_paste_cb(GtkAction *action, gpointer data)
10992 Compose *compose = (Compose *)data;
10993 gint prev_autowrap;
10994 GtkTextBuffer *buffer;
10996 if (compose->focused_editable
10997 #ifndef GENERIC_UMPC
10998 && gtk_widget_has_focus(compose->focused_editable)
11001 entry_paste_clipboard(compose, compose->focused_editable,
11002 prefs_common.linewrap_pastes,
11003 GDK_SELECTION_CLIPBOARD, NULL);
11008 #ifndef GENERIC_UMPC
11009 gtk_widget_has_focus(compose->text) &&
11011 compose->gtkaspell &&
11012 compose->gtkaspell->check_while_typing)
11013 gtkaspell_highlight_all(compose->gtkaspell);
11017 static void compose_paste_as_quote_cb(GtkAction *action, gpointer data)
11019 Compose *compose = (Compose *)data;
11020 gint wrap_quote = prefs_common.linewrap_quote;
11021 if (compose->focused_editable
11022 #ifndef GENERIC_UMPC
11023 && gtk_widget_has_focus(compose->focused_editable)
11026 /* let text_insert() (called directly or at a later time
11027 * after the gtk_editable_paste_clipboard) know that
11028 * text is to be inserted as a quotation. implemented
11029 * by using a simple refcount... */
11030 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data(
11031 G_OBJECT(compose->focused_editable),
11032 "paste_as_quotation"));
11033 g_object_set_data(G_OBJECT(compose->focused_editable),
11034 "paste_as_quotation",
11035 GINT_TO_POINTER(paste_as_quotation + 1));
11036 prefs_common.linewrap_quote = prefs_common.linewrap_pastes;
11037 entry_paste_clipboard(compose, compose->focused_editable,
11038 prefs_common.linewrap_pastes,
11039 GDK_SELECTION_CLIPBOARD, NULL);
11040 prefs_common.linewrap_quote = wrap_quote;
11044 static void compose_paste_no_wrap_cb(GtkAction *action, gpointer data)
11046 Compose *compose = (Compose *)data;
11047 gint prev_autowrap;
11048 GtkTextBuffer *buffer;
11050 if (compose->focused_editable
11051 #ifndef GENERIC_UMPC
11052 && gtk_widget_has_focus(compose->focused_editable)
11055 entry_paste_clipboard(compose, compose->focused_editable, FALSE,
11056 GDK_SELECTION_CLIPBOARD, NULL);
11061 #ifndef GENERIC_UMPC
11062 gtk_widget_has_focus(compose->text) &&
11064 compose->gtkaspell &&
11065 compose->gtkaspell->check_while_typing)
11066 gtkaspell_highlight_all(compose->gtkaspell);
11070 static void compose_paste_wrap_cb(GtkAction *action, gpointer data)
11072 Compose *compose = (Compose *)data;
11073 gint prev_autowrap;
11074 GtkTextBuffer *buffer;
11076 if (compose->focused_editable
11077 #ifndef GENERIC_UMPC
11078 && gtk_widget_has_focus(compose->focused_editable)
11081 entry_paste_clipboard(compose, compose->focused_editable, TRUE,
11082 GDK_SELECTION_CLIPBOARD, NULL);
11087 #ifndef GENERIC_UMPC
11088 gtk_widget_has_focus(compose->text) &&
11090 compose->gtkaspell &&
11091 compose->gtkaspell->check_while_typing)
11092 gtkaspell_highlight_all(compose->gtkaspell);
11096 static void compose_allsel_cb(GtkAction *action, gpointer data)
11098 Compose *compose = (Compose *)data;
11099 if (compose->focused_editable
11100 #ifndef GENERIC_UMPC
11101 && gtk_widget_has_focus(compose->focused_editable)
11104 entry_allsel(compose->focused_editable);
11107 static void textview_move_beginning_of_line (GtkTextView *text)
11109 GtkTextBuffer *buffer;
11113 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11115 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11116 mark = gtk_text_buffer_get_insert(buffer);
11117 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11118 gtk_text_iter_set_line_offset(&ins, 0);
11119 gtk_text_buffer_place_cursor(buffer, &ins);
11122 static void textview_move_forward_character (GtkTextView *text)
11124 GtkTextBuffer *buffer;
11128 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11130 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11131 mark = gtk_text_buffer_get_insert(buffer);
11132 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11133 if (gtk_text_iter_forward_cursor_position(&ins))
11134 gtk_text_buffer_place_cursor(buffer, &ins);
11137 static void textview_move_backward_character (GtkTextView *text)
11139 GtkTextBuffer *buffer;
11143 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11145 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11146 mark = gtk_text_buffer_get_insert(buffer);
11147 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11148 if (gtk_text_iter_backward_cursor_position(&ins))
11149 gtk_text_buffer_place_cursor(buffer, &ins);
11152 static void textview_move_forward_word (GtkTextView *text)
11154 GtkTextBuffer *buffer;
11159 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11161 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11162 mark = gtk_text_buffer_get_insert(buffer);
11163 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11164 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
11165 if (gtk_text_iter_forward_word_ends(&ins, count)) {
11166 gtk_text_iter_backward_word_start(&ins);
11167 gtk_text_buffer_place_cursor(buffer, &ins);
11171 static void textview_move_backward_word (GtkTextView *text)
11173 GtkTextBuffer *buffer;
11177 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11179 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11180 mark = gtk_text_buffer_get_insert(buffer);
11181 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11182 if (gtk_text_iter_backward_word_starts(&ins, 1))
11183 gtk_text_buffer_place_cursor(buffer, &ins);
11186 static void textview_move_end_of_line (GtkTextView *text)
11188 GtkTextBuffer *buffer;
11192 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11194 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11195 mark = gtk_text_buffer_get_insert(buffer);
11196 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11197 if (gtk_text_iter_forward_to_line_end(&ins))
11198 gtk_text_buffer_place_cursor(buffer, &ins);
11201 static void textview_move_next_line (GtkTextView *text)
11203 GtkTextBuffer *buffer;
11208 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11210 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11211 mark = gtk_text_buffer_get_insert(buffer);
11212 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11213 offset = gtk_text_iter_get_line_offset(&ins);
11214 if (gtk_text_iter_forward_line(&ins)) {
11215 gtk_text_iter_set_line_offset(&ins, offset);
11216 gtk_text_buffer_place_cursor(buffer, &ins);
11220 static void textview_move_previous_line (GtkTextView *text)
11222 GtkTextBuffer *buffer;
11227 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11229 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11230 mark = gtk_text_buffer_get_insert(buffer);
11231 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11232 offset = gtk_text_iter_get_line_offset(&ins);
11233 if (gtk_text_iter_backward_line(&ins)) {
11234 gtk_text_iter_set_line_offset(&ins, offset);
11235 gtk_text_buffer_place_cursor(buffer, &ins);
11239 static void textview_delete_forward_character (GtkTextView *text)
11241 GtkTextBuffer *buffer;
11243 GtkTextIter ins, end_iter;
11245 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11247 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11248 mark = gtk_text_buffer_get_insert(buffer);
11249 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11251 if (gtk_text_iter_forward_char(&end_iter)) {
11252 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11256 static void textview_delete_backward_character (GtkTextView *text)
11258 GtkTextBuffer *buffer;
11260 GtkTextIter ins, end_iter;
11262 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11264 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11265 mark = gtk_text_buffer_get_insert(buffer);
11266 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11268 if (gtk_text_iter_backward_char(&end_iter)) {
11269 gtk_text_buffer_delete(buffer, &end_iter, &ins);
11273 static void textview_delete_forward_word (GtkTextView *text)
11275 GtkTextBuffer *buffer;
11277 GtkTextIter ins, end_iter;
11279 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11281 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11282 mark = gtk_text_buffer_get_insert(buffer);
11283 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11285 if (gtk_text_iter_forward_word_end(&end_iter)) {
11286 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11290 static void textview_delete_backward_word (GtkTextView *text)
11292 GtkTextBuffer *buffer;
11294 GtkTextIter ins, end_iter;
11296 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11298 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11299 mark = gtk_text_buffer_get_insert(buffer);
11300 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11302 if (gtk_text_iter_backward_word_start(&end_iter)) {
11303 gtk_text_buffer_delete(buffer, &end_iter, &ins);
11307 static void textview_delete_line (GtkTextView *text)
11309 GtkTextBuffer *buffer;
11311 GtkTextIter ins, start_iter, end_iter;
11313 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11315 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11316 mark = gtk_text_buffer_get_insert(buffer);
11317 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11320 gtk_text_iter_set_line_offset(&start_iter, 0);
11323 if (gtk_text_iter_ends_line(&end_iter)){
11324 if (!gtk_text_iter_forward_char(&end_iter))
11325 gtk_text_iter_backward_char(&start_iter);
11328 gtk_text_iter_forward_to_line_end(&end_iter);
11329 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
11332 static void textview_delete_to_line_end (GtkTextView *text)
11334 GtkTextBuffer *buffer;
11336 GtkTextIter ins, end_iter;
11338 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11340 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11341 mark = gtk_text_buffer_get_insert(buffer);
11342 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11344 if (gtk_text_iter_ends_line(&end_iter))
11345 gtk_text_iter_forward_char(&end_iter);
11347 gtk_text_iter_forward_to_line_end(&end_iter);
11348 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11351 #define DO_ACTION(name, act) { \
11352 if(!strcmp(name, a_name)) { \
11356 static ComposeCallAdvancedAction compose_call_advanced_action_from_path(GtkAction *action)
11358 const gchar *a_name = gtk_action_get_name(action);
11359 DO_ACTION("Edit/Advanced/BackChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER);
11360 DO_ACTION("Edit/Advanced/ForwChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER);
11361 DO_ACTION("Edit/Advanced/BackWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD);
11362 DO_ACTION("Edit/Advanced/ForwWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD);
11363 DO_ACTION("Edit/Advanced/BegLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE);
11364 DO_ACTION("Edit/Advanced/EndLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE);
11365 DO_ACTION("Edit/Advanced/PrevLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE);
11366 DO_ACTION("Edit/Advanced/NextLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE);
11367 DO_ACTION("Edit/Advanced/DelBackChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER);
11368 DO_ACTION("Edit/Advanced/DelForwChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER);
11369 DO_ACTION("Edit/Advanced/DelBackWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD);
11370 DO_ACTION("Edit/Advanced/DelForwWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD);
11371 DO_ACTION("Edit/Advanced/DelLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE);
11372 DO_ACTION("Edit/Advanced/DelEndLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END);
11373 return COMPOSE_CALL_ADVANCED_ACTION_UNDEFINED;
11376 static void compose_advanced_action_cb(GtkAction *gaction, gpointer data)
11378 Compose *compose = (Compose *)data;
11379 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11380 ComposeCallAdvancedAction action = COMPOSE_CALL_ADVANCED_ACTION_UNDEFINED;
11382 action = compose_call_advanced_action_from_path(gaction);
11385 void (*do_action) (GtkTextView *text);
11386 } action_table[] = {
11387 {textview_move_beginning_of_line},
11388 {textview_move_forward_character},
11389 {textview_move_backward_character},
11390 {textview_move_forward_word},
11391 {textview_move_backward_word},
11392 {textview_move_end_of_line},
11393 {textview_move_next_line},
11394 {textview_move_previous_line},
11395 {textview_delete_forward_character},
11396 {textview_delete_backward_character},
11397 {textview_delete_forward_word},
11398 {textview_delete_backward_word},
11399 {textview_delete_line},
11400 {textview_delete_to_line_end}
11403 if (!gtk_widget_has_focus(GTK_WIDGET(text))) return;
11405 if (action >= COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE &&
11406 action <= COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END) {
11407 if (action_table[action].do_action)
11408 action_table[action].do_action(text);
11410 g_warning("Not implemented yet.");
11414 static void compose_grab_focus_cb(GtkWidget *widget, Compose *compose)
11416 GtkAllocation allocation;
11420 if (GTK_IS_EDITABLE(widget)) {
11421 str = gtk_editable_get_chars(GTK_EDITABLE(widget), 0, -1);
11422 gtk_editable_set_position(GTK_EDITABLE(widget),
11425 if ((parent = gtk_widget_get_parent(widget))
11426 && (parent = gtk_widget_get_parent(parent))
11427 && (parent = gtk_widget_get_parent(parent))) {
11428 if (GTK_IS_SCROLLED_WINDOW(parent)) {
11429 gtk_widget_get_allocation(widget, &allocation);
11430 gint y = allocation.y;
11431 gint height = allocation.height;
11432 GtkAdjustment *shown = gtk_scrolled_window_get_vadjustment
11433 (GTK_SCROLLED_WINDOW(parent));
11435 gfloat value = gtk_adjustment_get_value(shown);
11436 gfloat upper = gtk_adjustment_get_upper(shown);
11437 gfloat page_size = gtk_adjustment_get_page_size(shown);
11438 if (y < (int)value) {
11439 gtk_adjustment_set_value(shown, y - 1);
11441 if ((y + height) > ((int)value + (int)page_size)) {
11442 if ((y - height - 1) < ((int)upper - (int)page_size)) {
11443 gtk_adjustment_set_value(shown,
11444 y + height - (int)page_size - 1);
11446 gtk_adjustment_set_value(shown,
11447 (int)upper - (int)page_size - 1);
11454 if (GTK_IS_EDITABLE(widget) || GTK_IS_TEXT_VIEW(widget))
11455 compose->focused_editable = widget;
11457 #ifdef GENERIC_UMPC
11458 if (GTK_IS_TEXT_VIEW(widget)
11459 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->edit_vbox) {
11460 g_object_ref(compose->notebook);
11461 g_object_ref(compose->edit_vbox);
11462 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
11463 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
11464 gtk_paned_add1(GTK_PANED(compose->paned), compose->edit_vbox);
11465 gtk_paned_add2(GTK_PANED(compose->paned), compose->notebook);
11466 g_object_unref(compose->notebook);
11467 g_object_unref(compose->edit_vbox);
11468 g_signal_handlers_block_by_func(G_OBJECT(widget),
11469 G_CALLBACK(compose_grab_focus_cb),
11471 gtk_widget_grab_focus(widget);
11472 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
11473 G_CALLBACK(compose_grab_focus_cb),
11475 } else if (!GTK_IS_TEXT_VIEW(widget)
11476 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->notebook) {
11477 g_object_ref(compose->notebook);
11478 g_object_ref(compose->edit_vbox);
11479 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
11480 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
11481 gtk_paned_add1(GTK_PANED(compose->paned), compose->notebook);
11482 gtk_paned_add2(GTK_PANED(compose->paned), compose->edit_vbox);
11483 g_object_unref(compose->notebook);
11484 g_object_unref(compose->edit_vbox);
11485 g_signal_handlers_block_by_func(G_OBJECT(widget),
11486 G_CALLBACK(compose_grab_focus_cb),
11488 gtk_widget_grab_focus(widget);
11489 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
11490 G_CALLBACK(compose_grab_focus_cb),
11496 static void compose_changed_cb(GtkTextBuffer *textbuf, Compose *compose)
11498 compose->modified = TRUE;
11499 /* compose_beautify_paragraph(compose, NULL, TRUE); */
11500 #ifndef GENERIC_UMPC
11501 compose_set_title(compose);
11505 static void compose_wrap_cb(GtkAction *action, gpointer data)
11507 Compose *compose = (Compose *)data;
11508 compose_beautify_paragraph(compose, NULL, TRUE);
11511 static void compose_wrap_all_cb(GtkAction *action, gpointer data)
11513 Compose *compose = (Compose *)data;
11514 compose_wrap_all_full(compose, TRUE);
11517 static void compose_find_cb(GtkAction *action, gpointer data)
11519 Compose *compose = (Compose *)data;
11521 message_search_compose(compose);
11524 static void compose_toggle_autowrap_cb(GtkToggleAction *action,
11527 Compose *compose = (Compose *)data;
11528 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11529 if (compose->autowrap)
11530 compose_wrap_all_full(compose, TRUE);
11531 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11534 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
11537 Compose *compose = (Compose *)data;
11538 compose->autoindent = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11541 static void compose_toggle_sign_cb(GtkToggleAction *action, gpointer data)
11543 Compose *compose = (Compose *)data;
11545 compose->use_signing = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11546 gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(compose->toolbar->privacy_sign_btn), compose->use_signing);
11549 static void compose_toggle_encrypt_cb(GtkToggleAction *action, gpointer data)
11551 Compose *compose = (Compose *)data;
11553 compose->use_encryption = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11554 gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(compose->toolbar->privacy_encrypt_btn), compose->use_encryption);
11557 static void compose_activate_privacy_system(Compose *compose, PrefsAccount *account, gboolean warn)
11559 g_free(compose->privacy_system);
11560 g_free(compose->encdata);
11562 compose->privacy_system = g_strdup(account->default_privacy_system);
11563 compose_update_privacy_system_menu_item(compose, warn);
11566 static void compose_apply_folder_privacy_settings(Compose *compose, FolderItem *folder_item)
11568 if (folder_item != NULL) {
11569 if (folder_item->prefs->always_sign != SIGN_OR_ENCRYPT_DEFAULT &&
11570 privacy_system_can_sign(compose->privacy_system)) {
11571 compose_use_signing(compose,
11572 (folder_item->prefs->always_sign == SIGN_OR_ENCRYPT_ALWAYS) ? TRUE : FALSE);
11574 if (folder_item->prefs->always_encrypt != SIGN_OR_ENCRYPT_DEFAULT &&
11575 privacy_system_can_encrypt(compose->privacy_system)) {
11576 compose_use_encryption(compose,
11577 (folder_item->prefs->always_encrypt == SIGN_OR_ENCRYPT_ALWAYS) ? TRUE : FALSE);
11582 static void compose_toggle_ruler_cb(GtkToggleAction *action, gpointer data)
11584 Compose *compose = (Compose *)data;
11586 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action))) {
11587 gtk_widget_show(compose->ruler_hbox);
11588 prefs_common.show_ruler = TRUE;
11590 gtk_widget_hide(compose->ruler_hbox);
11591 gtk_widget_queue_resize(compose->edit_vbox);
11592 prefs_common.show_ruler = FALSE;
11596 static void compose_attach_drag_received_cb (GtkWidget *widget,
11597 GdkDragContext *context,
11600 GtkSelectionData *data,
11603 gpointer user_data)
11605 Compose *compose = (Compose *)user_data;
11609 type = gtk_selection_data_get_data_type(data);
11610 if ((gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list"))
11611 && gtk_drag_get_source_widget(context) !=
11612 summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
11613 list = uri_list_extract_filenames(
11614 (const gchar *)gtk_selection_data_get_data(data));
11615 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11616 gchar *utf8_filename = conv_filename_to_utf8((const gchar *)tmp->data);
11617 compose_attach_append
11618 (compose, (const gchar *)tmp->data,
11619 utf8_filename, NULL, NULL);
11620 g_free(utf8_filename);
11623 compose_changed_cb(NULL, compose);
11624 list_free_strings_full(list);
11625 } else if (gtk_drag_get_source_widget(context)
11626 == summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
11627 /* comes from our summaryview */
11628 SummaryView * summaryview = NULL;
11629 GSList * list = NULL, *cur = NULL;
11631 if (mainwindow_get_mainwindow())
11632 summaryview = mainwindow_get_mainwindow()->summaryview;
11635 list = summary_get_selected_msg_list(summaryview);
11637 for (cur = list; cur; cur = cur->next) {
11638 MsgInfo *msginfo = (MsgInfo *)cur->data;
11639 gchar *file = NULL;
11641 file = procmsg_get_message_file_full(msginfo,
11644 compose_attach_append(compose, (const gchar *)file,
11645 (const gchar *)file, "message/rfc822", NULL);
11649 g_slist_free(list);
11653 static gboolean compose_drag_drop(GtkWidget *widget,
11654 GdkDragContext *drag_context,
11656 guint time, gpointer user_data)
11658 /* not handling this signal makes compose_insert_drag_received_cb
11663 static gboolean completion_set_focus_to_subject
11664 (GtkWidget *widget,
11665 GdkEventKey *event,
11668 cm_return_val_if_fail(compose != NULL, FALSE);
11670 /* make backtab move to subject field */
11671 if(event->keyval == GDK_KEY_ISO_Left_Tab) {
11672 gtk_widget_grab_focus(compose->subject_entry);
11678 static void compose_insert_drag_received_cb (GtkWidget *widget,
11679 GdkDragContext *drag_context,
11682 GtkSelectionData *data,
11685 gpointer user_data)
11687 Compose *compose = (Compose *)user_data;
11693 /* strangely, testing data->type == gdk_atom_intern("text/uri-list", TRUE)
11695 type = gtk_selection_data_get_data_type(data);
11696 if (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list")) {
11697 AlertValue val = G_ALERTDEFAULT;
11698 const gchar* ddata = (const gchar *)gtk_selection_data_get_data(data);
11700 list = uri_list_extract_filenames(ddata);
11701 num_files = g_list_length(list);
11702 if (list == NULL && strstr(ddata, "://")) {
11703 /* Assume a list of no files, and data has ://, is a remote link */
11704 gchar *tmpdata = g_strstrip(g_strdup(ddata));
11705 gchar *tmpfile = get_tmp_file();
11706 str_write_to_file(tmpdata, tmpfile, TRUE);
11708 compose_insert_file(compose, tmpfile);
11709 claws_unlink(tmpfile);
11711 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11712 compose_beautify_paragraph(compose, NULL, TRUE);
11715 switch (prefs_common.compose_dnd_mode) {
11716 case COMPOSE_DND_ASK:
11717 msg = g_strdup_printf(
11719 "Do you want to insert the contents of the file "
11720 "into the message body, or attach it to the email?",
11721 "Do you want to insert the contents of the %d files "
11722 "into the message body, or attach them to the email?",
11725 val = alertpanel_full(_("Insert or attach?"), msg,
11726 GTK_STOCK_CANCEL, _("_Insert"), _("_Attach"),
11728 TRUE, NULL, ALERT_QUESTION);
11731 case COMPOSE_DND_INSERT:
11732 val = G_ALERTALTERNATE;
11734 case COMPOSE_DND_ATTACH:
11735 val = G_ALERTOTHER;
11738 /* unexpected case */
11739 g_warning("error: unexpected compose_dnd_mode option value in compose_insert_drag_received_cb()");
11742 if (val & G_ALERTDISABLE) {
11743 val &= ~G_ALERTDISABLE;
11744 /* remember what action to perform by default, only if we don't click Cancel */
11745 if (val == G_ALERTALTERNATE)
11746 prefs_common.compose_dnd_mode = COMPOSE_DND_INSERT;
11747 else if (val == G_ALERTOTHER)
11748 prefs_common.compose_dnd_mode = COMPOSE_DND_ATTACH;
11751 if (val == G_ALERTDEFAULT || val == G_ALERTCANCEL) {
11752 gtk_drag_finish(drag_context, FALSE, FALSE, time);
11753 list_free_strings_full(list);
11755 } else if (val == G_ALERTOTHER) {
11756 compose_attach_drag_received_cb(widget, drag_context, x, y, data, info, time, user_data);
11757 list_free_strings_full(list);
11761 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11762 compose_insert_file(compose, (const gchar *)tmp->data);
11764 list_free_strings_full(list);
11765 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11770 static void compose_header_drag_received_cb (GtkWidget *widget,
11771 GdkDragContext *drag_context,
11774 GtkSelectionData *data,
11777 gpointer user_data)
11779 GtkEditable *entry = (GtkEditable *)user_data;
11780 const gchar *email = (const gchar *)gtk_selection_data_get_data(data);
11782 /* strangely, testing data->type == gdk_atom_intern("text/plain", TRUE)
11785 if (!strncmp(email, "mailto:", strlen("mailto:"))) {
11786 gchar *decoded=g_new(gchar, strlen(email));
11789 decode_uri(decoded, email + strlen("mailto:")); /* will fit */
11790 gtk_editable_delete_text(entry, 0, -1);
11791 gtk_editable_insert_text(entry, decoded, strlen(decoded), &start);
11792 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11796 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11799 static void compose_toggle_return_receipt_cb(GtkToggleAction *action, gpointer data)
11801 Compose *compose = (Compose *)data;
11803 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11804 compose->return_receipt = TRUE;
11806 compose->return_receipt = FALSE;
11809 static void compose_toggle_remove_refs_cb(GtkToggleAction *action, gpointer data)
11811 Compose *compose = (Compose *)data;
11813 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11814 compose->remove_references = TRUE;
11816 compose->remove_references = FALSE;
11819 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
11820 ComposeHeaderEntry *headerentry)
11822 gtk_entry_set_text(GTK_ENTRY(headerentry->entry), "");
11823 gtk_widget_modify_base(GTK_WIDGET(headerentry->entry), GTK_STATE_NORMAL, NULL);
11824 gtk_widget_modify_text(GTK_WIDGET(headerentry->entry), GTK_STATE_NORMAL, NULL);
11828 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
11829 GdkEventKey *event,
11830 ComposeHeaderEntry *headerentry)
11832 if ((g_slist_length(headerentry->compose->header_list) > 0) &&
11833 ((headerentry->headernum + 1) != headerentry->compose->header_nextrow) &&
11834 !(event->state & GDK_MODIFIER_MASK) &&
11835 (event->keyval == GDK_KEY_BackSpace) &&
11836 (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) == 0)) {
11837 gtk_container_remove
11838 (GTK_CONTAINER(headerentry->compose->header_table),
11839 headerentry->combo);
11840 gtk_container_remove
11841 (GTK_CONTAINER(headerentry->compose->header_table),
11842 headerentry->entry);
11843 headerentry->compose->header_list =
11844 g_slist_remove(headerentry->compose->header_list,
11846 g_free(headerentry);
11847 } else if (event->keyval == GDK_KEY_Tab) {
11848 if (headerentry->compose->header_last == headerentry) {
11849 /* Override default next focus, and give it to subject_entry
11850 * instead of notebook tabs
11852 g_signal_stop_emission_by_name(G_OBJECT(entry), "key-press-event");
11853 gtk_widget_grab_focus(headerentry->compose->subject_entry);
11860 static gboolean scroll_postpone(gpointer data)
11862 Compose *compose = (Compose *)data;
11864 if (compose->batch)
11867 GTK_EVENTS_FLUSH();
11868 compose_show_first_last_header(compose, FALSE);
11872 static void compose_headerentry_changed_cb(GtkWidget *entry,
11873 ComposeHeaderEntry *headerentry)
11875 if (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) != 0) {
11876 compose_create_header_entry(headerentry->compose);
11877 g_signal_handlers_disconnect_matched
11878 (G_OBJECT(entry), G_SIGNAL_MATCH_DATA,
11879 0, 0, NULL, NULL, headerentry);
11881 if (!headerentry->compose->batch)
11882 g_timeout_add(0, scroll_postpone, headerentry->compose);
11886 static gboolean compose_defer_auto_save_draft(Compose *compose)
11888 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
11889 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
11893 static void compose_show_first_last_header(Compose *compose, gboolean show_first)
11895 GtkAdjustment *vadj;
11897 cm_return_if_fail(compose);
11902 cm_return_if_fail(GTK_IS_WIDGET(compose->header_table));
11903 cm_return_if_fail(GTK_IS_VIEWPORT(gtk_widget_get_parent(compose->header_table)));
11904 vadj = gtk_viewport_get_vadjustment(GTK_VIEWPORT(
11905 gtk_widget_get_parent(compose->header_table)));
11906 gtk_adjustment_set_value(vadj, (show_first ?
11907 gtk_adjustment_get_lower(vadj) :
11908 (gtk_adjustment_get_upper(vadj) -
11909 gtk_adjustment_get_page_size(vadj))));
11910 gtk_adjustment_changed(vadj);
11913 static void text_inserted(GtkTextBuffer *buffer, GtkTextIter *iter,
11914 const gchar *text, gint len, Compose *compose)
11916 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data
11917 (G_OBJECT(compose->text), "paste_as_quotation"));
11920 cm_return_if_fail(text != NULL);
11922 g_signal_handlers_block_by_func(G_OBJECT(buffer),
11923 G_CALLBACK(text_inserted),
11925 if (paste_as_quotation) {
11927 const gchar *qmark;
11929 GtkTextIter start_iter;
11932 len = strlen(text);
11934 new_text = g_strndup(text, len);
11936 qmark = compose_quote_char_from_context(compose);
11938 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11939 gtk_text_buffer_place_cursor(buffer, iter);
11941 pos = gtk_text_iter_get_offset(iter);
11943 compose_quote_fmt(compose, NULL, "%Q", qmark, new_text, TRUE, FALSE,
11944 _("Quote format error at line %d."));
11945 quote_fmt_reset_vartable();
11947 g_object_set_data(G_OBJECT(compose->text), "paste_as_quotation",
11948 GINT_TO_POINTER(paste_as_quotation - 1));
11950 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11951 gtk_text_buffer_place_cursor(buffer, iter);
11952 gtk_text_buffer_delete_mark(buffer, mark);
11954 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, pos);
11955 mark = gtk_text_buffer_create_mark(buffer, NULL, &start_iter, FALSE);
11956 compose_beautify_paragraph(compose, &start_iter, FALSE);
11957 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark);
11958 gtk_text_buffer_delete_mark(buffer, mark);
11960 if (strcmp(text, "\n") || compose->automatic_break
11961 || gtk_text_iter_starts_line(iter)) {
11962 GtkTextIter before_ins;
11963 gtk_text_buffer_insert(buffer, iter, text, len);
11964 if (!strstr(text, "\n") && gtk_text_iter_has_tag(iter, compose->no_join_tag)) {
11965 before_ins = *iter;
11966 gtk_text_iter_backward_chars(&before_ins, len);
11967 gtk_text_buffer_remove_tag_by_name(buffer, "no_join", &before_ins, iter);
11970 /* check if the preceding is just whitespace or quote */
11971 GtkTextIter start_line;
11972 gchar *tmp = NULL, *quote = NULL;
11973 gint quote_len = 0, is_normal = 0;
11974 start_line = *iter;
11975 gtk_text_iter_set_line_offset(&start_line, 0);
11976 tmp = gtk_text_buffer_get_text(buffer, &start_line, iter, FALSE);
11979 if (*tmp == '\0') {
11982 quote = compose_get_quote_str(buffer, &start_line, "e_len);
11990 gtk_text_buffer_insert(buffer, iter, text, len);
11992 gtk_text_buffer_insert_with_tags_by_name(buffer,
11993 iter, text, len, "no_join", NULL);
11998 if (!paste_as_quotation) {
11999 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
12000 compose_beautify_paragraph(compose, iter, FALSE);
12001 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
12002 gtk_text_buffer_delete_mark(buffer, mark);
12005 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
12006 G_CALLBACK(text_inserted),
12008 g_signal_stop_emission_by_name(G_OBJECT(buffer), "insert-text");
12010 if (compose_can_autosave(compose) &&
12011 gtk_text_buffer_get_char_count(buffer) % prefs_common.autosave_length == 0 &&
12012 compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN /* disabled while loading */)
12013 compose->draft_timeout_tag = g_timeout_add
12014 (500, (GSourceFunc) compose_defer_auto_save_draft, compose);
12018 static void compose_check_all(GtkAction *action, gpointer data)
12020 Compose *compose = (Compose *)data;
12021 if (!compose->gtkaspell)
12024 if (gtk_widget_has_focus(compose->subject_entry))
12025 claws_spell_entry_check_all(
12026 CLAWS_SPELL_ENTRY(compose->subject_entry));
12028 gtkaspell_check_all(compose->gtkaspell);
12031 static void compose_highlight_all(GtkAction *action, gpointer data)
12033 Compose *compose = (Compose *)data;
12034 if (compose->gtkaspell) {
12035 claws_spell_entry_recheck_all(
12036 CLAWS_SPELL_ENTRY(compose->subject_entry));
12037 gtkaspell_highlight_all(compose->gtkaspell);
12041 static void compose_check_backwards(GtkAction *action, gpointer data)
12043 Compose *compose = (Compose *)data;
12044 if (!compose->gtkaspell) {
12045 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
12049 if (gtk_widget_has_focus(compose->subject_entry))
12050 claws_spell_entry_check_backwards(
12051 CLAWS_SPELL_ENTRY(compose->subject_entry));
12053 gtkaspell_check_backwards(compose->gtkaspell);
12056 static void compose_check_forwards_go(GtkAction *action, gpointer data)
12058 Compose *compose = (Compose *)data;
12059 if (!compose->gtkaspell) {
12060 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
12064 if (gtk_widget_has_focus(compose->subject_entry))
12065 claws_spell_entry_check_forwards_go(
12066 CLAWS_SPELL_ENTRY(compose->subject_entry));
12068 gtkaspell_check_forwards_go(compose->gtkaspell);
12073 *\brief Guess originating forward account from MsgInfo and several
12074 * "common preference" settings. Return NULL if no guess.
12076 static PrefsAccount *compose_find_account(MsgInfo *msginfo)
12078 PrefsAccount *account = NULL;
12080 cm_return_val_if_fail(msginfo, NULL);
12081 cm_return_val_if_fail(msginfo->folder, NULL);
12082 cm_return_val_if_fail(msginfo->folder->prefs, NULL);
12084 if (msginfo->folder->prefs->enable_default_account)
12085 account = account_find_from_id(msginfo->folder->prefs->default_account);
12087 if (!account && msginfo->to && prefs_common.forward_account_autosel) {
12089 Xstrdup_a(to, msginfo->to, return NULL);
12090 extract_address(to);
12091 account = account_find_from_address(to, FALSE);
12094 if (!account && prefs_common.forward_account_autosel) {
12096 if (!procheader_get_header_from_msginfo
12097 (msginfo, &cc, "Cc:")) {
12098 gchar *buf = cc + strlen("Cc:");
12099 extract_address(buf);
12100 account = account_find_from_address(buf, FALSE);
12105 if (!account && prefs_common.forward_account_autosel) {
12106 gchar *deliveredto = NULL;
12107 if (!procheader_get_header_from_msginfo
12108 (msginfo, &deliveredto, "Delivered-To:")) {
12109 gchar *buf = deliveredto + strlen("Delivered-To:");
12110 extract_address(buf);
12111 account = account_find_from_address(buf, FALSE);
12112 g_free(deliveredto);
12117 account = msginfo->folder->folder->account;
12122 gboolean compose_close(Compose *compose)
12126 cm_return_val_if_fail(compose, FALSE);
12128 if (!g_mutex_trylock(compose->mutex)) {
12129 /* we have to wait for the (possibly deferred by auto-save)
12130 * drafting to be done, before destroying the compose under
12132 debug_print("waiting for drafting to finish...\n");
12133 compose_allow_user_actions(compose, FALSE);
12134 if (compose->close_timeout_tag == 0) {
12135 compose->close_timeout_tag =
12136 g_timeout_add (500, (GSourceFunc) compose_close,
12142 if (compose->draft_timeout_tag >= 0) {
12143 g_source_remove(compose->draft_timeout_tag);
12144 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN;
12147 gtkut_widget_get_uposition(compose->window, &x, &y);
12148 if (!compose->batch) {
12149 prefs_common.compose_x = x;
12150 prefs_common.compose_y = y;
12152 g_mutex_unlock(compose->mutex);
12153 compose_destroy(compose);
12158 * Add entry field for each address in list.
12159 * \param compose E-Mail composition object.
12160 * \param listAddress List of (formatted) E-Mail addresses.
12162 static void compose_add_field_list( Compose *compose, GList *listAddress ) {
12165 node = listAddress;
12167 addr = ( gchar * ) node->data;
12168 compose_entry_append( compose, addr, COMPOSE_TO, PREF_NONE );
12169 node = g_list_next( node );
12173 static void compose_reply_from_messageview_real(MessageView *msgview, GSList *msginfo_list,
12174 guint action, gboolean opening_multiple)
12176 gchar *body = NULL;
12177 GSList *new_msglist = NULL;
12178 MsgInfo *tmp_msginfo = NULL;
12179 gboolean originally_enc = FALSE;
12180 gboolean originally_sig = FALSE;
12181 Compose *compose = NULL;
12182 gchar *s_system = NULL;
12184 cm_return_if_fail(msginfo_list != NULL);
12186 if (g_slist_length(msginfo_list) == 1 && !opening_multiple && msgview != NULL) {
12187 MimeInfo *mimeinfo = messageview_get_selected_mime_part(msgview);
12188 MsgInfo *orig_msginfo = (MsgInfo *)msginfo_list->data;
12190 if (mimeinfo != NULL && mimeinfo->type == MIMETYPE_MESSAGE &&
12191 !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
12192 tmp_msginfo = procmsg_msginfo_new_from_mimeinfo(
12193 orig_msginfo, mimeinfo);
12194 if (tmp_msginfo != NULL) {
12195 new_msglist = g_slist_append(NULL, tmp_msginfo);
12197 originally_enc = MSG_IS_ENCRYPTED(orig_msginfo->flags);
12198 privacy_msginfo_get_signed_state(orig_msginfo, &s_system);
12199 originally_sig = MSG_IS_SIGNED(orig_msginfo->flags);
12201 tmp_msginfo->folder = orig_msginfo->folder;
12202 tmp_msginfo->msgnum = orig_msginfo->msgnum;
12203 if (orig_msginfo->tags) {
12204 tmp_msginfo->tags = g_slist_copy(orig_msginfo->tags);
12205 tmp_msginfo->folder->tags_dirty = TRUE;
12211 if (!opening_multiple && msgview != NULL)
12212 body = messageview_get_selection(msgview);
12215 compose = compose_reply_mode((ComposeMode)action, new_msglist, body);
12216 procmsg_msginfo_free(&tmp_msginfo);
12217 g_slist_free(new_msglist);
12219 compose = compose_reply_mode((ComposeMode)action, msginfo_list, body);
12221 if (compose && originally_enc) {
12222 compose_force_encryption(compose, compose->account, FALSE, s_system);
12225 if (compose && originally_sig && compose->account->default_sign_reply) {
12226 compose_force_signing(compose, compose->account, s_system);
12230 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
12233 void compose_reply_from_messageview(MessageView *msgview, GSList *msginfo_list,
12236 if ((!prefs_common.forward_as_attachment || action != COMPOSE_FORWARD)
12237 && msginfo_list != NULL
12238 && action != COMPOSE_FORWARD_AS_ATTACH && g_slist_length(msginfo_list) > 1) {
12239 GSList *cur = msginfo_list;
12240 gchar *msg = g_strdup_printf(_("You are about to reply to %d "
12241 "messages. Opening the windows "
12242 "could take some time. Do you "
12243 "want to continue?"),
12244 g_slist_length(msginfo_list));
12245 if (g_slist_length(msginfo_list) > 9
12246 && alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, GTK_STOCK_YES, NULL,
12247 ALERTFOCUS_SECOND) != G_ALERTALTERNATE) {
12252 /* We'll open multiple compose windows */
12253 /* let the WM place the next windows */
12254 compose_force_window_origin = FALSE;
12255 for (; cur; cur = cur->next) {
12257 tmplist.data = cur->data;
12258 tmplist.next = NULL;
12259 compose_reply_from_messageview_real(msgview, &tmplist, action, TRUE);
12261 compose_force_window_origin = TRUE;
12263 /* forwarding multiple mails as attachments is done via a
12264 * single compose window */
12265 if (msginfo_list != NULL) {
12266 compose_reply_from_messageview_real(msgview, msginfo_list, action, FALSE);
12267 } else if (msgview != NULL) {
12269 tmplist.data = msgview->msginfo;
12270 tmplist.next = NULL;
12271 compose_reply_from_messageview_real(msgview, &tmplist, action, FALSE);
12273 debug_print("Nothing to reply to\n");
12278 void compose_check_for_email_account(Compose *compose)
12280 PrefsAccount *ac = NULL, *curr = NULL;
12286 if (compose->account && compose->account->protocol == A_NNTP) {
12287 ac = account_get_cur_account();
12288 if (ac->protocol == A_NNTP) {
12289 list = account_get_list();
12291 for( ; list != NULL ; list = g_list_next(list)) {
12292 curr = (PrefsAccount *) list->data;
12293 if (curr->protocol != A_NNTP) {
12299 combobox_select_by_data(GTK_COMBO_BOX(compose->account_combo),
12304 void compose_reply_to_address(MessageView *msgview, MsgInfo *msginfo,
12305 const gchar *address)
12307 GSList *msginfo_list = NULL;
12308 gchar *body = messageview_get_selection(msgview);
12311 msginfo_list = g_slist_prepend(msginfo_list, msginfo);
12313 compose = compose_reply_mode(COMPOSE_REPLY_TO_ADDRESS, msginfo_list, body);
12314 compose_check_for_email_account(compose);
12315 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
12316 compose_entry_append(compose, address, COMPOSE_TO, PREF_NONE);
12317 compose_reply_set_subject(compose, msginfo);
12320 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
12323 void compose_set_position(Compose *compose, gint pos)
12325 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
12327 gtkut_text_view_set_position(text, pos);
12330 gboolean compose_search_string(Compose *compose,
12331 const gchar *str, gboolean case_sens)
12333 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
12335 return gtkut_text_view_search_string(text, str, case_sens);
12338 gboolean compose_search_string_backward(Compose *compose,
12339 const gchar *str, gboolean case_sens)
12341 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
12343 return gtkut_text_view_search_string_backward(text, str, case_sens);
12346 /* allocate a msginfo structure and populate its data from a compose data structure */
12347 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose)
12349 MsgInfo *newmsginfo;
12351 gchar date[RFC822_DATE_BUFFSIZE];
12353 cm_return_val_if_fail( compose != NULL, NULL );
12355 newmsginfo = procmsg_msginfo_new();
12358 get_rfc822_date(date, sizeof(date));
12359 newmsginfo->date = g_strdup(date);
12362 if (compose->from_name) {
12363 newmsginfo->from = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
12364 newmsginfo->fromname = procheader_get_fromname(newmsginfo->from);
12368 if (compose->subject_entry)
12369 newmsginfo->subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
12371 /* to, cc, reply-to, newsgroups */
12372 for (list = compose->header_list; list; list = list->next) {
12373 gchar *header = gtk_editable_get_chars(
12375 gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
12376 gchar *entry = gtk_editable_get_chars(
12377 GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
12379 if ( strcasecmp(header, prefs_common_translated_header_name("To:")) == 0 ) {
12380 if ( newmsginfo->to == NULL ) {
12381 newmsginfo->to = g_strdup(entry);
12382 } else if (entry && *entry) {
12383 gchar *tmp = g_strconcat(newmsginfo->to, ", ", entry, NULL);
12384 g_free(newmsginfo->to);
12385 newmsginfo->to = tmp;
12388 if ( strcasecmp(header, prefs_common_translated_header_name("Cc:")) == 0 ) {
12389 if ( newmsginfo->cc == NULL ) {
12390 newmsginfo->cc = g_strdup(entry);
12391 } else if (entry && *entry) {
12392 gchar *tmp = g_strconcat(newmsginfo->cc, ", ", entry, NULL);
12393 g_free(newmsginfo->cc);
12394 newmsginfo->cc = tmp;
12397 if ( strcasecmp(header,
12398 prefs_common_translated_header_name("Newsgroups:")) == 0 ) {
12399 if ( newmsginfo->newsgroups == NULL ) {
12400 newmsginfo->newsgroups = g_strdup(entry);
12401 } else if (entry && *entry) {
12402 gchar *tmp = g_strconcat(newmsginfo->newsgroups, ", ", entry, NULL);
12403 g_free(newmsginfo->newsgroups);
12404 newmsginfo->newsgroups = tmp;
12412 /* other data is unset */
12418 /* update compose's dictionaries from folder dict settings */
12419 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
12420 FolderItem *folder_item)
12422 cm_return_if_fail(compose != NULL);
12424 if (compose->gtkaspell && folder_item && folder_item->prefs) {
12425 FolderItemPrefs *prefs = folder_item->prefs;
12427 if (prefs->enable_default_dictionary)
12428 gtkaspell_change_dict(compose->gtkaspell,
12429 prefs->default_dictionary, FALSE);
12430 if (folder_item->prefs->enable_default_alt_dictionary)
12431 gtkaspell_change_alt_dict(compose->gtkaspell,
12432 prefs->default_alt_dictionary);
12433 if (prefs->enable_default_dictionary
12434 || prefs->enable_default_alt_dictionary)
12435 compose_spell_menu_changed(compose);
12440 static void compose_subject_entry_activated(GtkWidget *widget, gpointer data)
12442 Compose *compose = (Compose *)data;
12444 cm_return_if_fail(compose != NULL);
12446 gtk_widget_grab_focus(compose->text);
12449 static void from_name_activate_cb(GtkWidget *widget, gpointer data)
12451 gtk_combo_box_popup(GTK_COMBO_BOX(data));