2 * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2016 Hiroyuki Yamamoto and the Claws Mail team
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 3 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
21 #include "claws-features.h"
26 #ifndef PANGO_ENABLE_ENGINE
27 # define PANGO_ENABLE_ENGINE
31 #include <glib/gi18n.h>
32 #include <gdk/gdkkeysyms.h>
35 #include <pango/pango-break.h>
40 #include <sys/types.h>
46 # include <sys/wait.h>
50 #ifndef G_OS_WIN32 /* fixme we should have a configure test. */
54 #if (HAVE_WCTYPE_H && HAVE_WCHAR_H)
61 #include "mainwindow.h"
63 #ifndef USE_ALT_ADDRBOOK
64 #include "addressbook.h"
66 #include "addressbook-dbus.h"
67 #include "addressadd.h"
69 #include "folderview.h"
72 #include "stock_pixmap.h"
73 #include "send_message.h"
76 #include "customheader.h"
77 #include "prefs_common.h"
78 #include "prefs_account.h"
82 #include "procheader.h"
84 #include "statusbar.h"
86 #include "quoted-printable.h"
90 #include "gtkshruler.h"
92 #include "alertpanel.h"
93 #include "manage_window.h"
95 #include "folder_item_prefs.h"
96 #include "addr_compl.h"
97 #include "quote_fmt.h"
99 #include "foldersel.h"
102 #include "message_search.h"
103 #include "combobox.h"
107 #include "autofaces.h"
108 #include "spell_entry.h"
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 /* override from name if mailto asked for it */
1015 gtk_entry_set_text(GTK_ENTRY(compose->from_name), mailto_from);
1016 g_free(mailto_from);
1018 /* override from name according to folder properties */
1019 if (item && item->prefs &&
1020 item->prefs->compose_with_format &&
1021 item->prefs->compose_override_from_format &&
1022 *item->prefs->compose_override_from_format != '\0') {
1027 dummyinfo = compose_msginfo_new_from_compose(compose);
1029 /* decode \-escape sequences in the internal representation of the quote format */
1030 tmp = g_malloc(strlen(item->prefs->compose_override_from_format)+1);
1031 pref_get_unescaped_pref(tmp, item->prefs->compose_override_from_format);
1034 quote_fmt_init(dummyinfo, NULL, NULL, FALSE, compose->account, FALSE,
1035 compose->gtkaspell);
1037 quote_fmt_init(dummyinfo, NULL, NULL, FALSE, compose->account, FALSE);
1039 quote_fmt_scan_string(tmp);
1042 buf = quote_fmt_get_buffer();
1044 alertpanel_error(_("New message From format error."));
1046 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1047 quote_fmt_reset_vartable();
1048 quote_fmtlex_destroy();
1053 compose->replyinfo = NULL;
1054 compose->fwdinfo = NULL;
1056 textview = GTK_TEXT_VIEW(compose->text);
1057 textbuf = gtk_text_view_get_buffer(textview);
1058 compose_create_tags(textview, compose);
1060 undo_block(compose->undostruct);
1062 compose_set_dictionaries_from_folder_prefs(compose, item);
1065 if (account->auto_sig)
1066 compose_insert_sig(compose, FALSE);
1067 gtk_text_buffer_get_start_iter(textbuf, &iter);
1068 gtk_text_buffer_place_cursor(textbuf, &iter);
1070 if (account->protocol != A_NNTP) {
1071 if (mailto && *mailto != '\0') {
1072 mfield = compose_entries_set(compose, mailto, COMPOSE_TO);
1075 compose_set_folder_prefs(compose, item, TRUE);
1077 if (item && item->ret_rcpt) {
1078 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
1081 if (mailto && *mailto != '\0') {
1082 if (!strchr(mailto, '@'))
1083 mfield = compose_entries_set(compose, mailto, COMPOSE_NEWSGROUPS);
1085 mfield = compose_entries_set(compose, mailto, COMPOSE_TO);
1086 } else if (item && FOLDER_CLASS(item->folder) == news_get_class()) {
1087 compose_entry_append(compose, item->path, COMPOSE_NEWSGROUPS, PREF_FOLDER);
1088 mfield = TO_FIELD_PRESENT;
1091 * CLAWS: just don't allow return receipt request, even if the user
1092 * may want to send an email. simple but foolproof.
1094 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", FALSE);
1096 compose_add_field_list( compose, listAddress );
1098 if (item && item->prefs && item->prefs->compose_with_format) {
1099 subject_format = item->prefs->compose_subject_format;
1100 body_format = item->prefs->compose_body_format;
1101 } else if (account->compose_with_format) {
1102 subject_format = account->compose_subject_format;
1103 body_format = account->compose_body_format;
1104 } else if (prefs_common.compose_with_format) {
1105 subject_format = prefs_common.compose_subject_format;
1106 body_format = prefs_common.compose_body_format;
1109 if (subject_format || body_format) {
1112 && *subject_format != '\0' )
1114 gchar *subject = NULL;
1119 dummyinfo = compose_msginfo_new_from_compose(compose);
1121 /* decode \-escape sequences in the internal representation of the quote format */
1122 tmp = g_malloc(strlen(subject_format)+1);
1123 pref_get_unescaped_pref(tmp, subject_format);
1125 subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1127 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account, FALSE,
1128 compose->gtkaspell);
1130 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account, FALSE);
1132 quote_fmt_scan_string(tmp);
1135 buf = quote_fmt_get_buffer();
1137 alertpanel_error(_("New message subject format error."));
1139 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1140 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1141 quote_fmt_reset_vartable();
1142 quote_fmtlex_destroy();
1146 mfield = SUBJECT_FIELD_PRESENT;
1150 && *body_format != '\0' )
1153 GtkTextBuffer *buffer;
1154 GtkTextIter start, end;
1158 dummyinfo = compose_msginfo_new_from_compose(compose);
1160 text = GTK_TEXT_VIEW(compose->text);
1161 buffer = gtk_text_view_get_buffer(text);
1162 gtk_text_buffer_get_start_iter(buffer, &start);
1163 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
1164 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
1166 compose_quote_fmt(compose, dummyinfo,
1168 NULL, tmp, FALSE, TRUE,
1169 _("The body of the \"New message\" template has an error at line %d."));
1170 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1171 quote_fmt_reset_vartable();
1175 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1176 gtkaspell_highlight_all(compose->gtkaspell);
1178 mfield = BODY_FIELD_PRESENT;
1182 procmsg_msginfo_free( &dummyinfo );
1188 for (curr = attach_files ; curr != NULL ; curr = curr->next) {
1189 ainfo = (AttachInfo *) curr->data;
1191 compose_insert_file(compose, ainfo->file);
1193 compose_attach_append(compose, ainfo->file, ainfo->file,
1194 ainfo->content_type, ainfo->charset);
1198 compose_show_first_last_header(compose, TRUE);
1200 /* Set save folder */
1201 if (item && item->prefs && item->prefs->save_copy_to_folder) {
1202 gchar *folderidentifier;
1204 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
1205 folderidentifier = folder_item_get_identifier(item);
1206 compose_set_save_to(compose, folderidentifier);
1207 g_free(folderidentifier);
1210 /* Place cursor according to provided input (mfield) */
1212 case NO_FIELD_PRESENT:
1213 if (compose->header_last)
1214 gtk_widget_grab_focus(compose->header_last->entry);
1216 case TO_FIELD_PRESENT:
1217 buf = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1219 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1222 gtk_widget_grab_focus(compose->subject_entry);
1224 case SUBJECT_FIELD_PRESENT:
1225 textview = GTK_TEXT_VIEW(compose->text);
1228 textbuf = gtk_text_view_get_buffer(textview);
1231 mark = gtk_text_buffer_get_insert(textbuf);
1232 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
1233 gtk_text_buffer_insert(textbuf, &iter, "", -1);
1235 * SUBJECT_FIELD_PRESENT and BODY_FIELD_PRESENT
1236 * only defers where it comes to the variable body
1237 * is not null. If no body is present compose->text
1238 * will be null in which case you cannot place the
1239 * cursor inside the component so. An empty component
1240 * is therefore created before placing the cursor
1242 case BODY_FIELD_PRESENT:
1243 cursor_pos = quote_fmt_get_cursor_pos();
1244 if (cursor_pos == -1)
1245 gtk_widget_grab_focus(compose->header_last->entry);
1247 gtk_widget_grab_focus(compose->text);
1251 undo_unblock(compose->undostruct);
1253 if (prefs_common.auto_exteditor)
1254 compose_exec_ext_editor(compose);
1256 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
1258 SCROLL_TO_CURSOR(compose);
1260 compose->modified = FALSE;
1261 compose_set_title(compose);
1263 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
1268 static void compose_force_encryption(Compose *compose, PrefsAccount *account,
1269 gboolean override_pref, const gchar *system)
1271 const gchar *privacy = NULL;
1273 cm_return_if_fail(compose != NULL);
1274 cm_return_if_fail(account != NULL);
1276 if (override_pref == FALSE && account->default_encrypt_reply == FALSE)
1279 if (account->default_privacy_system && strlen(account->default_privacy_system))
1280 privacy = account->default_privacy_system;
1284 GSList *privacy_avail = privacy_get_system_ids();
1285 if (privacy_avail && g_slist_length(privacy_avail)) {
1286 privacy = (gchar *)(privacy_avail->data);
1288 g_slist_free_full(privacy_avail, g_free);
1290 if (privacy != NULL) {
1292 g_free(compose->privacy_system);
1293 compose->privacy_system = NULL;
1294 g_free(compose->encdata);
1295 compose->encdata = NULL;
1297 if (compose->privacy_system == NULL)
1298 compose->privacy_system = g_strdup(privacy);
1299 else if (*(compose->privacy_system) == '\0') {
1300 g_free(compose->privacy_system);
1301 g_free(compose->encdata);
1302 compose->encdata = NULL;
1303 compose->privacy_system = g_strdup(privacy);
1305 compose_update_privacy_system_menu_item(compose, FALSE);
1306 compose_use_encryption(compose, TRUE);
1310 static void compose_force_signing(Compose *compose, PrefsAccount *account, const gchar *system)
1312 const gchar *privacy = NULL;
1314 if (account->default_privacy_system && strlen(account->default_privacy_system))
1315 privacy = account->default_privacy_system;
1319 GSList *privacy_avail = privacy_get_system_ids();
1320 if (privacy_avail && g_slist_length(privacy_avail)) {
1321 privacy = (gchar *)(privacy_avail->data);
1325 if (privacy != NULL) {
1327 g_free(compose->privacy_system);
1328 compose->privacy_system = NULL;
1329 g_free(compose->encdata);
1330 compose->encdata = NULL;
1332 if (compose->privacy_system == NULL)
1333 compose->privacy_system = g_strdup(privacy);
1334 compose_update_privacy_system_menu_item(compose, FALSE);
1335 compose_use_signing(compose, TRUE);
1339 static Compose *compose_reply_mode(ComposeMode mode, GSList *msginfo_list, gchar *body)
1343 Compose *compose = NULL;
1345 cm_return_val_if_fail(msginfo_list != NULL, NULL);
1347 msginfo = (MsgInfo*)g_slist_nth_data(msginfo_list, 0);
1348 cm_return_val_if_fail(msginfo != NULL, NULL);
1350 list_len = g_slist_length(msginfo_list);
1354 case COMPOSE_REPLY_TO_ADDRESS:
1355 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1356 FALSE, prefs_common.default_reply_list, FALSE, body);
1358 case COMPOSE_REPLY_WITH_QUOTE:
1359 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1360 FALSE, prefs_common.default_reply_list, FALSE, body);
1362 case COMPOSE_REPLY_WITHOUT_QUOTE:
1363 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1364 FALSE, prefs_common.default_reply_list, FALSE, NULL);
1366 case COMPOSE_REPLY_TO_SENDER:
1367 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1368 FALSE, FALSE, TRUE, body);
1370 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1371 compose = compose_followup_and_reply_to(msginfo,
1372 COMPOSE_QUOTE_CHECK,
1373 FALSE, FALSE, body);
1375 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1376 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1377 FALSE, FALSE, TRUE, body);
1379 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1380 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1381 FALSE, FALSE, TRUE, NULL);
1383 case COMPOSE_REPLY_TO_ALL:
1384 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1385 TRUE, FALSE, FALSE, body);
1387 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1388 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1389 TRUE, FALSE, FALSE, body);
1391 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1392 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1393 TRUE, FALSE, FALSE, NULL);
1395 case COMPOSE_REPLY_TO_LIST:
1396 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1397 FALSE, TRUE, FALSE, body);
1399 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1400 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1401 FALSE, TRUE, FALSE, body);
1403 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1404 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1405 FALSE, TRUE, FALSE, NULL);
1407 case COMPOSE_FORWARD:
1408 if (prefs_common.forward_as_attachment) {
1409 compose = compose_reply_mode(COMPOSE_FORWARD_AS_ATTACH, msginfo_list, body);
1412 compose = compose_reply_mode(COMPOSE_FORWARD_INLINE, msginfo_list, body);
1416 case COMPOSE_FORWARD_INLINE:
1417 /* check if we reply to more than one Message */
1418 if (list_len == 1) {
1419 compose = compose_forward(NULL, msginfo, FALSE, body, FALSE, FALSE);
1422 /* more messages FALL THROUGH */
1423 case COMPOSE_FORWARD_AS_ATTACH:
1424 compose = compose_forward_multiple(NULL, msginfo_list);
1426 case COMPOSE_REDIRECT:
1427 compose = compose_redirect(NULL, msginfo, FALSE);
1430 g_warning("compose_reply_mode(): invalid Compose Mode: %d", mode);
1433 if (compose == NULL) {
1434 alertpanel_error(_("Unable to reply. The original email probably doesn't exist."));
1438 compose->rmode = mode;
1439 switch (compose->rmode) {
1441 case COMPOSE_REPLY_WITH_QUOTE:
1442 case COMPOSE_REPLY_WITHOUT_QUOTE:
1443 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1444 debug_print("reply mode Normal\n");
1445 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/Normal", TRUE);
1446 compose_reply_change_mode(compose, COMPOSE_REPLY); /* force update */
1448 case COMPOSE_REPLY_TO_SENDER:
1449 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1450 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1451 debug_print("reply mode Sender\n");
1452 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/Sender", TRUE);
1454 case COMPOSE_REPLY_TO_ALL:
1455 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1456 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1457 debug_print("reply mode All\n");
1458 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/All", TRUE);
1460 case COMPOSE_REPLY_TO_LIST:
1461 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1462 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1463 debug_print("reply mode List\n");
1464 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/List", TRUE);
1466 case COMPOSE_REPLY_TO_ADDRESS:
1467 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", FALSE);
1475 static Compose *compose_reply(MsgInfo *msginfo,
1476 ComposeQuoteMode quote_mode,
1482 return compose_generic_reply(msginfo, quote_mode, to_all, to_ml,
1483 to_sender, FALSE, body);
1486 static Compose *compose_followup_and_reply_to(MsgInfo *msginfo,
1487 ComposeQuoteMode quote_mode,
1492 return compose_generic_reply(msginfo, quote_mode, to_all, FALSE,
1493 to_sender, TRUE, body);
1496 static void compose_extract_original_charset(Compose *compose)
1498 MsgInfo *info = NULL;
1499 if (compose->replyinfo) {
1500 info = compose->replyinfo;
1501 } else if (compose->fwdinfo) {
1502 info = compose->fwdinfo;
1503 } else if (compose->targetinfo) {
1504 info = compose->targetinfo;
1507 MimeInfo *mimeinfo = procmime_scan_message_short(info);
1508 MimeInfo *partinfo = mimeinfo;
1509 while (partinfo && partinfo->type != MIMETYPE_TEXT)
1510 partinfo = procmime_mimeinfo_next(partinfo);
1512 compose->orig_charset =
1513 g_strdup(procmime_mimeinfo_get_parameter(
1514 partinfo, "charset"));
1516 procmime_mimeinfo_free_all(&mimeinfo);
1520 #define SIGNAL_BLOCK(buffer) { \
1521 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1522 G_CALLBACK(compose_changed_cb), \
1524 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1525 G_CALLBACK(text_inserted), \
1529 #define SIGNAL_UNBLOCK(buffer) { \
1530 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1531 G_CALLBACK(compose_changed_cb), \
1533 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1534 G_CALLBACK(text_inserted), \
1538 static Compose *compose_generic_reply(MsgInfo *msginfo,
1539 ComposeQuoteMode quote_mode,
1540 gboolean to_all, gboolean to_ml,
1542 gboolean followup_and_reply_to,
1546 PrefsAccount *account = NULL;
1547 GtkTextView *textview;
1548 GtkTextBuffer *textbuf;
1549 gboolean quote = FALSE;
1550 const gchar *qmark = NULL;
1551 const gchar *body_fmt = NULL;
1552 gchar *s_system = NULL;
1554 cm_return_val_if_fail(msginfo != NULL, NULL);
1555 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
1557 account = account_get_reply_account(msginfo, prefs_common.reply_account_autosel);
1559 cm_return_val_if_fail(account != NULL, NULL);
1561 compose = compose_create(account, msginfo->folder, COMPOSE_REPLY, FALSE);
1562 compose_apply_folder_privacy_settings(compose, msginfo->folder);
1564 compose->updating = TRUE;
1566 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
1567 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", TRUE);
1569 compose->replyinfo = procmsg_msginfo_get_full_info(msginfo);
1570 if (!compose->replyinfo)
1571 compose->replyinfo = procmsg_msginfo_copy(msginfo);
1573 compose_extract_original_charset(compose);
1575 if (msginfo->folder && msginfo->folder->ret_rcpt)
1576 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
1578 /* Set save folder */
1579 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1580 gchar *folderidentifier;
1582 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1583 folderidentifier = folder_item_get_identifier(msginfo->folder);
1584 compose_set_save_to(compose, folderidentifier);
1585 g_free(folderidentifier);
1588 if (compose_parse_header(compose, msginfo) < 0) {
1589 compose->updating = FALSE;
1590 compose_destroy(compose);
1594 /* override from name according to folder properties */
1595 if (msginfo->folder && msginfo->folder->prefs &&
1596 msginfo->folder->prefs->reply_with_format &&
1597 msginfo->folder->prefs->reply_override_from_format &&
1598 *msginfo->folder->prefs->reply_override_from_format != '\0') {
1603 /* decode \-escape sequences in the internal representation of the quote format */
1604 tmp = g_malloc(strlen(msginfo->folder->prefs->reply_override_from_format)+1);
1605 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->reply_override_from_format);
1608 quote_fmt_init(compose->replyinfo, NULL, NULL, FALSE, compose->account, FALSE,
1609 compose->gtkaspell);
1611 quote_fmt_init(compose->replyinfo, NULL, NULL, FALSE, compose->account, FALSE);
1613 quote_fmt_scan_string(tmp);
1616 buf = quote_fmt_get_buffer();
1618 alertpanel_error(_("The \"From\" field of the \"Reply\" template contains an invalid email address."));
1620 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1621 quote_fmt_reset_vartable();
1622 quote_fmtlex_destroy();
1627 textview = (GTK_TEXT_VIEW(compose->text));
1628 textbuf = gtk_text_view_get_buffer(textview);
1629 compose_create_tags(textview, compose);
1631 undo_block(compose->undostruct);
1633 compose_set_dictionaries_from_folder_prefs(compose, msginfo->folder);
1634 gtkaspell_block_check(compose->gtkaspell);
1637 if (quote_mode == COMPOSE_QUOTE_FORCED ||
1638 (quote_mode == COMPOSE_QUOTE_CHECK && prefs_common.reply_with_quote)) {
1639 /* use the reply format of folder (if enabled), or the account's one
1640 (if enabled) or fallback to the global reply format, which is always
1641 enabled (even if empty), and use the relevant quotemark */
1643 if (msginfo->folder && msginfo->folder->prefs &&
1644 msginfo->folder->prefs->reply_with_format) {
1645 qmark = msginfo->folder->prefs->reply_quotemark;
1646 body_fmt = msginfo->folder->prefs->reply_body_format;
1648 } else if (account->reply_with_format) {
1649 qmark = account->reply_quotemark;
1650 body_fmt = account->reply_body_format;
1653 qmark = prefs_common.quotemark;
1654 if (prefs_common.quotefmt && *prefs_common.quotefmt)
1655 body_fmt = gettext(prefs_common.quotefmt);
1662 /* empty quotemark is not allowed */
1663 if (qmark == NULL || *qmark == '\0')
1665 compose_quote_fmt(compose, compose->replyinfo,
1666 body_fmt, qmark, body, FALSE, TRUE,
1667 _("The body of the \"Reply\" template has an error at line %d."));
1668 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1669 quote_fmt_reset_vartable();
1672 if (MSG_IS_ENCRYPTED(compose->replyinfo->flags)) {
1673 compose_force_encryption(compose, account, FALSE, s_system);
1676 privacy_msginfo_get_signed_state(compose->replyinfo, &s_system);
1677 if (MSG_IS_SIGNED(compose->replyinfo->flags) && account->default_sign_reply) {
1678 compose_force_signing(compose, account, s_system);
1682 SIGNAL_BLOCK(textbuf);
1684 if (account->auto_sig)
1685 compose_insert_sig(compose, FALSE);
1687 compose_wrap_all(compose);
1690 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1691 gtkaspell_highlight_all(compose->gtkaspell);
1692 gtkaspell_unblock_check(compose->gtkaspell);
1694 SIGNAL_UNBLOCK(textbuf);
1696 gtk_widget_grab_focus(compose->text);
1698 undo_unblock(compose->undostruct);
1700 if (prefs_common.auto_exteditor)
1701 compose_exec_ext_editor(compose);
1703 compose->modified = FALSE;
1704 compose_set_title(compose);
1706 compose->updating = FALSE;
1707 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
1708 SCROLL_TO_CURSOR(compose);
1710 if (compose->deferred_destroy) {
1711 compose_destroy(compose);
1719 #define INSERT_FW_HEADER(var, hdr) \
1720 if (msginfo->var && *msginfo->var) { \
1721 gtk_stext_insert(text, NULL, NULL, NULL, hdr, -1); \
1722 gtk_stext_insert(text, NULL, NULL, NULL, msginfo->var, -1); \
1723 gtk_stext_insert(text, NULL, NULL, NULL, "\n", 1); \
1726 Compose *compose_forward(PrefsAccount *account, MsgInfo *msginfo,
1727 gboolean as_attach, const gchar *body,
1728 gboolean no_extedit,
1732 GtkTextView *textview;
1733 GtkTextBuffer *textbuf;
1734 gint cursor_pos = -1;
1737 cm_return_val_if_fail(msginfo != NULL, NULL);
1738 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
1740 if (!account && !(account = compose_find_account(msginfo)))
1741 account = cur_account;
1743 if (!prefs_common.forward_as_attachment)
1744 mode = COMPOSE_FORWARD_INLINE;
1746 mode = COMPOSE_FORWARD;
1747 compose = compose_create(account, msginfo->folder, mode, batch);
1748 compose_apply_folder_privacy_settings(compose, msginfo->folder);
1750 compose->updating = TRUE;
1751 compose->fwdinfo = procmsg_msginfo_get_full_info(msginfo);
1752 if (!compose->fwdinfo)
1753 compose->fwdinfo = procmsg_msginfo_copy(msginfo);
1755 compose_extract_original_charset(compose);
1757 if (msginfo->subject && *msginfo->subject) {
1758 gchar *buf, *buf2, *p;
1760 buf = p = g_strdup(msginfo->subject);
1761 p += subject_get_prefix_length(p);
1762 memmove(buf, p, strlen(p) + 1);
1764 buf2 = g_strdup_printf("Fw: %s", buf);
1765 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
1771 /* override from name according to folder properties */
1772 if (msginfo->folder && msginfo->folder->prefs &&
1773 msginfo->folder->prefs->forward_with_format &&
1774 msginfo->folder->prefs->forward_override_from_format &&
1775 *msginfo->folder->prefs->forward_override_from_format != '\0') {
1779 MsgInfo *full_msginfo = NULL;
1782 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1784 full_msginfo = procmsg_msginfo_copy(msginfo);
1786 /* decode \-escape sequences in the internal representation of the quote format */
1787 tmp = g_malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
1788 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
1791 gtkaspell_block_check(compose->gtkaspell);
1792 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE,
1793 compose->gtkaspell);
1795 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE);
1797 quote_fmt_scan_string(tmp);
1800 buf = quote_fmt_get_buffer();
1802 alertpanel_error(_("The \"From\" field of the \"Forward\" template contains an invalid email address."));
1804 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1805 quote_fmt_reset_vartable();
1806 quote_fmtlex_destroy();
1809 procmsg_msginfo_free(&full_msginfo);
1812 textview = GTK_TEXT_VIEW(compose->text);
1813 textbuf = gtk_text_view_get_buffer(textview);
1814 compose_create_tags(textview, compose);
1816 undo_block(compose->undostruct);
1820 msgfile = procmsg_get_message_file(msginfo);
1821 if (!is_file_exist(msgfile))
1822 g_warning("%s: file does not exist", msgfile);
1824 compose_attach_append(compose, msgfile, msgfile,
1825 "message/rfc822", NULL);
1829 const gchar *qmark = NULL;
1830 const gchar *body_fmt = NULL;
1831 MsgInfo *full_msginfo;
1833 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1835 full_msginfo = procmsg_msginfo_copy(msginfo);
1837 /* use the forward format of folder (if enabled), or the account's one
1838 (if enabled) or fallback to the global forward format, which is always
1839 enabled (even if empty), and use the relevant quotemark */
1840 if (msginfo->folder && msginfo->folder->prefs &&
1841 msginfo->folder->prefs->forward_with_format) {
1842 qmark = msginfo->folder->prefs->forward_quotemark;
1843 body_fmt = msginfo->folder->prefs->forward_body_format;
1845 } else if (account->forward_with_format) {
1846 qmark = account->forward_quotemark;
1847 body_fmt = account->forward_body_format;
1850 qmark = prefs_common.fw_quotemark;
1851 if (prefs_common.fw_quotefmt && *prefs_common.fw_quotefmt)
1852 body_fmt = gettext(prefs_common.fw_quotefmt);
1857 /* empty quotemark is not allowed */
1858 if (qmark == NULL || *qmark == '\0')
1861 compose_quote_fmt(compose, full_msginfo,
1862 body_fmt, qmark, body, FALSE, TRUE,
1863 _("The body of the \"Forward\" template has an error at line %d."));
1864 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1865 quote_fmt_reset_vartable();
1866 compose_attach_parts(compose, msginfo);
1868 procmsg_msginfo_free(&full_msginfo);
1871 SIGNAL_BLOCK(textbuf);
1873 if (account->auto_sig)
1874 compose_insert_sig(compose, FALSE);
1876 compose_wrap_all(compose);
1879 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1880 gtkaspell_highlight_all(compose->gtkaspell);
1881 gtkaspell_unblock_check(compose->gtkaspell);
1883 SIGNAL_UNBLOCK(textbuf);
1885 cursor_pos = quote_fmt_get_cursor_pos();
1886 if (cursor_pos == -1)
1887 gtk_widget_grab_focus(compose->header_last->entry);
1889 gtk_widget_grab_focus(compose->text);
1891 if (!no_extedit && prefs_common.auto_exteditor)
1892 compose_exec_ext_editor(compose);
1895 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1896 gchar *folderidentifier;
1898 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1899 folderidentifier = folder_item_get_identifier(msginfo->folder);
1900 compose_set_save_to(compose, folderidentifier);
1901 g_free(folderidentifier);
1904 undo_unblock(compose->undostruct);
1906 compose->modified = FALSE;
1907 compose_set_title(compose);
1909 compose->updating = FALSE;
1910 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
1911 SCROLL_TO_CURSOR(compose);
1913 if (compose->deferred_destroy) {
1914 compose_destroy(compose);
1918 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
1923 #undef INSERT_FW_HEADER
1925 static Compose *compose_forward_multiple(PrefsAccount *account, GSList *msginfo_list)
1928 GtkTextView *textview;
1929 GtkTextBuffer *textbuf;
1933 gboolean single_mail = TRUE;
1935 cm_return_val_if_fail(msginfo_list != NULL, NULL);
1937 if (g_slist_length(msginfo_list) > 1)
1938 single_mail = FALSE;
1940 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next)
1941 if (((MsgInfo *)msginfo->data)->folder == NULL)
1944 /* guess account from first selected message */
1946 !(account = compose_find_account(msginfo_list->data)))
1947 account = cur_account;
1949 cm_return_val_if_fail(account != NULL, NULL);
1951 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
1952 if (msginfo->data) {
1953 MSG_UNSET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_REPLIED);
1954 MSG_SET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_FORWARDED);
1958 if (msginfo_list == NULL || msginfo_list->data == NULL) {
1959 g_warning("no msginfo_list");
1963 compose = compose_create(account, ((MsgInfo *)msginfo_list->data)->folder, COMPOSE_FORWARD, FALSE);
1964 compose_apply_folder_privacy_settings(compose, ((MsgInfo *)msginfo_list->data)->folder);
1966 compose->updating = TRUE;
1968 /* override from name according to folder properties */
1969 if (msginfo_list->data) {
1970 MsgInfo *msginfo = msginfo_list->data;
1972 if (msginfo->folder && msginfo->folder->prefs &&
1973 msginfo->folder->prefs->forward_with_format &&
1974 msginfo->folder->prefs->forward_override_from_format &&
1975 *msginfo->folder->prefs->forward_override_from_format != '\0') {
1980 /* decode \-escape sequences in the internal representation of the quote format */
1981 tmp = g_malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
1982 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
1985 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
1986 compose->gtkaspell);
1988 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
1990 quote_fmt_scan_string(tmp);
1993 buf = quote_fmt_get_buffer();
1995 alertpanel_error(_("The \"From\" field of the \"Forward\" template contains an invalid email address."));
1997 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1998 quote_fmt_reset_vartable();
1999 quote_fmtlex_destroy();
2005 textview = GTK_TEXT_VIEW(compose->text);
2006 textbuf = gtk_text_view_get_buffer(textview);
2007 compose_create_tags(textview, compose);
2009 undo_block(compose->undostruct);
2010 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
2011 msgfile = procmsg_get_message_file((MsgInfo *)msginfo->data);
2013 if (!is_file_exist(msgfile))
2014 g_warning("%s: file does not exist", msgfile);
2016 compose_attach_append(compose, msgfile, msgfile,
2017 "message/rfc822", NULL);
2022 MsgInfo *info = (MsgInfo *)msginfo_list->data;
2023 if (info->subject && *info->subject) {
2024 gchar *buf, *buf2, *p;
2026 buf = p = g_strdup(info->subject);
2027 p += subject_get_prefix_length(p);
2028 memmove(buf, p, strlen(p) + 1);
2030 buf2 = g_strdup_printf("Fw: %s", buf);
2031 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
2037 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2038 _("Fw: multiple emails"));
2041 SIGNAL_BLOCK(textbuf);
2043 if (account->auto_sig)
2044 compose_insert_sig(compose, FALSE);
2046 compose_wrap_all(compose);
2048 SIGNAL_UNBLOCK(textbuf);
2050 gtk_text_buffer_get_start_iter(textbuf, &iter);
2051 gtk_text_buffer_place_cursor(textbuf, &iter);
2053 if (prefs_common.auto_exteditor)
2054 compose_exec_ext_editor(compose);
2056 gtk_widget_grab_focus(compose->header_last->entry);
2057 undo_unblock(compose->undostruct);
2058 compose->modified = FALSE;
2059 compose_set_title(compose);
2061 compose->updating = FALSE;
2062 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2063 SCROLL_TO_CURSOR(compose);
2065 if (compose->deferred_destroy) {
2066 compose_destroy(compose);
2070 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2075 static gboolean compose_is_sig_separator(Compose *compose, GtkTextBuffer *textbuf, GtkTextIter *iter)
2077 GtkTextIter start = *iter;
2078 GtkTextIter end_iter;
2079 int start_pos = gtk_text_iter_get_offset(&start);
2081 if (!compose->account->sig_sep)
2084 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
2085 start_pos+strlen(compose->account->sig_sep));
2087 /* check sig separator */
2088 str = gtk_text_iter_get_text(&start, &end_iter);
2089 if (!strcmp(str, compose->account->sig_sep)) {
2091 /* check end of line (\n) */
2092 gtk_text_buffer_get_iter_at_offset(textbuf, &start,
2093 start_pos+strlen(compose->account->sig_sep));
2094 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
2095 start_pos+strlen(compose->account->sig_sep)+1);
2096 tmp = gtk_text_iter_get_text(&start, &end_iter);
2097 if (!strcmp(tmp,"\n")) {
2109 static gboolean compose_update_folder_hook(gpointer source, gpointer data)
2111 FolderUpdateData *hookdata = (FolderUpdateData *)source;
2112 Compose *compose = (Compose *)data;
2113 FolderItem *old_item = NULL;
2114 FolderItem *new_item = NULL;
2115 gchar *old_id, *new_id;
2117 if (!(hookdata->update_flags & FOLDER_REMOVE_FOLDERITEM)
2118 && !(hookdata->update_flags & FOLDER_MOVE_FOLDERITEM))
2121 old_item = hookdata->item;
2122 new_item = hookdata->item2;
2124 old_id = folder_item_get_identifier(old_item);
2125 new_id = new_item ? folder_item_get_identifier(new_item) : g_strdup("NULL");
2127 if (compose->targetinfo && compose->targetinfo->folder == old_item) {
2128 debug_print("updating targetinfo folder: %s -> %s\n", old_id, new_id);
2129 compose->targetinfo->folder = new_item;
2132 if (compose->replyinfo && compose->replyinfo->folder == old_item) {
2133 debug_print("updating replyinfo folder: %s -> %s\n", old_id, new_id);
2134 compose->replyinfo->folder = new_item;
2137 if (compose->fwdinfo && compose->fwdinfo->folder == old_item) {
2138 debug_print("updating fwdinfo folder: %s -> %s\n", old_id, new_id);
2139 compose->fwdinfo->folder = new_item;
2147 static void compose_colorize_signature(Compose *compose)
2149 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
2151 GtkTextIter end_iter;
2152 gtk_text_buffer_get_start_iter(buffer, &iter);
2153 while (gtk_text_iter_forward_line(&iter))
2154 if (compose_is_sig_separator(compose, buffer, &iter)) {
2155 gtk_text_buffer_get_end_iter(buffer, &end_iter);
2156 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &end_iter);
2160 #define BLOCK_WRAP() { \
2161 prev_autowrap = compose->autowrap; \
2162 buffer = gtk_text_view_get_buffer( \
2163 GTK_TEXT_VIEW(compose->text)); \
2164 compose->autowrap = FALSE; \
2166 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2167 G_CALLBACK(compose_changed_cb), \
2169 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2170 G_CALLBACK(text_inserted), \
2173 #define UNBLOCK_WRAP() { \
2174 compose->autowrap = prev_autowrap; \
2175 if (compose->autowrap) { \
2176 gint old = compose->draft_timeout_tag; \
2177 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN; \
2178 compose_wrap_all(compose); \
2179 compose->draft_timeout_tag = old; \
2182 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2183 G_CALLBACK(compose_changed_cb), \
2185 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2186 G_CALLBACK(text_inserted), \
2190 Compose *compose_reedit(MsgInfo *msginfo, gboolean batch)
2192 Compose *compose = NULL;
2193 PrefsAccount *account = NULL;
2194 GtkTextView *textview;
2195 GtkTextBuffer *textbuf;
2199 gboolean use_signing = FALSE;
2200 gboolean use_encryption = FALSE;
2201 gchar *privacy_system = NULL;
2202 int priority = PRIORITY_NORMAL;
2203 MsgInfo *replyinfo = NULL, *fwdinfo = NULL;
2204 gboolean autowrap = prefs_common.autowrap;
2205 gboolean autoindent = prefs_common.auto_indent;
2206 HeaderEntry *manual_headers = NULL;
2208 cm_return_val_if_fail(msginfo != NULL, NULL);
2209 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
2211 if (compose_put_existing_to_front(msginfo)) {
2215 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2216 folder_has_parent_of_type(msginfo->folder, F_DRAFT) ||
2217 folder_has_parent_of_type(msginfo->folder, F_OUTBOX)) {
2218 gchar *queueheader_buf = NULL;
2221 /* Select Account from queue headers */
2222 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2223 "X-Claws-Account-Id:")) {
2224 id = atoi(&queueheader_buf[strlen("X-Claws-Account-Id:")]);
2225 account = account_find_from_id(id);
2226 g_free(queueheader_buf);
2228 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2229 "X-Sylpheed-Account-Id:")) {
2230 id = atoi(&queueheader_buf[strlen("X-Sylpheed-Account-Id:")]);
2231 account = account_find_from_id(id);
2232 g_free(queueheader_buf);
2234 if (!account && !procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2236 id = atoi(&queueheader_buf[strlen("NAID:")]);
2237 account = account_find_from_id(id);
2238 g_free(queueheader_buf);
2240 if (!account && !procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2242 id = atoi(&queueheader_buf[strlen("MAID:")]);
2243 account = account_find_from_id(id);
2244 g_free(queueheader_buf);
2246 if (!account && !procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2248 account = account_find_from_address(queueheader_buf, FALSE);
2249 g_free(queueheader_buf);
2251 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2253 param = atoi(&queueheader_buf[strlen("X-Claws-Sign:")]);
2254 use_signing = param;
2255 g_free(queueheader_buf);
2257 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2258 "X-Sylpheed-Sign:")) {
2259 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Sign:")]);
2260 use_signing = param;
2261 g_free(queueheader_buf);
2263 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2264 "X-Claws-Encrypt:")) {
2265 param = atoi(&queueheader_buf[strlen("X-Claws-Encrypt:")]);
2266 use_encryption = param;
2267 g_free(queueheader_buf);
2269 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2270 "X-Sylpheed-Encrypt:")) {
2271 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Encrypt:")]);
2272 use_encryption = param;
2273 g_free(queueheader_buf);
2275 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2276 "X-Claws-Auto-Wrapping:")) {
2277 param = atoi(&queueheader_buf[strlen("X-Claws-Auto-Wrapping:")]);
2279 g_free(queueheader_buf);
2281 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2282 "X-Claws-Auto-Indent:")) {
2283 param = atoi(&queueheader_buf[strlen("X-Claws-Auto-Indent:")]);
2285 g_free(queueheader_buf);
2287 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2288 "X-Claws-Privacy-System:")) {
2289 privacy_system = g_strdup(&queueheader_buf[strlen("X-Claws-Privacy-System:")]);
2290 g_free(queueheader_buf);
2292 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2293 "X-Sylpheed-Privacy-System:")) {
2294 privacy_system = g_strdup(&queueheader_buf[strlen("X-Sylpheed-Privacy-System:")]);
2295 g_free(queueheader_buf);
2297 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2299 param = atoi(&queueheader_buf[strlen("X-Priority: ")]); /* mind the space */
2301 g_free(queueheader_buf);
2303 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2305 gchar **tokens = g_strsplit(&queueheader_buf[strlen("RMID:")], "\t", 0);
2306 if (tokens && tokens[0] && tokens[1] && tokens[2]) {
2307 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2308 if (orig_item != NULL) {
2309 replyinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2313 g_free(queueheader_buf);
2315 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2317 gchar **tokens = g_strsplit(&queueheader_buf[strlen("FMID:")], "\t", 0);
2318 if (tokens && tokens[0] && tokens[1] && tokens[2]) {
2319 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2320 if (orig_item != NULL) {
2321 fwdinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2325 g_free(queueheader_buf);
2327 /* Get manual headers */
2328 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2329 "X-Claws-Manual-Headers:")) {
2330 gchar *listmh = g_strdup(&queueheader_buf[strlen("X-Claws-Manual-Headers:")]);
2331 if (listmh && *listmh != '\0') {
2332 debug_print("Got manual headers: %s\n", listmh);
2333 manual_headers = procheader_entries_from_str(listmh);
2336 g_free(queueheader_buf);
2339 account = msginfo->folder->folder->account;
2342 if (!account && prefs_common.reedit_account_autosel) {
2344 if (!procheader_get_header_from_msginfo(msginfo, &from, "FROM:")) {
2345 extract_address(from);
2346 account = account_find_from_address(from, FALSE);
2351 account = cur_account;
2353 cm_return_val_if_fail(account != NULL, NULL);
2355 compose = compose_create(account, msginfo->folder, COMPOSE_REEDIT, batch);
2357 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", autowrap);
2358 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", autoindent);
2359 compose->autowrap = autowrap;
2360 compose->replyinfo = replyinfo;
2361 compose->fwdinfo = fwdinfo;
2363 compose->updating = TRUE;
2364 compose->priority = priority;
2366 if (privacy_system != NULL) {
2367 compose->privacy_system = privacy_system;
2368 compose_use_signing(compose, use_signing);
2369 compose_use_encryption(compose, use_encryption);
2370 compose_update_privacy_system_menu_item(compose, FALSE);
2372 compose_activate_privacy_system(compose, account, FALSE);
2374 compose_apply_folder_privacy_settings(compose, msginfo->folder);
2376 compose->targetinfo = procmsg_msginfo_copy(msginfo);
2377 compose->targetinfo->tags = g_slist_copy(msginfo->tags);
2379 compose_extract_original_charset(compose);
2381 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2382 folder_has_parent_of_type(msginfo->folder, F_DRAFT) ||
2383 folder_has_parent_of_type(msginfo->folder, F_OUTBOX)) {
2384 gchar *queueheader_buf = NULL;
2386 /* Set message save folder */
2387 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf, "SCF:")) {
2388 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
2389 compose_set_save_to(compose, &queueheader_buf[4]);
2390 g_free(queueheader_buf);
2392 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf, "RRCPT:")) {
2393 gint active = atoi(&queueheader_buf[strlen("RRCPT:")]);
2395 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
2397 g_free(queueheader_buf);
2401 if (compose_parse_header(compose, msginfo) < 0) {
2402 compose->updating = FALSE;
2403 compose_destroy(compose);
2406 compose_reedit_set_entry(compose, msginfo);
2408 textview = GTK_TEXT_VIEW(compose->text);
2409 textbuf = gtk_text_view_get_buffer(textview);
2410 compose_create_tags(textview, compose);
2412 mark = gtk_text_buffer_get_insert(textbuf);
2413 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
2415 g_signal_handlers_block_by_func(G_OBJECT(textbuf),
2416 G_CALLBACK(compose_changed_cb),
2419 if (MSG_IS_ENCRYPTED(msginfo->flags)) {
2420 fp = procmime_get_first_encrypted_text_content(msginfo);
2422 compose_force_encryption(compose, account, TRUE, NULL);
2425 fp = procmime_get_first_text_content(msginfo);
2428 g_warning("Can't get text part");
2432 gchar buf[BUFFSIZE];
2433 gboolean prev_autowrap;
2434 GtkTextBuffer *buffer;
2436 while (claws_fgets(buf, sizeof(buf), fp) != NULL) {
2438 gtk_text_buffer_insert(textbuf, &iter, buf, -1);
2444 compose_attach_parts(compose, msginfo);
2446 compose_colorize_signature(compose);
2448 g_signal_handlers_unblock_by_func(G_OBJECT(textbuf),
2449 G_CALLBACK(compose_changed_cb),
2452 if (manual_headers != NULL) {
2453 if (compose_parse_manual_headers(compose, msginfo, manual_headers) < 0) {
2454 procheader_entries_free(manual_headers);
2455 compose->updating = FALSE;
2456 compose_destroy(compose);
2459 procheader_entries_free(manual_headers);
2462 gtk_widget_grab_focus(compose->text);
2464 if (prefs_common.auto_exteditor) {
2465 compose_exec_ext_editor(compose);
2467 compose->modified = FALSE;
2468 compose_set_title(compose);
2470 compose->updating = FALSE;
2471 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2472 SCROLL_TO_CURSOR(compose);
2474 if (compose->deferred_destroy) {
2475 compose_destroy(compose);
2479 compose->sig_str = account_get_signature_str(compose->account);
2481 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2486 Compose *compose_redirect(PrefsAccount *account, MsgInfo *msginfo,
2493 cm_return_val_if_fail(msginfo != NULL, NULL);
2496 account = account_get_reply_account(msginfo,
2497 prefs_common.reply_account_autosel);
2498 cm_return_val_if_fail(account != NULL, NULL);
2500 compose = compose_create(account, msginfo->folder, COMPOSE_REDIRECT, batch);
2501 compose_apply_folder_privacy_settings(compose, msginfo->folder);
2503 compose->updating = TRUE;
2505 compose_create_tags(GTK_TEXT_VIEW(compose->text), compose);
2506 compose->replyinfo = NULL;
2507 compose->fwdinfo = NULL;
2509 compose_show_first_last_header(compose, TRUE);
2511 gtk_widget_grab_focus(compose->header_last->entry);
2513 filename = procmsg_get_message_file(msginfo);
2515 if (filename == NULL) {
2516 compose->updating = FALSE;
2517 compose_destroy(compose);
2522 compose->redirect_filename = filename;
2524 /* Set save folder */
2525 item = msginfo->folder;
2526 if (item && item->prefs && item->prefs->save_copy_to_folder) {
2527 gchar *folderidentifier;
2529 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
2530 folderidentifier = folder_item_get_identifier(item);
2531 compose_set_save_to(compose, folderidentifier);
2532 g_free(folderidentifier);
2535 compose_attach_parts(compose, msginfo);
2537 if (msginfo->subject)
2538 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2540 gtk_editable_set_editable(GTK_EDITABLE(compose->subject_entry), FALSE);
2542 compose_quote_fmt(compose, msginfo, "%M", NULL, NULL, FALSE, FALSE,
2543 _("The body of the \"Redirect\" template has an error at line %d."));
2544 quote_fmt_reset_vartable();
2545 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), FALSE);
2547 compose_colorize_signature(compose);
2550 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Add", FALSE);
2551 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", FALSE);
2552 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", FALSE);
2554 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/Save", FALSE);
2555 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertFile", FALSE);
2556 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/AttachFile", FALSE);
2557 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertSig", FALSE);
2558 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/ReplaceSig", FALSE);
2559 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", FALSE);
2560 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", FALSE);
2561 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/ShowRuler", FALSE);
2562 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Actions", FALSE);
2564 if (compose->toolbar->draft_btn)
2565 gtk_widget_set_sensitive(compose->toolbar->draft_btn, FALSE);
2566 if (compose->toolbar->insert_btn)
2567 gtk_widget_set_sensitive(compose->toolbar->insert_btn, FALSE);
2568 if (compose->toolbar->attach_btn)
2569 gtk_widget_set_sensitive(compose->toolbar->attach_btn, FALSE);
2570 if (compose->toolbar->sig_btn)
2571 gtk_widget_set_sensitive(compose->toolbar->sig_btn, FALSE);
2572 if (compose->toolbar->exteditor_btn)
2573 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, FALSE);
2574 if (compose->toolbar->linewrap_current_btn)
2575 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, FALSE);
2576 if (compose->toolbar->linewrap_all_btn)
2577 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, FALSE);
2579 compose->modified = FALSE;
2580 compose_set_title(compose);
2581 compose->updating = FALSE;
2582 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2583 SCROLL_TO_CURSOR(compose);
2585 if (compose->deferred_destroy) {
2586 compose_destroy(compose);
2590 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2595 const GList *compose_get_compose_list(void)
2597 return compose_list;
2600 void compose_entry_append(Compose *compose, const gchar *address,
2601 ComposeEntryType type, ComposePrefType pref_type)
2603 const gchar *header;
2605 gboolean in_quote = FALSE;
2606 if (!address || *address == '\0') return;
2613 header = N_("Bcc:");
2615 case COMPOSE_REPLYTO:
2616 header = N_("Reply-To:");
2618 case COMPOSE_NEWSGROUPS:
2619 header = N_("Newsgroups:");
2621 case COMPOSE_FOLLOWUPTO:
2622 header = N_( "Followup-To:");
2624 case COMPOSE_INREPLYTO:
2625 header = N_( "In-Reply-To:");
2632 header = prefs_common_translated_header_name(header);
2634 cur = begin = (gchar *)address;
2636 /* we separate the line by commas, but not if we're inside a quoted
2638 while (*cur != '\0') {
2640 in_quote = !in_quote;
2641 if (*cur == ',' && !in_quote) {
2642 gchar *tmp = g_strdup(begin);
2644 tmp[cur-begin]='\0';
2647 while (*tmp == ' ' || *tmp == '\t')
2649 compose_add_header_entry(compose, header, tmp, pref_type);
2650 compose_entry_indicate(compose, tmp);
2657 gchar *tmp = g_strdup(begin);
2659 tmp[cur-begin]='\0';
2660 while (*tmp == ' ' || *tmp == '\t')
2662 compose_add_header_entry(compose, header, tmp, pref_type);
2663 compose_entry_indicate(compose, tmp);
2668 static void compose_entry_indicate(Compose *compose, const gchar *mailto)
2673 for (h_list = compose->header_list; h_list != NULL; h_list = h_list->next) {
2674 entry = GTK_ENTRY(((ComposeHeaderEntry *)h_list->data)->entry);
2675 if (gtk_entry_get_text(entry) &&
2676 !g_utf8_collate(gtk_entry_get_text(entry), mailto)) {
2677 gtk_widget_modify_base(
2678 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2679 GTK_STATE_NORMAL, &default_header_bgcolor);
2680 gtk_widget_modify_text(
2681 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2682 GTK_STATE_NORMAL, &default_header_color);
2687 void compose_toolbar_cb(gint action, gpointer data)
2689 ToolbarItem *toolbar_item = (ToolbarItem*)data;
2690 Compose *compose = (Compose*)toolbar_item->parent;
2692 cm_return_if_fail(compose != NULL);
2696 compose_send_cb(NULL, compose);
2699 compose_send_later_cb(NULL, compose);
2702 compose_draft(compose, COMPOSE_QUIT_EDITING);
2705 compose_insert_file_cb(NULL, compose);
2708 compose_attach_cb(NULL, compose);
2711 compose_insert_sig(compose, FALSE);
2714 compose_insert_sig(compose, TRUE);
2717 compose_ext_editor_cb(NULL, compose);
2719 case A_LINEWRAP_CURRENT:
2720 compose_beautify_paragraph(compose, NULL, TRUE);
2722 case A_LINEWRAP_ALL:
2723 compose_wrap_all_full(compose, TRUE);
2726 compose_address_cb(NULL, compose);
2729 case A_CHECK_SPELLING:
2730 compose_check_all(NULL, compose);
2733 case A_PRIVACY_SIGN:
2735 case A_PRIVACY_ENCRYPT:
2742 static MailField compose_entries_set(Compose *compose, const gchar *mailto, ComposeEntryType to_type)
2747 gchar *subject = NULL;
2751 gchar **attach = NULL;
2752 gchar *inreplyto = NULL;
2753 MailField mfield = NO_FIELD_PRESENT;
2755 /* get mailto parts but skip from */
2756 scan_mailto_url(mailto, NULL, &to, &cc, &bcc, &subject, &body, &attach, &inreplyto);
2759 compose_entry_append(compose, to, to_type, PREF_MAILTO);
2760 mfield = TO_FIELD_PRESENT;
2763 compose_entry_append(compose, cc, COMPOSE_CC, PREF_MAILTO);
2765 compose_entry_append(compose, bcc, COMPOSE_BCC, PREF_MAILTO);
2767 if (!g_utf8_validate (subject, -1, NULL)) {
2768 temp = g_locale_to_utf8 (subject, -1, NULL, &len, NULL);
2769 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), temp);
2772 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), subject);
2774 mfield = SUBJECT_FIELD_PRESENT;
2777 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2778 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2781 gboolean prev_autowrap = compose->autowrap;
2783 compose->autowrap = FALSE;
2785 mark = gtk_text_buffer_get_insert(buffer);
2786 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2788 if (!g_utf8_validate (body, -1, NULL)) {
2789 temp = g_locale_to_utf8 (body, -1, NULL, &len, NULL);
2790 gtk_text_buffer_insert(buffer, &iter, temp, -1);
2793 gtk_text_buffer_insert(buffer, &iter, body, -1);
2795 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
2797 compose->autowrap = prev_autowrap;
2798 if (compose->autowrap)
2799 compose_wrap_all(compose);
2800 mfield = BODY_FIELD_PRESENT;
2804 gint i = 0, att = 0;
2805 gchar *warn_files = NULL;
2806 while (attach[i] != NULL) {
2807 gchar *utf8_filename = conv_filename_to_utf8(attach[i]);
2808 if (utf8_filename) {
2809 if (compose_attach_append(compose, attach[i], utf8_filename, NULL, NULL)) {
2810 gchar *tmp = g_strdup_printf("%s%s\n",
2811 warn_files?warn_files:"",
2817 g_free(utf8_filename);
2819 alertpanel_error(_("Couldn't attach a file (charset conversion failed)."));
2824 alertpanel_notice(ngettext(
2825 "The following file has been attached: \n%s",
2826 "The following files have been attached: \n%s", att), warn_files);
2831 compose_entry_append(compose, inreplyto, COMPOSE_INREPLYTO, PREF_MAILTO);
2844 static gint compose_parse_header(Compose *compose, MsgInfo *msginfo)
2846 static HeaderEntry hentry[] = {
2847 {"Reply-To:", NULL, TRUE },
2848 {"Cc:", NULL, TRUE },
2849 {"References:", NULL, FALSE },
2850 {"Bcc:", NULL, TRUE },
2851 {"Newsgroups:", NULL, TRUE },
2852 {"Followup-To:", NULL, TRUE },
2853 {"List-Post:", NULL, FALSE },
2854 {"X-Priority:", NULL, FALSE },
2855 {NULL, NULL, FALSE }
2872 cm_return_val_if_fail(msginfo != NULL, -1);
2874 if ((fp = procmsg_open_message(msginfo)) == NULL) return -1;
2875 procheader_get_header_fields(fp, hentry);
2878 if (hentry[H_REPLY_TO].body != NULL) {
2879 if (hentry[H_REPLY_TO].body[0] != '\0') {
2881 conv_unmime_header(hentry[H_REPLY_TO].body,
2884 g_free(hentry[H_REPLY_TO].body);
2885 hentry[H_REPLY_TO].body = NULL;
2887 if (hentry[H_CC].body != NULL) {
2888 compose->cc = conv_unmime_header(hentry[H_CC].body, NULL, TRUE);
2889 g_free(hentry[H_CC].body);
2890 hentry[H_CC].body = NULL;
2892 if (hentry[H_REFERENCES].body != NULL) {
2893 if (compose->mode == COMPOSE_REEDIT)
2894 compose->references = hentry[H_REFERENCES].body;
2896 compose->references = compose_parse_references
2897 (hentry[H_REFERENCES].body, msginfo->msgid);
2898 g_free(hentry[H_REFERENCES].body);
2900 hentry[H_REFERENCES].body = NULL;
2902 if (hentry[H_BCC].body != NULL) {
2903 if (compose->mode == COMPOSE_REEDIT)
2905 conv_unmime_header(hentry[H_BCC].body, NULL, TRUE);
2906 g_free(hentry[H_BCC].body);
2907 hentry[H_BCC].body = NULL;
2909 if (hentry[H_NEWSGROUPS].body != NULL) {
2910 compose->newsgroups = hentry[H_NEWSGROUPS].body;
2911 hentry[H_NEWSGROUPS].body = NULL;
2913 if (hentry[H_FOLLOWUP_TO].body != NULL) {
2914 if (hentry[H_FOLLOWUP_TO].body[0] != '\0') {
2915 compose->followup_to =
2916 conv_unmime_header(hentry[H_FOLLOWUP_TO].body,
2919 g_free(hentry[H_FOLLOWUP_TO].body);
2920 hentry[H_FOLLOWUP_TO].body = NULL;
2922 if (hentry[H_LIST_POST].body != NULL) {
2923 gchar *to = NULL, *start = NULL;
2925 extract_address(hentry[H_LIST_POST].body);
2926 if (hentry[H_LIST_POST].body[0] != '\0') {
2927 start = strstr(hentry[H_LIST_POST].body, "mailto:");
2929 scan_mailto_url(start ? start : hentry[H_LIST_POST].body,
2930 NULL, &to, NULL, NULL, NULL, NULL, NULL, NULL);
2933 g_free(compose->ml_post);
2934 compose->ml_post = to;
2937 g_free(hentry[H_LIST_POST].body);
2938 hentry[H_LIST_POST].body = NULL;
2941 /* CLAWS - X-Priority */
2942 if (compose->mode == COMPOSE_REEDIT)
2943 if (hentry[H_X_PRIORITY].body != NULL) {
2946 priority = atoi(hentry[H_X_PRIORITY].body);
2947 g_free(hentry[H_X_PRIORITY].body);
2949 hentry[H_X_PRIORITY].body = NULL;
2951 if (priority < PRIORITY_HIGHEST ||
2952 priority > PRIORITY_LOWEST)
2953 priority = PRIORITY_NORMAL;
2955 compose->priority = priority;
2958 if (compose->mode == COMPOSE_REEDIT) {
2959 if (msginfo->inreplyto && *msginfo->inreplyto)
2960 compose->inreplyto = g_strdup(msginfo->inreplyto);
2962 if (msginfo->msgid && *msginfo->msgid &&
2963 compose->folder != NULL &&
2964 compose->folder->stype == F_DRAFT)
2965 compose->msgid = g_strdup(msginfo->msgid);
2967 if (msginfo->msgid && *msginfo->msgid)
2968 compose->inreplyto = g_strdup(msginfo->msgid);
2970 if (!compose->references) {
2971 if (msginfo->msgid && *msginfo->msgid) {
2972 if (msginfo->inreplyto && *msginfo->inreplyto)
2973 compose->references =
2974 g_strdup_printf("<%s>\n\t<%s>",
2978 compose->references =
2979 g_strconcat("<", msginfo->msgid, ">",
2981 } else if (msginfo->inreplyto && *msginfo->inreplyto) {
2982 compose->references =
2983 g_strconcat("<", msginfo->inreplyto, ">",
2992 static gint compose_parse_manual_headers(Compose *compose, MsgInfo *msginfo, HeaderEntry *entries)
2997 cm_return_val_if_fail(msginfo != NULL, -1);
2999 if ((fp = procmsg_open_message(msginfo)) == NULL) return -1;
3000 procheader_get_header_fields(fp, entries);
3004 while (he != NULL && he->name != NULL) {
3006 GtkListStore *model = NULL;
3008 debug_print("Adding manual header: %s with value %s\n", he->name, he->body);
3009 model = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(compose->header_last->combo)));
3010 COMBOBOX_ADD(model, he->name, COMPOSE_TO);
3011 gtk_combo_box_set_active_iter(GTK_COMBO_BOX(compose->header_last->combo), &iter);
3012 gtk_entry_set_text(GTK_ENTRY(compose->header_last->entry), he->body);
3019 static gchar *compose_parse_references(const gchar *ref, const gchar *msgid)
3021 GSList *ref_id_list, *cur;
3025 ref_id_list = references_list_append(NULL, ref);
3026 if (!ref_id_list) return NULL;
3027 if (msgid && *msgid)
3028 ref_id_list = g_slist_append(ref_id_list, g_strdup(msgid));
3033 for (cur = ref_id_list; cur != NULL; cur = cur->next)
3034 /* "<" + Message-ID + ">" + CR+LF+TAB */
3035 len += strlen((gchar *)cur->data) + 5;
3037 if (len > MAX_REFERENCES_LEN) {
3038 /* remove second message-ID */
3039 if (ref_id_list && ref_id_list->next &&
3040 ref_id_list->next->next) {
3041 g_free(ref_id_list->next->data);
3042 ref_id_list = g_slist_remove
3043 (ref_id_list, ref_id_list->next->data);
3045 slist_free_strings_full(ref_id_list);
3052 new_ref = g_string_new("");
3053 for (cur = ref_id_list; cur != NULL; cur = cur->next) {
3054 if (new_ref->len > 0)
3055 g_string_append(new_ref, "\n\t");
3056 g_string_append_printf(new_ref, "<%s>", (gchar *)cur->data);
3059 slist_free_strings_full(ref_id_list);
3061 new_ref_str = new_ref->str;
3062 g_string_free(new_ref, FALSE);
3067 static gchar *compose_quote_fmt(Compose *compose, MsgInfo *msginfo,
3068 const gchar *fmt, const gchar *qmark,
3069 const gchar *body, gboolean rewrap,
3070 gboolean need_unescape,
3071 const gchar *err_msg)
3073 MsgInfo* dummyinfo = NULL;
3074 gchar *quote_str = NULL;
3076 gboolean prev_autowrap;
3077 const gchar *trimmed_body = body;
3078 gint cursor_pos = -1;
3079 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3080 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3085 SIGNAL_BLOCK(buffer);
3088 dummyinfo = compose_msginfo_new_from_compose(compose);
3089 msginfo = dummyinfo;
3092 if (qmark != NULL) {
3094 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
3095 compose->gtkaspell);
3097 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
3099 quote_fmt_scan_string(qmark);
3102 buf = quote_fmt_get_buffer();
3105 alertpanel_error(_("The \"Quotation mark\" of the template is invalid."));
3107 Xstrdup_a(quote_str, buf, goto error)
3110 if (fmt && *fmt != '\0') {
3113 while (*trimmed_body == '\n')
3117 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE,
3118 compose->gtkaspell);
3120 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE);
3122 if (need_unescape) {
3125 /* decode \-escape sequences in the internal representation of the quote format */
3126 tmp = g_malloc(strlen(fmt)+1);
3127 pref_get_unescaped_pref(tmp, fmt);
3128 quote_fmt_scan_string(tmp);
3132 quote_fmt_scan_string(fmt);
3136 buf = quote_fmt_get_buffer();
3139 gint line = quote_fmt_get_line();
3140 alertpanel_error(err_msg, line);
3148 prev_autowrap = compose->autowrap;
3149 compose->autowrap = FALSE;
3151 mark = gtk_text_buffer_get_insert(buffer);
3152 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3153 if (g_utf8_validate(buf, -1, NULL)) {
3154 gtk_text_buffer_insert(buffer, &iter, buf, -1);
3156 gchar *tmpout = NULL;
3157 tmpout = conv_codeset_strdup
3158 (buf, conv_get_locale_charset_str_no_utf8(),
3160 if (!tmpout || !g_utf8_validate(tmpout, -1, NULL)) {
3162 tmpout = g_malloc(strlen(buf)*2+1);
3163 conv_localetodisp(tmpout, strlen(buf)*2+1, buf);
3165 gtk_text_buffer_insert(buffer, &iter, tmpout, -1);
3169 cursor_pos = quote_fmt_get_cursor_pos();
3170 if (cursor_pos == -1)
3171 cursor_pos = gtk_text_iter_get_offset(&iter);
3172 compose->set_cursor_pos = cursor_pos;
3174 gtk_text_buffer_get_start_iter(buffer, &iter);
3175 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
3176 gtk_text_buffer_place_cursor(buffer, &iter);
3178 compose->autowrap = prev_autowrap;
3179 if (compose->autowrap && rewrap)
3180 compose_wrap_all(compose);
3187 SIGNAL_UNBLOCK(buffer);
3189 procmsg_msginfo_free( &dummyinfo );
3194 /* if ml_post is of type addr@host and from is of type
3195 * addr-anything@host, return TRUE
3197 static gboolean is_subscription(const gchar *ml_post, const gchar *from)
3199 gchar *left_ml = NULL;
3200 gchar *right_ml = NULL;
3201 gchar *left_from = NULL;
3202 gchar *right_from = NULL;
3203 gboolean result = FALSE;
3205 if (!ml_post || !from)
3208 left_ml = g_strdup(ml_post);
3209 if (strstr(left_ml, "@")) {
3210 right_ml = strstr(left_ml, "@")+1;
3211 *(strstr(left_ml, "@")) = '\0';
3214 left_from = g_strdup(from);
3215 if (strstr(left_from, "@")) {
3216 right_from = strstr(left_from, "@")+1;
3217 *(strstr(left_from, "@")) = '\0';
3220 if (right_ml && right_from
3221 && !strncmp(left_from, left_ml, strlen(left_ml))
3222 && !strcmp(right_from, right_ml)) {
3231 static void compose_set_folder_prefs(Compose *compose, FolderItem *folder,
3232 gboolean respect_default_to)
3236 if (!folder || !folder->prefs)
3239 if (respect_default_to && folder->prefs->enable_default_to) {
3240 compose_entry_append(compose, folder->prefs->default_to,
3241 COMPOSE_TO, PREF_FOLDER);
3242 compose_entry_indicate(compose, folder->prefs->default_to);
3244 if (folder->prefs->enable_default_cc) {
3245 compose_entry_append(compose, folder->prefs->default_cc,
3246 COMPOSE_CC, PREF_FOLDER);
3247 compose_entry_indicate(compose, folder->prefs->default_cc);
3249 if (folder->prefs->enable_default_bcc) {
3250 compose_entry_append(compose, folder->prefs->default_bcc,
3251 COMPOSE_BCC, PREF_FOLDER);
3252 compose_entry_indicate(compose, folder->prefs->default_bcc);
3254 if (folder->prefs->enable_default_replyto) {
3255 compose_entry_append(compose, folder->prefs->default_replyto,
3256 COMPOSE_REPLYTO, PREF_FOLDER);
3257 compose_entry_indicate(compose, folder->prefs->default_replyto);
3261 static void compose_reply_set_subject(Compose *compose, MsgInfo *msginfo)
3266 if (!compose || !msginfo)
3269 if (msginfo->subject && *msginfo->subject) {
3270 buf = p = g_strdup(msginfo->subject);
3271 p += subject_get_prefix_length(p);
3272 memmove(buf, p, strlen(p) + 1);
3274 buf2 = g_strdup_printf("Re: %s", buf);
3275 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
3280 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), "Re: ");
3283 static void compose_reply_set_entry(Compose *compose, MsgInfo *msginfo,
3284 gboolean to_all, gboolean to_ml,
3286 gboolean followup_and_reply_to)
3288 GSList *cc_list = NULL;
3291 gchar *replyto = NULL;
3292 gchar *ac_email = NULL;
3294 gboolean reply_to_ml = FALSE;
3295 gboolean default_reply_to = FALSE;
3297 cm_return_if_fail(compose->account != NULL);
3298 cm_return_if_fail(msginfo != NULL);
3300 reply_to_ml = to_ml && compose->ml_post;
3302 default_reply_to = msginfo->folder &&
3303 msginfo->folder->prefs->enable_default_reply_to;
3305 if (compose->account->protocol != A_NNTP) {
3306 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
3308 if (reply_to_ml && !default_reply_to) {
3310 gboolean is_subscr = is_subscription(compose->ml_post,
3313 /* normal answer to ml post with a reply-to */
3314 compose_entry_append(compose,
3316 COMPOSE_TO, PREF_ML);
3317 if (compose->replyto)
3318 compose_entry_append(compose,
3320 COMPOSE_CC, PREF_ML);
3322 /* answer to subscription confirmation */
3323 if (compose->replyto)
3324 compose_entry_append(compose,
3326 COMPOSE_TO, PREF_ML);
3327 else if (msginfo->from)
3328 compose_entry_append(compose,
3330 COMPOSE_TO, PREF_ML);
3333 else if (!(to_all || to_sender) && default_reply_to) {
3334 compose_entry_append(compose,
3335 msginfo->folder->prefs->default_reply_to,
3336 COMPOSE_TO, PREF_FOLDER);
3337 compose_entry_indicate(compose,
3338 msginfo->folder->prefs->default_reply_to);
3344 compose_entry_append(compose, msginfo->from,
3345 COMPOSE_TO, PREF_NONE);
3347 Xstrdup_a(tmp1, msginfo->from, return);
3348 extract_address(tmp1);
3349 compose_entry_append(compose,
3350 (!account_find_from_address(tmp1, FALSE))
3353 COMPOSE_TO, PREF_NONE);
3354 if (compose->replyto)
3355 compose_entry_append(compose,
3357 COMPOSE_CC, PREF_NONE);
3359 if (!folder_has_parent_of_type(msginfo->folder, F_QUEUE) &&
3360 !folder_has_parent_of_type(msginfo->folder, F_OUTBOX) &&
3361 !folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
3362 if (compose->replyto) {
3363 compose_entry_append(compose,
3365 COMPOSE_TO, PREF_NONE);
3367 compose_entry_append(compose,
3368 msginfo->from ? msginfo->from : "",
3369 COMPOSE_TO, PREF_NONE);
3372 /* replying to own mail, use original recp */
3373 compose_entry_append(compose,
3374 msginfo->to ? msginfo->to : "",
3375 COMPOSE_TO, PREF_NONE);
3376 compose_entry_append(compose,
3377 msginfo->cc ? msginfo->cc : "",
3378 COMPOSE_CC, PREF_NONE);
3383 if (to_sender || (compose->followup_to &&
3384 !strncmp(compose->followup_to, "poster", 6)))
3385 compose_entry_append
3387 (compose->replyto ? compose->replyto :
3388 msginfo->from ? msginfo->from : ""),
3389 COMPOSE_TO, PREF_NONE);
3391 else if (followup_and_reply_to || to_all) {
3392 compose_entry_append
3394 (compose->replyto ? compose->replyto :
3395 msginfo->from ? msginfo->from : ""),
3396 COMPOSE_TO, PREF_NONE);
3398 compose_entry_append
3400 compose->followup_to ? compose->followup_to :
3401 compose->newsgroups ? compose->newsgroups : "",
3402 COMPOSE_NEWSGROUPS, PREF_NONE);
3404 compose_entry_append
3406 msginfo->cc ? msginfo->cc : "",
3407 COMPOSE_CC, PREF_NONE);
3410 compose_entry_append
3412 compose->followup_to ? compose->followup_to :
3413 compose->newsgroups ? compose->newsgroups : "",
3414 COMPOSE_NEWSGROUPS, PREF_NONE);
3416 compose_reply_set_subject(compose, msginfo);
3418 if (to_ml && compose->ml_post) return;
3419 if (!to_all || compose->account->protocol == A_NNTP) return;
3421 if (compose->replyto) {
3422 Xstrdup_a(replyto, compose->replyto, return);
3423 extract_address(replyto);
3425 if (msginfo->from) {
3426 Xstrdup_a(from, msginfo->from, return);
3427 extract_address(from);
3430 if (replyto && from)
3431 cc_list = address_list_append_with_comments(cc_list, from);
3432 if (to_all && msginfo->folder &&
3433 msginfo->folder->prefs->enable_default_reply_to)
3434 cc_list = address_list_append_with_comments(cc_list,
3435 msginfo->folder->prefs->default_reply_to);
3436 cc_list = address_list_append_with_comments(cc_list, msginfo->to);
3437 cc_list = address_list_append_with_comments(cc_list, compose->cc);
3439 ac_email = g_utf8_strdown(compose->account->address, -1);
3442 for (cur = cc_list; cur != NULL; cur = cur->next) {
3443 gchar *addr = g_utf8_strdown(cur->data, -1);
3444 extract_address(addr);
3446 if (strcmp(ac_email, addr))
3447 compose_entry_append(compose, (gchar *)cur->data,
3448 COMPOSE_CC, PREF_NONE);
3450 debug_print("Cc address same as compose account's, ignoring\n");
3455 slist_free_strings_full(cc_list);
3461 #define SET_ENTRY(entry, str) \
3464 gtk_entry_set_text(GTK_ENTRY(compose->entry), str); \
3467 #define SET_ADDRESS(type, str) \
3470 compose_entry_append(compose, str, type, PREF_NONE); \
3473 static void compose_reedit_set_entry(Compose *compose, MsgInfo *msginfo)
3475 cm_return_if_fail(msginfo != NULL);
3477 SET_ENTRY(subject_entry, msginfo->subject);
3478 SET_ENTRY(from_name, msginfo->from);
3479 SET_ADDRESS(COMPOSE_TO, msginfo->to);
3480 SET_ADDRESS(COMPOSE_CC, compose->cc);
3481 SET_ADDRESS(COMPOSE_BCC, compose->bcc);
3482 SET_ADDRESS(COMPOSE_REPLYTO, compose->replyto);
3483 SET_ADDRESS(COMPOSE_NEWSGROUPS, compose->newsgroups);
3484 SET_ADDRESS(COMPOSE_FOLLOWUPTO, compose->followup_to);
3486 compose_update_priority_menu_item(compose);
3487 compose_update_privacy_system_menu_item(compose, FALSE);
3488 compose_show_first_last_header(compose, TRUE);
3494 static void compose_insert_sig(Compose *compose, gboolean replace)
3496 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3497 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3499 GtkTextIter iter, iter_end;
3500 gint cur_pos, ins_pos;
3501 gboolean prev_autowrap;
3502 gboolean found = FALSE;
3503 gboolean exists = FALSE;
3505 cm_return_if_fail(compose->account != NULL);
3509 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3510 G_CALLBACK(compose_changed_cb),
3513 mark = gtk_text_buffer_get_insert(buffer);
3514 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3515 cur_pos = gtk_text_iter_get_offset (&iter);
3518 gtk_text_buffer_get_end_iter(buffer, &iter);
3520 exists = (compose->sig_str != NULL);
3523 GtkTextIter first_iter, start_iter, end_iter;
3525 gtk_text_buffer_get_start_iter(buffer, &first_iter);
3527 if (!exists || compose->sig_str[0] == '\0')
3530 found = gtk_text_iter_forward_to_tag_toggle(&first_iter,
3531 compose->signature_tag);
3534 /* include previous \n\n */
3535 gtk_text_iter_backward_chars(&first_iter, 1);
3536 start_iter = first_iter;
3537 end_iter = first_iter;
3539 found = gtk_text_iter_forward_to_tag_toggle(&end_iter,
3540 compose->signature_tag);
3541 found &= gtk_text_iter_forward_to_tag_toggle(&end_iter,
3542 compose->signature_tag);
3544 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
3550 g_free(compose->sig_str);
3551 compose->sig_str = account_get_signature_str(compose->account);
3553 cur_pos = gtk_text_iter_get_offset(&iter);
3555 if (!compose->sig_str || (replace && !compose->account->auto_sig)) {
3556 g_free(compose->sig_str);
3557 compose->sig_str = NULL;
3559 if (compose->sig_inserted == FALSE)
3560 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
3561 compose->sig_inserted = TRUE;
3563 cur_pos = gtk_text_iter_get_offset(&iter);
3564 gtk_text_buffer_insert(buffer, &iter, compose->sig_str, -1);
3566 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cur_pos);
3567 gtk_text_iter_forward_chars(&iter, 1);
3568 gtk_text_buffer_get_end_iter(buffer, &iter_end);
3569 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &iter_end);
3571 if (cur_pos > gtk_text_buffer_get_char_count (buffer))
3572 cur_pos = gtk_text_buffer_get_char_count (buffer);
3575 /* put the cursor where it should be
3576 * either where the quote_fmt says, either where it was */
3577 if (compose->set_cursor_pos < 0)
3578 gtk_text_buffer_get_iter_at_offset(buffer, &iter, ins_pos);
3580 gtk_text_buffer_get_iter_at_offset(buffer, &iter,
3581 compose->set_cursor_pos);
3583 compose->set_cursor_pos = -1;
3584 gtk_text_buffer_place_cursor(buffer, &iter);
3585 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3586 G_CALLBACK(compose_changed_cb),
3592 static ComposeInsertResult compose_insert_file(Compose *compose, const gchar *file)
3595 GtkTextBuffer *buffer;
3598 const gchar *cur_encoding;
3599 gchar buf[BUFFSIZE];
3602 gboolean prev_autowrap;
3606 GError *error = NULL;
3612 GString *file_contents = NULL;
3613 ComposeInsertResult result = COMPOSE_INSERT_SUCCESS;
3615 cm_return_val_if_fail(file != NULL, COMPOSE_INSERT_NO_FILE);
3617 /* get the size of the file we are about to insert */
3619 f = g_file_new_for_path(file);
3620 fi = g_file_query_info(f, "standard::size",
3621 G_FILE_QUERY_INFO_NONE, NULL, &error);
3623 if (error != NULL) {
3624 g_warning(error->message);
3626 g_error_free(error);
3630 ret = g_stat(file, &file_stat);
3633 gchar *shortfile = g_path_get_basename(file);
3634 alertpanel_error(_("Could not get size of file '%s'."), shortfile);
3636 return COMPOSE_INSERT_NO_FILE;
3637 } else if (prefs_common.warn_large_insert == TRUE) {
3639 size = g_file_info_get_size(fi);
3643 size = file_stat.st_size;
3646 /* ask user for confirmation if the file is large */
3647 if (prefs_common.warn_large_insert_size < 0 ||
3648 size > ((goffset) prefs_common.warn_large_insert_size * 1024)) {
3652 msg = g_strdup_printf(_("You are about to insert a file of %s "
3653 "in the message body. Are you sure you want to do that?"),
3654 to_human_readable(size));
3655 aval = alertpanel_full(_("Are you sure?"), msg, GTK_STOCK_CANCEL,
3656 _("_Insert"), NULL, ALERTFOCUS_SECOND, TRUE,
3657 NULL, ALERT_QUESTION);
3660 /* do we ask for confirmation next time? */
3661 if (aval & G_ALERTDISABLE) {
3662 /* no confirmation next time, disable feature in preferences */
3663 aval &= ~G_ALERTDISABLE;
3664 prefs_common.warn_large_insert = FALSE;
3667 /* abort file insertion if user canceled action */
3668 if (aval != G_ALERTALTERNATE) {
3669 return COMPOSE_INSERT_NO_FILE;
3675 if ((fp = claws_fopen(file, "rb")) == NULL) {
3676 FILE_OP_ERROR(file, "claws_fopen");
3677 return COMPOSE_INSERT_READ_ERROR;
3680 prev_autowrap = compose->autowrap;
3681 compose->autowrap = FALSE;
3683 text = GTK_TEXT_VIEW(compose->text);
3684 buffer = gtk_text_view_get_buffer(text);
3685 mark = gtk_text_buffer_get_insert(buffer);
3686 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3688 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3689 G_CALLBACK(text_inserted),
3692 cur_encoding = conv_get_locale_charset_str_no_utf8();
3694 file_contents = g_string_new("");
3695 while (claws_fgets(buf, sizeof(buf), fp) != NULL) {
3698 if (g_utf8_validate(buf, -1, NULL) == TRUE)
3699 str = g_strdup(buf);
3701 codeconv_set_strict(TRUE);
3702 str = conv_codeset_strdup
3703 (buf, cur_encoding, CS_INTERNAL);
3704 codeconv_set_strict(FALSE);
3707 result = COMPOSE_INSERT_INVALID_CHARACTER;
3713 /* strip <CR> if DOS/Windows file,
3714 replace <CR> with <LF> if Macintosh file. */
3717 if (len > 0 && str[len - 1] != '\n') {
3719 if (str[len] == '\r') str[len] = '\n';
3722 file_contents = g_string_append(file_contents, str);
3726 if (result == COMPOSE_INSERT_SUCCESS) {
3727 gtk_text_buffer_insert(buffer, &iter, file_contents->str, -1);
3729 compose_changed_cb(NULL, compose);
3730 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3731 G_CALLBACK(text_inserted),
3733 compose->autowrap = prev_autowrap;
3734 if (compose->autowrap)
3735 compose_wrap_all(compose);
3738 g_string_free(file_contents, TRUE);
3744 static gboolean compose_attach_append(Compose *compose, const gchar *file,
3745 const gchar *filename,
3746 const gchar *content_type,
3747 const gchar *charset)
3755 GtkListStore *store;
3757 gboolean has_binary = FALSE;
3759 if (!is_file_exist(file)) {
3760 gchar *file_from_uri = g_filename_from_uri(file, NULL, NULL);
3761 gboolean result = FALSE;
3762 if (file_from_uri && is_file_exist(file_from_uri)) {
3763 result = compose_attach_append(
3764 compose, file_from_uri,
3765 filename, content_type,
3768 g_free(file_from_uri);
3771 alertpanel_error("File %s doesn't exist or permission denied\n", filename);
3774 if ((size = get_file_size(file)) < 0) {
3775 alertpanel_error("Can't get file size of %s\n", filename);
3779 /* In batch mode, we allow 0-length files to be attached no questions asked */
3780 if (size == 0 && !compose->batch) {
3781 gchar * msg = g_strdup_printf(_("File %s is empty."), filename);
3782 AlertValue aval = alertpanel_full(_("Empty file"), msg,
3783 GTK_STOCK_CANCEL, _("_Attach anyway"), NULL,
3784 ALERTFOCUS_SECOND, FALSE, NULL, ALERT_WARNING);
3787 if (aval != G_ALERTALTERNATE) {
3791 if ((fp = claws_fopen(file, "rb")) == NULL) {
3792 alertpanel_error(_("Can't read %s."), filename);
3797 ainfo = g_new0(AttachInfo, 1);
3798 auto_ainfo = g_auto_pointer_new_with_free
3799 (ainfo, (GFreeFunc) compose_attach_info_free);
3800 ainfo->file = g_strdup(file);
3803 ainfo->content_type = g_strdup(content_type);
3804 if (!g_ascii_strcasecmp(content_type, "message/rfc822")) {
3806 MsgFlags flags = {0, 0};
3808 if (procmime_get_encoding_for_text_file(file, &has_binary) == ENC_7BIT)
3809 ainfo->encoding = ENC_7BIT;
3811 ainfo->encoding = ENC_8BIT;
3813 msginfo = procheader_parse_file(file, flags, FALSE, FALSE);
3814 if (msginfo && msginfo->subject)
3815 name = g_strdup(msginfo->subject);
3817 name = g_path_get_basename(filename ? filename : file);
3819 ainfo->name = g_strdup_printf(_("Message: %s"), name);
3821 procmsg_msginfo_free(&msginfo);
3823 if (!g_ascii_strncasecmp(content_type, "text/", 5)) {
3824 ainfo->charset = g_strdup(charset);
3825 ainfo->encoding = procmime_get_encoding_for_text_file(file, &has_binary);
3827 ainfo->encoding = ENC_BASE64;
3829 name = g_path_get_basename(filename ? filename : file);
3830 ainfo->name = g_strdup(name);
3834 ainfo->content_type = procmime_get_mime_type(file);
3835 if (!ainfo->content_type) {
3836 ainfo->content_type =
3837 g_strdup("application/octet-stream");
3838 ainfo->encoding = ENC_BASE64;
3839 } else if (!g_ascii_strncasecmp(ainfo->content_type, "text/", 5))
3841 procmime_get_encoding_for_text_file(file, &has_binary);
3843 ainfo->encoding = ENC_BASE64;
3844 name = g_path_get_basename(filename ? filename : file);
3845 ainfo->name = g_strdup(name);
3849 if (ainfo->name != NULL
3850 && !strcmp(ainfo->name, ".")) {
3851 g_free(ainfo->name);
3855 if (!strcmp(ainfo->content_type, "unknown") || has_binary) {
3856 g_free(ainfo->content_type);
3857 ainfo->content_type = g_strdup("application/octet-stream");
3858 g_free(ainfo->charset);
3859 ainfo->charset = NULL;
3862 ainfo->size = (goffset)size;
3863 size_text = to_human_readable((goffset)size);
3865 store = GTK_LIST_STORE(gtk_tree_view_get_model
3866 (GTK_TREE_VIEW(compose->attach_clist)));
3868 gtk_list_store_append(store, &iter);
3869 gtk_list_store_set(store, &iter,
3870 COL_MIMETYPE, ainfo->content_type,
3871 COL_SIZE, size_text,
3872 COL_NAME, ainfo->name,
3873 COL_CHARSET, ainfo->charset,
3875 COL_AUTODATA, auto_ainfo,
3878 g_auto_pointer_free(auto_ainfo);
3879 compose_attach_update_label(compose);
3883 void compose_use_signing(Compose *compose, gboolean use_signing)
3885 compose->use_signing = use_signing;
3886 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", use_signing);
3889 void compose_use_encryption(Compose *compose, gboolean use_encryption)
3891 compose->use_encryption = use_encryption;
3892 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", use_encryption);
3895 #define NEXT_PART_NOT_CHILD(info) \
3897 node = info->node; \
3898 while (node->children) \
3899 node = g_node_last_child(node); \
3900 info = procmime_mimeinfo_next((MimeInfo *)node->data); \
3903 static void compose_attach_parts(Compose *compose, MsgInfo *msginfo)
3907 MimeInfo *firsttext = NULL;
3908 MimeInfo *encrypted = NULL;
3911 const gchar *partname = NULL;
3913 mimeinfo = procmime_scan_message(msginfo);
3914 if (!mimeinfo) return;
3916 if (mimeinfo->node->children == NULL) {
3917 procmime_mimeinfo_free_all(&mimeinfo);
3921 /* find first content part */
3922 child = (MimeInfo *) mimeinfo->node->children->data;
3923 while (child && child->node->children && (child->type == MIMETYPE_MULTIPART))
3924 child = (MimeInfo *)child->node->children->data;
3927 if (child->type == MIMETYPE_TEXT) {
3929 debug_print("First text part found\n");
3930 } else if (compose->mode == COMPOSE_REEDIT &&
3931 child->type == MIMETYPE_APPLICATION &&
3932 !g_ascii_strcasecmp(child->subtype, "pgp-encrypted")) {
3933 encrypted = (MimeInfo *)child->node->parent->data;
3936 child = (MimeInfo *) mimeinfo->node->children->data;
3937 while (child != NULL) {
3940 if (child == encrypted) {
3941 /* skip this part of tree */
3942 NEXT_PART_NOT_CHILD(child);
3946 if (child->type == MIMETYPE_MULTIPART) {
3947 /* get the actual content */
3948 child = procmime_mimeinfo_next(child);
3952 if (child == firsttext) {
3953 child = procmime_mimeinfo_next(child);
3957 outfile = procmime_get_tmp_file_name(child);
3958 if ((err = procmime_get_part(outfile, child)) < 0)
3959 g_warning("Can't get the part of multipart message. (%s)", g_strerror(-err));
3961 gchar *content_type;
3963 content_type = procmime_get_content_type_str(child->type, child->subtype);
3965 /* if we meet a pgp signature, we don't attach it, but
3966 * we force signing. */
3967 if ((strcmp(content_type, "application/pgp-signature") &&
3968 strcmp(content_type, "application/pkcs7-signature") &&
3969 strcmp(content_type, "application/x-pkcs7-signature"))
3970 || compose->mode == COMPOSE_REDIRECT) {
3971 partname = procmime_mimeinfo_get_parameter(child, "filename");
3972 if (partname == NULL)
3973 partname = procmime_mimeinfo_get_parameter(child, "name");
3974 if (partname == NULL)
3976 compose_attach_append(compose, outfile,
3977 partname, content_type,
3978 procmime_mimeinfo_get_parameter(child, "charset"));
3980 compose_force_signing(compose, compose->account, NULL);
3982 g_free(content_type);
3985 NEXT_PART_NOT_CHILD(child);
3987 procmime_mimeinfo_free_all(&mimeinfo);
3990 #undef NEXT_PART_NOT_CHILD
3995 WAIT_FOR_INDENT_CHAR,
3996 WAIT_FOR_INDENT_CHAR_OR_SPACE,
3999 /* return indent length, we allow:
4000 indent characters followed by indent characters or spaces/tabs,
4001 alphabets and numbers immediately followed by indent characters,
4002 and the repeating sequences of the above
4003 If quote ends with multiple spaces, only the first one is included. */
4004 static gchar *compose_get_quote_str(GtkTextBuffer *buffer,
4005 const GtkTextIter *start, gint *len)
4007 GtkTextIter iter = *start;
4011 IndentState state = WAIT_FOR_INDENT_CHAR;
4014 gint alnum_count = 0;
4015 gint space_count = 0;
4018 if (prefs_common.quote_chars == NULL) {
4022 while (!gtk_text_iter_ends_line(&iter)) {
4023 wc = gtk_text_iter_get_char(&iter);
4024 if (g_unichar_iswide(wc))
4026 clen = g_unichar_to_utf8(wc, ch);
4030 is_indent = strchr(prefs_common.quote_chars, ch[0]) ? TRUE : FALSE;
4031 is_space = g_unichar_isspace(wc);
4033 if (state == WAIT_FOR_INDENT_CHAR) {
4034 if (!is_indent && !g_unichar_isalnum(wc))
4037 quote_len += alnum_count + space_count + 1;
4038 alnum_count = space_count = 0;
4039 state = WAIT_FOR_INDENT_CHAR_OR_SPACE;
4042 } else if (state == WAIT_FOR_INDENT_CHAR_OR_SPACE) {
4043 if (!is_indent && !is_space && !g_unichar_isalnum(wc))
4047 else if (is_indent) {
4048 quote_len += alnum_count + space_count + 1;
4049 alnum_count = space_count = 0;
4052 state = WAIT_FOR_INDENT_CHAR;
4056 gtk_text_iter_forward_char(&iter);
4059 if (quote_len > 0 && space_count > 0)
4065 if (quote_len > 0) {
4067 gtk_text_iter_forward_chars(&iter, quote_len);
4068 return gtk_text_buffer_get_text(buffer, start, &iter, FALSE);
4074 /* return >0 if the line is itemized */
4075 static int compose_itemized_length(GtkTextBuffer *buffer,
4076 const GtkTextIter *start)
4078 GtkTextIter iter = *start;
4083 if (gtk_text_iter_ends_line(&iter))
4088 wc = gtk_text_iter_get_char(&iter);
4089 if (!g_unichar_isspace(wc))
4091 gtk_text_iter_forward_char(&iter);
4092 if (gtk_text_iter_ends_line(&iter))
4096 clen = g_unichar_to_utf8(wc, ch);
4097 if (!((clen == 1 && strchr("*-+", ch[0])) ||
4099 wc == 0x2022 || /* BULLET */
4100 wc == 0x2023 || /* TRIANGULAR BULLET */
4101 wc == 0x2043 || /* HYPHEN BULLET */
4102 wc == 0x204c || /* BLACK LEFTWARDS BULLET */
4103 wc == 0x204d || /* BLACK RIGHTWARDS BULLET */
4104 wc == 0x2219 || /* BULLET OPERATOR */
4105 wc == 0x25d8 || /* INVERSE BULLET */
4106 wc == 0x25e6 || /* WHITE BULLET */
4107 wc == 0x2619 || /* REVERSED ROTATED FLORAL HEART BULLET */
4108 wc == 0x2765 || /* ROTATED HEAVY BLACK HEART BULLET */
4109 wc == 0x2767 || /* ROTATED FLORAL HEART BULLET */
4110 wc == 0x29be || /* CIRCLED WHITE BULLET */
4111 wc == 0x29bf /* CIRCLED BULLET */
4115 gtk_text_iter_forward_char(&iter);
4116 if (gtk_text_iter_ends_line(&iter))
4118 wc = gtk_text_iter_get_char(&iter);
4119 if (g_unichar_isspace(wc)) {
4125 /* return the string at the start of the itemization */
4126 static gchar * compose_get_itemized_chars(GtkTextBuffer *buffer,
4127 const GtkTextIter *start)
4129 GtkTextIter iter = *start;
4132 GString *item_chars = g_string_new("");
4135 if (gtk_text_iter_ends_line(&iter))
4140 wc = gtk_text_iter_get_char(&iter);
4141 if (!g_unichar_isspace(wc))
4143 gtk_text_iter_forward_char(&iter);
4144 if (gtk_text_iter_ends_line(&iter))
4146 g_string_append_unichar(item_chars, wc);
4149 str = item_chars->str;
4150 g_string_free(item_chars, FALSE);
4154 /* return the number of spaces at a line's start */
4155 static int compose_left_offset_length(GtkTextBuffer *buffer,
4156 const GtkTextIter *start)
4158 GtkTextIter iter = *start;
4161 if (gtk_text_iter_ends_line(&iter))
4165 wc = gtk_text_iter_get_char(&iter);
4166 if (!g_unichar_isspace(wc))
4169 gtk_text_iter_forward_char(&iter);
4170 if (gtk_text_iter_ends_line(&iter))
4174 gtk_text_iter_forward_char(&iter);
4175 if (gtk_text_iter_ends_line(&iter))
4180 static gboolean compose_get_line_break_pos(GtkTextBuffer *buffer,
4181 const GtkTextIter *start,
4182 GtkTextIter *break_pos,
4186 GtkTextIter iter = *start, line_end = *start;
4187 PangoLogAttr *attrs;
4194 gboolean can_break = FALSE;
4195 gboolean do_break = FALSE;
4196 gboolean was_white = FALSE;
4197 gboolean prev_dont_break = FALSE;
4199 gtk_text_iter_forward_to_line_end(&line_end);
4200 str = gtk_text_buffer_get_text(buffer, &iter, &line_end, FALSE);
4201 len = g_utf8_strlen(str, -1);
4205 g_warning("compose_get_line_break_pos: len = 0!");
4209 /* g_print("breaking line: %d: %s (len = %d)\n",
4210 gtk_text_iter_get_line(&iter), str, len); */
4212 attrs = g_new(PangoLogAttr, len + 1);
4214 pango_default_break(str, -1, NULL, attrs, len + 1);
4218 /* skip quote and leading spaces */
4219 for (i = 0; *p != '\0' && i < len; i++) {
4222 wc = g_utf8_get_char(p);
4223 if (i >= quote_len && !g_unichar_isspace(wc))
4225 if (g_unichar_iswide(wc))
4227 else if (*p == '\t')
4231 p = g_utf8_next_char(p);
4234 for (; *p != '\0' && i < len; i++) {
4235 PangoLogAttr *attr = attrs + i;
4239 if (attr->is_line_break && can_break && was_white && !prev_dont_break)
4242 was_white = attr->is_white;
4244 /* don't wrap URI */
4245 if ((uri_len = get_uri_len(p)) > 0) {
4247 if (pos > 0 && col > max_col) {
4257 wc = g_utf8_get_char(p);
4258 if (g_unichar_iswide(wc)) {
4260 if (prev_dont_break && can_break && attr->is_line_break)
4262 } else if (*p == '\t')
4266 if (pos > 0 && col > max_col) {
4271 if (*p == '-' || *p == '/')
4272 prev_dont_break = TRUE;
4274 prev_dont_break = FALSE;
4276 p = g_utf8_next_char(p);
4280 /* debug_print("compose_get_line_break_pos(): do_break = %d, pos = %d, col = %d\n", do_break, pos, col); */
4285 *break_pos = *start;
4286 gtk_text_iter_set_line_offset(break_pos, pos);
4291 static gboolean compose_join_next_line(Compose *compose,
4292 GtkTextBuffer *buffer,
4294 const gchar *quote_str)
4296 GtkTextIter iter_ = *iter, cur, prev, next, end;
4297 PangoLogAttr attrs[3];
4299 gchar *next_quote_str;
4302 gboolean keep_cursor = FALSE;
4304 if (!gtk_text_iter_forward_line(&iter_) ||
4305 gtk_text_iter_ends_line(&iter_)) {
4308 next_quote_str = compose_get_quote_str(buffer, &iter_, "e_len);
4310 if ((quote_str || next_quote_str) &&
4311 strcmp2(quote_str, next_quote_str) != 0) {
4312 g_free(next_quote_str);
4315 g_free(next_quote_str);
4318 if (quote_len > 0) {
4319 gtk_text_iter_forward_chars(&end, quote_len);
4320 if (gtk_text_iter_ends_line(&end)) {
4325 /* don't join itemized lines */
4326 if (compose_itemized_length(buffer, &end) > 0) {
4330 /* don't join signature separator */
4331 if (compose_is_sig_separator(compose, buffer, &iter_)) {
4334 /* delete quote str */
4336 gtk_text_buffer_delete(buffer, &iter_, &end);
4338 /* don't join line breaks put by the user */
4340 gtk_text_iter_backward_char(&cur);
4341 if (gtk_text_iter_has_tag(&cur, compose->no_join_tag)) {
4342 gtk_text_iter_forward_char(&cur);
4346 gtk_text_iter_forward_char(&cur);
4347 /* delete linebreak and extra spaces */
4348 while (gtk_text_iter_backward_char(&cur)) {
4349 wc1 = gtk_text_iter_get_char(&cur);
4350 if (!g_unichar_isspace(wc1))
4355 while (!gtk_text_iter_ends_line(&cur)) {
4356 wc1 = gtk_text_iter_get_char(&cur);
4357 if (!g_unichar_isspace(wc1))
4359 gtk_text_iter_forward_char(&cur);
4362 if (!gtk_text_iter_equal(&prev, &next)) {
4365 mark = gtk_text_buffer_get_insert(buffer);
4366 gtk_text_buffer_get_iter_at_mark(buffer, &cur, mark);
4367 if (gtk_text_iter_equal(&prev, &cur))
4369 gtk_text_buffer_delete(buffer, &prev, &next);
4373 /* insert space if required */
4374 gtk_text_iter_backward_char(&prev);
4375 wc1 = gtk_text_iter_get_char(&prev);
4376 wc2 = gtk_text_iter_get_char(&next);
4377 gtk_text_iter_forward_char(&next);
4378 str = gtk_text_buffer_get_text(buffer, &prev, &next, FALSE);
4379 pango_default_break(str, -1, NULL, attrs, 3);
4380 if (!attrs[1].is_line_break ||
4381 (!g_unichar_iswide(wc1) || !g_unichar_iswide(wc2))) {
4382 gtk_text_buffer_insert(buffer, &iter_, " ", 1);
4384 gtk_text_iter_backward_char(&iter_);
4385 gtk_text_buffer_place_cursor(buffer, &iter_);
4394 #define ADD_TXT_POS(bp_, ep_, pti_) \
4395 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
4396 last = last->next; \
4397 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
4398 last->next = NULL; \
4400 g_warning("alloc error scanning URIs"); \
4403 static gboolean compose_beautify_paragraph(Compose *compose, GtkTextIter *par_iter, gboolean force)
4405 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4406 GtkTextBuffer *buffer;
4407 GtkTextIter iter, break_pos, end_of_line;
4408 gchar *quote_str = NULL;
4410 gboolean wrap_quote = force || prefs_common.linewrap_quote;
4411 gboolean prev_autowrap = compose->autowrap;
4412 gint startq_offset = -1, noq_offset = -1;
4413 gint uri_start = -1, uri_stop = -1;
4414 gint nouri_start = -1, nouri_stop = -1;
4415 gint num_blocks = 0;
4416 gint quotelevel = -1;
4417 gboolean modified = force;
4418 gboolean removed = FALSE;
4419 gboolean modified_before_remove = FALSE;
4421 gboolean start = TRUE;
4422 gint itemized_len = 0, rem_item_len = 0;
4423 gchar *itemized_chars = NULL;
4424 gboolean item_continuation = FALSE;
4429 if (compose->draft_timeout_tag == COMPOSE_DRAFT_TIMEOUT_FORBIDDEN) {
4433 compose->autowrap = FALSE;
4435 buffer = gtk_text_view_get_buffer(text);
4436 undo_wrapping(compose->undostruct, TRUE);
4441 mark = gtk_text_buffer_get_insert(buffer);
4442 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
4446 if (compose->draft_timeout_tag == COMPOSE_DRAFT_TIMEOUT_FORBIDDEN) {
4447 if (gtk_text_iter_ends_line(&iter)) {
4448 while (gtk_text_iter_ends_line(&iter) &&
4449 gtk_text_iter_forward_line(&iter))
4452 while (gtk_text_iter_backward_line(&iter)) {
4453 if (gtk_text_iter_ends_line(&iter)) {
4454 gtk_text_iter_forward_line(&iter);
4460 /* move to line start */
4461 gtk_text_iter_set_line_offset(&iter, 0);
4464 itemized_len = compose_itemized_length(buffer, &iter);
4466 if (!itemized_len) {
4467 itemized_len = compose_left_offset_length(buffer, &iter);
4468 item_continuation = TRUE;
4472 itemized_chars = compose_get_itemized_chars(buffer, &iter);
4474 /* go until paragraph end (empty line) */
4475 while (start || !gtk_text_iter_ends_line(&iter)) {
4476 gchar *scanpos = NULL;
4477 /* parse table - in order of priority */
4479 const gchar *needle; /* token */
4481 /* token search function */
4482 gchar *(*search) (const gchar *haystack,
4483 const gchar *needle);
4484 /* part parsing function */
4485 gboolean (*parse) (const gchar *start,
4486 const gchar *scanpos,
4490 /* part to URI function */
4491 gchar *(*build_uri) (const gchar *bp,
4495 static struct table parser[] = {
4496 {"http://", strcasestr, get_uri_part, make_uri_string},
4497 {"https://", strcasestr, get_uri_part, make_uri_string},
4498 {"ftp://", strcasestr, get_uri_part, make_uri_string},
4499 {"sftp://", strcasestr, get_uri_part, make_uri_string},
4500 {"gopher://",strcasestr, get_uri_part, make_uri_string},
4501 {"www.", strcasestr, get_uri_part, make_http_string},
4502 {"mailto:", strcasestr, get_uri_part, make_uri_string},
4503 {"@", strcasestr, get_email_part, make_email_string}
4505 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
4506 gint last_index = PARSE_ELEMS;
4508 gchar *o_walk = NULL, *walk = NULL, *bp = NULL, *ep = NULL;
4512 if (!prev_autowrap && num_blocks == 0) {
4514 g_signal_handlers_block_by_func(G_OBJECT(buffer),
4515 G_CALLBACK(text_inserted),
4518 if (gtk_text_iter_has_tag(&iter, compose->no_wrap_tag) && !force)
4521 uri_start = uri_stop = -1;
4523 quote_str = compose_get_quote_str(buffer, &iter, "e_len);
4526 /* debug_print("compose_beautify_paragraph(): quote_str = '%s'\n", quote_str); */
4527 if (startq_offset == -1)
4528 startq_offset = gtk_text_iter_get_offset(&iter);
4529 quotelevel = get_quote_level(quote_str, prefs_common.quote_chars);
4530 if (quotelevel > 2) {
4531 /* recycle colors */
4532 if (prefs_common.recycle_quote_colors)
4541 if (startq_offset == -1)
4542 noq_offset = gtk_text_iter_get_offset(&iter);
4546 if (prev_autowrap == FALSE && !force && !wrap_quote) {
4549 if (gtk_text_iter_ends_line(&iter)) {
4551 } else if (compose_get_line_break_pos(buffer, &iter, &break_pos,
4552 prefs_common.linewrap_len,
4554 GtkTextIter prev, next, cur;
4555 if (prev_autowrap != FALSE || force) {
4556 compose->automatic_break = TRUE;
4558 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4559 compose->automatic_break = FALSE;
4560 if (itemized_len && compose->autoindent) {
4561 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4562 if (!item_continuation)
4563 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4565 } else if (quote_str && wrap_quote) {
4566 compose->automatic_break = TRUE;
4568 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4569 compose->automatic_break = FALSE;
4570 if (itemized_len && compose->autoindent) {
4571 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4572 if (!item_continuation)
4573 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4577 /* remove trailing spaces */
4579 rem_item_len = itemized_len;
4580 while (compose->autoindent && rem_item_len-- > 0)
4581 gtk_text_iter_backward_char(&cur);
4582 gtk_text_iter_backward_char(&cur);
4585 while (!gtk_text_iter_starts_line(&cur)) {
4588 gtk_text_iter_backward_char(&cur);
4589 wc = gtk_text_iter_get_char(&cur);
4590 if (!g_unichar_isspace(wc))
4594 if (!gtk_text_iter_equal(&prev, &next)) {
4595 gtk_text_buffer_delete(buffer, &prev, &next);
4597 gtk_text_iter_forward_char(&break_pos);
4601 gtk_text_buffer_insert(buffer, &break_pos,
4605 modified |= compose_join_next_line(compose, buffer, &iter, quote_str);
4607 /* move iter to current line start */
4608 gtk_text_iter_set_line_offset(&iter, 0);
4615 /* move iter to next line start */
4621 if (!prev_autowrap && num_blocks > 0) {
4623 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
4624 G_CALLBACK(text_inserted),
4628 while (!gtk_text_iter_ends_line(&end_of_line)) {
4629 gtk_text_iter_forward_char(&end_of_line);
4631 o_walk = walk = gtk_text_buffer_get_text(buffer, &iter, &end_of_line, FALSE);
4633 nouri_start = gtk_text_iter_get_offset(&iter);
4634 nouri_stop = gtk_text_iter_get_offset(&end_of_line);
4636 walk_pos = gtk_text_iter_get_offset(&iter);
4637 /* FIXME: this looks phony. scanning for anything in the parse table */
4638 for (n = 0; n < PARSE_ELEMS; n++) {
4641 tmp = parser[n].search(walk, parser[n].needle);
4643 if (scanpos == NULL || tmp < scanpos) {
4652 /* check if URI can be parsed */
4653 if (parser[last_index].parse(walk, scanpos, (const gchar **)&bp,
4654 (const gchar **)&ep, FALSE)
4655 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
4659 strlen(parser[last_index].needle);
4662 uri_start = walk_pos + (bp - o_walk);
4663 uri_stop = walk_pos + (ep - o_walk);
4667 gtk_text_iter_forward_line(&iter);
4670 if (startq_offset != -1) {
4671 GtkTextIter startquote, endquote;
4672 gtk_text_buffer_get_iter_at_offset(
4673 buffer, &startquote, startq_offset);
4676 switch (quotelevel) {
4678 if (!gtk_text_iter_has_tag(&startquote, compose->quote0_tag) ||
4679 !gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) {
4680 gtk_text_buffer_apply_tag_by_name(
4681 buffer, "quote0", &startquote, &endquote);
4682 gtk_text_buffer_remove_tag_by_name(
4683 buffer, "quote1", &startquote, &endquote);
4684 gtk_text_buffer_remove_tag_by_name(
4685 buffer, "quote2", &startquote, &endquote);
4690 if (!gtk_text_iter_has_tag(&startquote, compose->quote1_tag) ||
4691 !gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) {
4692 gtk_text_buffer_apply_tag_by_name(
4693 buffer, "quote1", &startquote, &endquote);
4694 gtk_text_buffer_remove_tag_by_name(
4695 buffer, "quote0", &startquote, &endquote);
4696 gtk_text_buffer_remove_tag_by_name(
4697 buffer, "quote2", &startquote, &endquote);
4702 if (!gtk_text_iter_has_tag(&startquote, compose->quote2_tag) ||
4703 !gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag)) {
4704 gtk_text_buffer_apply_tag_by_name(
4705 buffer, "quote2", &startquote, &endquote);
4706 gtk_text_buffer_remove_tag_by_name(
4707 buffer, "quote0", &startquote, &endquote);
4708 gtk_text_buffer_remove_tag_by_name(
4709 buffer, "quote1", &startquote, &endquote);
4715 } else if (noq_offset != -1) {
4716 GtkTextIter startnoquote, endnoquote;
4717 gtk_text_buffer_get_iter_at_offset(
4718 buffer, &startnoquote, noq_offset);
4721 if ((gtk_text_iter_has_tag(&startnoquote, compose->quote0_tag)
4722 && gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) ||
4723 (gtk_text_iter_has_tag(&startnoquote, compose->quote1_tag)
4724 && gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) ||
4725 (gtk_text_iter_has_tag(&startnoquote, compose->quote2_tag)
4726 && gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag))) {
4727 gtk_text_buffer_remove_tag_by_name(
4728 buffer, "quote0", &startnoquote, &endnoquote);
4729 gtk_text_buffer_remove_tag_by_name(
4730 buffer, "quote1", &startnoquote, &endnoquote);
4731 gtk_text_buffer_remove_tag_by_name(
4732 buffer, "quote2", &startnoquote, &endnoquote);
4738 if (uri_start != nouri_start && uri_stop != nouri_stop) {
4739 GtkTextIter nouri_start_iter, nouri_end_iter;
4740 gtk_text_buffer_get_iter_at_offset(
4741 buffer, &nouri_start_iter, nouri_start);
4742 gtk_text_buffer_get_iter_at_offset(
4743 buffer, &nouri_end_iter, nouri_stop);
4744 if (gtk_text_iter_has_tag(&nouri_start_iter, compose->uri_tag) &&
4745 gtk_text_iter_has_tag(&nouri_end_iter, compose->uri_tag)) {
4746 gtk_text_buffer_remove_tag_by_name(
4747 buffer, "link", &nouri_start_iter, &nouri_end_iter);
4748 modified_before_remove = modified;
4753 if (uri_start >= 0 && uri_stop > 0) {
4754 GtkTextIter uri_start_iter, uri_end_iter, back;
4755 gtk_text_buffer_get_iter_at_offset(
4756 buffer, &uri_start_iter, uri_start);
4757 gtk_text_buffer_get_iter_at_offset(
4758 buffer, &uri_end_iter, uri_stop);
4759 back = uri_end_iter;
4760 gtk_text_iter_backward_char(&back);
4761 if (!gtk_text_iter_has_tag(&uri_start_iter, compose->uri_tag) ||
4762 !gtk_text_iter_has_tag(&back, compose->uri_tag)) {
4763 gtk_text_buffer_apply_tag_by_name(
4764 buffer, "link", &uri_start_iter, &uri_end_iter);
4766 if (removed && !modified_before_remove) {
4772 /* debug_print("not modified, out after %d lines\n", lines); */
4776 /* debug_print("modified, out after %d lines\n", lines); */
4778 g_free(itemized_chars);
4781 undo_wrapping(compose->undostruct, FALSE);
4782 compose->autowrap = prev_autowrap;
4787 void compose_action_cb(void *data)
4789 Compose *compose = (Compose *)data;
4790 compose_wrap_all(compose);
4793 static void compose_wrap_all(Compose *compose)
4795 compose_wrap_all_full(compose, FALSE);
4798 static void compose_wrap_all_full(Compose *compose, gboolean force)
4800 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4801 GtkTextBuffer *buffer;
4803 gboolean modified = TRUE;
4805 buffer = gtk_text_view_get_buffer(text);
4807 gtk_text_buffer_get_start_iter(buffer, &iter);
4809 undo_wrapping(compose->undostruct, TRUE);
4811 while (!gtk_text_iter_is_end(&iter) && modified)
4812 modified = compose_beautify_paragraph(compose, &iter, force);
4814 undo_wrapping(compose->undostruct, FALSE);
4818 static void compose_set_title(Compose *compose)
4824 edited = compose->modified ? _(" [Edited]") : "";
4826 subject = gtk_editable_get_chars(
4827 GTK_EDITABLE(compose->subject_entry), 0, -1);
4829 #ifndef GENERIC_UMPC
4830 if (subject && strlen(subject))
4831 str = g_strdup_printf(_("%s - Compose message%s"),
4834 str = g_strdup_printf(_("[no subject] - Compose message%s"), edited);
4836 str = g_strdup(_("Compose message"));
4839 gtk_window_set_title(GTK_WINDOW(compose->window), str);
4845 * compose_current_mail_account:
4847 * Find a current mail account (the currently selected account, or the
4848 * default account, if a news account is currently selected). If a
4849 * mail account cannot be found, display an error message.
4851 * Return value: Mail account, or NULL if not found.
4853 static PrefsAccount *
4854 compose_current_mail_account(void)
4858 if (cur_account && cur_account->protocol != A_NNTP)
4861 ac = account_get_default();
4862 if (!ac || ac->protocol == A_NNTP) {
4863 alertpanel_error(_("Account for sending mail is not specified.\n"
4864 "Please select a mail account before sending."));
4871 #define QUOTE_IF_REQUIRED(out, str) \
4873 if (*str != '"' && strpbrk(str, ",.:;[]<>()@\\\"")) { \
4877 len = strlen(str) + 3; \
4878 if ((__tmp = alloca(len)) == NULL) { \
4879 g_warning("can't allocate memory"); \
4880 g_string_free(header, TRUE); \
4883 g_snprintf(__tmp, len, "\"%s\"", str); \
4888 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4889 g_warning("can't allocate memory"); \
4890 g_string_free(header, TRUE); \
4893 strcpy(__tmp, str); \
4899 #define QUOTE_IF_REQUIRED_NORMAL(out, str, errret) \
4901 if (*str != '"' && strpbrk(str, ",.:;[]<>()@\\\"")) { \
4905 len = strlen(str) + 3; \
4906 if ((__tmp = alloca(len)) == NULL) { \
4907 g_warning("can't allocate memory"); \
4910 g_snprintf(__tmp, len, "\"%s\"", str); \
4915 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4916 g_warning("can't allocate memory"); \
4919 strcpy(__tmp, str); \
4925 static void compose_select_account(Compose *compose, PrefsAccount *account,
4928 gchar *from = NULL, *header = NULL;
4929 ComposeHeaderEntry *header_entry;
4932 cm_return_if_fail(account != NULL);
4934 compose->account = account;
4935 if (account->name && *account->name) {
4937 QUOTE_IF_REQUIRED_NORMAL(buf, account->name, return);
4938 qbuf = escape_internal_quotes(buf, '"');
4939 from = g_strdup_printf("%s <%s>",
4940 qbuf, account->address);
4943 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4945 from = g_strdup_printf("<%s>",
4947 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4952 compose_set_title(compose);
4954 if (account->default_sign && compose->mode != COMPOSE_REDIRECT)
4955 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", TRUE);
4957 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", FALSE);
4958 if (account->default_encrypt && compose->mode != COMPOSE_REDIRECT)
4959 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", TRUE);
4961 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", FALSE);
4963 compose_activate_privacy_system(compose, account, FALSE);
4965 if (!init && compose->mode != COMPOSE_REDIRECT) {
4966 undo_block(compose->undostruct);
4967 compose_insert_sig(compose, TRUE);
4968 undo_unblock(compose->undostruct);
4971 header_entry = (ComposeHeaderEntry *) compose->header_list->data;
4972 if (gtk_combo_box_get_active_iter(GTK_COMBO_BOX(header_entry->combo), &iter))
4973 gtk_tree_model_get(gtk_combo_box_get_model(GTK_COMBO_BOX(
4974 header_entry->combo)), &iter, COMBOBOX_TEXT, &header, -1);
4976 if (header && !strlen(gtk_entry_get_text(GTK_ENTRY(header_entry->entry)))) {
4977 if (account->protocol == A_NNTP) {
4978 if (!strcmp(header, _("To:")))
4979 combobox_select_by_text(
4980 GTK_COMBO_BOX(header_entry->combo),
4983 if (!strcmp(header, _("Newsgroups:")))
4984 combobox_select_by_text(
4985 GTK_COMBO_BOX(header_entry->combo),
4993 /* use account's dict info if set */
4994 if (compose->gtkaspell) {
4995 if (account->enable_default_dictionary)
4996 gtkaspell_change_dict(compose->gtkaspell,
4997 account->default_dictionary, FALSE);
4998 if (account->enable_default_alt_dictionary)
4999 gtkaspell_change_alt_dict(compose->gtkaspell,
5000 account->default_alt_dictionary);
5001 if (account->enable_default_dictionary
5002 || account->enable_default_alt_dictionary)
5003 compose_spell_menu_changed(compose);
5008 gboolean compose_check_for_valid_recipient(Compose *compose) {
5009 gchar *recipient_headers_mail[] = {"To:", "Cc:", "Bcc:", NULL};
5010 gchar *recipient_headers_news[] = {"Newsgroups:", NULL};
5011 gboolean recipient_found = FALSE;
5015 /* free to and newsgroup list */
5016 slist_free_strings_full(compose->to_list);
5017 compose->to_list = NULL;
5019 slist_free_strings_full(compose->newsgroup_list);
5020 compose->newsgroup_list = NULL;
5022 /* search header entries for to and newsgroup entries */
5023 for (list = compose->header_list; list; list = list->next) {
5026 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5027 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5030 if (entry[0] != '\0') {
5031 for (strptr = recipient_headers_mail; *strptr != NULL; strptr++) {
5032 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
5033 compose->to_list = address_list_append(compose->to_list, entry);
5034 recipient_found = TRUE;
5037 for (strptr = recipient_headers_news; *strptr != NULL; strptr++) {
5038 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
5039 compose->newsgroup_list = newsgroup_list_append(compose->newsgroup_list, entry);
5040 recipient_found = TRUE;
5047 return recipient_found;
5050 static gboolean compose_check_for_set_recipients(Compose *compose)
5052 if (compose->account->set_autocc && compose->account->auto_cc) {
5053 gboolean found_other = FALSE;
5055 /* search header entries for to and newsgroup entries */
5056 for (list = compose->header_list; list; list = list->next) {
5059 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5060 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5063 if (strcmp(entry, compose->account->auto_cc)
5064 || strcmp(header, prefs_common_translated_header_name("Cc:"))) {
5075 if (compose->batch) {
5076 gtk_widget_show_all(compose->window);
5078 text = g_strdup_printf(_("The only recipient is the default '%s' address. Send anyway?"),
5079 prefs_common_translated_header_name("Cc"));
5080 aval = alertpanel(_("Send"),
5082 GTK_STOCK_CANCEL, _("_Send"), NULL, ALERTFOCUS_SECOND);
5084 if (aval != G_ALERTALTERNATE)
5088 if (compose->account->set_autobcc && compose->account->auto_bcc) {
5089 gboolean found_other = FALSE;
5091 /* search header entries for to and newsgroup entries */
5092 for (list = compose->header_list; list; list = list->next) {
5095 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5096 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5099 if (strcmp(entry, compose->account->auto_bcc)
5100 || strcmp(header, prefs_common_translated_header_name("Bcc:"))) {
5112 if (compose->batch) {
5113 gtk_widget_show_all(compose->window);
5115 text = g_strdup_printf(_("The only recipient is the default '%s' address. Send anyway?"),
5116 prefs_common_translated_header_name("Bcc"));
5117 aval = alertpanel(_("Send"),
5119 GTK_STOCK_CANCEL, _("_Send"), NULL, ALERTFOCUS_SECOND);
5121 if (aval != G_ALERTALTERNATE)
5128 static gboolean compose_check_entries(Compose *compose, gboolean check_everything)
5132 if (compose_check_for_valid_recipient(compose) == FALSE) {
5133 if (compose->batch) {
5134 gtk_widget_show_all(compose->window);
5136 alertpanel_error(_("Recipient is not specified."));
5140 if (compose_check_for_set_recipients(compose) == FALSE) {
5144 if (!compose->batch && prefs_common.warn_empty_subj == TRUE) {
5145 str = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5146 if (*str == '\0' && check_everything == TRUE &&
5147 compose->mode != COMPOSE_REDIRECT) {
5151 message = g_strdup_printf(_("Subject is empty. %s"),
5152 compose->sending?_("Send it anyway?"):
5153 _("Queue it anyway?"));
5155 aval = alertpanel_full(compose->sending?_("Send"):_("Send later"), message,
5156 GTK_STOCK_CANCEL, compose->sending?_("_Send"):_("_Queue"), NULL,
5157 ALERTFOCUS_FIRST, TRUE, NULL, ALERT_QUESTION);
5159 if (aval & G_ALERTDISABLE) {
5160 aval &= ~G_ALERTDISABLE;
5161 prefs_common.warn_empty_subj = FALSE;
5163 if (aval != G_ALERTALTERNATE)
5168 if (!compose->batch && prefs_common.warn_sending_many_recipients_num > 0
5169 && check_everything == TRUE) {
5173 /* count To and Cc recipients */
5174 for (list = compose->header_list; list; list = list->next) {
5178 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5179 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5182 if ((entry[0] != '\0') &&
5183 (!strcmp(header, prefs_common_translated_header_name("To:")) ||
5184 !strcmp(header, prefs_common_translated_header_name("Cc:")))) {
5190 if (cnt > prefs_common.warn_sending_many_recipients_num) {
5194 message = g_strdup_printf(_("Sending to %d recipients. %s"), cnt,
5195 compose->sending?_("Send it anyway?"):
5196 _("Queue it anyway?"));
5198 aval = alertpanel_full(compose->sending?_("Send"):_("Send later"), message,
5199 GTK_STOCK_CANCEL, compose->sending?_("_Send"):_("_Queue"), NULL,
5200 ALERTFOCUS_FIRST, TRUE, NULL, ALERT_QUESTION);
5202 if (aval & G_ALERTDISABLE) {
5203 aval &= ~G_ALERTDISABLE;
5204 prefs_common.warn_sending_many_recipients_num = 0;
5206 if (aval != G_ALERTALTERNATE)
5211 if (check_everything && hooks_invoke(COMPOSE_CHECK_BEFORE_SEND_HOOKLIST, compose))
5217 static void _display_queue_error(ComposeQueueResult val)
5220 case COMPOSE_QUEUE_SUCCESS:
5222 case COMPOSE_QUEUE_ERROR_NO_MSG:
5223 alertpanel_error(_("Could not queue message."));
5225 case COMPOSE_QUEUE_ERROR_WITH_ERRNO:
5226 alertpanel_error(_("Could not queue message:\n\n%s."),
5229 case COMPOSE_QUEUE_ERROR_SIGNING_FAILED:
5230 alertpanel_error(_("Could not queue message for sending:\n\n"
5231 "Signature failed: %s"),
5232 privacy_peek_error() ? privacy_get_error() : _("Unknown error"));
5234 case COMPOSE_QUEUE_ERROR_ENCRYPT_FAILED:
5235 alertpanel_error(_("Could not queue message for sending:\n\n"
5236 "Encryption failed: %s"),
5237 privacy_peek_error() ? privacy_get_error() : _("Unknown error"));
5239 case COMPOSE_QUEUE_ERROR_CHAR_CONVERSION:
5240 alertpanel_error(_("Could not queue message for sending:\n\n"
5241 "Charset conversion failed."));
5243 case COMPOSE_QUEUE_ERROR_NO_ENCRYPTION_KEY:
5244 alertpanel_error(_("Could not queue message for sending:\n\n"
5245 "Couldn't get recipient encryption key."));
5248 /* unhandled error */
5249 debug_print("oops, unhandled compose_queue() return value %d\n",
5255 gint compose_send(Compose *compose)
5258 FolderItem *folder = NULL;
5259 ComposeQueueResult val = COMPOSE_QUEUE_ERROR_NO_MSG;
5260 gchar *msgpath = NULL;
5261 gboolean discard_window = FALSE;
5262 gchar *errstr = NULL;
5263 gchar *tmsgid = NULL;
5264 MainWindow *mainwin = mainwindow_get_mainwindow();
5265 gboolean queued_removed = FALSE;
5267 if (prefs_common.send_dialog_invisible
5268 || compose->batch == TRUE)
5269 discard_window = TRUE;
5271 compose_allow_user_actions (compose, FALSE);
5272 compose->sending = TRUE;
5274 if (compose_check_entries(compose, TRUE) == FALSE) {
5275 if (compose->batch) {
5276 gtk_widget_show_all(compose->window);
5282 val = compose_queue(compose, &msgnum, &folder, &msgpath, TRUE);
5284 if (val != COMPOSE_QUEUE_SUCCESS) {
5285 if (compose->batch) {
5286 gtk_widget_show_all(compose->window);
5289 _display_queue_error(val);
5294 tmsgid = compose->msgid ? g_strdup(compose->msgid) : NULL;
5295 if (discard_window) {
5296 compose->sending = FALSE;
5297 compose_close(compose);
5298 /* No more compose access in the normal codepath
5299 * after this point! */
5304 alertpanel_error(_("The message was queued but could not be "
5305 "sent.\nUse \"Send queued messages\" from "
5306 "the main window to retry."));
5307 if (!discard_window) {
5314 if (msgpath == NULL) {
5315 msgpath = folder_item_fetch_msg(folder, msgnum);
5316 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5319 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5320 claws_unlink(msgpath);
5323 if (!discard_window) {
5325 if (!queued_removed)
5326 folder_item_remove_msg(folder, msgnum);
5327 folder_item_scan(folder);
5329 /* make sure we delete that */
5330 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5332 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5333 folder_item_remove_msg(folder, tmp->msgnum);
5334 procmsg_msginfo_free(&tmp);
5341 if (!queued_removed)
5342 folder_item_remove_msg(folder, msgnum);
5343 folder_item_scan(folder);
5345 /* make sure we delete that */
5346 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5348 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5349 folder_item_remove_msg(folder, tmp->msgnum);
5350 procmsg_msginfo_free(&tmp);
5353 if (!discard_window) {
5354 compose->sending = FALSE;
5355 compose_allow_user_actions (compose, TRUE);
5356 compose_close(compose);
5360 alertpanel_error_log(_("%s\nUse \"Send queued messages\" from "
5361 "the main window to retry."), errstr);
5364 alertpanel_error_log(_("The message was queued but could not be "
5365 "sent.\nUse \"Send queued messages\" from "
5366 "the main window to retry."));
5368 if (!discard_window) {
5377 toolbar_main_set_sensitive(mainwin);
5378 main_window_set_menu_sensitive(mainwin);
5384 compose_allow_user_actions (compose, TRUE);
5385 compose->sending = FALSE;
5386 compose->modified = TRUE;
5387 toolbar_main_set_sensitive(mainwin);
5388 main_window_set_menu_sensitive(mainwin);
5393 static gboolean compose_use_attach(Compose *compose)
5395 GtkTreeModel *model = gtk_tree_view_get_model
5396 (GTK_TREE_VIEW(compose->attach_clist));
5397 return gtk_tree_model_iter_n_children(model, NULL) > 0;
5400 static gint compose_redirect_write_headers_from_headerlist(Compose *compose,
5403 gchar buf[BUFFSIZE];
5405 gboolean first_to_address;
5406 gboolean first_cc_address;
5408 ComposeHeaderEntry *headerentry;
5409 const gchar *headerentryname;
5410 const gchar *cc_hdr;
5411 const gchar *to_hdr;
5412 gboolean err = FALSE;
5414 debug_print("Writing redirect header\n");
5416 cc_hdr = prefs_common_translated_header_name("Cc:");
5417 to_hdr = prefs_common_translated_header_name("To:");
5419 first_to_address = TRUE;
5420 for (list = compose->header_list; list; list = list->next) {
5421 headerentry = ((ComposeHeaderEntry *)list->data);
5422 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5424 if (g_utf8_collate(headerentryname, to_hdr) == 0) {
5425 const gchar *entstr = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5426 Xstrdup_a(str, entstr, return -1);
5428 if (str[0] != '\0') {
5429 compose_convert_header
5430 (compose, buf, sizeof(buf), str,
5431 strlen("Resent-To") + 2, TRUE);
5433 if (first_to_address) {
5434 err |= (fprintf(fp, "Resent-To: ") < 0);
5435 first_to_address = FALSE;
5437 err |= (fprintf(fp, ",") < 0);
5439 err |= (fprintf(fp, "%s", buf) < 0);
5443 if (!first_to_address) {
5444 err |= (fprintf(fp, "\n") < 0);
5447 first_cc_address = TRUE;
5448 for (list = compose->header_list; list; list = list->next) {
5449 headerentry = ((ComposeHeaderEntry *)list->data);
5450 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5452 if (g_utf8_collate(headerentryname, cc_hdr) == 0) {
5453 const gchar *strg = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5454 Xstrdup_a(str, strg, return -1);
5456 if (str[0] != '\0') {
5457 compose_convert_header
5458 (compose, buf, sizeof(buf), str,
5459 strlen("Resent-Cc") + 2, TRUE);
5461 if (first_cc_address) {
5462 err |= (fprintf(fp, "Resent-Cc: ") < 0);
5463 first_cc_address = FALSE;
5465 err |= (fprintf(fp, ",") < 0);
5467 err |= (fprintf(fp, "%s", buf) < 0);
5471 if (!first_cc_address) {
5472 err |= (fprintf(fp, "\n") < 0);
5475 return (err ? -1:0);
5478 static gint compose_redirect_write_headers(Compose *compose, FILE *fp)
5480 gchar date[RFC822_DATE_BUFFSIZE];
5481 gchar buf[BUFFSIZE];
5483 const gchar *entstr;
5484 /* struct utsname utsbuf; */
5485 gboolean err = FALSE;
5487 cm_return_val_if_fail(fp != NULL, -1);
5488 cm_return_val_if_fail(compose->account != NULL, -1);
5489 cm_return_val_if_fail(compose->account->address != NULL, -1);
5492 if (prefs_common.hide_timezone)
5493 get_rfc822_date_hide_tz(date, sizeof(date));
5495 get_rfc822_date(date, sizeof(date));
5496 err |= (fprintf(fp, "Resent-Date: %s\n", date) < 0);
5499 if (compose->account->name && *compose->account->name) {
5500 compose_convert_header
5501 (compose, buf, sizeof(buf), compose->account->name,
5502 strlen("From: "), TRUE);
5503 err |= (fprintf(fp, "Resent-From: %s <%s>\n",
5504 buf, compose->account->address) < 0);
5506 err |= (fprintf(fp, "Resent-From: %s\n", compose->account->address) < 0);
5509 entstr = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5510 if (*entstr != '\0') {
5511 Xstrdup_a(str, entstr, return -1);
5514 compose_convert_header(compose, buf, sizeof(buf), str,
5515 strlen("Subject: "), FALSE);
5516 err |= (fprintf(fp, "Subject: %s\n", buf) < 0);
5520 /* Resent-Message-ID */
5521 if (compose->account->gen_msgid) {
5522 gchar *addr = prefs_account_generate_msgid(compose->account);
5523 err |= (fprintf(fp, "Resent-Message-ID: <%s>\n", addr) < 0);
5525 g_free(compose->msgid);
5526 compose->msgid = addr;
5528 compose->msgid = NULL;
5531 if (compose_redirect_write_headers_from_headerlist(compose, fp))
5534 /* separator between header and body */
5535 err |= (claws_fputs("\n", fp) == EOF);
5537 return (err ? -1:0);
5540 static gint compose_redirect_write_to_file(Compose *compose, FILE *fdest)
5545 gchar rewrite_buf[BUFFSIZE];
5547 gboolean skip = FALSE;
5548 gboolean err = FALSE;
5549 gchar *not_included[]={
5550 "Return-Path:", "Delivered-To:", "Received:",
5551 "Subject:", "X-UIDL:", "AF:",
5552 "NF:", "PS:", "SRH:",
5553 "SFN:", "DSR:", "MID:",
5554 "CFG:", "PT:", "S:",
5555 "RQ:", "SSV:", "NSV:",
5556 "SSH:", "R:", "MAID:",
5557 "NAID:", "RMID:", "FMID:",
5558 "SCF:", "RRCPT:", "NG:",
5559 "X-Claws-Privacy", "X-Claws-Sign:", "X-Claws-Encrypt",
5560 "X-Claws-End-Special-Headers:", "X-Claws-Account-Id:",
5561 "X-Sylpheed-Privacy", "X-Sylpheed-Sign:", "X-Sylpheed-Encrypt",
5562 "X-Sylpheed-End-Special-Headers:", "X-Sylpheed-Account-Id:",
5563 "X-Claws-Auto-Wrapping:", "X-Claws-Auto-Indent:",
5568 if ((fp = claws_fopen(compose->redirect_filename, "rb")) == NULL) {
5569 FILE_OP_ERROR(compose->redirect_filename, "claws_fopen");
5573 while ((ret = procheader_get_one_field_asis(&buf, fp)) != -1) {
5575 for (i = 0; not_included[i] != NULL; i++) {
5576 if (g_ascii_strncasecmp(buf, not_included[i],
5577 strlen(not_included[i])) == 0) {
5587 if (claws_fputs(buf, fdest) == -1) {
5593 if (!prefs_common.redirect_keep_from) {
5594 if (g_ascii_strncasecmp(buf, "From:",
5595 strlen("From:")) == 0) {
5596 err |= (claws_fputs(" (by way of ", fdest) == EOF);
5597 if (compose->account->name
5598 && *compose->account->name) {
5599 gchar buffer[BUFFSIZE];
5601 compose_convert_header
5602 (compose, buffer, sizeof(buffer),
5603 compose->account->name,
5606 err |= (fprintf(fdest, "%s <%s>",
5608 compose->account->address) < 0);
5610 err |= (fprintf(fdest, "%s",
5611 compose->account->address) < 0);
5612 err |= (claws_fputs(")", fdest) == EOF);
5618 if (claws_fputs("\n", fdest) == -1)
5625 if (compose_redirect_write_headers(compose, fdest))
5628 while ((len = claws_fread(rewrite_buf, sizeof(gchar), sizeof(rewrite_buf), fp)) > 0) {
5629 if (claws_fwrite(rewrite_buf, sizeof(gchar), len, fdest) != len)
5643 static gint compose_write_to_file(Compose *compose, FILE *fp, gint action, gboolean attach_parts)
5645 GtkTextBuffer *buffer;
5646 GtkTextIter start, end, tmp;
5647 gchar *chars, *tmp_enc_file, *content;
5649 const gchar *out_codeset;
5650 EncodingType encoding = ENC_UNKNOWN;
5651 MimeInfo *mimemsg, *mimetext;
5653 const gchar *src_codeset = CS_INTERNAL;
5654 gchar *from_addr = NULL;
5655 gchar *from_name = NULL;
5658 if (action == COMPOSE_WRITE_FOR_SEND) {
5659 attach_parts = TRUE;
5661 /* We're sending the message, generate a Message-ID
5663 if (compose->msgid == NULL &&
5664 compose->account->gen_msgid) {
5665 compose->msgid = prefs_account_generate_msgid(compose->account);
5669 /* create message MimeInfo */
5670 mimemsg = procmime_mimeinfo_new();
5671 mimemsg->type = MIMETYPE_MESSAGE;
5672 mimemsg->subtype = g_strdup("rfc822");
5673 mimemsg->content = MIMECONTENT_MEM;
5674 mimemsg->tmp = TRUE; /* must free content later */
5675 mimemsg->data.mem = compose_get_header(compose);
5677 /* Create text part MimeInfo */
5678 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5679 gtk_text_buffer_get_end_iter(buffer, &end);
5682 /* We make sure that there is a newline at the end. */
5683 if (action == COMPOSE_WRITE_FOR_SEND && gtk_text_iter_backward_char(&tmp)) {
5684 chars = gtk_text_buffer_get_text(buffer, &tmp, &end, FALSE);
5685 if (*chars != '\n') {
5686 gtk_text_buffer_insert(buffer, &end, "\n", 1);
5691 /* get all composed text */
5692 gtk_text_buffer_get_start_iter(buffer, &start);
5693 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5695 out_codeset = conv_get_charset_str(compose->out_encoding);
5697 if (!out_codeset && is_ascii_str(chars)) {
5698 out_codeset = CS_US_ASCII;
5699 } else if (prefs_common.outgoing_fallback_to_ascii &&
5700 is_ascii_str(chars)) {
5701 out_codeset = CS_US_ASCII;
5702 encoding = ENC_7BIT;
5706 gchar *test_conv_global_out = NULL;
5707 gchar *test_conv_reply = NULL;
5709 /* automatic mode. be automatic. */
5710 codeconv_set_strict(TRUE);
5712 out_codeset = conv_get_outgoing_charset_str();
5714 debug_print("trying to convert to %s\n", out_codeset);
5715 test_conv_global_out = conv_codeset_strdup(chars, src_codeset, out_codeset);
5718 if (!test_conv_global_out && compose->orig_charset
5719 && strcmp(compose->orig_charset, CS_US_ASCII)) {
5720 out_codeset = compose->orig_charset;
5721 debug_print("failure; trying to convert to %s\n", out_codeset);
5722 test_conv_reply = conv_codeset_strdup(chars, src_codeset, out_codeset);
5725 if (!test_conv_global_out && !test_conv_reply) {
5727 out_codeset = CS_INTERNAL;
5728 debug_print("failure; finally using %s\n", out_codeset);
5730 g_free(test_conv_global_out);
5731 g_free(test_conv_reply);
5732 codeconv_set_strict(FALSE);
5735 if (encoding == ENC_UNKNOWN) {
5736 if (prefs_common.encoding_method == CTE_BASE64)
5737 encoding = ENC_BASE64;
5738 else if (prefs_common.encoding_method == CTE_QUOTED_PRINTABLE)
5739 encoding = ENC_QUOTED_PRINTABLE;
5740 else if (prefs_common.encoding_method == CTE_8BIT)
5741 encoding = ENC_8BIT;
5743 encoding = procmime_get_encoding_for_charset(out_codeset);
5746 debug_print("src encoding = %s, out encoding = %s, transfer encoding = %s\n",
5747 src_codeset, out_codeset, procmime_get_encoding_str(encoding));
5749 if (action == COMPOSE_WRITE_FOR_SEND) {
5750 codeconv_set_strict(TRUE);
5751 buf = conv_codeset_strdup(chars, src_codeset, out_codeset);
5752 codeconv_set_strict(FALSE);
5757 msg = g_strdup_printf(_("Can't convert the character encoding of the message \n"
5758 "to the specified %s charset.\n"
5759 "Send it as %s?"), out_codeset, src_codeset);
5760 aval = alertpanel_full(_("Error"), msg, GTK_STOCK_CANCEL,
5761 _("_Send"), NULL, ALERTFOCUS_SECOND, FALSE,
5765 if (aval != G_ALERTALTERNATE) {
5767 return COMPOSE_QUEUE_ERROR_CHAR_CONVERSION;
5770 out_codeset = src_codeset;
5776 out_codeset = src_codeset;
5781 if (prefs_common.rewrite_first_from && (encoding == ENC_8BIT || encoding == ENC_7BIT)) {
5782 if (!strncmp(buf, "From ", sizeof("From ")-1) ||
5783 strstr(buf, "\nFrom ") != NULL) {
5784 encoding = ENC_QUOTED_PRINTABLE;
5788 mimetext = procmime_mimeinfo_new();
5789 mimetext->content = MIMECONTENT_MEM;
5790 mimetext->tmp = TRUE; /* must free content later */
5791 /* dup'ed because procmime_encode_content can turn it into a tmpfile
5792 * and free the data, which we need later. */
5793 mimetext->data.mem = g_strdup(buf);
5794 mimetext->type = MIMETYPE_TEXT;
5795 mimetext->subtype = g_strdup("plain");
5796 g_hash_table_insert(mimetext->typeparameters, g_strdup("charset"),
5797 g_strdup(out_codeset));
5799 /* protect trailing spaces when signing message */
5800 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5801 privacy_system_can_sign(compose->privacy_system)) {
5802 encoding = ENC_QUOTED_PRINTABLE;
5806 debug_print("main text: %Id bytes encoded as %s in %d\n",
5808 debug_print("main text: %zd bytes encoded as %s in %d\n",
5810 strlen(buf), out_codeset, encoding);
5812 /* check for line length limit */
5813 if (action == COMPOSE_WRITE_FOR_SEND &&
5814 encoding != ENC_QUOTED_PRINTABLE && encoding != ENC_BASE64 &&
5815 check_line_length(buf, 1000, &line) < 0) {
5818 msg = g_strdup_printf
5819 (_("Line %d exceeds the line length limit (998 bytes).\n"
5820 "The contents of the message might be broken on the way to the delivery.\n"
5822 "Send it anyway?"), line + 1);
5823 aval = alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, GTK_STOCK_OK, NULL,
5826 if (aval != G_ALERTALTERNATE) {
5828 return COMPOSE_QUEUE_ERROR_NO_MSG;
5832 if (encoding != ENC_UNKNOWN)
5833 procmime_encode_content(mimetext, encoding);
5835 /* append attachment parts */
5836 if (compose_use_attach(compose) && attach_parts) {
5837 MimeInfo *mimempart;
5838 gchar *boundary = NULL;
5839 mimempart = procmime_mimeinfo_new();
5840 mimempart->content = MIMECONTENT_EMPTY;
5841 mimempart->type = MIMETYPE_MULTIPART;
5842 mimempart->subtype = g_strdup("mixed");
5846 boundary = generate_mime_boundary(NULL);
5847 } while (strstr(buf, boundary) != NULL);
5849 g_hash_table_insert(mimempart->typeparameters, g_strdup("boundary"),
5852 mimetext->disposition = DISPOSITIONTYPE_INLINE;
5854 g_node_append(mimempart->node, mimetext->node);
5855 g_node_append(mimemsg->node, mimempart->node);
5857 if (compose_add_attachments(compose, mimempart) < 0)
5858 return COMPOSE_QUEUE_ERROR_NO_MSG;
5860 g_node_append(mimemsg->node, mimetext->node);
5864 if (strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) != 0) {
5865 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
5866 /* extract name and address */
5867 if (strstr(spec, " <") && strstr(spec, ">")) {
5868 from_addr = g_strdup(strrchr(spec, '<')+1);
5869 *(strrchr(from_addr, '>')) = '\0';
5870 from_name = g_strdup(spec);
5871 *(strrchr(from_name, '<')) = '\0';
5878 /* sign message if sending */
5879 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5880 privacy_system_can_sign(compose->privacy_system))
5881 if (!privacy_sign(compose->privacy_system, mimemsg,
5882 compose->account, from_addr)) {
5885 return COMPOSE_QUEUE_ERROR_SIGNING_FAILED;
5890 if (compose->use_encryption) {
5891 if (compose->encdata != NULL &&
5892 strcmp(compose->encdata, "_DONT_ENCRYPT_")) {
5894 /* First, write an unencrypted copy and save it to outbox, if
5895 * user wants that. */
5896 if (compose->account->save_encrypted_as_clear_text) {
5897 debug_print("saving sent message unencrypted...\n");
5898 FILE *tmpfp = get_tmpfile_in_dir(get_mime_tmp_dir(), &tmp_enc_file);
5900 claws_fclose(tmpfp);
5902 /* fp now points to a file with headers written,
5903 * let's make a copy. */
5905 content = file_read_stream_to_str(fp);
5907 str_write_to_file(content, tmp_enc_file, TRUE);
5910 /* Now write the unencrypted body. */
5911 if ((tmpfp = claws_fopen(tmp_enc_file, "a")) != NULL) {
5912 procmime_write_mimeinfo(mimemsg, tmpfp);
5913 claws_fclose(tmpfp);
5915 outbox = folder_find_item_from_identifier(compose_get_save_to(compose));
5917 outbox = folder_get_default_outbox();
5919 procmsg_save_to_outbox(outbox, tmp_enc_file, TRUE);
5920 claws_unlink(tmp_enc_file);
5922 g_warning("Can't open file '%s'", tmp_enc_file);
5925 g_warning("couldn't get tempfile");
5928 if (!privacy_encrypt(compose->privacy_system, mimemsg, compose->encdata)) {
5929 debug_print("Couldn't encrypt mime structure: %s.\n",
5930 privacy_get_error());
5931 return COMPOSE_QUEUE_ERROR_ENCRYPT_FAILED;
5936 procmime_write_mimeinfo(mimemsg, fp);
5938 procmime_mimeinfo_free_all(&mimemsg);
5943 static gint compose_write_body_to_file(Compose *compose, const gchar *file)
5945 GtkTextBuffer *buffer;
5946 GtkTextIter start, end;
5951 if ((fp = claws_fopen(file, "wb")) == NULL) {
5952 FILE_OP_ERROR(file, "claws_fopen");
5956 /* chmod for security */
5957 if (change_file_mode_rw(fp, file) < 0) {
5958 FILE_OP_ERROR(file, "chmod");
5959 g_warning("can't change file mode");
5962 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5963 gtk_text_buffer_get_start_iter(buffer, &start);
5964 gtk_text_buffer_get_end_iter(buffer, &end);
5965 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5967 chars = conv_codeset_strdup
5968 (tmp, CS_INTERNAL, conv_get_locale_charset_str());
5977 len = strlen(chars);
5978 if (claws_fwrite(chars, sizeof(gchar), len, fp) != len) {
5979 FILE_OP_ERROR(file, "claws_fwrite");
5988 if (claws_safe_fclose(fp) == EOF) {
5989 FILE_OP_ERROR(file, "claws_fclose");
5996 static gint compose_remove_reedit_target(Compose *compose, gboolean force)
5999 MsgInfo *msginfo = compose->targetinfo;
6001 cm_return_val_if_fail(compose->mode == COMPOSE_REEDIT, -1);
6002 if (!msginfo) return -1;
6004 if (!force && MSG_IS_LOCKED(msginfo->flags))
6007 item = msginfo->folder;
6008 cm_return_val_if_fail(item != NULL, -1);
6010 if (procmsg_msg_exist(msginfo) &&
6011 (folder_has_parent_of_type(item, F_QUEUE) ||
6012 folder_has_parent_of_type(item, F_DRAFT)
6013 || msginfo == compose->autosaved_draft)) {
6014 if (folder_item_remove_msg(item, msginfo->msgnum) < 0) {
6015 g_warning("can't remove the old message");
6018 debug_print("removed reedit target %d\n", msginfo->msgnum);
6025 static void compose_remove_draft(Compose *compose)
6028 MsgInfo *msginfo = compose->targetinfo;
6029 drafts = account_get_special_folder(compose->account, F_DRAFT);
6031 if (procmsg_msg_exist(msginfo)) {
6032 folder_item_remove_msg(drafts, msginfo->msgnum);
6037 ComposeQueueResult compose_queue(Compose *compose, gint *msgnum, FolderItem **item, gchar **msgpath,
6038 gboolean remove_reedit_target)
6040 return compose_queue_sub (compose, msgnum, item, msgpath, FALSE, remove_reedit_target);
6043 static gboolean compose_warn_encryption(Compose *compose)
6045 const gchar *warning = privacy_get_encrypt_warning(compose->privacy_system);
6046 AlertValue val = G_ALERTALTERNATE;
6048 if (warning == NULL)
6051 val = alertpanel_full(_("Encryption warning"), warning,
6052 GTK_STOCK_CANCEL, _("C_ontinue"), NULL, ALERTFOCUS_SECOND,
6053 TRUE, NULL, ALERT_WARNING);
6054 if (val & G_ALERTDISABLE) {
6055 val &= ~G_ALERTDISABLE;
6056 if (val == G_ALERTALTERNATE)
6057 privacy_inhibit_encrypt_warning(compose->privacy_system,
6061 if (val == G_ALERTALTERNATE) {
6068 static ComposeQueueResult compose_queue_sub(Compose *compose, gint *msgnum, FolderItem **item,
6069 gchar **msgpath, gboolean perform_checks,
6070 gboolean remove_reedit_target)
6077 PrefsAccount *mailac = NULL, *newsac = NULL;
6078 gboolean err = FALSE;
6080 debug_print("queueing message...\n");
6081 cm_return_val_if_fail(compose->account != NULL, -1);
6083 if (compose_check_entries(compose, perform_checks) == FALSE) {
6084 if (compose->batch) {
6085 gtk_widget_show_all(compose->window);
6087 return COMPOSE_QUEUE_ERROR_NO_MSG;
6090 if (!compose->to_list && !compose->newsgroup_list) {
6091 g_warning("can't get recipient list.");
6092 return COMPOSE_QUEUE_ERROR_NO_MSG;
6095 if (compose->to_list) {
6096 if (compose->account->protocol != A_NNTP)
6097 mailac = compose->account;
6098 else if (cur_account && cur_account->protocol != A_NNTP)
6099 mailac = cur_account;
6100 else if (!(mailac = compose_current_mail_account())) {
6101 alertpanel_error(_("No account for sending mails available!"));
6102 return COMPOSE_QUEUE_ERROR_NO_MSG;
6106 if (compose->newsgroup_list) {
6107 if (compose->account->protocol == A_NNTP)
6108 newsac = compose->account;
6110 alertpanel_error(_("Selected account isn't NNTP: Posting is impossible."));
6111 return COMPOSE_QUEUE_ERROR_NO_MSG;
6115 /* write queue header */
6116 tmp = g_strdup_printf("%s%cqueue.%p%08x", get_tmp_dir(),
6117 G_DIR_SEPARATOR, compose, (guint) rand());
6118 debug_print("queuing to %s\n", tmp);
6119 if ((fp = claws_fopen(tmp, "w+b")) == NULL) {
6120 FILE_OP_ERROR(tmp, "claws_fopen");
6122 return COMPOSE_QUEUE_ERROR_WITH_ERRNO;
6125 if (change_file_mode_rw(fp, tmp) < 0) {
6126 FILE_OP_ERROR(tmp, "chmod");
6127 g_warning("can't change file mode");
6130 /* queueing variables */
6131 err |= (fprintf(fp, "AF:\n") < 0);
6132 err |= (fprintf(fp, "NF:0\n") < 0);
6133 err |= (fprintf(fp, "PS:10\n") < 0);
6134 err |= (fprintf(fp, "SRH:1\n") < 0);
6135 err |= (fprintf(fp, "SFN:\n") < 0);
6136 err |= (fprintf(fp, "DSR:\n") < 0);
6138 err |= (fprintf(fp, "MID:<%s>\n", compose->msgid) < 0);
6140 err |= (fprintf(fp, "MID:\n") < 0);
6141 err |= (fprintf(fp, "CFG:\n") < 0);
6142 err |= (fprintf(fp, "PT:0\n") < 0);
6143 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
6144 err |= (fprintf(fp, "RQ:\n") < 0);
6146 err |= (fprintf(fp, "SSV:%s\n", mailac->smtp_server) < 0);
6148 err |= (fprintf(fp, "SSV:\n") < 0);
6150 err |= (fprintf(fp, "NSV:%s\n", newsac->nntp_server) < 0);
6152 err |= (fprintf(fp, "NSV:\n") < 0);
6153 err |= (fprintf(fp, "SSH:\n") < 0);
6154 /* write recipient list */
6155 if (compose->to_list) {
6156 err |= (fprintf(fp, "R:<%s>", (gchar *)compose->to_list->data) < 0);
6157 for (cur = compose->to_list->next; cur != NULL;
6159 err |= (fprintf(fp, ",<%s>", (gchar *)cur->data) < 0);
6160 err |= (fprintf(fp, "\n") < 0);
6162 /* write newsgroup list */
6163 if (compose->newsgroup_list) {
6164 err |= (fprintf(fp, "NG:") < 0);
6165 err |= (fprintf(fp, "%s", (gchar *)compose->newsgroup_list->data) < 0);
6166 for (cur = compose->newsgroup_list->next; cur != NULL; cur = cur->next)
6167 err |= (fprintf(fp, ",%s", (gchar *)cur->data) < 0);
6168 err |= (fprintf(fp, "\n") < 0);
6172 err |= (fprintf(fp, "MAID:%d\n", mailac->account_id) < 0);
6174 err |= (fprintf(fp, "NAID:%d\n", newsac->account_id) < 0);
6177 if (compose->privacy_system != NULL) {
6178 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
6179 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
6180 if (compose->use_encryption) {
6181 if (!compose_warn_encryption(compose)) {
6185 return COMPOSE_QUEUE_ERROR_NO_MSG;
6187 if (mailac && mailac->encrypt_to_self) {
6188 GSList *tmp_list = g_slist_copy(compose->to_list);
6189 tmp_list = g_slist_append(tmp_list, compose->account->address);
6190 compose->encdata = privacy_get_encrypt_data(compose->privacy_system, tmp_list);
6191 g_slist_free(tmp_list);
6193 compose->encdata = privacy_get_encrypt_data(compose->privacy_system, compose->to_list);
6195 if (compose->encdata != NULL) {
6196 if (strcmp(compose->encdata, "_DONT_ENCRYPT_")) {
6197 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
6198 err |= (fprintf(fp, "X-Claws-Encrypt-Data:%s\n",
6199 compose->encdata) < 0);
6200 } /* else we finally dont want to encrypt */
6202 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
6203 /* and if encdata was null, it means there's been a problem in
6206 g_warning("failed to write queue message");
6210 return COMPOSE_QUEUE_ERROR_NO_ENCRYPTION_KEY;
6215 /* Save copy folder */
6216 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
6217 gchar *savefolderid;
6219 savefolderid = compose_get_save_to(compose);
6220 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
6221 g_free(savefolderid);
6223 /* Save copy folder */
6224 if (compose->return_receipt) {
6225 err |= (fprintf(fp, "RRCPT:1\n") < 0);
6227 /* Message-ID of message replying to */
6228 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
6229 gchar *folderid = NULL;
6231 if (compose->replyinfo->folder)
6232 folderid = folder_item_get_identifier(compose->replyinfo->folder);
6233 if (folderid == NULL)
6234 folderid = g_strdup("NULL");
6236 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
6239 /* Message-ID of message forwarding to */
6240 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
6241 gchar *folderid = NULL;
6243 if (compose->fwdinfo->folder)
6244 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
6245 if (folderid == NULL)
6246 folderid = g_strdup("NULL");
6248 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
6252 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
6253 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
6255 /* end of headers */
6256 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
6258 if (compose->redirect_filename != NULL) {
6259 if (compose_redirect_write_to_file(compose, fp) < 0) {
6263 return COMPOSE_QUEUE_ERROR_WITH_ERRNO;
6267 if ((result = compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_SEND, TRUE)) < 0) {
6275 g_warning("failed to write queue message");
6279 return COMPOSE_QUEUE_ERROR_WITH_ERRNO;
6281 if (claws_safe_fclose(fp) == EOF) {
6282 FILE_OP_ERROR(tmp, "claws_fclose");
6285 return COMPOSE_QUEUE_ERROR_WITH_ERRNO;
6288 if (item && *item) {
6291 queue = account_get_special_folder(compose->account, F_QUEUE);
6294 g_warning("can't find queue folder");
6297 return COMPOSE_QUEUE_ERROR_NO_MSG;
6299 folder_item_scan(queue);
6300 if ((num = folder_item_add_msg(queue, tmp, NULL, FALSE)) < 0) {
6301 g_warning("can't queue the message");
6304 return COMPOSE_QUEUE_ERROR_NO_MSG;
6307 if (msgpath == NULL) {
6313 if (compose->mode == COMPOSE_REEDIT && compose->targetinfo) {
6314 MsgInfo *mi = folder_item_get_msginfo(queue, num);
6316 procmsg_msginfo_change_flags(mi,
6317 compose->targetinfo->flags.perm_flags,
6318 compose->targetinfo->flags.tmp_flags & ~(MSG_COPY | MSG_MOVE | MSG_MOVE_DONE),
6321 g_slist_free(mi->tags);
6322 mi->tags = g_slist_copy(compose->targetinfo->tags);
6323 procmsg_msginfo_free(&mi);
6327 if (compose->mode == COMPOSE_REEDIT && remove_reedit_target) {
6328 compose_remove_reedit_target(compose, FALSE);
6331 if ((msgnum != NULL) && (item != NULL)) {
6336 return COMPOSE_QUEUE_SUCCESS;
6339 static int compose_add_attachments(Compose *compose, MimeInfo *parent)
6342 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
6347 GError *error = NULL;
6352 gchar *type, *subtype;
6353 GtkTreeModel *model;
6356 model = gtk_tree_view_get_model(tree_view);
6358 if (!gtk_tree_model_get_iter_first(model, &iter))
6361 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
6363 if (!is_file_exist(ainfo->file)) {
6364 gchar *msg = g_strdup_printf(_("Attachment %s doesn't exist anymore. Ignore?"), ainfo->file);
6365 AlertValue val = alertpanel_full(_("Warning"), msg,
6366 _("Cancel sending"), _("Ignore attachment"), NULL,
6367 ALERTFOCUS_FIRST, FALSE, NULL, ALERT_WARNING);
6369 if (val == G_ALERTDEFAULT) {
6375 f = g_file_new_for_path(ainfo->file);
6376 fi = g_file_query_info(f, "standard::size",
6377 G_FILE_QUERY_INFO_NONE, NULL, &error);
6378 if (error != NULL) {
6379 g_warning(error->message);
6380 g_error_free(error);
6384 size = g_file_info_get_size(fi);
6388 if (g_stat(ainfo->file, &statbuf) < 0)
6390 size = statbuf.st_size;
6393 mimepart = procmime_mimeinfo_new();
6394 mimepart->content = MIMECONTENT_FILE;
6395 mimepart->data.filename = g_strdup(ainfo->file);
6396 mimepart->tmp = FALSE; /* or we destroy our attachment */
6397 mimepart->offset = 0;
6398 mimepart->length = size;
6400 type = g_strdup(ainfo->content_type);
6402 if (!strchr(type, '/')) {
6404 type = g_strdup("application/octet-stream");
6407 subtype = strchr(type, '/') + 1;
6408 *(subtype - 1) = '\0';
6409 mimepart->type = procmime_get_media_type(type);
6410 mimepart->subtype = g_strdup(subtype);
6413 if (mimepart->type == MIMETYPE_MESSAGE &&
6414 !g_ascii_strcasecmp(mimepart->subtype, "rfc822")) {
6415 mimepart->disposition = DISPOSITIONTYPE_INLINE;
6416 } else if (mimepart->type == MIMETYPE_TEXT) {
6417 if (!ainfo->name && g_ascii_strcasecmp(mimepart->subtype, "plain")) {
6418 /* Text parts with no name come from multipart/alternative
6419 * forwards. Make sure the recipient won't look at the
6420 * original HTML part by mistake. */
6421 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6422 ainfo->name = g_strdup_printf(_("Original %s part"),
6426 g_hash_table_insert(mimepart->typeparameters,
6427 g_strdup("charset"), g_strdup(ainfo->charset));
6429 if (ainfo->name && mimepart->type != MIMETYPE_MESSAGE) {
6430 if (mimepart->type == MIMETYPE_APPLICATION &&
6431 !strcmp2(mimepart->subtype, "octet-stream"))
6432 g_hash_table_insert(mimepart->typeparameters,
6433 g_strdup("name"), g_strdup(ainfo->name));
6434 g_hash_table_insert(mimepart->dispositionparameters,
6435 g_strdup("filename"), g_strdup(ainfo->name));
6436 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6439 if (mimepart->type == MIMETYPE_MESSAGE
6440 || mimepart->type == MIMETYPE_MULTIPART)
6441 ainfo->encoding = ENC_BINARY;
6442 else if (compose->use_signing) {
6443 if (ainfo->encoding == ENC_7BIT)
6444 ainfo->encoding = ENC_QUOTED_PRINTABLE;
6445 else if (ainfo->encoding == ENC_8BIT)
6446 ainfo->encoding = ENC_BASE64;
6449 procmime_encode_content(mimepart, ainfo->encoding);
6451 g_node_append(parent->node, mimepart->node);
6452 } while (gtk_tree_model_iter_next(model, &iter));
6457 static gchar *compose_quote_list_of_addresses(gchar *str)
6459 GSList *list = NULL, *item = NULL;
6460 gchar *qname = NULL, *faddr = NULL, *result = NULL;
6462 list = address_list_append_with_comments(list, str);
6463 for (item = list; item != NULL; item = item->next) {
6464 gchar *spec = item->data;
6465 gchar *endofname = strstr(spec, " <");
6466 if (endofname != NULL) {
6469 QUOTE_IF_REQUIRED_NORMAL(qname, spec, return NULL);
6470 qqname = escape_internal_quotes(qname, '"');
6472 if (*qname != *spec || qqname != qname) { /* has been quoted, compute new */
6473 gchar *addr = g_strdup(endofname);
6474 gchar *name = (qqname != qname)? qqname: g_strdup(qname);
6475 faddr = g_strconcat(name, addr, NULL);
6478 debug_print("new auto-quoted address: '%s'\n", faddr);
6482 result = g_strdup((faddr != NULL)? faddr: spec);
6484 result = g_strconcat(result,
6486 (faddr != NULL)? faddr: spec,
6489 if (faddr != NULL) {
6494 slist_free_strings_full(list);
6499 #define IS_IN_CUSTOM_HEADER(header) \
6500 (compose->account->add_customhdr && \
6501 custom_header_find(compose->account->customhdr_list, header) != NULL)
6503 static const gchar *compose_untranslated_header_name(gchar *header_name)
6505 /* return the untranslated header name, if header_name is a known
6506 header name, in either its translated or untranslated form, with
6507 or without trailing colon. otherwise, returns header_name. */
6508 gchar *translated_header_name;
6509 gchar *translated_header_name_wcolon;
6510 const gchar *untranslated_header_name;
6511 const gchar *untranslated_header_name_wcolon;
6514 cm_return_val_if_fail(header_name != NULL, NULL);
6516 for (i = 0; HEADERS[i].header_name != NULL; i++) {
6517 untranslated_header_name = HEADERS[i].header_name;
6518 untranslated_header_name_wcolon = HEADERS[i].header_name_w_colon;
6520 translated_header_name = gettext(untranslated_header_name);
6521 translated_header_name_wcolon = gettext(untranslated_header_name_wcolon);
6523 if (!strcmp(header_name, untranslated_header_name) ||
6524 !strcmp(header_name, translated_header_name)) {
6525 return untranslated_header_name;
6527 if (!strcmp(header_name, untranslated_header_name_wcolon) ||
6528 !strcmp(header_name, translated_header_name_wcolon)) {
6529 return untranslated_header_name_wcolon;
6533 debug_print("compose_untranslated_header_name: unknown header '%s'\n", header_name);
6537 static void compose_add_headerfield_from_headerlist(Compose *compose,
6539 const gchar *fieldname,
6540 const gchar *seperator)
6542 gchar *str, *fieldname_w_colon;
6543 gboolean add_field = FALSE;
6545 ComposeHeaderEntry *headerentry;
6546 const gchar *headerentryname;
6547 const gchar *trans_fieldname;
6550 if (IS_IN_CUSTOM_HEADER(fieldname))
6553 debug_print("Adding %s-fields\n", fieldname);
6555 fieldstr = g_string_sized_new(64);
6557 fieldname_w_colon = g_strconcat(fieldname, ":", NULL);
6558 trans_fieldname = prefs_common_translated_header_name(fieldname_w_colon);
6560 for (list = compose->header_list; list; list = list->next) {
6561 headerentry = ((ComposeHeaderEntry *)list->data);
6562 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
6564 if (!g_utf8_collate(trans_fieldname, headerentryname)) {
6565 gchar * ustr = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
6567 str = compose_quote_list_of_addresses(ustr);
6569 if (str != NULL && str[0] != '\0') {
6571 g_string_append(fieldstr, seperator);
6572 g_string_append(fieldstr, str);
6581 buf = g_new0(gchar, fieldstr->len * 4 + 256);
6582 compose_convert_header
6583 (compose, buf, fieldstr->len * 4 + 256, fieldstr->str,
6584 strlen(fieldname) + 2, TRUE);
6585 g_string_append_printf(header, "%s: %s\n", fieldname, buf);
6589 g_free(fieldname_w_colon);
6590 g_string_free(fieldstr, TRUE);
6595 static gchar *compose_get_manual_headers_info(Compose *compose)
6597 GString *sh_header = g_string_new(" ");
6599 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6601 for (list = compose->header_list; list; list = list->next) {
6602 ComposeHeaderEntry *headerentry;
6605 gchar *headername_wcolon;
6606 const gchar *headername_trans;
6608 gboolean standard_header = FALSE;
6610 headerentry = ((ComposeHeaderEntry *)list->data);
6612 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6614 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6619 if (!strstr(tmp, ":")) {
6620 headername_wcolon = g_strconcat(tmp, ":", NULL);
6621 headername = g_strdup(tmp);
6623 headername_wcolon = g_strdup(tmp);
6624 headername = g_strdup(strtok(tmp, ":"));
6628 string = std_headers;
6629 while (*string != NULL) {
6630 headername_trans = prefs_common_translated_header_name(*string);
6631 if (!strcmp(headername_trans, headername_wcolon))
6632 standard_header = TRUE;
6635 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
6636 g_string_append_printf(sh_header, "%s ", headername);
6638 g_free(headername_wcolon);
6640 g_string_truncate(sh_header, strlen(sh_header->str) - 1); /* remove last space */
6641 return g_string_free(sh_header, FALSE);
6644 static gchar *compose_get_header(Compose *compose)
6646 gchar date[RFC822_DATE_BUFFSIZE];
6647 gchar buf[BUFFSIZE];
6648 const gchar *entry_str;
6652 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6654 gchar *from_name = NULL, *from_address = NULL;
6657 cm_return_val_if_fail(compose->account != NULL, NULL);
6658 cm_return_val_if_fail(compose->account->address != NULL, NULL);
6660 header = g_string_sized_new(64);
6663 if (prefs_common.hide_timezone)
6664 get_rfc822_date_hide_tz(date, sizeof(date));
6666 get_rfc822_date(date, sizeof(date));
6667 g_string_append_printf(header, "Date: %s\n", date);
6671 if (compose->account->name && *compose->account->name) {
6673 QUOTE_IF_REQUIRED(buf, compose->account->name);
6674 tmp = g_strdup_printf("%s <%s>",
6675 buf, compose->account->address);
6677 tmp = g_strdup_printf("%s",
6678 compose->account->address);
6680 if (!strcmp(gtk_entry_get_text(GTK_ENTRY(compose->from_name)), tmp)
6681 || strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) == 0) {
6683 from_name = compose->account->name ? g_strdup(compose->account->name):NULL;
6684 from_address = g_strdup(compose->account->address);
6686 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
6687 /* extract name and address */
6688 if (strstr(spec, " <") && strstr(spec, ">")) {
6689 from_address = g_strdup(strrchr(spec, '<')+1);
6690 *(strrchr(from_address, '>')) = '\0';
6691 from_name = g_strdup(spec);
6692 *(strrchr(from_name, '<')) = '\0';
6695 from_address = g_strdup(spec);
6702 if (from_name && *from_name) {
6704 compose_convert_header
6705 (compose, buf, sizeof(buf), from_name,
6706 strlen("From: "), TRUE);
6707 QUOTE_IF_REQUIRED(name, buf);
6708 qname = escape_internal_quotes(name, '"');
6710 g_string_append_printf(header, "From: %s <%s>\n",
6711 qname, from_address);
6712 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To") &&
6713 compose->return_receipt) {
6714 compose_convert_header(compose, buf, sizeof(buf), from_name,
6715 strlen("Disposition-Notification-To: "),
6717 g_string_append_printf(header, "Disposition-Notification-To: %s <%s>\n", buf, from_address);
6722 g_string_append_printf(header, "From: %s\n", from_address);
6723 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To") &&
6724 compose->return_receipt)
6725 g_string_append_printf(header, "Disposition-Notification-To: %s\n", from_address);
6729 g_free(from_address);
6732 compose_add_headerfield_from_headerlist(compose, header, "To", ", ");
6735 compose_add_headerfield_from_headerlist(compose, header, "Newsgroups", ",");
6738 compose_add_headerfield_from_headerlist(compose, header, "Cc", ", ");
6742 * If this account is a NNTP account remove Bcc header from
6743 * message body since it otherwise will be publicly shown
6745 if (compose->account->protocol != A_NNTP)
6746 compose_add_headerfield_from_headerlist(compose, header, "Bcc", ", ");
6749 str = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
6751 if (*str != '\0' && !IS_IN_CUSTOM_HEADER("Subject")) {
6754 compose_convert_header(compose, buf, sizeof(buf), str,
6755 strlen("Subject: "), FALSE);
6756 g_string_append_printf(header, "Subject: %s\n", buf);
6762 if (compose->msgid != NULL && strlen(compose->msgid) > 0) {
6763 g_string_append_printf(header, "Message-ID: <%s>\n",
6767 if (compose->remove_references == FALSE) {
6769 if (compose->inreplyto && compose->to_list)
6770 g_string_append_printf(header, "In-Reply-To: <%s>\n", compose->inreplyto);
6773 if (compose->references)
6774 g_string_append_printf(header, "References: %s\n", compose->references);
6778 compose_add_headerfield_from_headerlist(compose, header, "Followup-To", ",");
6781 compose_add_headerfield_from_headerlist(compose, header, "Reply-To", ", ");
6784 if (compose->account->organization &&
6785 strlen(compose->account->organization) &&
6786 !IS_IN_CUSTOM_HEADER("Organization")) {
6787 compose_convert_header(compose, buf, sizeof(buf),
6788 compose->account->organization,
6789 strlen("Organization: "), FALSE);
6790 g_string_append_printf(header, "Organization: %s\n", buf);
6793 /* Program version and system info */
6794 if (compose->account->gen_xmailer &&
6795 g_slist_length(compose->to_list) && !IS_IN_CUSTOM_HEADER("X-Mailer") &&
6796 !compose->newsgroup_list) {
6797 g_string_append_printf(header, "X-Mailer: %s (GTK+ %d.%d.%d; %s)\n",
6799 gtk_major_version, gtk_minor_version, gtk_micro_version,
6802 if (compose->account->gen_xmailer &&
6803 g_slist_length(compose->newsgroup_list) && !IS_IN_CUSTOM_HEADER("X-Newsreader")) {
6804 g_string_append_printf(header, "X-Newsreader: %s (GTK+ %d.%d.%d; %s)\n",
6806 gtk_major_version, gtk_minor_version, gtk_micro_version,
6810 /* custom headers */
6811 if (compose->account->add_customhdr) {
6814 for (cur = compose->account->customhdr_list; cur != NULL;
6816 CustomHeader *chdr = (CustomHeader *)cur->data;
6818 if (custom_header_is_allowed(chdr->name)
6819 && chdr->value != NULL
6820 && *(chdr->value) != '\0') {
6821 compose_convert_header
6822 (compose, buf, sizeof(buf),
6824 strlen(chdr->name) + 2, FALSE);
6825 g_string_append_printf(header, "%s: %s\n", chdr->name, buf);
6830 /* Automatic Faces and X-Faces */
6831 if (get_account_xface (buf, sizeof(buf), compose->account->account_name) == 0) {
6832 g_string_append_printf(header, "X-Face: %s\n", buf);
6834 else if (get_default_xface (buf, sizeof(buf)) == 0) {
6835 g_string_append_printf(header, "X-Face: %s\n", buf);
6837 if (get_account_face (buf, sizeof(buf), compose->account->account_name) == 0) {
6838 g_string_append_printf(header, "Face: %s\n", buf);
6840 else if (get_default_face (buf, sizeof(buf)) == 0) {
6841 g_string_append_printf(header, "Face: %s\n", buf);
6845 switch (compose->priority) {
6846 case PRIORITY_HIGHEST: g_string_append_printf(header, "Importance: high\n"
6847 "X-Priority: 1 (Highest)\n");
6849 case PRIORITY_HIGH: g_string_append_printf(header, "Importance: high\n"
6850 "X-Priority: 2 (High)\n");
6852 case PRIORITY_NORMAL: break;
6853 case PRIORITY_LOW: g_string_append_printf(header, "Importance: low\n"
6854 "X-Priority: 4 (Low)\n");
6856 case PRIORITY_LOWEST: g_string_append_printf(header, "Importance: low\n"
6857 "X-Priority: 5 (Lowest)\n");
6859 default: debug_print("compose: priority unknown : %d\n",
6863 /* get special headers */
6864 for (list = compose->header_list; list; list = list->next) {
6865 ComposeHeaderEntry *headerentry;
6868 gchar *headername_wcolon;
6869 const gchar *headername_trans;
6872 gboolean standard_header = FALSE;
6874 headerentry = ((ComposeHeaderEntry *)list->data);
6876 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6878 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6883 if (!strstr(tmp, ":")) {
6884 headername_wcolon = g_strconcat(tmp, ":", NULL);
6885 headername = g_strdup(tmp);
6887 headername_wcolon = g_strdup(tmp);
6888 headername = g_strdup(strtok(tmp, ":"));
6892 entry_str = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
6893 Xstrdup_a(headervalue, entry_str, return NULL);
6894 subst_char(headervalue, '\r', ' ');
6895 subst_char(headervalue, '\n', ' ');
6896 g_strstrip(headervalue);
6897 if (*headervalue != '\0') {
6898 string = std_headers;
6899 while (*string != NULL && !standard_header) {
6900 headername_trans = prefs_common_translated_header_name(*string);
6901 /* support mixed translated and untranslated headers */
6902 if (!strcmp(headername_trans, headername_wcolon) || !strcmp(*string, headername_wcolon))
6903 standard_header = TRUE;
6906 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername)) {
6907 /* store untranslated header name */
6908 g_string_append_printf(header, "%s %s\n",
6909 compose_untranslated_header_name(headername_wcolon), headervalue);
6913 g_free(headername_wcolon);
6917 g_string_free(header, FALSE);
6922 #undef IS_IN_CUSTOM_HEADER
6924 static void compose_convert_header(Compose *compose, gchar *dest, gint len, gchar *src,
6925 gint header_len, gboolean addr_field)
6927 gchar *tmpstr = NULL;
6928 const gchar *out_codeset = NULL;
6930 cm_return_if_fail(src != NULL);
6931 cm_return_if_fail(dest != NULL);
6933 if (len < 1) return;
6935 tmpstr = g_strdup(src);
6937 subst_char(tmpstr, '\n', ' ');
6938 subst_char(tmpstr, '\r', ' ');
6941 if (!g_utf8_validate(tmpstr, -1, NULL)) {
6942 gchar *mybuf = g_malloc(strlen(tmpstr)*2 +1);
6943 conv_localetodisp(mybuf, strlen(tmpstr)*2 +1, tmpstr);
6948 codeconv_set_strict(TRUE);
6949 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6950 conv_get_charset_str(compose->out_encoding));
6951 codeconv_set_strict(FALSE);
6953 if (!dest || *dest == '\0') {
6954 gchar *test_conv_global_out = NULL;
6955 gchar *test_conv_reply = NULL;
6957 /* automatic mode. be automatic. */
6958 codeconv_set_strict(TRUE);
6960 out_codeset = conv_get_outgoing_charset_str();
6962 debug_print("trying to convert to %s\n", out_codeset);
6963 test_conv_global_out = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6966 if (!test_conv_global_out && compose->orig_charset
6967 && strcmp(compose->orig_charset, CS_US_ASCII)) {
6968 out_codeset = compose->orig_charset;
6969 debug_print("failure; trying to convert to %s\n", out_codeset);
6970 test_conv_reply = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6973 if (!test_conv_global_out && !test_conv_reply) {
6975 out_codeset = CS_INTERNAL;
6976 debug_print("finally using %s\n", out_codeset);
6978 g_free(test_conv_global_out);
6979 g_free(test_conv_reply);
6980 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6982 codeconv_set_strict(FALSE);
6987 static void compose_add_to_addressbook_cb(GtkMenuItem *menuitem, gpointer user_data)
6991 cm_return_if_fail(user_data != NULL);
6993 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(user_data)));
6994 g_strstrip(address);
6995 if (*address != '\0') {
6996 gchar *name = procheader_get_fromname(address);
6997 extract_address(address);
6998 #ifndef USE_ALT_ADDRBOOK
6999 addressbook_add_contact(name, address, NULL, NULL);
7001 debug_print("%s: %s\n", name, address);
7002 if (addressadd_selection(name, address, NULL, NULL)) {
7003 debug_print( "addressbook_add_contact - added\n" );
7010 static void compose_entry_popup_extend(GtkEntry *entry, GtkMenu *menu, gpointer user_data)
7012 GtkWidget *menuitem;
7015 cm_return_if_fail(menu != NULL);
7016 cm_return_if_fail(GTK_IS_MENU_SHELL(menu));
7018 menuitem = gtk_separator_menu_item_new();
7019 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
7020 gtk_widget_show(menuitem);
7022 menuitem = gtk_menu_item_new_with_mnemonic(_("Add to address _book"));
7023 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
7025 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
7026 g_strstrip(address);
7027 if (*address == '\0') {
7028 gtk_widget_set_sensitive(GTK_WIDGET(menuitem), FALSE);
7031 g_signal_connect(G_OBJECT(menuitem), "activate",
7032 G_CALLBACK(compose_add_to_addressbook_cb), entry);
7033 gtk_widget_show(menuitem);
7036 void compose_add_extra_header(gchar *header, GtkListStore *model)
7039 if (strcmp(header, "")) {
7040 COMBOBOX_ADD(model, header, COMPOSE_TO);
7044 void compose_add_extra_header_entries(GtkListStore *model)
7048 gchar buf[BUFFSIZE];
7051 if (extra_headers == NULL) {
7052 exhrc = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, "extraheaderrc", NULL);
7053 if ((exh = claws_fopen(exhrc, "rb")) == NULL) {
7054 debug_print("extra headers file not found\n");
7055 goto extra_headers_done;
7057 while (claws_fgets(buf, BUFFSIZE, exh) != NULL) {
7058 lastc = strlen(buf) - 1; /* remove trailing control chars */
7059 while (lastc >= 0 && buf[lastc] != ':')
7060 buf[lastc--] = '\0';
7061 if (lastc > 0 && buf[0] != '#' && buf[lastc] == ':') {
7062 buf[lastc] = '\0'; /* remove trailing : for comparison */
7063 if (custom_header_is_allowed(buf)) {
7065 extra_headers = g_slist_prepend(extra_headers, g_strdup(buf));
7068 g_message("disallowed extra header line: %s\n", buf);
7072 g_message("invalid extra header line: %s\n", buf);
7078 extra_headers = g_slist_prepend(extra_headers, g_strdup("")); /* end of list */
7079 extra_headers = g_slist_reverse(extra_headers);
7081 g_slist_foreach(extra_headers, (GFunc)compose_add_extra_header, (gpointer)model);
7085 static void _ldap_srv_func(gpointer data, gpointer user_data)
7087 LdapServer *server = (LdapServer *)data;
7088 gboolean *enable = (gboolean *)user_data;
7090 debug_print("%s server '%s'\n", (*enable == TRUE ? "enabling" : "disabling"), server->control->hostName);
7091 server->searchFlag = *enable;
7095 static void compose_create_header_entry(Compose *compose)
7097 gchar *headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
7104 const gchar *header = NULL;
7105 ComposeHeaderEntry *headerentry;
7106 gboolean standard_header = FALSE;
7107 GtkListStore *model;
7110 headerentry = g_new0(ComposeHeaderEntry, 1);
7112 /* Combo box model */
7113 model = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_BOOLEAN);
7114 COMBOBOX_ADD(model, prefs_common_translated_header_name("To:"),
7116 COMBOBOX_ADD(model, prefs_common_translated_header_name("Cc:"),
7118 COMBOBOX_ADD(model, prefs_common_translated_header_name("Bcc:"),
7120 COMBOBOX_ADD(model, prefs_common_translated_header_name("Newsgroups:"),
7121 COMPOSE_NEWSGROUPS);
7122 COMBOBOX_ADD(model, prefs_common_translated_header_name("Reply-To:"),
7124 COMBOBOX_ADD(model, prefs_common_translated_header_name("Followup-To:"),
7125 COMPOSE_FOLLOWUPTO);
7126 compose_add_extra_header_entries(model);
7129 combo = gtk_combo_box_new_with_model_and_entry(GTK_TREE_MODEL(model));
7130 GtkCellRenderer *cell = gtk_cell_renderer_text_new();
7131 gtk_cell_renderer_set_alignment(cell, 0.0, 0.5);
7132 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), cell, TRUE);
7133 gtk_combo_box_set_entry_text_column(GTK_COMBO_BOX(combo), 0);
7134 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
7135 g_signal_connect(G_OBJECT(gtk_bin_get_child(GTK_BIN(combo))), "grab_focus",
7136 G_CALLBACK(compose_grab_focus_cb), compose);
7137 gtk_widget_show(combo);
7139 /* Putting only the combobox child into focus chain of its parent causes
7140 * the parent to be skipped when changing focus via Tab or Shift+Tab.
7141 * This eliminates need to pres Tab twice in order to really get from the
7142 * combobox to next widget. */
7144 l = g_list_prepend(l, gtk_bin_get_child(GTK_BIN(combo)));
7145 gtk_container_set_focus_chain(GTK_CONTAINER(combo), l);
7148 gtk_table_attach(GTK_TABLE(compose->header_table), combo, 0, 1,
7149 compose->header_nextrow, compose->header_nextrow+1,
7150 GTK_SHRINK, GTK_FILL, 0, 0);
7151 if (compose->header_last && (compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN)) {
7152 const gchar *last_header_entry = gtk_entry_get_text(
7153 GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
7155 while (*string != NULL) {
7156 if (!strcmp(prefs_common_translated_header_name(*string), last_header_entry))
7157 standard_header = TRUE;
7160 if (standard_header)
7161 header = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
7163 if (!compose->header_last || !standard_header) {
7164 switch(compose->account->protocol) {
7166 header = prefs_common_translated_header_name("Newsgroups:");
7169 header = prefs_common_translated_header_name("To:");
7174 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((combo)))), header);
7176 gtk_editable_set_editable(
7177 GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((combo)))),
7178 prefs_common.type_any_header);
7180 g_signal_connect_after(G_OBJECT(gtk_bin_get_child(GTK_BIN((combo)))), "grab_focus",
7181 G_CALLBACK(compose_grab_focus_cb), compose);
7183 /* Entry field with cleanup button */
7184 button = gtk_button_new();
7185 gtk_button_set_image(GTK_BUTTON(button),
7186 gtk_image_new_from_stock(GTK_STOCK_CLEAR, GTK_ICON_SIZE_MENU));
7187 gtk_widget_show(button);
7188 CLAWS_SET_TIP(button,
7189 _("Delete entry contents"));
7190 entry = gtk_entry_new();
7191 gtk_widget_show(entry);
7192 CLAWS_SET_TIP(entry,
7193 _("Use <tab> to autocomplete from addressbook"));
7194 hbox = gtk_hbox_new (FALSE, 0);
7195 gtk_widget_show(hbox);
7196 gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, TRUE, 0);
7197 gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
7198 gtk_table_attach(GTK_TABLE(compose->header_table), hbox, 1, 2,
7199 compose->header_nextrow, compose->header_nextrow+1,
7200 GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
7202 g_signal_connect(G_OBJECT(entry), "key-press-event",
7203 G_CALLBACK(compose_headerentry_key_press_event_cb),
7205 g_signal_connect(G_OBJECT(entry), "changed",
7206 G_CALLBACK(compose_headerentry_changed_cb),
7208 g_signal_connect_after(G_OBJECT(entry), "grab_focus",
7209 G_CALLBACK(compose_grab_focus_cb), compose);
7211 g_signal_connect(G_OBJECT(button), "clicked",
7212 G_CALLBACK(compose_headerentry_button_clicked_cb),
7216 gtk_drag_dest_set(entry, GTK_DEST_DEFAULT_ALL, compose_mime_types,
7217 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7218 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7219 g_signal_connect(G_OBJECT(entry), "drag_data_received",
7220 G_CALLBACK(compose_header_drag_received_cb),
7222 g_signal_connect(G_OBJECT(entry), "drag-drop",
7223 G_CALLBACK(compose_drag_drop),
7225 g_signal_connect(G_OBJECT(entry), "populate-popup",
7226 G_CALLBACK(compose_entry_popup_extend),
7230 #ifndef PASSWORD_CRYPTO_OLD
7231 GSList *pwd_servers = addrindex_get_password_protected_ldap_servers();
7232 if (pwd_servers != NULL && master_passphrase() == NULL) {
7233 gboolean enable = FALSE;
7234 debug_print("Master passphrase not available, disabling password-protected LDAP servers for this compose window.\n");
7235 /* Temporarily disable password-protected LDAP servers,
7236 * because user did not provide a master passphrase.
7237 * We can safely enable searchFlag on all servers in this list
7238 * later, since addrindex_get_password_protected_ldap_servers()
7239 * includes servers which have it enabled initially. */
7240 g_slist_foreach(pwd_servers, _ldap_srv_func, &enable);
7241 compose->passworded_ldap_servers = pwd_servers;
7243 #endif /* PASSWORD_CRYPTO_OLD */
7244 #endif /* USE_LDAP */
7246 address_completion_register_entry(GTK_ENTRY(entry), TRUE);
7248 headerentry->compose = compose;
7249 headerentry->combo = combo;
7250 headerentry->entry = entry;
7251 headerentry->button = button;
7252 headerentry->hbox = hbox;
7253 headerentry->headernum = compose->header_nextrow;
7254 headerentry->type = PREF_NONE;
7256 compose->header_nextrow++;
7257 compose->header_last = headerentry;
7258 compose->header_list =
7259 g_slist_append(compose->header_list,
7263 static void compose_add_header_entry(Compose *compose, const gchar *header,
7264 gchar *text, ComposePrefType pref_type)
7266 ComposeHeaderEntry *last_header = compose->header_last;
7267 gchar *tmp = g_strdup(text), *email;
7268 gboolean replyto_hdr;
7270 replyto_hdr = (!strcasecmp(header,
7271 prefs_common_translated_header_name("Reply-To:")) ||
7273 prefs_common_translated_header_name("Followup-To:")) ||
7275 prefs_common_translated_header_name("In-Reply-To:")));
7277 extract_address(tmp);
7278 email = g_utf8_strdown(tmp, -1);
7280 if (replyto_hdr == FALSE &&
7281 g_hash_table_lookup(compose->email_hashtable, email) != NULL)
7283 debug_print("Ignoring duplicate address - %s %s, pref_type: %d\n",
7284 header, text, (gint) pref_type);
7290 if (!strcasecmp(header, prefs_common_translated_header_name("In-Reply-To:")))
7291 gtk_entry_set_text(GTK_ENTRY(
7292 gtk_bin_get_child(GTK_BIN(last_header->combo))), header);
7294 combobox_select_by_text(GTK_COMBO_BOX(last_header->combo), header);
7295 gtk_entry_set_text(GTK_ENTRY(last_header->entry), text);
7296 last_header->type = pref_type;
7298 if (replyto_hdr == FALSE)
7299 g_hash_table_insert(compose->email_hashtable, email,
7300 GUINT_TO_POINTER(1));
7307 static void compose_destroy_headerentry(Compose *compose,
7308 ComposeHeaderEntry *headerentry)
7310 gchar *text = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
7313 extract_address(text);
7314 email = g_utf8_strdown(text, -1);
7315 g_hash_table_remove(compose->email_hashtable, email);
7319 gtk_widget_destroy(headerentry->combo);
7320 gtk_widget_destroy(headerentry->entry);
7321 gtk_widget_destroy(headerentry->button);
7322 gtk_widget_destroy(headerentry->hbox);
7323 g_free(headerentry);
7326 static void compose_remove_header_entries(Compose *compose)
7329 for (list = compose->header_list; list; list = list->next)
7330 compose_destroy_headerentry(compose, (ComposeHeaderEntry *)list->data);
7332 compose->header_last = NULL;
7333 g_slist_free(compose->header_list);
7334 compose->header_list = NULL;
7335 compose->header_nextrow = 1;
7336 compose_create_header_entry(compose);
7339 static GtkWidget *compose_create_header(Compose *compose)
7341 GtkWidget *from_optmenu_hbox;
7342 GtkWidget *header_table_main;
7343 GtkWidget *header_scrolledwin;
7344 GtkWidget *header_table;
7346 /* parent with account selection and from header */
7347 header_table_main = gtk_table_new(2, 2, FALSE);
7348 gtk_widget_show(header_table_main);
7349 gtk_container_set_border_width(GTK_CONTAINER(header_table_main), BORDER_WIDTH);
7351 from_optmenu_hbox = compose_account_option_menu_create(compose);
7352 gtk_table_attach(GTK_TABLE(header_table_main), from_optmenu_hbox,
7353 0, 2, 0, 1, GTK_EXPAND | GTK_FILL, GTK_SHRINK, 0, 0);
7355 /* child with header labels and entries */
7356 header_scrolledwin = gtk_scrolled_window_new(NULL, NULL);
7357 gtk_widget_show(header_scrolledwin);
7358 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
7360 header_table = gtk_table_new(2, 2, FALSE);
7361 gtk_widget_show(header_table);
7362 gtk_container_set_border_width(GTK_CONTAINER(header_table), 0);
7363 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin), header_table);
7364 gtk_container_set_focus_vadjustment(GTK_CONTAINER(header_table),
7365 gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(header_scrolledwin)));
7366 gtk_viewport_set_shadow_type(GTK_VIEWPORT(gtk_bin_get_child(GTK_BIN(header_scrolledwin))), GTK_SHADOW_NONE);
7368 gtk_table_attach(GTK_TABLE(header_table_main), header_scrolledwin,
7369 0, 2, 1, 2, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 2);
7371 compose->header_table = header_table;
7372 compose->header_list = NULL;
7373 compose->header_nextrow = 0;
7375 compose_create_header_entry(compose);
7377 compose->table = NULL;
7379 return header_table_main;
7382 static gboolean popup_attach_button_pressed(GtkWidget *widget, gpointer data)
7384 Compose *compose = (Compose *)data;
7385 GdkEventButton event;
7388 event.time = gtk_get_current_event_time();
7390 return attach_button_pressed(compose->attach_clist, &event, compose);
7393 static GtkWidget *compose_create_attach(Compose *compose)
7395 GtkWidget *attach_scrwin;
7396 GtkWidget *attach_clist;
7398 GtkListStore *store;
7399 GtkCellRenderer *renderer;
7400 GtkTreeViewColumn *column;
7401 GtkTreeSelection *selection;
7403 /* attachment list */
7404 attach_scrwin = gtk_scrolled_window_new(NULL, NULL);
7405 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(attach_scrwin),
7406 GTK_POLICY_AUTOMATIC,
7407 GTK_POLICY_AUTOMATIC);
7408 gtk_widget_set_size_request(attach_scrwin, -1, 80);
7410 store = gtk_list_store_new(N_ATTACH_COLS,
7416 G_TYPE_AUTO_POINTER,
7418 attach_clist = GTK_WIDGET(gtk_tree_view_new_with_model
7419 (GTK_TREE_MODEL(store)));
7420 gtk_container_add(GTK_CONTAINER(attach_scrwin), attach_clist);
7421 g_object_unref(store);
7423 renderer = gtk_cell_renderer_text_new();
7424 column = gtk_tree_view_column_new_with_attributes
7425 (_("Mime type"), renderer, "text",
7426 COL_MIMETYPE, NULL);
7427 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7429 renderer = gtk_cell_renderer_text_new();
7430 column = gtk_tree_view_column_new_with_attributes
7431 (_("Size"), renderer, "text",
7433 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7435 renderer = gtk_cell_renderer_text_new();
7436 column = gtk_tree_view_column_new_with_attributes
7437 (_("Name"), renderer, "text",
7439 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7441 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(attach_clist),
7442 prefs_common.use_stripes_everywhere);
7443 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(attach_clist));
7444 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
7446 g_signal_connect(G_OBJECT(attach_clist), "row_activated",
7447 G_CALLBACK(attach_selected), compose);
7448 g_signal_connect(G_OBJECT(attach_clist), "button_press_event",
7449 G_CALLBACK(attach_button_pressed), compose);
7450 g_signal_connect(G_OBJECT(attach_clist), "popup-menu",
7451 G_CALLBACK(popup_attach_button_pressed), compose);
7452 g_signal_connect(G_OBJECT(attach_clist), "key_press_event",
7453 G_CALLBACK(attach_key_pressed), compose);
7456 gtk_drag_dest_set(attach_clist,
7457 GTK_DEST_DEFAULT_ALL, compose_mime_types,
7458 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7459 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7460 g_signal_connect(G_OBJECT(attach_clist), "drag_data_received",
7461 G_CALLBACK(compose_attach_drag_received_cb),
7463 g_signal_connect(G_OBJECT(attach_clist), "drag-drop",
7464 G_CALLBACK(compose_drag_drop),
7467 compose->attach_scrwin = attach_scrwin;
7468 compose->attach_clist = attach_clist;
7470 return attach_scrwin;
7473 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose);
7474 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose);
7476 static GtkWidget *compose_create_others(Compose *compose)
7479 GtkWidget *savemsg_checkbtn;
7480 GtkWidget *savemsg_combo;
7481 GtkWidget *savemsg_select;
7484 gchar *folderidentifier;
7486 /* Table for settings */
7487 table = gtk_table_new(3, 1, FALSE);
7488 gtk_container_set_border_width(GTK_CONTAINER(table), BORDER_WIDTH);
7489 gtk_widget_show(table);
7490 gtk_table_set_row_spacings(GTK_TABLE(table), VSPACING_NARROW);
7493 /* Save Message to folder */
7494 savemsg_checkbtn = gtk_check_button_new_with_label(_("Save Message to "));
7495 gtk_widget_show(savemsg_checkbtn);
7496 gtk_table_attach(GTK_TABLE(table), savemsg_checkbtn, 0, 1, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7497 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7498 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), prefs_common.savemsg);
7500 g_signal_connect(G_OBJECT(savemsg_checkbtn), "toggled",
7501 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
7503 savemsg_combo = gtk_combo_box_text_new_with_entry();
7504 compose->savemsg_checkbtn = savemsg_checkbtn;
7505 compose->savemsg_combo = savemsg_combo;
7506 gtk_widget_show(savemsg_combo);
7508 if (prefs_common.compose_save_to_history)
7509 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(savemsg_combo),
7510 prefs_common.compose_save_to_history);
7511 gtk_table_attach(GTK_TABLE(table), savemsg_combo, 1, 2, rowcount, rowcount + 1, GTK_FILL|GTK_EXPAND, GTK_SHRINK, 0, 0);
7512 gtk_widget_set_sensitive(GTK_WIDGET(savemsg_combo), prefs_common.savemsg);
7513 g_signal_connect_after(G_OBJECT(savemsg_combo), "grab_focus",
7514 G_CALLBACK(compose_grab_focus_cb), compose);
7515 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7516 folderidentifier = folder_item_get_identifier(account_get_special_folder
7517 (compose->account, F_OUTBOX));
7518 compose_set_save_to(compose, folderidentifier);
7519 g_free(folderidentifier);
7522 savemsg_select = gtkut_get_browse_file_btn(_("_Browse"));
7523 gtk_widget_show(savemsg_select);
7524 gtk_table_attach(GTK_TABLE(table), savemsg_select, 2, 3, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7525 g_signal_connect(G_OBJECT(savemsg_select), "clicked",
7526 G_CALLBACK(compose_savemsg_select_cb),
7532 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose)
7534 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo),
7535 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn)));
7538 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose)
7543 dest = foldersel_folder_sel(NULL, FOLDER_SEL_COPY, NULL, FALSE,
7544 _("Select folder to save message to"));
7547 path = folder_item_get_identifier(dest);
7549 compose_set_save_to(compose, path);
7553 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry, gboolean wrap,
7554 GdkAtom clip, GtkTextIter *insert_place);
7557 static gboolean text_clicked(GtkWidget *text, GdkEventButton *event,
7561 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
7563 if (event->button == 3) {
7565 GtkTextIter sel_start, sel_end;
7566 gboolean stuff_selected;
7568 /* move the cursor to allow GtkAspell to check the word
7569 * under the mouse */
7570 if (event->x && event->y) {
7571 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7572 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7574 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7577 GtkTextMark *mark = gtk_text_buffer_get_insert(buffer);
7578 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7581 stuff_selected = gtk_text_buffer_get_selection_bounds(
7583 &sel_start, &sel_end);
7585 gtk_text_buffer_place_cursor (buffer, &iter);
7586 /* reselect stuff */
7588 && gtk_text_iter_in_range(&iter, &sel_start, &sel_end)) {
7589 gtk_text_buffer_select_range(buffer,
7590 &sel_start, &sel_end);
7592 return FALSE; /* pass the event so that the right-click goes through */
7595 if (event->button == 2) {
7600 /* get the middle-click position to paste at the correct place */
7601 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7602 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7604 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7607 entry_paste_clipboard(compose, text,
7608 prefs_common.linewrap_pastes,
7609 GDK_SELECTION_PRIMARY, &iter);
7617 static void compose_spell_menu_changed(void *data)
7619 Compose *compose = (Compose *)data;
7621 GtkWidget *menuitem;
7622 GtkWidget *parent_item;
7623 GtkMenu *menu = GTK_MENU(gtk_menu_new());
7626 if (compose->gtkaspell == NULL)
7629 parent_item = gtk_ui_manager_get_widget(compose->ui_manager,
7630 "/Menu/Spelling/Options");
7632 /* setting the submenu removes /Spelling/Options from the factory
7633 * so we need to save it */
7635 if (parent_item == NULL) {
7636 parent_item = compose->aspell_options_menu;
7637 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), NULL);
7639 compose->aspell_options_menu = parent_item;
7641 spell_menu = gtkaspell_make_config_menu(compose->gtkaspell);
7643 spell_menu = g_slist_reverse(spell_menu);
7644 for (items = spell_menu;
7645 items; items = items->next) {
7646 menuitem = GTK_WIDGET(GTK_MENU_ITEM(items->data));
7647 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), GTK_WIDGET(menuitem));
7648 gtk_widget_show(GTK_WIDGET(menuitem));
7650 g_slist_free(spell_menu);
7652 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), GTK_WIDGET(menu));
7653 gtk_widget_show(parent_item);
7656 static void compose_dict_changed(void *data)
7658 Compose *compose = (Compose *) data;
7660 if(!compose->gtkaspell)
7662 if(compose->gtkaspell->recheck_when_changing_dict == FALSE)
7665 gtkaspell_highlight_all(compose->gtkaspell);
7666 claws_spell_entry_recheck_all(CLAWS_SPELL_ENTRY(compose->subject_entry));
7670 static gboolean compose_popup_menu(GtkWidget *widget, gpointer data)
7672 Compose *compose = (Compose *)data;
7673 GdkEventButton event;
7676 event.time = gtk_get_current_event_time();
7680 return text_clicked(compose->text, &event, compose);
7683 static gboolean compose_force_window_origin = TRUE;
7684 static Compose *compose_create(PrefsAccount *account,
7693 GtkWidget *handlebox;
7695 GtkWidget *notebook;
7697 GtkWidget *attach_hbox;
7698 GtkWidget *attach_lab1;
7699 GtkWidget *attach_lab2;
7704 GtkWidget *subject_hbox;
7705 GtkWidget *subject_frame;
7706 GtkWidget *subject_entry;
7710 GtkWidget *edit_vbox;
7711 GtkWidget *ruler_hbox;
7713 GtkWidget *scrolledwin;
7715 GtkTextBuffer *buffer;
7716 GtkClipboard *clipboard;
7718 UndoMain *undostruct;
7720 GtkWidget *popupmenu;
7721 GtkWidget *tmpl_menu;
7722 GtkActionGroup *action_group = NULL;
7725 GtkAspell * gtkaspell = NULL;
7728 static GdkGeometry geometry;
7730 cm_return_val_if_fail(account != NULL, NULL);
7732 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_DEFAULT_HEADER_BG],
7733 &default_header_bgcolor);
7734 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_DEFAULT_HEADER],
7735 &default_header_color);
7737 debug_print("Creating compose window...\n");
7738 compose = g_new0(Compose, 1);
7740 compose->batch = batch;
7741 compose->account = account;
7742 compose->folder = folder;
7744 compose->mutex = cm_mutex_new();
7745 compose->set_cursor_pos = -1;
7747 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose");
7749 gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
7750 gtk_widget_set_size_request(window, prefs_common.compose_width,
7751 prefs_common.compose_height);
7753 if (!geometry.max_width) {
7754 geometry.max_width = gdk_screen_width();
7755 geometry.max_height = gdk_screen_height();
7758 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7759 &geometry, GDK_HINT_MAX_SIZE);
7760 if (!geometry.min_width) {
7761 geometry.min_width = 600;
7762 geometry.min_height = 440;
7764 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7765 &geometry, GDK_HINT_MIN_SIZE);
7767 #ifndef GENERIC_UMPC
7768 if (compose_force_window_origin)
7769 gtk_window_move(GTK_WINDOW(window), prefs_common.compose_x,
7770 prefs_common.compose_y);
7772 g_signal_connect(G_OBJECT(window), "delete_event",
7773 G_CALLBACK(compose_delete_cb), compose);
7774 MANAGE_WINDOW_SIGNALS_CONNECT(window);
7775 gtk_widget_realize(window);
7777 gtkut_widget_set_composer_icon(window);
7779 vbox = gtk_vbox_new(FALSE, 0);
7780 gtk_container_add(GTK_CONTAINER(window), vbox);
7782 compose->ui_manager = gtk_ui_manager_new();
7783 action_group = cm_menu_create_action_group_full(compose->ui_manager,"Menu", compose_entries,
7784 G_N_ELEMENTS(compose_entries), (gpointer)compose);
7785 gtk_action_group_add_toggle_actions(action_group, compose_toggle_entries,
7786 G_N_ELEMENTS(compose_toggle_entries), (gpointer)compose);
7787 gtk_action_group_add_radio_actions(action_group, compose_radio_rm_entries,
7788 G_N_ELEMENTS(compose_radio_rm_entries), COMPOSE_REPLY, G_CALLBACK(compose_reply_change_mode_cb), (gpointer)compose);
7789 gtk_action_group_add_radio_actions(action_group, compose_radio_prio_entries,
7790 G_N_ELEMENTS(compose_radio_prio_entries), PRIORITY_NORMAL, G_CALLBACK(compose_set_priority_cb), (gpointer)compose);
7791 gtk_action_group_add_radio_actions(action_group, compose_radio_enc_entries,
7792 G_N_ELEMENTS(compose_radio_enc_entries), C_AUTO, G_CALLBACK(compose_set_encoding_cb), (gpointer)compose);
7794 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Menu", NULL, GTK_UI_MANAGER_MENUBAR)
7796 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Message", "Message", GTK_UI_MANAGER_MENU)
7797 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Edit", "Edit", GTK_UI_MANAGER_MENU)
7799 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Spelling", "Spelling", GTK_UI_MANAGER_MENU)
7801 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Options", "Options", GTK_UI_MANAGER_MENU)
7802 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Tools", "Tools", GTK_UI_MANAGER_MENU)
7803 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Help", "Help", GTK_UI_MANAGER_MENU)
7806 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Send", "Message/Send", GTK_UI_MANAGER_MENUITEM)
7807 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "SendLater", "Message/SendLater", GTK_UI_MANAGER_MENUITEM)
7808 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator1", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7809 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "AttachFile", "Message/AttachFile", GTK_UI_MANAGER_MENUITEM)
7810 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertFile", "Message/InsertFile", GTK_UI_MANAGER_MENUITEM)
7811 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertSig", "Message/InsertSig", GTK_UI_MANAGER_MENUITEM)
7812 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "ReplaceSig", "Message/ReplaceSig", GTK_UI_MANAGER_MENUITEM)
7813 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator2", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7814 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Save", "Message/Save", GTK_UI_MANAGER_MENUITEM)
7815 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator3", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7816 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Print", "Message/Print", GTK_UI_MANAGER_MENUITEM)
7817 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator4", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7818 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Close", "Message/Close", GTK_UI_MANAGER_MENUITEM)
7821 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Undo", "Edit/Undo", GTK_UI_MANAGER_MENUITEM)
7822 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Redo", "Edit/Redo", GTK_UI_MANAGER_MENUITEM)
7823 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator1", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7825 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Cut", "Edit/Cut", GTK_UI_MANAGER_MENUITEM)
7826 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Copy", "Edit/Copy", GTK_UI_MANAGER_MENUITEM)
7827 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Paste", "Edit/Paste", GTK_UI_MANAGER_MENUITEM)
7829 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SpecialPaste", "Edit/SpecialPaste", GTK_UI_MANAGER_MENU)
7830 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "AsQuotation", "Edit/SpecialPaste/AsQuotation", GTK_UI_MANAGER_MENUITEM)
7831 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Wrapped", "Edit/SpecialPaste/Wrapped", GTK_UI_MANAGER_MENUITEM)
7832 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Unwrapped", "Edit/SpecialPaste/Unwrapped", GTK_UI_MANAGER_MENUITEM)
7834 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SelectAll", "Edit/SelectAll", GTK_UI_MANAGER_MENUITEM)
7836 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Advanced", "Edit/Advanced", GTK_UI_MANAGER_MENU)
7837 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackChar", "Edit/Advanced/BackChar", GTK_UI_MANAGER_MENUITEM)
7838 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwChar", "Edit/Advanced/ForwChar", GTK_UI_MANAGER_MENUITEM)
7839 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackWord", "Edit/Advanced/BackWord", GTK_UI_MANAGER_MENUITEM)
7840 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwWord", "Edit/Advanced/ForwWord", GTK_UI_MANAGER_MENUITEM)
7841 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BegLine", "Edit/Advanced/BegLine", GTK_UI_MANAGER_MENUITEM)
7842 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "EndLine", "Edit/Advanced/EndLine", GTK_UI_MANAGER_MENUITEM)
7843 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "PrevLine", "Edit/Advanced/PrevLine", GTK_UI_MANAGER_MENUITEM)
7844 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "NextLine", "Edit/Advanced/NextLine", GTK_UI_MANAGER_MENUITEM)
7845 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackChar", "Edit/Advanced/DelBackChar", GTK_UI_MANAGER_MENUITEM)
7846 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwChar", "Edit/Advanced/DelForwChar", GTK_UI_MANAGER_MENUITEM)
7847 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackWord", "Edit/Advanced/DelBackWord", GTK_UI_MANAGER_MENUITEM)
7848 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwWord", "Edit/Advanced/DelForwWord", GTK_UI_MANAGER_MENUITEM)
7849 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelLine", "Edit/Advanced/DelLine", GTK_UI_MANAGER_MENUITEM)
7850 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelEndLine", "Edit/Advanced/DelEndLine", GTK_UI_MANAGER_MENUITEM)
7852 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator2", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7854 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Find", "Edit/Find", GTK_UI_MANAGER_MENUITEM)
7855 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapPara", "Edit/WrapPara", GTK_UI_MANAGER_MENUITEM)
7856 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapAllLines", "Edit/WrapAllLines", GTK_UI_MANAGER_MENUITEM)
7857 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoWrap", "Edit/AutoWrap", GTK_UI_MANAGER_MENUITEM)
7858 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoIndent", "Edit/AutoIndent", GTK_UI_MANAGER_MENUITEM)
7860 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator3", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7862 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "ExtEditor", "Edit/ExtEditor", GTK_UI_MANAGER_MENUITEM)
7866 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckAllSel", "Spelling/CheckAllSel", GTK_UI_MANAGER_MENUITEM)
7867 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "HighlightAll", "Spelling/HighlightAll", GTK_UI_MANAGER_MENUITEM)
7868 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckBackwards", "Spelling/CheckBackwards", GTK_UI_MANAGER_MENUITEM)
7869 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "ForwardNext", "Spelling/ForwardNext", GTK_UI_MANAGER_MENUITEM)
7870 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Separator1", "Spelling/---", GTK_UI_MANAGER_SEPARATOR)
7871 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Options", "Spelling/Options", GTK_UI_MANAGER_MENU)
7875 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "ReplyMode", "Options/ReplyMode", GTK_UI_MANAGER_MENU)
7876 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Normal", "Options/ReplyMode/Normal", GTK_UI_MANAGER_MENUITEM)
7877 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "All", "Options/ReplyMode/All", GTK_UI_MANAGER_MENUITEM)
7878 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Sender", "Options/ReplyMode/Sender", GTK_UI_MANAGER_MENUITEM)
7879 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "List", "Options/ReplyMode/List", GTK_UI_MANAGER_MENUITEM)
7881 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator1", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7882 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "PrivacySystem", "Options/PrivacySystem", GTK_UI_MANAGER_MENU)
7883 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/PrivacySystem", "PlaceHolder", "Options/PrivacySystem/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7884 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Sign", "Options/Sign", GTK_UI_MANAGER_MENUITEM)
7885 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encrypt", "Options/Encrypt", GTK_UI_MANAGER_MENUITEM)
7888 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator2", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7889 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Priority", "Options/Priority", GTK_UI_MANAGER_MENU)
7890 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Highest", "Options/Priority/Highest", GTK_UI_MANAGER_MENUITEM)
7891 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "High", "Options/Priority/High", GTK_UI_MANAGER_MENUITEM)
7892 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Normal", "Options/Priority/Normal", GTK_UI_MANAGER_MENUITEM)
7893 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Low", "Options/Priority/Low", GTK_UI_MANAGER_MENUITEM)
7894 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Lowest", "Options/Priority/Lowest", GTK_UI_MANAGER_MENUITEM)
7896 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator3", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7897 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RequestRetRcpt", "Options/RequestRetRcpt", GTK_UI_MANAGER_MENUITEM)
7898 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator4", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7899 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RemoveReferences", "Options/RemoveReferences", GTK_UI_MANAGER_MENUITEM)
7900 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator5", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7902 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encoding", "Options/Encoding", GTK_UI_MANAGER_MENU)
7904 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_AUTO, "Options/Encoding/"CS_AUTO, GTK_UI_MANAGER_MENUITEM)
7905 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator1", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7906 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_US_ASCII, "Options/Encoding/"CS_US_ASCII, GTK_UI_MANAGER_MENUITEM)
7907 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_UTF_8, "Options/Encoding/"CS_UTF_8, GTK_UI_MANAGER_MENUITEM)
7908 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator2", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7910 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Western", "Options/Encoding/Western", GTK_UI_MANAGER_MENU)
7911 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)
7912 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)
7913 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Western", CS_WINDOWS_1252, "Options/Encoding/Western/"CS_WINDOWS_1252, GTK_UI_MANAGER_MENUITEM)
7915 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_2, "Options/Encoding/"CS_ISO_8859_2, GTK_UI_MANAGER_MENUITEM)
7917 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Baltic", "Options/Encoding/Baltic", GTK_UI_MANAGER_MENU)
7918 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)
7919 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)
7921 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_7, "Options/Encoding/"CS_ISO_8859_7, GTK_UI_MANAGER_MENUITEM)
7923 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Hebrew", "Options/Encoding/Hebrew", GTK_UI_MANAGER_MENU)
7924 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)
7925 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Hebrew", CS_WINDOWS_1255, "Options/Encoding/Hebrew/"CS_WINDOWS_1255, GTK_UI_MANAGER_MENUITEM)
7927 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Arabic", "Options/Encoding/Arabic", GTK_UI_MANAGER_MENU)
7928 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)
7929 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Arabic", CS_WINDOWS_1256, "Options/Encoding/Arabic/"CS_WINDOWS_1256, GTK_UI_MANAGER_MENUITEM)
7931 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_9, "Options/Encoding/"CS_ISO_8859_9, GTK_UI_MANAGER_MENUITEM)
7933 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Cyrillic", "Options/Encoding/Cyrillic", GTK_UI_MANAGER_MENU)
7934 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)
7935 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_R, "Options/Encoding/Cyrillic/"CS_KOI8_R, GTK_UI_MANAGER_MENUITEM)
7936 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_MACCYR, "Options/Encoding/Cyrillic/"CS_MACCYR, GTK_UI_MANAGER_MENUITEM)
7937 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_U, "Options/Encoding/Cyrillic/"CS_KOI8_U, GTK_UI_MANAGER_MENUITEM)
7938 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_WINDOWS_1251, "Options/Encoding/Cyrillic/"CS_WINDOWS_1251, GTK_UI_MANAGER_MENUITEM)
7940 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Japanese", "Options/Encoding/Japanese", GTK_UI_MANAGER_MENU)
7941 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)
7942 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)
7943 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_EUC_JP, "Options/Encoding/Japanese/"CS_EUC_JP, GTK_UI_MANAGER_MENUITEM)
7944 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_SHIFT_JIS, "Options/Encoding/Japanese/"CS_SHIFT_JIS, GTK_UI_MANAGER_MENUITEM)
7946 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Chinese", "Options/Encoding/Chinese", GTK_UI_MANAGER_MENU)
7947 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB18030, "Options/Encoding/Chinese/"CS_GB18030, GTK_UI_MANAGER_MENUITEM)
7948 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB2312, "Options/Encoding/Chinese/"CS_GB2312, GTK_UI_MANAGER_MENUITEM)
7949 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GBK, "Options/Encoding/Chinese/"CS_GBK, GTK_UI_MANAGER_MENUITEM)
7950 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_BIG5, "Options/Encoding/Chinese/"CS_BIG5, GTK_UI_MANAGER_MENUITEM)
7951 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_EUC_TW, "Options/Encoding/Chinese/"CS_EUC_TW, GTK_UI_MANAGER_MENUITEM)
7953 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Korean", "Options/Encoding/Korean", GTK_UI_MANAGER_MENU)
7954 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Korean", CS_EUC_KR, "Options/Encoding/Korean/"CS_EUC_KR, GTK_UI_MANAGER_MENUITEM)
7955 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)
7957 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Thai", "Options/Encoding/Thai", GTK_UI_MANAGER_MENU)
7958 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_TIS_620, "Options/Encoding/Thai/"CS_TIS_620, GTK_UI_MANAGER_MENUITEM)
7959 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_WINDOWS_874, "Options/Encoding/Thai/"CS_WINDOWS_874, GTK_UI_MANAGER_MENUITEM)
7963 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "ShowRuler", "Tools/ShowRuler", GTK_UI_MANAGER_MENUITEM)
7964 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "AddressBook", "Tools/AddressBook", GTK_UI_MANAGER_MENUITEM)
7965 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Template", "Tools/Template", GTK_UI_MANAGER_MENU)
7966 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Template", "PlaceHolder", "Tools/Template/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7967 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Actions", "Tools/Actions", GTK_UI_MANAGER_MENU)
7968 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Actions", "PlaceHolder", "Tools/Actions/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7971 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Help", "About", "Help/About", GTK_UI_MANAGER_MENUITEM)
7973 menubar = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu");
7974 gtk_widget_show_all(menubar);
7976 gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(compose->ui_manager));
7977 gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, TRUE, 0);
7979 if (prefs_common.toolbar_detachable) {
7980 handlebox = gtk_handle_box_new();
7982 handlebox = gtk_hbox_new(FALSE, 0);
7984 gtk_box_pack_start(GTK_BOX(vbox), handlebox, FALSE, FALSE, 0);
7986 gtk_widget_realize(handlebox);
7987 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, handlebox,
7990 vbox2 = gtk_vbox_new(FALSE, 2);
7991 gtk_box_pack_start(GTK_BOX(vbox), vbox2, TRUE, TRUE, 0);
7992 gtk_container_set_border_width(GTK_CONTAINER(vbox2), 0);
7995 notebook = gtk_notebook_new();
7996 gtk_widget_show(notebook);
7998 /* header labels and entries */
7999 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
8000 compose_create_header(compose),
8001 gtk_label_new_with_mnemonic(_("Hea_der")));
8002 /* attachment list */
8003 attach_hbox = gtk_hbox_new(FALSE, 0);
8004 gtk_widget_show(attach_hbox);
8006 attach_lab1 = gtk_label_new_with_mnemonic(_("_Attachments"));
8007 gtk_widget_show(attach_lab1);
8008 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab1, TRUE, TRUE, 0);
8010 attach_lab2 = gtk_label_new("");
8011 gtk_widget_show(attach_lab2);
8012 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab2, FALSE, FALSE, 0);
8014 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
8015 compose_create_attach(compose),
8018 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
8019 compose_create_others(compose),
8020 gtk_label_new_with_mnemonic(_("Othe_rs")));
8023 subject_hbox = gtk_hbox_new(FALSE, 0);
8024 gtk_widget_show(subject_hbox);
8026 subject_frame = gtk_frame_new(NULL);
8027 gtk_frame_set_shadow_type(GTK_FRAME(subject_frame), GTK_SHADOW_NONE);
8028 gtk_box_pack_start(GTK_BOX(subject_hbox), subject_frame, TRUE, TRUE, 0);
8029 gtk_widget_show(subject_frame);
8031 subject = gtk_hbox_new(FALSE, HSPACING_NARROW);
8032 gtk_container_set_border_width(GTK_CONTAINER(subject), 0);
8033 gtk_widget_show(subject);
8035 label = gtk_label_new_with_mnemonic(_("S_ubject:"));
8036 gtk_box_pack_start(GTK_BOX(subject), label, FALSE, FALSE, 0);
8037 gtk_widget_show(label);
8040 subject_entry = claws_spell_entry_new();
8042 subject_entry = gtk_entry_new();
8044 gtk_box_pack_start(GTK_BOX(subject), subject_entry, TRUE, TRUE, 0);
8045 g_signal_connect_after(G_OBJECT(subject_entry), "grab_focus",
8046 G_CALLBACK(compose_grab_focus_cb), compose);
8047 gtk_label_set_mnemonic_widget(GTK_LABEL(label), subject_entry);
8048 gtk_widget_show(subject_entry);
8049 compose->subject_entry = subject_entry;
8050 gtk_container_add(GTK_CONTAINER(subject_frame), subject);
8052 edit_vbox = gtk_vbox_new(FALSE, 0);
8054 gtk_box_pack_start(GTK_BOX(edit_vbox), subject_hbox, FALSE, FALSE, 0);
8057 ruler_hbox = gtk_hbox_new(FALSE, 0);
8058 gtk_box_pack_start(GTK_BOX(edit_vbox), ruler_hbox, FALSE, FALSE, 0);
8060 ruler = gtk_shruler_new(GTK_ORIENTATION_HORIZONTAL);
8061 gtk_shruler_set_range(GTK_SHRULER(ruler), 0.0, 100.0, 1.0);
8062 gtk_box_pack_start(GTK_BOX(ruler_hbox), ruler, TRUE, TRUE,
8066 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
8067 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
8068 GTK_POLICY_AUTOMATIC,
8069 GTK_POLICY_AUTOMATIC);
8070 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
8072 gtk_box_pack_start(GTK_BOX(edit_vbox), scrolledwin, TRUE, TRUE, 0);
8074 text = gtk_text_view_new();
8075 if (prefs_common.show_compose_margin) {
8076 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text), 6);
8077 gtk_text_view_set_right_margin(GTK_TEXT_VIEW(text), 6);
8079 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
8080 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
8081 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), TRUE);
8082 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
8083 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
8085 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
8086 g_signal_connect_after(G_OBJECT(text), "size_allocate",
8087 G_CALLBACK(compose_edit_size_alloc),
8089 g_signal_connect(G_OBJECT(buffer), "changed",
8090 G_CALLBACK(compose_changed_cb), compose);
8091 g_signal_connect(G_OBJECT(text), "grab_focus",
8092 G_CALLBACK(compose_grab_focus_cb), compose);
8093 g_signal_connect(G_OBJECT(buffer), "insert_text",
8094 G_CALLBACK(text_inserted), compose);
8095 g_signal_connect(G_OBJECT(text), "button_press_event",
8096 G_CALLBACK(text_clicked), compose);
8097 g_signal_connect(G_OBJECT(text), "popup-menu",
8098 G_CALLBACK(compose_popup_menu), compose);
8099 g_signal_connect(G_OBJECT(subject_entry), "changed",
8100 G_CALLBACK(compose_changed_cb), compose);
8101 g_signal_connect(G_OBJECT(subject_entry), "activate",
8102 G_CALLBACK(compose_subject_entry_activated), compose);
8105 gtk_drag_dest_set(text, GTK_DEST_DEFAULT_ALL, compose_mime_types,
8106 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
8107 GDK_ACTION_COPY | GDK_ACTION_MOVE);
8108 g_signal_connect(G_OBJECT(text), "drag_data_received",
8109 G_CALLBACK(compose_insert_drag_received_cb),
8111 g_signal_connect(G_OBJECT(text), "drag-drop",
8112 G_CALLBACK(compose_drag_drop),
8114 g_signal_connect(G_OBJECT(text), "key-press-event",
8115 G_CALLBACK(completion_set_focus_to_subject),
8117 gtk_widget_show_all(vbox);
8119 /* pane between attach clist and text */
8120 paned = gtk_vpaned_new();
8121 gtk_container_add(GTK_CONTAINER(vbox2), paned);
8122 gtk_paned_pack1(GTK_PANED(paned), notebook, FALSE, FALSE);
8123 gtk_paned_pack2(GTK_PANED(paned), edit_vbox, TRUE, FALSE);
8124 gtk_paned_set_position(GTK_PANED(paned), prefs_common.compose_notebook_height);
8125 g_signal_connect(G_OBJECT(notebook), "size_allocate",
8126 G_CALLBACK(compose_notebook_size_alloc), paned);
8128 gtk_widget_show_all(paned);
8131 if (prefs_common.textfont) {
8132 PangoFontDescription *font_desc;
8134 font_desc = pango_font_description_from_string
8135 (prefs_common.textfont);
8137 gtk_widget_modify_font(text, font_desc);
8138 pango_font_description_free(font_desc);
8142 gtk_action_group_add_actions(action_group, compose_popup_entries,
8143 G_N_ELEMENTS(compose_popup_entries), (gpointer)compose);
8144 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Popup", NULL, GTK_UI_MANAGER_MENUBAR)
8145 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup", "Compose", "Compose", GTK_UI_MANAGER_MENU)
8146 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Add", "Compose/Add", GTK_UI_MANAGER_MENUITEM)
8147 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Remove", "Compose/Remove", GTK_UI_MANAGER_MENUITEM)
8148 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Separator1", "Compose/---", GTK_UI_MANAGER_SEPARATOR)
8149 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Properties", "Compose/Properties", GTK_UI_MANAGER_MENUITEM)
8151 popupmenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(gtk_ui_manager_get_widget(compose->ui_manager, "/Popup/Compose")));
8153 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
8154 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
8155 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
8157 tmpl_menu = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu/Tools/Template");
8159 undostruct = undo_init(text);
8160 undo_set_change_state_func(undostruct, &compose_undo_state_changed,
8163 address_completion_start(window);
8165 compose->window = window;
8166 compose->vbox = vbox;
8167 compose->menubar = menubar;
8168 compose->handlebox = handlebox;
8170 compose->vbox2 = vbox2;
8172 compose->paned = paned;
8174 compose->attach_label = attach_lab2;
8176 compose->notebook = notebook;
8177 compose->edit_vbox = edit_vbox;
8178 compose->ruler_hbox = ruler_hbox;
8179 compose->ruler = ruler;
8180 compose->scrolledwin = scrolledwin;
8181 compose->text = text;
8183 compose->focused_editable = NULL;
8185 compose->popupmenu = popupmenu;
8187 compose->tmpl_menu = tmpl_menu;
8189 compose->mode = mode;
8190 compose->rmode = mode;
8192 compose->targetinfo = NULL;
8193 compose->replyinfo = NULL;
8194 compose->fwdinfo = NULL;
8196 compose->email_hashtable = g_hash_table_new_full(g_str_hash,
8197 g_str_equal, (GDestroyNotify) g_free, NULL);
8199 compose->replyto = NULL;
8201 compose->bcc = NULL;
8202 compose->followup_to = NULL;
8204 compose->ml_post = NULL;
8206 compose->inreplyto = NULL;
8207 compose->references = NULL;
8208 compose->msgid = NULL;
8209 compose->boundary = NULL;
8211 compose->autowrap = prefs_common.autowrap;
8212 compose->autoindent = prefs_common.auto_indent;
8213 compose->use_signing = FALSE;
8214 compose->use_encryption = FALSE;
8215 compose->privacy_system = NULL;
8216 compose->encdata = NULL;
8218 compose->modified = FALSE;
8220 compose->return_receipt = FALSE;
8222 compose->to_list = NULL;
8223 compose->newsgroup_list = NULL;
8225 compose->undostruct = undostruct;
8227 compose->sig_str = NULL;
8229 compose->exteditor_file = NULL;
8230 compose->exteditor_pid = -1;
8231 compose->exteditor_tag = -1;
8232 compose->exteditor_socket = NULL;
8233 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN; /* inhibit auto-drafting while loading */
8235 compose->folder_update_callback_id =
8236 hooks_register_hook(FOLDER_UPDATE_HOOKLIST,
8237 compose_update_folder_hook,
8238 (gpointer) compose);
8241 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
8242 if (mode != COMPOSE_REDIRECT) {
8243 if (prefs_common.enable_aspell && prefs_common.dictionary &&
8244 strcmp(prefs_common.dictionary, "")) {
8245 gtkaspell = gtkaspell_new(prefs_common.dictionary,
8246 prefs_common.alt_dictionary,
8247 conv_get_locale_charset_str(),
8248 prefs_common.color[COL_MISSPELLED],
8249 prefs_common.check_while_typing,
8250 prefs_common.recheck_when_changing_dict,
8251 prefs_common.use_alternate,
8252 prefs_common.use_both_dicts,
8253 GTK_TEXT_VIEW(text),
8254 GTK_WINDOW(compose->window),
8255 compose_dict_changed,
8256 compose_spell_menu_changed,
8259 alertpanel_error(_("Spell checker could not "
8261 gtkaspell_checkers_strerror());
8262 gtkaspell_checkers_reset_error();
8264 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", TRUE);
8268 compose->gtkaspell = gtkaspell;
8269 compose_spell_menu_changed(compose);
8270 claws_spell_entry_set_gtkaspell(CLAWS_SPELL_ENTRY(subject_entry), gtkaspell);
8273 compose_select_account(compose, account, TRUE);
8275 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", prefs_common.autowrap);
8276 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", prefs_common.auto_indent);
8278 if (account->set_autocc && account->auto_cc && mode != COMPOSE_REEDIT)
8279 compose_entry_append(compose, account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8281 if (account->set_autobcc && account->auto_bcc && mode != COMPOSE_REEDIT)
8282 compose_entry_append(compose, account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8284 if (account->set_autoreplyto && account->auto_replyto && mode != COMPOSE_REEDIT)
8285 compose_entry_append(compose, account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8287 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", compose->mode == COMPOSE_REPLY);
8288 if (account->protocol != A_NNTP)
8289 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
8290 prefs_common_translated_header_name("To:"));
8292 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
8293 prefs_common_translated_header_name("Newsgroups:"));
8295 #ifndef USE_ALT_ADDRBOOK
8296 addressbook_set_target_compose(compose);
8298 if (mode != COMPOSE_REDIRECT)
8299 compose_set_template_menu(compose);
8301 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Template", FALSE);
8304 compose_list = g_list_append(compose_list, compose);
8306 if (!prefs_common.show_ruler)
8307 gtk_widget_hide(ruler_hbox);
8309 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Tools/ShowRuler", prefs_common.show_ruler);
8312 compose->priority = PRIORITY_NORMAL;
8313 compose_update_priority_menu_item(compose);
8315 compose_set_out_encoding(compose);
8318 compose_update_actions_menu(compose);
8320 /* Privacy Systems menu */
8321 compose_update_privacy_systems_menu(compose);
8322 compose_activate_privacy_system(compose, account, TRUE);
8324 toolbar_set_style(compose->toolbar->toolbar, compose->handlebox, prefs_common.toolbar_style);
8326 gtk_widget_realize(window);
8328 gtk_widget_show(window);
8334 static GtkWidget *compose_account_option_menu_create(Compose *compose)
8339 GtkWidget *optmenubox;
8340 GtkWidget *fromlabel;
8343 GtkWidget *from_name = NULL;
8345 gint num = 0, def_menu = 0;
8347 accounts = account_get_list();
8348 cm_return_val_if_fail(accounts != NULL, NULL);
8350 optmenubox = gtk_event_box_new();
8351 optmenu = gtkut_sc_combobox_create(optmenubox, FALSE);
8352 menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
8354 hbox = gtk_hbox_new(FALSE, 4);
8355 from_name = gtk_entry_new();
8357 g_signal_connect_after(G_OBJECT(from_name), "grab_focus",
8358 G_CALLBACK(compose_grab_focus_cb), compose);
8359 g_signal_connect_after(G_OBJECT(from_name), "activate",
8360 G_CALLBACK(from_name_activate_cb), optmenu);
8362 for (; accounts != NULL; accounts = accounts->next, num++) {
8363 PrefsAccount *ac = (PrefsAccount *)accounts->data;
8364 gchar *name, *from = NULL;
8366 if (ac == compose->account) def_menu = num;
8368 name = g_markup_printf_escaped("<i>%s</i>",
8371 if (ac == compose->account) {
8372 if (ac->name && *ac->name) {
8374 QUOTE_IF_REQUIRED_NORMAL(buf, ac->name, return NULL);
8375 from = g_strdup_printf("%s <%s>",
8377 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8379 from = g_strdup_printf("%s",
8381 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8383 if (cur_account != compose->account) {
8384 gtk_widget_modify_base(
8385 GTK_WIDGET(from_name),
8386 GTK_STATE_NORMAL, &default_header_bgcolor);
8387 gtk_widget_modify_text(
8388 GTK_WIDGET(from_name),
8389 GTK_STATE_NORMAL, &default_header_color);
8392 COMBOBOX_ADD(menu, name, ac->account_id);
8397 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), def_menu);
8399 g_signal_connect(G_OBJECT(optmenu), "changed",
8400 G_CALLBACK(account_activated),
8402 g_signal_connect(G_OBJECT(from_name), "populate-popup",
8403 G_CALLBACK(compose_entry_popup_extend),
8406 fromlabel = gtk_label_new_with_mnemonic(_("_From:"));
8407 gtk_label_set_mnemonic_widget(GTK_LABEL(fromlabel), from_name);
8409 gtk_box_pack_start(GTK_BOX(hbox), fromlabel, FALSE, FALSE, 4);
8410 gtk_box_pack_start(GTK_BOX(hbox), optmenubox, FALSE, FALSE, 0);
8411 gtk_box_pack_start(GTK_BOX(hbox), from_name, TRUE, TRUE, 0);
8413 /* Putting only the GtkEntry into focus chain of parent hbox causes
8414 * the account selector combobox next to it to be unreachable when
8415 * navigating widgets in GtkTable with up/down arrow keys.
8416 * Note: gtk_widget_set_can_focus() was not enough. */
8418 l = g_list_prepend(l, from_name);
8419 gtk_container_set_focus_chain(GTK_CONTAINER(hbox), l);
8422 CLAWS_SET_TIP(optmenubox,
8423 _("Account to use for this email"));
8424 CLAWS_SET_TIP(from_name,
8425 _("Sender address to be used"));
8427 compose->account_combo = optmenu;
8428 compose->from_name = from_name;
8433 static void compose_set_priority_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8435 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8436 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8437 Compose *compose = (Compose *) data;
8439 compose->priority = value;
8443 static void compose_reply_change_mode(Compose *compose,
8446 gboolean was_modified = compose->modified;
8448 gboolean all = FALSE, ml = FALSE, sender = FALSE, followup = FALSE;
8450 cm_return_if_fail(compose->replyinfo != NULL);
8452 if (action == COMPOSE_REPLY && prefs_common.default_reply_list)
8454 if (action == COMPOSE_REPLY && compose->rmode == COMPOSE_FOLLOWUP_AND_REPLY_TO)
8456 if (action == COMPOSE_REPLY_TO_ALL)
8458 if (action == COMPOSE_REPLY_TO_SENDER)
8460 if (action == COMPOSE_REPLY_TO_LIST)
8463 compose_remove_header_entries(compose);
8464 compose_reply_set_entry(compose, compose->replyinfo, all, ml, sender, followup);
8465 if (compose->account->set_autocc && compose->account->auto_cc)
8466 compose_entry_append(compose, compose->account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8468 if (compose->account->set_autobcc && compose->account->auto_bcc)
8469 compose_entry_append(compose, compose->account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8471 if (compose->account->set_autoreplyto && compose->account->auto_replyto)
8472 compose_entry_append(compose, compose->account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8473 compose_show_first_last_header(compose, TRUE);
8474 compose->modified = was_modified;
8475 compose_set_title(compose);
8478 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8480 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8481 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8482 Compose *compose = (Compose *) data;
8485 compose_reply_change_mode(compose, value);
8488 static void compose_update_priority_menu_item(Compose * compose)
8490 GtkWidget *menuitem = NULL;
8491 switch (compose->priority) {
8492 case PRIORITY_HIGHEST:
8493 menuitem = gtk_ui_manager_get_widget
8494 (compose->ui_manager, "/Menu/Options/Priority/Highest");
8497 menuitem = gtk_ui_manager_get_widget
8498 (compose->ui_manager, "/Menu/Options/Priority/High");
8500 case PRIORITY_NORMAL:
8501 menuitem = gtk_ui_manager_get_widget
8502 (compose->ui_manager, "/Menu/Options/Priority/Normal");
8505 menuitem = gtk_ui_manager_get_widget
8506 (compose->ui_manager, "/Menu/Options/Priority/Low");
8508 case PRIORITY_LOWEST:
8509 menuitem = gtk_ui_manager_get_widget
8510 (compose->ui_manager, "/Menu/Options/Priority/Lowest");
8513 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8516 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data)
8518 Compose *compose = (Compose *) data;
8520 gboolean can_sign = FALSE, can_encrypt = FALSE;
8522 cm_return_if_fail(GTK_IS_CHECK_MENU_ITEM(widget));
8524 if (!gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget)))
8527 systemid = g_object_get_data(G_OBJECT(widget), "privacy_system");
8528 g_free(compose->privacy_system);
8529 compose->privacy_system = NULL;
8530 g_free(compose->encdata);
8531 compose->encdata = NULL;
8532 if (systemid != NULL) {
8533 compose->privacy_system = g_strdup(systemid);
8535 can_sign = privacy_system_can_sign(systemid);
8536 can_encrypt = privacy_system_can_encrypt(systemid);
8539 debug_print("activated privacy system: %s\n", systemid != NULL ? systemid : "None");
8541 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8542 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8543 if (compose->toolbar->privacy_sign_btn != NULL) {
8544 gtk_widget_set_sensitive(
8545 GTK_WIDGET(compose->toolbar->privacy_sign_btn),
8547 gtk_toggle_tool_button_set_active(
8548 GTK_TOGGLE_TOOL_BUTTON(compose->toolbar->privacy_sign_btn),
8549 can_sign ? compose->use_signing : FALSE);
8551 if (compose->toolbar->privacy_encrypt_btn != NULL) {
8552 gtk_widget_set_sensitive(
8553 GTK_WIDGET(compose->toolbar->privacy_encrypt_btn),
8555 gtk_toggle_tool_button_set_active(
8556 GTK_TOGGLE_TOOL_BUTTON(compose->toolbar->privacy_encrypt_btn),
8557 can_encrypt ? compose->use_encryption : FALSE);
8561 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn)
8563 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8564 GtkWidget *menuitem = NULL;
8565 GList *children, *amenu;
8566 gboolean can_sign = FALSE, can_encrypt = FALSE;
8567 gboolean found = FALSE;
8569 if (compose->privacy_system != NULL) {
8571 menuitem = gtk_menu_item_get_submenu(GTK_MENU_ITEM(
8572 gtk_ui_manager_get_widget(compose->ui_manager, branch_path)));
8573 cm_return_if_fail(menuitem != NULL);
8575 children = gtk_container_get_children(GTK_CONTAINER(GTK_MENU_SHELL(menuitem)));
8578 while (amenu != NULL) {
8579 systemid = g_object_get_data(G_OBJECT(amenu->data), "privacy_system");
8580 if (systemid != NULL) {
8581 if (strcmp(systemid, compose->privacy_system) == 0 &&
8582 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8583 menuitem = GTK_WIDGET(amenu->data);
8585 can_sign = privacy_system_can_sign(systemid);
8586 can_encrypt = privacy_system_can_encrypt(systemid);
8590 } else if (strlen(compose->privacy_system) == 0 &&
8591 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8592 menuitem = GTK_WIDGET(amenu->data);
8595 can_encrypt = FALSE;
8600 amenu = amenu->next;
8602 g_list_free(children);
8603 if (menuitem != NULL)
8604 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8606 if (warn && !found && strlen(compose->privacy_system)) {
8607 alertpanel_warning(_("The privacy system '%s' cannot be loaded. You "
8608 "will not be able to sign or encrypt this message."),
8609 compose->privacy_system);
8613 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8614 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8615 if (compose->toolbar->privacy_sign_btn != NULL) {
8616 gtk_widget_set_sensitive(
8617 GTK_WIDGET(compose->toolbar->privacy_sign_btn),
8620 if (compose->toolbar->privacy_encrypt_btn != NULL) {
8621 gtk_widget_set_sensitive(
8622 GTK_WIDGET(compose->toolbar->privacy_encrypt_btn),
8627 static void compose_set_out_encoding(Compose *compose)
8629 CharSet out_encoding;
8630 const gchar *branch = NULL;
8631 out_encoding = conv_get_charset_from_str(prefs_common.outgoing_charset);
8633 switch(out_encoding) {
8634 case C_AUTO: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8635 case C_US_ASCII: branch = "Menu/Options/Encoding/" CS_US_ASCII; break;
8636 case C_UTF_8: branch = "Menu/Options/Encoding/" CS_UTF_8; break;
8637 case C_ISO_8859_2: branch = "Menu/Options/Encoding/" CS_ISO_8859_2; break;
8638 case C_ISO_8859_7: branch = "Menu/Options/Encoding/" CS_ISO_8859_7; break;
8639 case C_ISO_8859_9: branch = "Menu/Options/Encoding/" CS_ISO_8859_9; break;
8640 case C_ISO_8859_1: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_1; break;
8641 case C_ISO_8859_15: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_15; break;
8642 case C_WINDOWS_1252: branch = "Menu/Options/Encoding/Western/" CS_WINDOWS_1252; break;
8643 case C_ISO_8859_13: branch = "Menu/Options/Encoding/Baltic/" CS_ISO_8859_13; break;
8644 case C_ISO_8859_4: branch = "Menu/Options/Encoding/Baltic" CS_ISO_8859_4; break;
8645 case C_ISO_8859_8: branch = "Menu/Options/Encoding/Hebrew/" CS_ISO_8859_8; break;
8646 case C_WINDOWS_1255: branch = "Menu/Options/Encoding/Hebrew/" CS_WINDOWS_1255; break;
8647 case C_ISO_8859_6: branch = "Menu/Options/Encoding/Arabic/" CS_ISO_8859_6; break;
8648 case C_WINDOWS_1256: branch = "Menu/Options/Encoding/Arabic/" CS_WINDOWS_1256; break;
8649 case C_ISO_8859_5: branch = "Menu/Options/Encoding/Cyrillic/" CS_ISO_8859_5; break;
8650 case C_KOI8_R: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_R; break;
8651 case C_MACCYR: branch = "Menu/Options/Encoding/Cyrillic/" CS_MACCYR; break;
8652 case C_KOI8_U: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_U; break;
8653 case C_WINDOWS_1251: branch = "Menu/Options/Encoding/Cyrillic/" CS_WINDOWS_1251; break;
8654 case C_ISO_2022_JP: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP; break;
8655 case C_ISO_2022_JP_2: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP_2; break;
8656 case C_EUC_JP: branch = "Menu/Options/Encoding/Japanese/" CS_EUC_JP; break;
8657 case C_SHIFT_JIS: branch = "Menu/Options/Encoding/Japanese/" CS_SHIFT_JIS; break;
8658 case C_GB18030: branch = "Menu/Options/Encoding/Chinese/" CS_GB18030; break;
8659 case C_GB2312: branch = "Menu/Options/Encoding/Chinese/" CS_GB2312; break;
8660 case C_GBK: branch = "Menu/Options/Encoding/Chinese/" CS_GBK; break;
8661 case C_BIG5: branch = "Menu/Options/Encoding/Chinese/" CS_BIG5; break;
8662 case C_EUC_TW: branch = "Menu/Options/Encoding/Chinese/" CS_EUC_TW; break;
8663 case C_EUC_KR: branch = "Menu/Options/Encoding/Korean/" CS_EUC_KR; break;
8664 case C_ISO_2022_KR: branch = "Menu/Options/Encoding/Korean/" CS_ISO_2022_KR; break;
8665 case C_TIS_620: branch = "Menu/Options/Encoding/Thai/" CS_TIS_620; break;
8666 case C_WINDOWS_874: branch = "Menu/Options/Encoding/Thai/" CS_WINDOWS_874; break;
8667 default: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8669 cm_toggle_menu_set_active_full(compose->ui_manager, (gchar *)branch, TRUE);
8672 static void compose_set_template_menu(Compose *compose)
8674 GSList *tmpl_list, *cur;
8678 tmpl_list = template_get_config();
8680 menu = gtk_menu_new();
8682 gtk_menu_set_accel_group (GTK_MENU (menu),
8683 gtk_ui_manager_get_accel_group(compose->ui_manager));
8684 for (cur = tmpl_list; cur != NULL; cur = cur->next) {
8685 Template *tmpl = (Template *)cur->data;
8686 gchar *accel_path = NULL;
8687 item = gtk_menu_item_new_with_label(tmpl->name);
8688 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
8689 g_signal_connect(G_OBJECT(item), "activate",
8690 G_CALLBACK(compose_template_activate_cb),
8692 g_object_set_data(G_OBJECT(item), "template", tmpl);
8693 gtk_widget_show(item);
8694 accel_path = g_strconcat("<ComposeTemplates>" , "/", tmpl->name, NULL);
8695 gtk_menu_item_set_accel_path(GTK_MENU_ITEM(item), accel_path);
8699 gtk_widget_show(menu);
8700 gtk_menu_item_set_submenu(GTK_MENU_ITEM(compose->tmpl_menu), menu);
8703 void compose_update_actions_menu(Compose *compose)
8705 action_update_compose_menu(compose->ui_manager, "/Menu/Tools/Actions", compose);
8708 static void compose_update_privacy_systems_menu(Compose *compose)
8710 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8711 GSList *systems, *cur;
8713 GtkWidget *system_none;
8715 GtkWidget *privacy_menuitem = gtk_ui_manager_get_widget(compose->ui_manager, branch_path);
8716 GtkWidget *privacy_menu = gtk_menu_new();
8718 system_none = gtk_radio_menu_item_new_with_mnemonic(NULL, _("_None"));
8719 g_object_set_data_full(G_OBJECT(system_none), "privacy_system", NULL, NULL);
8721 g_signal_connect(G_OBJECT(system_none), "activate",
8722 G_CALLBACK(compose_set_privacy_system_cb), compose);
8724 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), system_none);
8725 gtk_widget_show(system_none);
8727 systems = privacy_get_system_ids();
8728 for (cur = systems; cur != NULL; cur = g_slist_next(cur)) {
8729 gchar *systemid = cur->data;
8731 group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(system_none));
8732 widget = gtk_radio_menu_item_new_with_label(group,
8733 privacy_system_get_name(systemid));
8734 g_object_set_data_full(G_OBJECT(widget), "privacy_system",
8735 g_strdup(systemid), g_free);
8736 g_signal_connect(G_OBJECT(widget), "activate",
8737 G_CALLBACK(compose_set_privacy_system_cb), compose);
8739 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), widget);
8740 gtk_widget_show(widget);
8743 g_slist_free(systems);
8744 gtk_menu_item_set_submenu(GTK_MENU_ITEM(privacy_menuitem), privacy_menu);
8745 gtk_widget_show_all(privacy_menu);
8746 gtk_widget_show_all(privacy_menuitem);
8749 void compose_reflect_prefs_all(void)
8754 for (cur = compose_list; cur != NULL; cur = cur->next) {
8755 compose = (Compose *)cur->data;
8756 compose_set_template_menu(compose);
8760 void compose_reflect_prefs_pixmap_theme(void)
8765 for (cur = compose_list; cur != NULL; cur = cur->next) {
8766 compose = (Compose *)cur->data;
8767 toolbar_update(TOOLBAR_COMPOSE, compose);
8771 static const gchar *compose_quote_char_from_context(Compose *compose)
8773 const gchar *qmark = NULL;
8775 cm_return_val_if_fail(compose != NULL, NULL);
8777 switch (compose->mode) {
8778 /* use forward-specific quote char */
8779 case COMPOSE_FORWARD:
8780 case COMPOSE_FORWARD_AS_ATTACH:
8781 case COMPOSE_FORWARD_INLINE:
8782 if (compose->folder && compose->folder->prefs &&
8783 compose->folder->prefs->forward_with_format)
8784 qmark = compose->folder->prefs->forward_quotemark;
8785 else if (compose->account->forward_with_format)
8786 qmark = compose->account->forward_quotemark;
8788 qmark = prefs_common.fw_quotemark;
8791 /* use reply-specific quote char in all other modes */
8793 if (compose->folder && compose->folder->prefs &&
8794 compose->folder->prefs->reply_with_format)
8795 qmark = compose->folder->prefs->reply_quotemark;
8796 else if (compose->account->reply_with_format)
8797 qmark = compose->account->reply_quotemark;
8799 qmark = prefs_common.quotemark;
8803 if (qmark == NULL || *qmark == '\0')
8809 static void compose_template_apply(Compose *compose, Template *tmpl,
8813 GtkTextBuffer *buffer;
8817 gchar *parsed_str = NULL;
8818 gint cursor_pos = 0;
8819 const gchar *err_msg = _("The body of the template has an error at line %d.");
8822 /* process the body */
8824 text = GTK_TEXT_VIEW(compose->text);
8825 buffer = gtk_text_view_get_buffer(text);
8828 qmark = compose_quote_char_from_context(compose);
8830 if (compose->replyinfo != NULL) {
8833 gtk_text_buffer_set_text(buffer, "", -1);
8834 mark = gtk_text_buffer_get_insert(buffer);
8835 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8837 parsed_str = compose_quote_fmt(compose, compose->replyinfo,
8838 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8840 } else if (compose->fwdinfo != NULL) {
8843 gtk_text_buffer_set_text(buffer, "", -1);
8844 mark = gtk_text_buffer_get_insert(buffer);
8845 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8847 parsed_str = compose_quote_fmt(compose, compose->fwdinfo,
8848 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8851 MsgInfo* dummyinfo = compose_msginfo_new_from_compose(compose);
8853 GtkTextIter start, end;
8856 gtk_text_buffer_get_start_iter(buffer, &start);
8857 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
8858 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
8860 /* clear the buffer now */
8862 gtk_text_buffer_set_text(buffer, "", -1);
8864 parsed_str = compose_quote_fmt(compose, dummyinfo,
8865 tmpl->value, qmark, tmp, FALSE, FALSE, err_msg);
8866 procmsg_msginfo_free( &dummyinfo );
8872 gtk_text_buffer_set_text(buffer, "", -1);
8873 mark = gtk_text_buffer_get_insert(buffer);
8874 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8877 if (replace && parsed_str && compose->account->auto_sig)
8878 compose_insert_sig(compose, FALSE);
8880 if (replace && parsed_str) {
8881 gtk_text_buffer_get_start_iter(buffer, &iter);
8882 gtk_text_buffer_place_cursor(buffer, &iter);
8886 cursor_pos = quote_fmt_get_cursor_pos();
8887 compose->set_cursor_pos = cursor_pos;
8888 if (cursor_pos == -1)
8890 gtk_text_buffer_get_start_iter(buffer, &iter);
8891 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
8892 gtk_text_buffer_place_cursor(buffer, &iter);
8895 /* process the other fields */
8897 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
8898 compose_template_apply_fields(compose, tmpl);
8899 quote_fmt_reset_vartable();
8900 quote_fmtlex_destroy();
8902 compose_changed_cb(NULL, compose);
8905 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
8906 gtkaspell_highlight_all(compose->gtkaspell);
8910 static void compose_template_apply_fields_error(const gchar *header)
8915 tr = g_strdup(C_("'%s' stands for a header name",
8916 "Template '%s' format error."));
8917 text = g_strdup_printf(tr, prefs_common_translated_header_name(header));
8918 alertpanel_error("%s", text);
8924 static void compose_template_apply_fields(Compose *compose, Template *tmpl)
8926 MsgInfo* dummyinfo = NULL;
8927 MsgInfo *msginfo = NULL;
8930 if (compose->replyinfo != NULL)
8931 msginfo = compose->replyinfo;
8932 else if (compose->fwdinfo != NULL)
8933 msginfo = compose->fwdinfo;
8935 dummyinfo = compose_msginfo_new_from_compose(compose);
8936 msginfo = dummyinfo;
8939 if (tmpl->from && *tmpl->from != '\0') {
8941 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8942 compose->gtkaspell);
8944 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8946 quote_fmt_scan_string(tmpl->from);
8949 buf = quote_fmt_get_buffer();
8951 compose_template_apply_fields_error("From");
8953 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
8956 quote_fmt_reset_vartable();
8957 quote_fmtlex_destroy();
8960 if (tmpl->to && *tmpl->to != '\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->to);
8970 buf = quote_fmt_get_buffer();
8972 compose_template_apply_fields_error("To");
8974 compose_entry_append(compose, buf, COMPOSE_TO, PREF_TEMPLATE);
8977 quote_fmt_reset_vartable();
8978 quote_fmtlex_destroy();
8981 if (tmpl->cc && *tmpl->cc != '\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->cc);
8991 buf = quote_fmt_get_buffer();
8993 compose_template_apply_fields_error("Cc");
8995 compose_entry_append(compose, buf, COMPOSE_CC, PREF_TEMPLATE);
8998 quote_fmt_reset_vartable();
8999 quote_fmtlex_destroy();
9002 if (tmpl->bcc && *tmpl->bcc != '\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->bcc);
9012 buf = quote_fmt_get_buffer();
9014 compose_template_apply_fields_error("Bcc");
9016 compose_entry_append(compose, buf, COMPOSE_BCC, PREF_TEMPLATE);
9019 quote_fmt_reset_vartable();
9020 quote_fmtlex_destroy();
9023 if (tmpl->replyto && *tmpl->replyto != '\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->replyto);
9033 buf = quote_fmt_get_buffer();
9035 compose_template_apply_fields_error("Reply-To");
9037 compose_entry_append(compose, buf, COMPOSE_REPLYTO, PREF_TEMPLATE);
9040 quote_fmt_reset_vartable();
9041 quote_fmtlex_destroy();
9044 /* process the subject */
9045 if (tmpl->subject && *tmpl->subject != '\0') {
9047 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
9048 compose->gtkaspell);
9050 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
9052 quote_fmt_scan_string(tmpl->subject);
9055 buf = quote_fmt_get_buffer();
9057 compose_template_apply_fields_error("Subject");
9059 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
9062 quote_fmt_reset_vartable();
9063 quote_fmtlex_destroy();
9066 procmsg_msginfo_free( &dummyinfo );
9069 static void compose_destroy(Compose *compose)
9071 GtkAllocation allocation;
9072 GtkTextBuffer *buffer;
9073 GtkClipboard *clipboard;
9075 compose_list = g_list_remove(compose_list, compose);
9078 gboolean enable = TRUE;
9079 g_slist_foreach(compose->passworded_ldap_servers,
9080 _ldap_srv_func, &enable);
9081 g_slist_free(compose->passworded_ldap_servers);
9084 if (compose->updating) {
9085 debug_print("danger, not destroying anything now\n");
9086 compose->deferred_destroy = TRUE;
9090 /* NOTE: address_completion_end() does nothing with the window
9091 * however this may change. */
9092 address_completion_end(compose->window);
9094 slist_free_strings_full(compose->to_list);
9095 slist_free_strings_full(compose->newsgroup_list);
9096 slist_free_strings_full(compose->header_list);
9098 slist_free_strings_full(extra_headers);
9099 extra_headers = NULL;
9101 compose->header_list = compose->newsgroup_list = compose->to_list = NULL;
9103 g_hash_table_destroy(compose->email_hashtable);
9105 hooks_unregister_hook(FOLDER_UPDATE_HOOKLIST,
9106 compose->folder_update_callback_id);
9108 procmsg_msginfo_free(&(compose->targetinfo));
9109 procmsg_msginfo_free(&(compose->replyinfo));
9110 procmsg_msginfo_free(&(compose->fwdinfo));
9112 g_free(compose->replyto);
9113 g_free(compose->cc);
9114 g_free(compose->bcc);
9115 g_free(compose->newsgroups);
9116 g_free(compose->followup_to);
9118 g_free(compose->ml_post);
9120 g_free(compose->inreplyto);
9121 g_free(compose->references);
9122 g_free(compose->msgid);
9123 g_free(compose->boundary);
9125 g_free(compose->redirect_filename);
9126 if (compose->undostruct)
9127 undo_destroy(compose->undostruct);
9129 g_free(compose->sig_str);
9131 g_free(compose->exteditor_file);
9133 g_free(compose->orig_charset);
9135 g_free(compose->privacy_system);
9136 g_free(compose->encdata);
9138 #ifndef USE_ALT_ADDRBOOK
9139 if (addressbook_get_target_compose() == compose)
9140 addressbook_set_target_compose(NULL);
9143 if (compose->gtkaspell) {
9144 gtkaspell_delete(compose->gtkaspell);
9145 compose->gtkaspell = NULL;
9149 if (!compose->batch) {
9150 gtk_widget_get_allocation(compose->window, &allocation);
9151 prefs_common.compose_width = allocation.width;
9152 prefs_common.compose_height = allocation.height;
9155 if (!gtk_widget_get_parent(compose->paned))
9156 gtk_widget_destroy(compose->paned);
9157 gtk_widget_destroy(compose->popupmenu);
9159 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
9160 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
9161 gtk_text_buffer_remove_selection_clipboard(buffer, clipboard);
9163 gtk_widget_destroy(compose->window);
9164 toolbar_destroy(compose->toolbar);
9165 g_free(compose->toolbar);
9166 cm_mutex_free(compose->mutex);
9170 static void compose_attach_info_free(AttachInfo *ainfo)
9172 g_free(ainfo->file);
9173 g_free(ainfo->content_type);
9174 g_free(ainfo->name);
9175 g_free(ainfo->charset);
9179 static void compose_attach_update_label(Compose *compose)
9184 GtkTreeModel *model;
9188 if (compose == NULL)
9191 model = gtk_tree_view_get_model(GTK_TREE_VIEW(compose->attach_clist));
9192 if (!gtk_tree_model_get_iter_first(model, &iter)) {
9193 gtk_label_set_text(GTK_LABEL(compose->attach_label), "");
9197 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
9198 total_size = ainfo->size;
9199 while(gtk_tree_model_iter_next(model, &iter)) {
9200 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
9201 total_size += ainfo->size;
9204 text = g_strdup_printf(" (%d/%s)", i, to_human_readable(total_size));
9205 gtk_label_set_text(GTK_LABEL(compose->attach_label), text);
9209 static void compose_attach_remove_selected(GtkAction *action, gpointer data)
9211 Compose *compose = (Compose *)data;
9212 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
9213 GtkTreeSelection *selection;
9215 GtkTreeModel *model;
9217 selection = gtk_tree_view_get_selection(tree_view);
9218 sel = gtk_tree_selection_get_selected_rows(selection, &model);
9219 cm_return_if_fail(sel);
9221 for (cur = sel; cur != NULL; cur = cur->next) {
9222 GtkTreePath *path = cur->data;
9223 GtkTreeRowReference *ref = gtk_tree_row_reference_new
9226 gtk_tree_path_free(path);
9229 for (cur = sel; cur != NULL; cur = cur->next) {
9230 GtkTreeRowReference *ref = cur->data;
9231 GtkTreePath *path = gtk_tree_row_reference_get_path(ref);
9234 if (gtk_tree_model_get_iter(model, &iter, path))
9235 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
9237 gtk_tree_path_free(path);
9238 gtk_tree_row_reference_free(ref);
9242 compose_attach_update_label(compose);
9245 static struct _AttachProperty
9248 GtkWidget *mimetype_entry;
9249 GtkWidget *encoding_optmenu;
9250 GtkWidget *path_entry;
9251 GtkWidget *filename_entry;
9253 GtkWidget *cancel_btn;
9256 static void gtk_tree_path_free_(gpointer ptr, gpointer data)
9258 gtk_tree_path_free((GtkTreePath *)ptr);
9261 static void compose_attach_property(GtkAction *action, gpointer data)
9263 Compose *compose = (Compose *)data;
9264 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
9266 GtkComboBox *optmenu;
9267 GtkTreeSelection *selection;
9269 GtkTreeModel *model;
9272 static gboolean cancelled;
9274 /* only if one selected */
9275 selection = gtk_tree_view_get_selection(tree_view);
9276 if (gtk_tree_selection_count_selected_rows(selection) != 1)
9279 sel = gtk_tree_selection_get_selected_rows(selection, &model);
9280 cm_return_if_fail(sel);
9282 path = (GtkTreePath *) sel->data;
9283 gtk_tree_model_get_iter(model, &iter, path);
9284 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
9287 g_list_foreach(sel, gtk_tree_path_free_, NULL);
9293 if (!attach_prop.window)
9294 compose_attach_property_create(&cancelled);
9295 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), TRUE);
9296 gtk_widget_grab_focus(attach_prop.ok_btn);
9297 gtk_widget_show(attach_prop.window);
9298 gtk_window_set_transient_for(GTK_WINDOW(attach_prop.window),
9299 GTK_WINDOW(compose->window));
9301 optmenu = GTK_COMBO_BOX(attach_prop.encoding_optmenu);
9302 if (ainfo->encoding == ENC_UNKNOWN)
9303 combobox_select_by_data(optmenu, ENC_BASE64);
9305 combobox_select_by_data(optmenu, ainfo->encoding);
9307 gtk_entry_set_text(GTK_ENTRY(attach_prop.mimetype_entry),
9308 ainfo->content_type ? ainfo->content_type : "");
9309 gtk_entry_set_text(GTK_ENTRY(attach_prop.path_entry),
9310 ainfo->file ? ainfo->file : "");
9311 gtk_entry_set_text(GTK_ENTRY(attach_prop.filename_entry),
9312 ainfo->name ? ainfo->name : "");
9315 const gchar *entry_text;
9317 gchar *cnttype = NULL;
9324 gtk_widget_hide(attach_prop.window);
9325 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), FALSE);
9330 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.mimetype_entry));
9331 if (*entry_text != '\0') {
9334 text = g_strstrip(g_strdup(entry_text));
9335 if ((p = strchr(text, '/')) && !strchr(p + 1, '/')) {
9336 cnttype = g_strdup(text);
9339 alertpanel_error(_("Invalid MIME type."));
9345 ainfo->encoding = combobox_get_active_data(optmenu);
9347 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.path_entry));
9348 if (*entry_text != '\0') {
9349 if (is_file_exist(entry_text) &&
9350 (size = get_file_size(entry_text)) > 0)
9351 file = g_strdup(entry_text);
9354 (_("File doesn't exist or is empty."));
9360 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.filename_entry));
9361 if (*entry_text != '\0') {
9362 g_free(ainfo->name);
9363 ainfo->name = g_strdup(entry_text);
9367 g_free(ainfo->content_type);
9368 ainfo->content_type = cnttype;
9371 g_free(ainfo->file);
9375 ainfo->size = (goffset)size;
9377 /* update tree store */
9378 text = to_human_readable(ainfo->size);
9379 gtk_tree_model_get_iter(model, &iter, path);
9380 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
9381 COL_MIMETYPE, ainfo->content_type,
9383 COL_NAME, ainfo->name,
9384 COL_CHARSET, ainfo->charset,
9390 gtk_tree_path_free(path);
9393 #define SET_LABEL_AND_ENTRY(str, entry, top) \
9395 label = gtk_label_new(str); \
9396 gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), \
9397 GTK_FILL, 0, 0, 0); \
9398 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); \
9400 entry = gtk_entry_new(); \
9401 gtk_table_attach(GTK_TABLE(table), entry, 1, 2, top, (top + 1), \
9402 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); \
9405 static void compose_attach_property_create(gboolean *cancelled)
9411 GtkWidget *mimetype_entry;
9414 GtkListStore *optmenu_menu;
9415 GtkWidget *path_entry;
9416 GtkWidget *filename_entry;
9419 GtkWidget *cancel_btn;
9420 GList *mime_type_list, *strlist;
9423 debug_print("Creating attach_property window...\n");
9425 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose_attach_property");
9426 gtk_widget_set_size_request(window, 480, -1);
9427 gtk_container_set_border_width(GTK_CONTAINER(window), 8);
9428 gtk_window_set_title(GTK_WINDOW(window), _("Properties"));
9429 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
9430 g_signal_connect(G_OBJECT(window), "delete_event",
9431 G_CALLBACK(attach_property_delete_event),
9433 g_signal_connect(G_OBJECT(window), "key_press_event",
9434 G_CALLBACK(attach_property_key_pressed),
9437 vbox = gtk_vbox_new(FALSE, 8);
9438 gtk_container_add(GTK_CONTAINER(window), vbox);
9440 table = gtk_table_new(4, 2, FALSE);
9441 gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
9442 gtk_table_set_row_spacings(GTK_TABLE(table), 8);
9443 gtk_table_set_col_spacings(GTK_TABLE(table), 8);
9445 label = gtk_label_new(_("MIME type"));
9446 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, (0 + 1),
9448 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
9449 mimetype_entry = gtk_combo_box_text_new_with_entry();
9450 gtk_table_attach(GTK_TABLE(table), mimetype_entry, 1, 2, 0, (0 + 1),
9451 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
9453 /* stuff with list */
9454 mime_type_list = procmime_get_mime_type_list();
9456 for (; mime_type_list != NULL; mime_type_list = mime_type_list->next) {
9457 MimeType *type = (MimeType *) mime_type_list->data;
9460 tmp = g_strdup_printf("%s/%s", type->type, type->sub_type);
9462 if (g_list_find_custom(strlist, tmp, (GCompareFunc)strcmp2))
9465 strlist = g_list_insert_sorted(strlist, (gpointer)tmp,
9466 (GCompareFunc)strcmp2);
9469 for (mime_type_list = strlist; mime_type_list != NULL;
9470 mime_type_list = mime_type_list->next) {
9471 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(mimetype_entry), mime_type_list->data);
9472 g_free(mime_type_list->data);
9474 g_list_free(strlist);
9475 gtk_combo_box_set_active(GTK_COMBO_BOX(mimetype_entry), 0);
9476 mimetype_entry = gtk_bin_get_child(GTK_BIN((mimetype_entry)));
9478 label = gtk_label_new(_("Encoding"));
9479 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2,
9481 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
9483 hbox = gtk_hbox_new(FALSE, 0);
9484 gtk_table_attach(GTK_TABLE(table), hbox, 1, 2, 1, 2,
9485 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
9487 optmenu = gtkut_sc_combobox_create(NULL, TRUE);
9488 optmenu_menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
9490 COMBOBOX_ADD(optmenu_menu, "7bit", ENC_7BIT);
9491 COMBOBOX_ADD(optmenu_menu, "8bit", ENC_8BIT);
9492 COMBOBOX_ADD(optmenu_menu, "quoted-printable", ENC_QUOTED_PRINTABLE);
9493 COMBOBOX_ADD(optmenu_menu, "base64", ENC_BASE64);
9494 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), 0);
9496 gtk_box_pack_start(GTK_BOX(hbox), optmenu, TRUE, TRUE, 0);
9498 SET_LABEL_AND_ENTRY(_("Path"), path_entry, 2);
9499 SET_LABEL_AND_ENTRY(_("File name"), filename_entry, 3);
9501 gtkut_stock_button_set_create(&hbbox, &cancel_btn, GTK_STOCK_CANCEL,
9502 &ok_btn, GTK_STOCK_OK,
9504 gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
9505 gtk_widget_grab_default(ok_btn);
9507 g_signal_connect(G_OBJECT(ok_btn), "clicked",
9508 G_CALLBACK(attach_property_ok),
9510 g_signal_connect(G_OBJECT(cancel_btn), "clicked",
9511 G_CALLBACK(attach_property_cancel),
9514 gtk_widget_show_all(vbox);
9516 attach_prop.window = window;
9517 attach_prop.mimetype_entry = mimetype_entry;
9518 attach_prop.encoding_optmenu = optmenu;
9519 attach_prop.path_entry = path_entry;
9520 attach_prop.filename_entry = filename_entry;
9521 attach_prop.ok_btn = ok_btn;
9522 attach_prop.cancel_btn = cancel_btn;
9525 #undef SET_LABEL_AND_ENTRY
9527 static void attach_property_ok(GtkWidget *widget, gboolean *cancelled)
9533 static void attach_property_cancel(GtkWidget *widget, gboolean *cancelled)
9539 static gint attach_property_delete_event(GtkWidget *widget, GdkEventAny *event,
9540 gboolean *cancelled)
9548 static gboolean attach_property_key_pressed(GtkWidget *widget,
9550 gboolean *cancelled)
9552 if (event && event->keyval == GDK_KEY_Escape) {
9556 if (event && event->keyval == GDK_KEY_Return) {
9564 static void compose_exec_ext_editor(Compose *compose)
9569 GdkNativeWindow socket_wid = 0;
9573 tmp = g_strdup_printf("%s%ctmpmsg.%p", get_tmp_dir(),
9574 G_DIR_SEPARATOR, compose);
9576 if (compose_get_ext_editor_uses_socket()) {
9577 /* Only allow one socket */
9578 if (compose->exteditor_socket != NULL) {
9579 if (gtk_widget_is_focus(compose->exteditor_socket)) {
9580 /* Move the focus off of the socket */
9581 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9586 /* Create the receiving GtkSocket */
9587 socket = gtk_socket_new ();
9588 g_signal_connect (GTK_OBJECT(socket), "plug-removed",
9589 G_CALLBACK(compose_ext_editor_plug_removed_cb),
9591 gtk_box_pack_start(GTK_BOX(compose->edit_vbox), socket, TRUE, TRUE, 0);
9592 gtk_widget_set_size_request(socket, prefs_common.compose_width, -1);
9593 /* Realize the socket so that we can use its ID */
9594 gtk_widget_realize(socket);
9595 socket_wid = gtk_socket_get_id(GTK_SOCKET (socket));
9596 compose->exteditor_socket = socket;
9599 if (pipe(pipe_fds) < 0) {
9605 if ((pid = fork()) < 0) {
9612 /* close the write side of the pipe */
9615 compose->exteditor_file = g_strdup(tmp);
9616 compose->exteditor_pid = pid;
9618 compose_set_ext_editor_sensitive(compose, FALSE);
9621 compose->exteditor_ch = g_io_channel_unix_new(pipe_fds[0]);
9623 compose->exteditor_ch = g_io_channel_win32_new_fd(pipe_fds[0]);
9625 compose->exteditor_tag = g_io_add_watch(compose->exteditor_ch,
9629 } else { /* process-monitoring process */
9635 /* close the read side of the pipe */
9638 if (compose_write_body_to_file(compose, tmp) < 0) {
9639 fd_write_all(pipe_fds[1], "2\n", 2);
9643 pid_ed = compose_exec_ext_editor_real(tmp, socket_wid);
9645 fd_write_all(pipe_fds[1], "1\n", 2);
9649 /* wait until editor is terminated */
9650 waitpid(pid_ed, NULL, 0);
9652 fd_write_all(pipe_fds[1], "0\n", 2);
9659 #endif /* G_OS_UNIX */
9662 static gboolean compose_can_autosave(Compose *compose)
9664 if (compose->privacy_system && compose->use_encryption)
9665 return prefs_common.autosave && prefs_common.autosave_encrypted;
9667 return prefs_common.autosave;
9671 static gboolean compose_get_ext_editor_cmd_valid()
9673 gboolean has_s = FALSE;
9674 gboolean has_w = FALSE;
9675 const gchar *p = prefs_common_get_ext_editor_cmd();
9678 while ((p = strchr(p, '%'))) {
9684 } else if (*p == 'w') {
9695 static gint compose_exec_ext_editor_real(const gchar *file, GdkNativeWindow socket_wid)
9702 cm_return_val_if_fail(file != NULL, -1);
9704 if ((pid = fork()) < 0) {
9709 if (pid != 0) return pid;
9711 /* grandchild process */
9713 if (setpgid(0, getppid()))
9716 if (compose_get_ext_editor_cmd_valid()) {
9717 if (compose_get_ext_editor_uses_socket()) {
9718 p = g_strdup(prefs_common_get_ext_editor_cmd());
9719 s = strstr(p, "%w");
9721 if (strstr(p, "%s") < s)
9722 buf = g_strdup_printf(p, file, socket_wid);
9724 buf = g_strdup_printf(p, socket_wid, file);
9727 buf = g_strdup_printf(prefs_common_get_ext_editor_cmd(), file);
9730 if (prefs_common_get_ext_editor_cmd())
9731 g_warning("External editor command-line is invalid: '%s'",
9732 prefs_common_get_ext_editor_cmd());
9733 buf = g_strdup_printf(DEFAULT_EDITOR_CMD, file);
9736 cmdline = strsplit_with_quote(buf, " ", 0);
9738 execvp(cmdline[0], cmdline);
9741 g_strfreev(cmdline);
9746 static gboolean compose_ext_editor_kill(Compose *compose)
9748 pid_t pgid = compose->exteditor_pid * -1;
9751 ret = kill(pgid, 0);
9753 if (ret == 0 || (ret == -1 && EPERM == errno)) {
9757 msg = g_strdup_printf
9758 (_("The external editor is still working.\n"
9759 "Force terminating the process?\n"
9760 "process group id: %d"), -pgid);
9761 val = alertpanel_full(_("Notice"), msg, GTK_STOCK_NO, GTK_STOCK_YES,
9762 NULL, ALERTFOCUS_FIRST, FALSE, NULL,
9767 if (val == G_ALERTALTERNATE) {
9768 g_source_remove(compose->exteditor_tag);
9769 g_io_channel_shutdown(compose->exteditor_ch,
9771 g_io_channel_unref(compose->exteditor_ch);
9773 if (kill(pgid, SIGTERM) < 0) perror("kill");
9774 waitpid(compose->exteditor_pid, NULL, 0);
9776 g_warning("Terminated process group id: %d. "
9777 "Temporary file: %s", -pgid, compose->exteditor_file);
9779 compose_set_ext_editor_sensitive(compose, TRUE);
9781 g_free(compose->exteditor_file);
9782 compose->exteditor_file = NULL;
9783 compose->exteditor_pid = -1;
9784 compose->exteditor_ch = NULL;
9785 compose->exteditor_tag = -1;
9793 static gboolean compose_input_cb(GIOChannel *source, GIOCondition condition,
9797 Compose *compose = (Compose *)data;
9800 debug_print("Compose: input from monitoring process\n");
9802 if (g_io_channel_read_chars(source, buf, sizeof(buf), &bytes_read, NULL) != G_IO_STATUS_NORMAL) {
9807 g_io_channel_shutdown(source, FALSE, NULL);
9808 g_io_channel_unref(source);
9810 waitpid(compose->exteditor_pid, NULL, 0);
9812 if (buf[0] == '0') { /* success */
9813 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9814 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
9815 GtkTextIter start, end;
9818 gtk_text_buffer_set_text(buffer, "", -1);
9819 compose_insert_file(compose, compose->exteditor_file);
9820 compose_changed_cb(NULL, compose);
9822 /* Check if we should save the draft or not */
9823 if (compose_can_autosave(compose))
9824 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
9826 if (claws_unlink(compose->exteditor_file) < 0)
9827 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9829 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
9830 gtk_text_buffer_get_start_iter(buffer, &start);
9831 gtk_text_buffer_get_end_iter(buffer, &end);
9832 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
9833 if (chars && strlen(chars) > 0)
9834 compose->modified = TRUE;
9836 } else if (buf[0] == '1') { /* failed */
9837 g_warning("Couldn't exec external editor");
9838 if (claws_unlink(compose->exteditor_file) < 0)
9839 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9840 } else if (buf[0] == '2') {
9841 g_warning("Couldn't write to file");
9842 } else if (buf[0] == '3') {
9843 g_warning("Pipe read failed");
9846 compose_set_ext_editor_sensitive(compose, TRUE);
9848 g_free(compose->exteditor_file);
9849 compose->exteditor_file = NULL;
9850 compose->exteditor_pid = -1;
9851 compose->exteditor_ch = NULL;
9852 compose->exteditor_tag = -1;
9853 if (compose->exteditor_socket) {
9854 gtk_widget_destroy(compose->exteditor_socket);
9855 compose->exteditor_socket = NULL;
9862 static char *ext_editor_menu_entries[] = {
9863 "Menu/Message/Send",
9864 "Menu/Message/SendLater",
9865 "Menu/Message/InsertFile",
9866 "Menu/Message/InsertSig",
9867 "Menu/Message/ReplaceSig",
9868 "Menu/Message/Save",
9869 "Menu/Message/Print",
9874 "Menu/Tools/ShowRuler",
9875 "Menu/Tools/Actions",
9880 static void compose_set_ext_editor_sensitive(Compose *compose,
9885 for (i = 0; ext_editor_menu_entries[i]; ++i) {
9886 cm_menu_set_sensitive_full(compose->ui_manager,
9887 ext_editor_menu_entries[i], sensitive);
9890 if (compose_get_ext_editor_uses_socket()) {
9892 if (compose->exteditor_socket)
9893 gtk_widget_hide(compose->exteditor_socket);
9894 gtk_widget_show(compose->scrolledwin);
9895 if (prefs_common.show_ruler)
9896 gtk_widget_show(compose->ruler_hbox);
9897 /* Fix the focus, as it doesn't go anywhere when the
9898 * socket is hidden or destroyed */
9899 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9901 g_assert (compose->exteditor_socket != NULL);
9902 /* Fix the focus, as it doesn't go anywhere when the
9903 * edit box is hidden */
9904 if (gtk_widget_is_focus(compose->text))
9905 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9906 gtk_widget_hide(compose->scrolledwin);
9907 gtk_widget_hide(compose->ruler_hbox);
9908 gtk_widget_show(compose->exteditor_socket);
9911 gtk_widget_set_sensitive(compose->text, sensitive);
9913 if (compose->toolbar->send_btn)
9914 gtk_widget_set_sensitive(compose->toolbar->send_btn, sensitive);
9915 if (compose->toolbar->sendl_btn)
9916 gtk_widget_set_sensitive(compose->toolbar->sendl_btn, sensitive);
9917 if (compose->toolbar->draft_btn)
9918 gtk_widget_set_sensitive(compose->toolbar->draft_btn, sensitive);
9919 if (compose->toolbar->insert_btn)
9920 gtk_widget_set_sensitive(compose->toolbar->insert_btn, sensitive);
9921 if (compose->toolbar->sig_btn)
9922 gtk_widget_set_sensitive(compose->toolbar->sig_btn, sensitive);
9923 if (compose->toolbar->exteditor_btn)
9924 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, sensitive);
9925 if (compose->toolbar->linewrap_current_btn)
9926 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, sensitive);
9927 if (compose->toolbar->linewrap_all_btn)
9928 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, sensitive);
9931 static gboolean compose_get_ext_editor_uses_socket()
9933 return (prefs_common_get_ext_editor_cmd() &&
9934 strstr(prefs_common_get_ext_editor_cmd(), "%w"));
9937 static gboolean compose_ext_editor_plug_removed_cb(GtkSocket *socket, Compose *compose)
9939 compose->exteditor_socket = NULL;
9940 /* returning FALSE allows destruction of the socket */
9943 #endif /* G_OS_UNIX */
9946 * compose_undo_state_changed:
9948 * Change the sensivity of the menuentries undo and redo
9950 static void compose_undo_state_changed(UndoMain *undostruct, gint undo_state,
9951 gint redo_state, gpointer data)
9953 Compose *compose = (Compose *)data;
9955 switch (undo_state) {
9956 case UNDO_STATE_TRUE:
9957 if (!undostruct->undo_state) {
9958 undostruct->undo_state = TRUE;
9959 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", TRUE);
9962 case UNDO_STATE_FALSE:
9963 if (undostruct->undo_state) {
9964 undostruct->undo_state = FALSE;
9965 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
9968 case UNDO_STATE_UNCHANGED:
9970 case UNDO_STATE_REFRESH:
9971 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", undostruct->undo_state);
9974 g_warning("Undo state not recognized");
9978 switch (redo_state) {
9979 case UNDO_STATE_TRUE:
9980 if (!undostruct->redo_state) {
9981 undostruct->redo_state = TRUE;
9982 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", TRUE);
9985 case UNDO_STATE_FALSE:
9986 if (undostruct->redo_state) {
9987 undostruct->redo_state = FALSE;
9988 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
9991 case UNDO_STATE_UNCHANGED:
9993 case UNDO_STATE_REFRESH:
9994 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", undostruct->redo_state);
9997 g_warning("Redo state not recognized");
10002 /* callback functions */
10004 static void compose_notebook_size_alloc(GtkNotebook *notebook,
10005 GtkAllocation *allocation,
10008 prefs_common.compose_notebook_height = gtk_paned_get_position(paned);
10011 /* compose_edit_size_alloc() - called when resized. don't know whether Gtk
10012 * includes "non-client" (windows-izm) in calculation, so this calculation
10013 * may not be accurate.
10015 static gboolean compose_edit_size_alloc(GtkEditable *widget,
10016 GtkAllocation *allocation,
10017 GtkSHRuler *shruler)
10019 if (prefs_common.show_ruler) {
10020 gint char_width = 0, char_height = 0;
10021 gint line_width_in_chars;
10023 gtkut_get_font_size(GTK_WIDGET(widget),
10024 &char_width, &char_height);
10025 line_width_in_chars =
10026 (allocation->width - allocation->x) / char_width;
10028 /* got the maximum */
10029 gtk_shruler_set_range(GTK_SHRULER(shruler),
10030 0.0, line_width_in_chars, 0);
10039 ComposePrefType type;
10040 gboolean entry_marked;
10041 } HeaderEntryState;
10043 static void account_activated(GtkComboBox *optmenu, gpointer data)
10045 Compose *compose = (Compose *)data;
10048 gchar *folderidentifier;
10049 gint account_id = 0;
10050 GtkTreeModel *menu;
10052 GSList *list, *saved_list = NULL;
10053 HeaderEntryState *state;
10055 /* Get ID of active account in the combo box */
10056 menu = gtk_combo_box_get_model(optmenu);
10057 cm_return_if_fail(gtk_combo_box_get_active_iter(optmenu, &iter));
10058 gtk_tree_model_get(menu, &iter, 1, &account_id, -1);
10060 ac = account_find_from_id(account_id);
10061 cm_return_if_fail(ac != NULL);
10063 if (ac != compose->account) {
10064 compose_select_account(compose, ac, FALSE);
10066 for (list = compose->header_list; list; list = list->next) {
10067 ComposeHeaderEntry *hentry=(ComposeHeaderEntry *)list->data;
10069 if (hentry->type == PREF_ACCOUNT || !list->next) {
10070 compose_destroy_headerentry(compose, hentry);
10073 state = g_malloc0(sizeof(HeaderEntryState));
10074 state->header = gtk_editable_get_chars(GTK_EDITABLE(
10075 gtk_bin_get_child(GTK_BIN(hentry->combo))), 0, -1);
10076 state->entry = gtk_editable_get_chars(
10077 GTK_EDITABLE(hentry->entry), 0, -1);
10078 state->type = hentry->type;
10080 saved_list = g_slist_append(saved_list, state);
10081 compose_destroy_headerentry(compose, hentry);
10084 compose->header_last = NULL;
10085 g_slist_free(compose->header_list);
10086 compose->header_list = NULL;
10087 compose->header_nextrow = 1;
10088 compose_create_header_entry(compose);
10090 if (ac->set_autocc && ac->auto_cc)
10091 compose_entry_append(compose, ac->auto_cc,
10092 COMPOSE_CC, PREF_ACCOUNT);
10093 if (ac->set_autobcc && ac->auto_bcc)
10094 compose_entry_append(compose, ac->auto_bcc,
10095 COMPOSE_BCC, PREF_ACCOUNT);
10096 if (ac->set_autoreplyto && ac->auto_replyto)
10097 compose_entry_append(compose, ac->auto_replyto,
10098 COMPOSE_REPLYTO, PREF_ACCOUNT);
10100 for (list = saved_list; list; list = list->next) {
10101 state = (HeaderEntryState *) list->data;
10103 compose_add_header_entry(compose, state->header,
10104 state->entry, state->type);
10106 g_free(state->header);
10107 g_free(state->entry);
10110 g_slist_free(saved_list);
10112 combobox_select_by_data(GTK_COMBO_BOX(compose->header_last->combo),
10113 (ac->protocol == A_NNTP) ?
10114 COMPOSE_NEWSGROUPS : COMPOSE_TO);
10117 /* Set message save folder */
10118 if (account_get_special_folder(compose->account, F_OUTBOX)) {
10119 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
10121 g_signal_connect(G_OBJECT(compose->savemsg_checkbtn), "toggled",
10122 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
10124 compose_set_save_to(compose, NULL);
10125 if (account_get_special_folder(compose->account, F_OUTBOX)) {
10126 folderidentifier = folder_item_get_identifier(account_get_special_folder
10127 (compose->account, F_OUTBOX));
10128 compose_set_save_to(compose, folderidentifier);
10129 g_free(folderidentifier);
10133 static void attach_selected(GtkTreeView *tree_view, GtkTreePath *tree_path,
10134 GtkTreeViewColumn *column, Compose *compose)
10136 compose_attach_property(NULL, compose);
10139 static gboolean attach_button_pressed(GtkWidget *widget, GdkEventButton *event,
10142 Compose *compose = (Compose *)data;
10143 GtkTreeSelection *attach_selection;
10144 gint attach_nr_selected;
10147 if (!event) return FALSE;
10149 if (event->button == 3) {
10150 attach_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
10151 attach_nr_selected = gtk_tree_selection_count_selected_rows(attach_selection);
10153 /* If no rows, or just one row is selected, right-click should
10154 * open menu relevant to the row being right-clicked on. We
10155 * achieve that by selecting the clicked row first. If more
10156 * than one row is selected, we shouldn't modify the selection,
10157 * as user may want to remove selected rows (attachments). */
10158 if (attach_nr_selected < 2) {
10159 gtk_tree_selection_unselect_all(attach_selection);
10160 attach_nr_selected = 0;
10161 gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget),
10162 event->x, event->y, &path, NULL, NULL, NULL);
10163 if (path != NULL) {
10164 gtk_tree_selection_select_path(attach_selection, path);
10165 gtk_tree_path_free(path);
10166 attach_nr_selected++;
10170 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", (attach_nr_selected > 0));
10171 /* Properties menu item makes no sense with more than one row
10172 * selected, the properties dialog can only edit one attachment. */
10173 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", (attach_nr_selected == 1));
10175 gtk_menu_popup(GTK_MENU(compose->popupmenu), NULL, NULL,
10176 NULL, NULL, event->button, event->time);
10183 static gboolean attach_key_pressed(GtkWidget *widget, GdkEventKey *event,
10186 Compose *compose = (Compose *)data;
10188 if (!event) return FALSE;
10190 switch (event->keyval) {
10191 case GDK_KEY_Delete:
10192 compose_attach_remove_selected(NULL, compose);
10198 static void compose_allow_user_actions (Compose *compose, gboolean allow)
10200 toolbar_comp_set_sensitive(compose, allow);
10201 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message", allow);
10202 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", allow);
10204 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", allow);
10206 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", allow);
10207 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools", allow);
10208 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Help", allow);
10210 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), allow);
10214 static void compose_send_cb(GtkAction *action, gpointer data)
10216 Compose *compose = (Compose *)data;
10219 if (compose->exteditor_tag != -1) {
10220 debug_print("ignoring send: external editor still open\n");
10224 if (prefs_common.work_offline &&
10225 !inc_offline_should_override(TRUE,
10226 _("Claws Mail needs network access in order "
10227 "to send this email.")))
10230 if (compose->draft_timeout_tag >= 0) { /* CLAWS: disable draft timeout */
10231 g_source_remove(compose->draft_timeout_tag);
10232 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
10235 compose_send(compose);
10238 static void compose_send_later_cb(GtkAction *action, gpointer data)
10240 Compose *compose = (Compose *)data;
10241 ComposeQueueResult val;
10244 compose_allow_user_actions(compose, FALSE);
10245 val = compose_queue_sub(compose, NULL, NULL, NULL, TRUE, TRUE);
10246 compose_allow_user_actions(compose, TRUE);
10249 if (val == COMPOSE_QUEUE_SUCCESS) {
10250 compose_close(compose);
10252 _display_queue_error(val);
10255 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
10258 #define DRAFTED_AT_EXIT "drafted_at_exit"
10259 static void compose_register_draft(MsgInfo *info)
10261 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10262 DRAFTED_AT_EXIT, NULL);
10263 FILE *fp = claws_fopen(filepath, "ab");
10266 fprintf(fp, "%s\t%d\n", folder_item_get_identifier(info->folder),
10274 gboolean compose_draft (gpointer data, guint action)
10276 Compose *compose = (Compose *)data;
10281 MsgFlags flag = {0, 0};
10282 static gboolean lock = FALSE;
10283 MsgInfo *newmsginfo;
10285 gboolean target_locked = FALSE;
10286 gboolean err = FALSE;
10288 if (lock) return FALSE;
10290 if (compose->sending)
10293 draft = account_get_special_folder(compose->account, F_DRAFT);
10294 cm_return_val_if_fail(draft != NULL, FALSE);
10296 if (!g_mutex_trylock(compose->mutex)) {
10297 /* we don't want to lock the mutex once it's available,
10298 * because as the only other part of compose.c locking
10299 * it is compose_close - which means once unlocked,
10300 * the compose struct will be freed */
10301 debug_print("couldn't lock mutex, probably sending\n");
10307 tmp = g_strdup_printf("%s%cdraft.%p", get_tmp_dir(),
10308 G_DIR_SEPARATOR, compose);
10309 if ((fp = claws_fopen(tmp, "wb")) == NULL) {
10310 FILE_OP_ERROR(tmp, "claws_fopen");
10314 /* chmod for security */
10315 if (change_file_mode_rw(fp, tmp) < 0) {
10316 FILE_OP_ERROR(tmp, "chmod");
10317 g_warning("can't change file mode");
10320 /* Save draft infos */
10321 err |= (fprintf(fp, "X-Claws-Account-Id:%d\n", compose->account->account_id) < 0);
10322 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
10324 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
10325 gchar *savefolderid;
10327 savefolderid = compose_get_save_to(compose);
10328 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
10329 g_free(savefolderid);
10331 if (compose->return_receipt) {
10332 err |= (fprintf(fp, "RRCPT:1\n") < 0);
10334 if (compose->privacy_system) {
10335 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
10336 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
10337 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
10340 /* Message-ID of message replying to */
10341 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
10342 gchar *folderid = NULL;
10344 if (compose->replyinfo->folder)
10345 folderid = folder_item_get_identifier(compose->replyinfo->folder);
10346 if (folderid == NULL)
10347 folderid = g_strdup("NULL");
10349 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
10352 /* Message-ID of message forwarding to */
10353 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
10354 gchar *folderid = NULL;
10356 if (compose->fwdinfo->folder)
10357 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
10358 if (folderid == NULL)
10359 folderid = g_strdup("NULL");
10361 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
10365 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
10366 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
10368 sheaders = compose_get_manual_headers_info(compose);
10369 err |= (fprintf(fp, "X-Claws-Manual-Headers:%s\n", sheaders) < 0);
10372 /* end of headers */
10373 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
10380 if (compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_STORE, action != COMPOSE_AUTO_SAVE) < 0) {
10384 if (claws_safe_fclose(fp) == EOF) {
10388 flag.perm_flags = MSG_NEW|MSG_UNREAD;
10389 if (compose->targetinfo) {
10390 target_locked = MSG_IS_LOCKED(compose->targetinfo->flags);
10392 flag.perm_flags |= MSG_LOCKED;
10394 flag.tmp_flags = MSG_DRAFT;
10396 folder_item_scan(draft);
10397 if ((msgnum = folder_item_add_msg(draft, tmp, &flag, TRUE)) < 0) {
10398 MsgInfo *tmpinfo = NULL;
10399 debug_print("didn't get msgnum after adding draft [%s]\n", compose->msgid?compose->msgid:"no msgid");
10400 if (compose->msgid) {
10401 tmpinfo = folder_item_get_msginfo_by_msgid(draft, compose->msgid);
10404 msgnum = tmpinfo->msgnum;
10405 procmsg_msginfo_free(&tmpinfo);
10406 debug_print("got draft msgnum %d from scanning\n", msgnum);
10408 debug_print("didn't get draft msgnum after scanning\n");
10411 debug_print("got draft msgnum %d from adding\n", msgnum);
10417 if (action != COMPOSE_AUTO_SAVE) {
10418 if (action != COMPOSE_DRAFT_FOR_EXIT)
10419 alertpanel_error(_("Could not save draft."));
10422 gtkut_window_popup(compose->window);
10423 val = alertpanel_full(_("Could not save draft"),
10424 _("Could not save draft.\n"
10425 "Do you want to cancel exit or discard this email?"),
10426 _("_Cancel exit"), _("_Discard email"), NULL, ALERTFOCUS_FIRST,
10427 FALSE, NULL, ALERT_QUESTION);
10428 if (val == G_ALERTALTERNATE) {
10430 g_mutex_unlock(compose->mutex); /* must be done before closing */
10431 compose_close(compose);
10435 g_mutex_unlock(compose->mutex); /* must be done before closing */
10444 if (compose->mode == COMPOSE_REEDIT) {
10445 compose_remove_reedit_target(compose, TRUE);
10448 newmsginfo = folder_item_get_msginfo(draft, msgnum);
10451 procmsg_msginfo_unset_flags(newmsginfo, ~0, ~0);
10453 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD|MSG_LOCKED, MSG_DRAFT);
10455 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD, MSG_DRAFT);
10456 if (compose_use_attach(compose) && action != COMPOSE_AUTO_SAVE)
10457 procmsg_msginfo_set_flags(newmsginfo, 0,
10458 MSG_HAS_ATTACHMENT);
10460 if (action == COMPOSE_DRAFT_FOR_EXIT) {
10461 compose_register_draft(newmsginfo);
10463 procmsg_msginfo_free(&newmsginfo);
10466 folder_item_scan(draft);
10468 if (action == COMPOSE_QUIT_EDITING || action == COMPOSE_DRAFT_FOR_EXIT) {
10470 g_mutex_unlock(compose->mutex); /* must be done before closing */
10471 compose_close(compose);
10478 GError *error = NULL;
10483 goffset size, mtime;
10485 path = folder_item_fetch_msg(draft, msgnum);
10486 if (path == NULL) {
10487 debug_print("can't fetch %s:%d\n", draft->path, msgnum);
10491 f = g_file_new_for_path(path);
10492 fi = g_file_query_info(f, "standard::size,time::modified",
10493 G_FILE_QUERY_INFO_NONE, NULL, &error);
10494 if (error != NULL) {
10495 debug_print("couldn't query file info for '%s': %s\n",
10496 path, error->message);
10497 g_error_free(error);
10502 size = g_file_info_get_size(fi);
10503 g_file_info_get_modification_time(fi, &tv);
10505 g_object_unref(fi);
10508 if (g_stat(path, &s) < 0) {
10509 FILE_OP_ERROR(path, "stat");
10514 mtime = s.st_mtime;
10518 procmsg_msginfo_free(&(compose->targetinfo));
10519 compose->targetinfo = procmsg_msginfo_new();
10520 compose->targetinfo->msgnum = msgnum;
10521 compose->targetinfo->size = size;
10522 compose->targetinfo->mtime = mtime;
10523 compose->targetinfo->folder = draft;
10525 procmsg_msginfo_set_flags(compose->targetinfo, MSG_LOCKED, 0);
10526 compose->mode = COMPOSE_REEDIT;
10528 if (action == COMPOSE_AUTO_SAVE) {
10529 compose->autosaved_draft = compose->targetinfo;
10531 compose->modified = FALSE;
10532 compose_set_title(compose);
10536 g_mutex_unlock(compose->mutex);
10540 void compose_clear_exit_drafts(void)
10542 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10543 DRAFTED_AT_EXIT, NULL);
10544 if (is_file_exist(filepath))
10545 claws_unlink(filepath);
10550 void compose_reopen_exit_drafts(void)
10552 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10553 DRAFTED_AT_EXIT, NULL);
10554 FILE *fp = claws_fopen(filepath, "rb");
10558 while (claws_fgets(buf, sizeof(buf), fp)) {
10559 gchar **parts = g_strsplit(buf, "\t", 2);
10560 const gchar *folder = parts[0];
10561 int msgnum = parts[1] ? atoi(parts[1]):-1;
10563 if (folder && *folder && msgnum > -1) {
10564 FolderItem *item = folder_find_item_from_identifier(folder);
10565 MsgInfo *info = folder_item_get_msginfo(item, msgnum);
10567 compose_reedit(info, FALSE);
10574 compose_clear_exit_drafts();
10577 static void compose_save_cb(GtkAction *action, gpointer data)
10579 Compose *compose = (Compose *)data;
10580 compose_draft(compose, COMPOSE_KEEP_EDITING);
10581 compose->rmode = COMPOSE_REEDIT;
10584 void compose_attach_from_list(Compose *compose, GList *file_list, gboolean free_data)
10586 if (compose && file_list) {
10589 for ( tmp = file_list; tmp; tmp = tmp->next) {
10590 gchar *file = (gchar *) tmp->data;
10591 gchar *utf8_filename = conv_filename_to_utf8(file);
10592 compose_attach_append(compose, file, utf8_filename, NULL, NULL);
10593 compose_changed_cb(NULL, compose);
10598 g_free(utf8_filename);
10603 static void compose_attach_cb(GtkAction *action, gpointer data)
10605 Compose *compose = (Compose *)data;
10608 if (compose->redirect_filename != NULL)
10611 /* Set focus_window properly, in case we were called via popup menu,
10612 * which unsets it (via focus_out_event callback on compose window). */
10613 manage_window_focus_in(compose->window, NULL, NULL);
10615 file_list = filesel_select_multiple_files_open(_("Select file"), NULL);
10618 compose_attach_from_list(compose, file_list, TRUE);
10619 g_list_free(file_list);
10623 static void compose_insert_file_cb(GtkAction *action, gpointer data)
10625 Compose *compose = (Compose *)data;
10627 gint files_inserted = 0;
10629 file_list = filesel_select_multiple_files_open(_("Select file"), NULL);
10634 for ( tmp = file_list; tmp; tmp = tmp->next) {
10635 gchar *file = (gchar *) tmp->data;
10636 gchar *filedup = g_strdup(file);
10637 gchar *shortfile = g_path_get_basename(filedup);
10638 ComposeInsertResult res;
10639 /* insert the file if the file is short or if the user confirmed that
10640 he/she wants to insert the large file */
10641 res = compose_insert_file(compose, file);
10642 if (res == COMPOSE_INSERT_READ_ERROR) {
10643 alertpanel_error(_("File '%s' could not be read."), shortfile);
10644 } else if (res == COMPOSE_INSERT_INVALID_CHARACTER) {
10645 alertpanel_error(_("File '%s' contained invalid characters\n"
10646 "for the current encoding, insertion may be incorrect."),
10648 } else if (res == COMPOSE_INSERT_SUCCESS)
10655 g_list_free(file_list);
10659 if (files_inserted > 0 && compose->gtkaspell &&
10660 compose->gtkaspell->check_while_typing)
10661 gtkaspell_highlight_all(compose->gtkaspell);
10665 static void compose_insert_sig_cb(GtkAction *action, gpointer data)
10667 Compose *compose = (Compose *)data;
10669 compose_insert_sig(compose, FALSE);
10672 static void compose_replace_sig_cb(GtkAction *action, gpointer data)
10674 Compose *compose = (Compose *)data;
10676 compose_insert_sig(compose, TRUE);
10679 static gint compose_delete_cb(GtkWidget *widget, GdkEventAny *event,
10683 Compose *compose = (Compose *)data;
10685 gtkut_widget_get_uposition(widget, &x, &y);
10686 if (!compose->batch) {
10687 prefs_common.compose_x = x;
10688 prefs_common.compose_y = y;
10690 if (compose->sending || compose->updating)
10692 compose_close_cb(NULL, compose);
10696 void compose_close_toolbar(Compose *compose)
10698 compose_close_cb(NULL, compose);
10701 static void compose_close_cb(GtkAction *action, gpointer data)
10703 Compose *compose = (Compose *)data;
10707 if (compose->exteditor_tag != -1) {
10708 if (!compose_ext_editor_kill(compose))
10713 if (compose->modified) {
10714 gboolean reedit = (compose->rmode == COMPOSE_REEDIT);
10715 if (!g_mutex_trylock(compose->mutex)) {
10716 /* we don't want to lock the mutex once it's available,
10717 * because as the only other part of compose.c locking
10718 * it is compose_close - which means once unlocked,
10719 * the compose struct will be freed */
10720 debug_print("couldn't lock mutex, probably sending\n");
10723 if (!reedit || compose->folder->stype == F_DRAFT) {
10724 val = alertpanel(_("Discard message"),
10725 _("This message has been modified. Discard it?"),
10726 _("_Discard"), _("_Save to Drafts"), GTK_STOCK_CANCEL,
10729 val = alertpanel(_("Save changes"),
10730 _("This message has been modified. Save the latest changes?"),
10731 _("_Don't save"), _("_Save to Drafts"), GTK_STOCK_CANCEL,
10732 ALERTFOCUS_SECOND);
10734 g_mutex_unlock(compose->mutex);
10736 case G_ALERTDEFAULT:
10737 if (compose_can_autosave(compose) && !reedit)
10738 compose_remove_draft(compose);
10740 case G_ALERTALTERNATE:
10741 compose_draft(data, COMPOSE_QUIT_EDITING);
10748 compose_close(compose);
10751 static void compose_print_cb(GtkAction *action, gpointer data)
10753 Compose *compose = (Compose *) data;
10755 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
10756 if (compose->targetinfo)
10757 messageview_print(compose->targetinfo, FALSE, -1, -1, 0);
10760 static void compose_set_encoding_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
10762 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
10763 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
10764 Compose *compose = (Compose *) data;
10767 compose->out_encoding = (CharSet)value;
10770 static void compose_address_cb(GtkAction *action, gpointer data)
10772 Compose *compose = (Compose *)data;
10774 #ifndef USE_ALT_ADDRBOOK
10775 addressbook_open(compose);
10777 GError* error = NULL;
10778 addressbook_connect_signals(compose);
10779 addressbook_dbus_open(TRUE, &error);
10781 g_warning("%s", error->message);
10782 g_error_free(error);
10787 static void about_show_cb(GtkAction *action, gpointer data)
10792 static void compose_template_activate_cb(GtkWidget *widget, gpointer data)
10794 Compose *compose = (Compose *)data;
10799 tmpl = g_object_get_data(G_OBJECT(widget), "template");
10800 cm_return_if_fail(tmpl != NULL);
10802 msg = g_strdup_printf(_("Do you want to apply the template '%s'?"),
10804 val = alertpanel(_("Apply template"), msg,
10805 _("_Replace"), _("_Insert"), GTK_STOCK_CANCEL, ALERTFOCUS_FIRST);
10808 if (val == G_ALERTDEFAULT)
10809 compose_template_apply(compose, tmpl, TRUE);
10810 else if (val == G_ALERTALTERNATE)
10811 compose_template_apply(compose, tmpl, FALSE);
10814 static void compose_ext_editor_cb(GtkAction *action, gpointer data)
10816 Compose *compose = (Compose *)data;
10819 if (compose->exteditor_tag != -1) {
10820 debug_print("ignoring open external editor: external editor still open\n");
10824 compose_exec_ext_editor(compose);
10827 static void compose_undo_cb(GtkAction *action, gpointer data)
10829 Compose *compose = (Compose *)data;
10830 gboolean prev_autowrap = compose->autowrap;
10832 compose->autowrap = FALSE;
10833 undo_undo(compose->undostruct);
10834 compose->autowrap = prev_autowrap;
10837 static void compose_redo_cb(GtkAction *action, gpointer data)
10839 Compose *compose = (Compose *)data;
10840 gboolean prev_autowrap = compose->autowrap;
10842 compose->autowrap = FALSE;
10843 undo_redo(compose->undostruct);
10844 compose->autowrap = prev_autowrap;
10847 static void entry_cut_clipboard(GtkWidget *entry)
10849 if (GTK_IS_EDITABLE(entry))
10850 gtk_editable_cut_clipboard (GTK_EDITABLE(entry));
10851 else if (GTK_IS_TEXT_VIEW(entry))
10852 gtk_text_buffer_cut_clipboard(
10853 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10854 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
10858 static void entry_copy_clipboard(GtkWidget *entry)
10860 if (GTK_IS_EDITABLE(entry))
10861 gtk_editable_copy_clipboard (GTK_EDITABLE(entry));
10862 else if (GTK_IS_TEXT_VIEW(entry))
10863 gtk_text_buffer_copy_clipboard(
10864 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10865 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
10868 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry,
10869 gboolean wrap, GdkAtom clip, GtkTextIter *insert_place)
10871 if (GTK_IS_TEXT_VIEW(entry)) {
10872 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10873 GtkTextMark *mark_start = gtk_text_buffer_get_insert(buffer);
10874 GtkTextIter start_iter, end_iter;
10876 gchar *contents = gtk_clipboard_wait_for_text(gtk_clipboard_get(clip));
10878 if (contents == NULL)
10881 /* we shouldn't delete the selection when middle-click-pasting, or we
10882 * can't mid-click-paste our own selection */
10883 if (clip != GDK_SELECTION_PRIMARY) {
10884 undo_paste_clipboard(GTK_TEXT_VIEW(compose->text), compose->undostruct);
10885 gtk_text_buffer_delete_selection(buffer, FALSE, TRUE);
10888 if (insert_place == NULL) {
10889 /* if insert_place isn't specified, insert at the cursor.
10890 * used for Ctrl-V pasting */
10891 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10892 start = gtk_text_iter_get_offset(&start_iter);
10893 gtk_text_buffer_insert(buffer, &start_iter, contents, strlen(contents));
10895 /* if insert_place is specified, paste here.
10896 * used for mid-click-pasting */
10897 start = gtk_text_iter_get_offset(insert_place);
10898 gtk_text_buffer_insert(buffer, insert_place, contents, strlen(contents));
10899 if (prefs_common.primary_paste_unselects)
10900 gtk_text_buffer_select_range(buffer, insert_place, insert_place);
10904 /* paste unwrapped: mark the paste so it's not wrapped later */
10905 end = start + strlen(contents);
10906 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, start);
10907 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, end);
10908 gtk_text_buffer_apply_tag_by_name(buffer, "no_wrap", &start_iter, &end_iter);
10909 } else if (wrap && clip == GDK_SELECTION_PRIMARY) {
10910 /* rewrap paragraph now (after a mid-click-paste) */
10911 mark_start = gtk_text_buffer_get_insert(buffer);
10912 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10913 gtk_text_iter_backward_char(&start_iter);
10914 compose_beautify_paragraph(compose, &start_iter, TRUE);
10916 } else if (GTK_IS_EDITABLE(entry))
10917 gtk_editable_paste_clipboard (GTK_EDITABLE(entry));
10919 compose->modified = TRUE;
10922 static void entry_allsel(GtkWidget *entry)
10924 if (GTK_IS_EDITABLE(entry))
10925 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
10926 else if (GTK_IS_TEXT_VIEW(entry)) {
10927 GtkTextIter startiter, enditer;
10928 GtkTextBuffer *textbuf;
10930 textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10931 gtk_text_buffer_get_start_iter(textbuf, &startiter);
10932 gtk_text_buffer_get_end_iter(textbuf, &enditer);
10934 gtk_text_buffer_move_mark_by_name(textbuf,
10935 "selection_bound", &startiter);
10936 gtk_text_buffer_move_mark_by_name(textbuf,
10937 "insert", &enditer);
10941 static void compose_cut_cb(GtkAction *action, gpointer data)
10943 Compose *compose = (Compose *)data;
10944 if (compose->focused_editable
10945 #ifndef GENERIC_UMPC
10946 && gtk_widget_has_focus(compose->focused_editable)
10949 entry_cut_clipboard(compose->focused_editable);
10952 static void compose_copy_cb(GtkAction *action, gpointer data)
10954 Compose *compose = (Compose *)data;
10955 if (compose->focused_editable
10956 #ifndef GENERIC_UMPC
10957 && gtk_widget_has_focus(compose->focused_editable)
10960 entry_copy_clipboard(compose->focused_editable);
10963 static void compose_paste_cb(GtkAction *action, gpointer data)
10965 Compose *compose = (Compose *)data;
10966 gint prev_autowrap;
10967 GtkTextBuffer *buffer;
10969 if (compose->focused_editable
10970 #ifndef GENERIC_UMPC
10971 && gtk_widget_has_focus(compose->focused_editable)
10974 entry_paste_clipboard(compose, compose->focused_editable,
10975 prefs_common.linewrap_pastes,
10976 GDK_SELECTION_CLIPBOARD, NULL);
10981 #ifndef GENERIC_UMPC
10982 gtk_widget_has_focus(compose->text) &&
10984 compose->gtkaspell &&
10985 compose->gtkaspell->check_while_typing)
10986 gtkaspell_highlight_all(compose->gtkaspell);
10990 static void compose_paste_as_quote_cb(GtkAction *action, gpointer data)
10992 Compose *compose = (Compose *)data;
10993 gint wrap_quote = prefs_common.linewrap_quote;
10994 if (compose->focused_editable
10995 #ifndef GENERIC_UMPC
10996 && gtk_widget_has_focus(compose->focused_editable)
10999 /* let text_insert() (called directly or at a later time
11000 * after the gtk_editable_paste_clipboard) know that
11001 * text is to be inserted as a quotation. implemented
11002 * by using a simple refcount... */
11003 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data(
11004 G_OBJECT(compose->focused_editable),
11005 "paste_as_quotation"));
11006 g_object_set_data(G_OBJECT(compose->focused_editable),
11007 "paste_as_quotation",
11008 GINT_TO_POINTER(paste_as_quotation + 1));
11009 prefs_common.linewrap_quote = prefs_common.linewrap_pastes;
11010 entry_paste_clipboard(compose, compose->focused_editable,
11011 prefs_common.linewrap_pastes,
11012 GDK_SELECTION_CLIPBOARD, NULL);
11013 prefs_common.linewrap_quote = wrap_quote;
11017 static void compose_paste_no_wrap_cb(GtkAction *action, gpointer data)
11019 Compose *compose = (Compose *)data;
11020 gint prev_autowrap;
11021 GtkTextBuffer *buffer;
11023 if (compose->focused_editable
11024 #ifndef GENERIC_UMPC
11025 && gtk_widget_has_focus(compose->focused_editable)
11028 entry_paste_clipboard(compose, compose->focused_editable, FALSE,
11029 GDK_SELECTION_CLIPBOARD, NULL);
11034 #ifndef GENERIC_UMPC
11035 gtk_widget_has_focus(compose->text) &&
11037 compose->gtkaspell &&
11038 compose->gtkaspell->check_while_typing)
11039 gtkaspell_highlight_all(compose->gtkaspell);
11043 static void compose_paste_wrap_cb(GtkAction *action, gpointer data)
11045 Compose *compose = (Compose *)data;
11046 gint prev_autowrap;
11047 GtkTextBuffer *buffer;
11049 if (compose->focused_editable
11050 #ifndef GENERIC_UMPC
11051 && gtk_widget_has_focus(compose->focused_editable)
11054 entry_paste_clipboard(compose, compose->focused_editable, TRUE,
11055 GDK_SELECTION_CLIPBOARD, NULL);
11060 #ifndef GENERIC_UMPC
11061 gtk_widget_has_focus(compose->text) &&
11063 compose->gtkaspell &&
11064 compose->gtkaspell->check_while_typing)
11065 gtkaspell_highlight_all(compose->gtkaspell);
11069 static void compose_allsel_cb(GtkAction *action, gpointer data)
11071 Compose *compose = (Compose *)data;
11072 if (compose->focused_editable
11073 #ifndef GENERIC_UMPC
11074 && gtk_widget_has_focus(compose->focused_editable)
11077 entry_allsel(compose->focused_editable);
11080 static void textview_move_beginning_of_line (GtkTextView *text)
11082 GtkTextBuffer *buffer;
11086 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11088 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11089 mark = gtk_text_buffer_get_insert(buffer);
11090 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11091 gtk_text_iter_set_line_offset(&ins, 0);
11092 gtk_text_buffer_place_cursor(buffer, &ins);
11095 static void textview_move_forward_character (GtkTextView *text)
11097 GtkTextBuffer *buffer;
11101 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11103 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11104 mark = gtk_text_buffer_get_insert(buffer);
11105 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11106 if (gtk_text_iter_forward_cursor_position(&ins))
11107 gtk_text_buffer_place_cursor(buffer, &ins);
11110 static void textview_move_backward_character (GtkTextView *text)
11112 GtkTextBuffer *buffer;
11116 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11118 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11119 mark = gtk_text_buffer_get_insert(buffer);
11120 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11121 if (gtk_text_iter_backward_cursor_position(&ins))
11122 gtk_text_buffer_place_cursor(buffer, &ins);
11125 static void textview_move_forward_word (GtkTextView *text)
11127 GtkTextBuffer *buffer;
11132 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11134 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11135 mark = gtk_text_buffer_get_insert(buffer);
11136 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11137 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
11138 if (gtk_text_iter_forward_word_ends(&ins, count)) {
11139 gtk_text_iter_backward_word_start(&ins);
11140 gtk_text_buffer_place_cursor(buffer, &ins);
11144 static void textview_move_backward_word (GtkTextView *text)
11146 GtkTextBuffer *buffer;
11150 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11152 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11153 mark = gtk_text_buffer_get_insert(buffer);
11154 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11155 if (gtk_text_iter_backward_word_starts(&ins, 1))
11156 gtk_text_buffer_place_cursor(buffer, &ins);
11159 static void textview_move_end_of_line (GtkTextView *text)
11161 GtkTextBuffer *buffer;
11165 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11167 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11168 mark = gtk_text_buffer_get_insert(buffer);
11169 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11170 if (gtk_text_iter_forward_to_line_end(&ins))
11171 gtk_text_buffer_place_cursor(buffer, &ins);
11174 static void textview_move_next_line (GtkTextView *text)
11176 GtkTextBuffer *buffer;
11181 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11183 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11184 mark = gtk_text_buffer_get_insert(buffer);
11185 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11186 offset = gtk_text_iter_get_line_offset(&ins);
11187 if (gtk_text_iter_forward_line(&ins)) {
11188 gtk_text_iter_set_line_offset(&ins, offset);
11189 gtk_text_buffer_place_cursor(buffer, &ins);
11193 static void textview_move_previous_line (GtkTextView *text)
11195 GtkTextBuffer *buffer;
11200 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11202 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11203 mark = gtk_text_buffer_get_insert(buffer);
11204 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11205 offset = gtk_text_iter_get_line_offset(&ins);
11206 if (gtk_text_iter_backward_line(&ins)) {
11207 gtk_text_iter_set_line_offset(&ins, offset);
11208 gtk_text_buffer_place_cursor(buffer, &ins);
11212 static void textview_delete_forward_character (GtkTextView *text)
11214 GtkTextBuffer *buffer;
11216 GtkTextIter ins, end_iter;
11218 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11220 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11221 mark = gtk_text_buffer_get_insert(buffer);
11222 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11224 if (gtk_text_iter_forward_char(&end_iter)) {
11225 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11229 static void textview_delete_backward_character (GtkTextView *text)
11231 GtkTextBuffer *buffer;
11233 GtkTextIter ins, end_iter;
11235 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11237 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11238 mark = gtk_text_buffer_get_insert(buffer);
11239 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11241 if (gtk_text_iter_backward_char(&end_iter)) {
11242 gtk_text_buffer_delete(buffer, &end_iter, &ins);
11246 static void textview_delete_forward_word (GtkTextView *text)
11248 GtkTextBuffer *buffer;
11250 GtkTextIter ins, end_iter;
11252 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11254 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11255 mark = gtk_text_buffer_get_insert(buffer);
11256 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11258 if (gtk_text_iter_forward_word_end(&end_iter)) {
11259 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11263 static void textview_delete_backward_word (GtkTextView *text)
11265 GtkTextBuffer *buffer;
11267 GtkTextIter ins, end_iter;
11269 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11271 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11272 mark = gtk_text_buffer_get_insert(buffer);
11273 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11275 if (gtk_text_iter_backward_word_start(&end_iter)) {
11276 gtk_text_buffer_delete(buffer, &end_iter, &ins);
11280 static void textview_delete_line (GtkTextView *text)
11282 GtkTextBuffer *buffer;
11284 GtkTextIter ins, start_iter, end_iter;
11286 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11288 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11289 mark = gtk_text_buffer_get_insert(buffer);
11290 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11293 gtk_text_iter_set_line_offset(&start_iter, 0);
11296 if (gtk_text_iter_ends_line(&end_iter)){
11297 if (!gtk_text_iter_forward_char(&end_iter))
11298 gtk_text_iter_backward_char(&start_iter);
11301 gtk_text_iter_forward_to_line_end(&end_iter);
11302 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
11305 static void textview_delete_to_line_end (GtkTextView *text)
11307 GtkTextBuffer *buffer;
11309 GtkTextIter ins, end_iter;
11311 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11313 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11314 mark = gtk_text_buffer_get_insert(buffer);
11315 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11317 if (gtk_text_iter_ends_line(&end_iter))
11318 gtk_text_iter_forward_char(&end_iter);
11320 gtk_text_iter_forward_to_line_end(&end_iter);
11321 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11324 #define DO_ACTION(name, act) { \
11325 if(!strcmp(name, a_name)) { \
11329 static ComposeCallAdvancedAction compose_call_advanced_action_from_path(GtkAction *action)
11331 const gchar *a_name = gtk_action_get_name(action);
11332 DO_ACTION("Edit/Advanced/BackChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER);
11333 DO_ACTION("Edit/Advanced/ForwChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER);
11334 DO_ACTION("Edit/Advanced/BackWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD);
11335 DO_ACTION("Edit/Advanced/ForwWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD);
11336 DO_ACTION("Edit/Advanced/BegLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE);
11337 DO_ACTION("Edit/Advanced/EndLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE);
11338 DO_ACTION("Edit/Advanced/PrevLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE);
11339 DO_ACTION("Edit/Advanced/NextLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE);
11340 DO_ACTION("Edit/Advanced/DelBackChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER);
11341 DO_ACTION("Edit/Advanced/DelForwChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER);
11342 DO_ACTION("Edit/Advanced/DelBackWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD);
11343 DO_ACTION("Edit/Advanced/DelForwWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD);
11344 DO_ACTION("Edit/Advanced/DelLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE);
11345 DO_ACTION("Edit/Advanced/DelEndLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END);
11346 return COMPOSE_CALL_ADVANCED_ACTION_UNDEFINED;
11349 static void compose_advanced_action_cb(GtkAction *gaction, gpointer data)
11351 Compose *compose = (Compose *)data;
11352 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11353 ComposeCallAdvancedAction action = COMPOSE_CALL_ADVANCED_ACTION_UNDEFINED;
11355 action = compose_call_advanced_action_from_path(gaction);
11358 void (*do_action) (GtkTextView *text);
11359 } action_table[] = {
11360 {textview_move_beginning_of_line},
11361 {textview_move_forward_character},
11362 {textview_move_backward_character},
11363 {textview_move_forward_word},
11364 {textview_move_backward_word},
11365 {textview_move_end_of_line},
11366 {textview_move_next_line},
11367 {textview_move_previous_line},
11368 {textview_delete_forward_character},
11369 {textview_delete_backward_character},
11370 {textview_delete_forward_word},
11371 {textview_delete_backward_word},
11372 {textview_delete_line},
11373 {textview_delete_to_line_end}
11376 if (!gtk_widget_has_focus(GTK_WIDGET(text))) return;
11378 if (action >= COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE &&
11379 action <= COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END) {
11380 if (action_table[action].do_action)
11381 action_table[action].do_action(text);
11383 g_warning("Not implemented yet.");
11387 static void compose_grab_focus_cb(GtkWidget *widget, Compose *compose)
11389 GtkAllocation allocation;
11393 if (GTK_IS_EDITABLE(widget)) {
11394 str = gtk_editable_get_chars(GTK_EDITABLE(widget), 0, -1);
11395 gtk_editable_set_position(GTK_EDITABLE(widget),
11398 if ((parent = gtk_widget_get_parent(widget))
11399 && (parent = gtk_widget_get_parent(parent))
11400 && (parent = gtk_widget_get_parent(parent))) {
11401 if (GTK_IS_SCROLLED_WINDOW(parent)) {
11402 gtk_widget_get_allocation(widget, &allocation);
11403 gint y = allocation.y;
11404 gint height = allocation.height;
11405 GtkAdjustment *shown = gtk_scrolled_window_get_vadjustment
11406 (GTK_SCROLLED_WINDOW(parent));
11408 gfloat value = gtk_adjustment_get_value(shown);
11409 gfloat upper = gtk_adjustment_get_upper(shown);
11410 gfloat page_size = gtk_adjustment_get_page_size(shown);
11411 if (y < (int)value) {
11412 gtk_adjustment_set_value(shown, y - 1);
11414 if ((y + height) > ((int)value + (int)page_size)) {
11415 if ((y - height - 1) < ((int)upper - (int)page_size)) {
11416 gtk_adjustment_set_value(shown,
11417 y + height - (int)page_size - 1);
11419 gtk_adjustment_set_value(shown,
11420 (int)upper - (int)page_size - 1);
11427 if (GTK_IS_EDITABLE(widget) || GTK_IS_TEXT_VIEW(widget))
11428 compose->focused_editable = widget;
11430 #ifdef GENERIC_UMPC
11431 if (GTK_IS_TEXT_VIEW(widget)
11432 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->edit_vbox) {
11433 g_object_ref(compose->notebook);
11434 g_object_ref(compose->edit_vbox);
11435 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
11436 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
11437 gtk_paned_add1(GTK_PANED(compose->paned), compose->edit_vbox);
11438 gtk_paned_add2(GTK_PANED(compose->paned), compose->notebook);
11439 g_object_unref(compose->notebook);
11440 g_object_unref(compose->edit_vbox);
11441 g_signal_handlers_block_by_func(G_OBJECT(widget),
11442 G_CALLBACK(compose_grab_focus_cb),
11444 gtk_widget_grab_focus(widget);
11445 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
11446 G_CALLBACK(compose_grab_focus_cb),
11448 } else if (!GTK_IS_TEXT_VIEW(widget)
11449 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->notebook) {
11450 g_object_ref(compose->notebook);
11451 g_object_ref(compose->edit_vbox);
11452 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
11453 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
11454 gtk_paned_add1(GTK_PANED(compose->paned), compose->notebook);
11455 gtk_paned_add2(GTK_PANED(compose->paned), compose->edit_vbox);
11456 g_object_unref(compose->notebook);
11457 g_object_unref(compose->edit_vbox);
11458 g_signal_handlers_block_by_func(G_OBJECT(widget),
11459 G_CALLBACK(compose_grab_focus_cb),
11461 gtk_widget_grab_focus(widget);
11462 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
11463 G_CALLBACK(compose_grab_focus_cb),
11469 static void compose_changed_cb(GtkTextBuffer *textbuf, Compose *compose)
11471 compose->modified = TRUE;
11472 /* compose_beautify_paragraph(compose, NULL, TRUE); */
11473 #ifndef GENERIC_UMPC
11474 compose_set_title(compose);
11478 static void compose_wrap_cb(GtkAction *action, gpointer data)
11480 Compose *compose = (Compose *)data;
11481 compose_beautify_paragraph(compose, NULL, TRUE);
11484 static void compose_wrap_all_cb(GtkAction *action, gpointer data)
11486 Compose *compose = (Compose *)data;
11487 compose_wrap_all_full(compose, TRUE);
11490 static void compose_find_cb(GtkAction *action, gpointer data)
11492 Compose *compose = (Compose *)data;
11494 message_search_compose(compose);
11497 static void compose_toggle_autowrap_cb(GtkToggleAction *action,
11500 Compose *compose = (Compose *)data;
11501 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11502 if (compose->autowrap)
11503 compose_wrap_all_full(compose, TRUE);
11504 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11507 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
11510 Compose *compose = (Compose *)data;
11511 compose->autoindent = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11514 static void compose_toggle_sign_cb(GtkToggleAction *action, gpointer data)
11516 Compose *compose = (Compose *)data;
11518 compose->use_signing = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11519 gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(compose->toolbar->privacy_sign_btn), compose->use_signing);
11522 static void compose_toggle_encrypt_cb(GtkToggleAction *action, gpointer data)
11524 Compose *compose = (Compose *)data;
11526 compose->use_encryption = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11527 gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(compose->toolbar->privacy_encrypt_btn), compose->use_encryption);
11530 static void compose_activate_privacy_system(Compose *compose, PrefsAccount *account, gboolean warn)
11532 g_free(compose->privacy_system);
11533 g_free(compose->encdata);
11535 compose->privacy_system = g_strdup(account->default_privacy_system);
11536 compose_update_privacy_system_menu_item(compose, warn);
11539 static void compose_apply_folder_privacy_settings(Compose *compose, FolderItem *folder_item)
11541 if (folder_item != NULL) {
11542 if (folder_item->prefs->always_sign != SIGN_OR_ENCRYPT_DEFAULT) {
11543 compose_use_signing(compose,
11544 (folder_item->prefs->always_sign == SIGN_OR_ENCRYPT_ALWAYS) ? TRUE : FALSE);
11546 if (folder_item->prefs->always_encrypt != SIGN_OR_ENCRYPT_DEFAULT) {
11547 compose_use_encryption(compose,
11548 (folder_item->prefs->always_encrypt == SIGN_OR_ENCRYPT_ALWAYS) ? TRUE : FALSE);
11553 static void compose_toggle_ruler_cb(GtkToggleAction *action, gpointer data)
11555 Compose *compose = (Compose *)data;
11557 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action))) {
11558 gtk_widget_show(compose->ruler_hbox);
11559 prefs_common.show_ruler = TRUE;
11561 gtk_widget_hide(compose->ruler_hbox);
11562 gtk_widget_queue_resize(compose->edit_vbox);
11563 prefs_common.show_ruler = FALSE;
11567 static void compose_attach_drag_received_cb (GtkWidget *widget,
11568 GdkDragContext *context,
11571 GtkSelectionData *data,
11574 gpointer user_data)
11576 Compose *compose = (Compose *)user_data;
11580 type = gtk_selection_data_get_data_type(data);
11581 if ((gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list"))
11582 && gtk_drag_get_source_widget(context) !=
11583 summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
11584 list = uri_list_extract_filenames(
11585 (const gchar *)gtk_selection_data_get_data(data));
11586 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11587 gchar *utf8_filename = conv_filename_to_utf8((const gchar *)tmp->data);
11588 compose_attach_append
11589 (compose, (const gchar *)tmp->data,
11590 utf8_filename, NULL, NULL);
11591 g_free(utf8_filename);
11594 compose_changed_cb(NULL, compose);
11595 list_free_strings_full(list);
11596 } else if (gtk_drag_get_source_widget(context)
11597 == summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
11598 /* comes from our summaryview */
11599 SummaryView * summaryview = NULL;
11600 GSList * list = NULL, *cur = NULL;
11602 if (mainwindow_get_mainwindow())
11603 summaryview = mainwindow_get_mainwindow()->summaryview;
11606 list = summary_get_selected_msg_list(summaryview);
11608 for (cur = list; cur; cur = cur->next) {
11609 MsgInfo *msginfo = (MsgInfo *)cur->data;
11610 gchar *file = NULL;
11612 file = procmsg_get_message_file_full(msginfo,
11615 compose_attach_append(compose, (const gchar *)file,
11616 (const gchar *)file, "message/rfc822", NULL);
11620 g_slist_free(list);
11624 static gboolean compose_drag_drop(GtkWidget *widget,
11625 GdkDragContext *drag_context,
11627 guint time, gpointer user_data)
11629 /* not handling this signal makes compose_insert_drag_received_cb
11634 static gboolean completion_set_focus_to_subject
11635 (GtkWidget *widget,
11636 GdkEventKey *event,
11639 cm_return_val_if_fail(compose != NULL, FALSE);
11641 /* make backtab move to subject field */
11642 if(event->keyval == GDK_KEY_ISO_Left_Tab) {
11643 gtk_widget_grab_focus(compose->subject_entry);
11649 static void compose_insert_drag_received_cb (GtkWidget *widget,
11650 GdkDragContext *drag_context,
11653 GtkSelectionData *data,
11656 gpointer user_data)
11658 Compose *compose = (Compose *)user_data;
11664 /* strangely, testing data->type == gdk_atom_intern("text/uri-list", TRUE)
11666 type = gtk_selection_data_get_data_type(data);
11667 if (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list")) {
11668 AlertValue val = G_ALERTDEFAULT;
11669 const gchar* ddata = (const gchar *)gtk_selection_data_get_data(data);
11671 list = uri_list_extract_filenames(ddata);
11672 num_files = g_list_length(list);
11673 if (list == NULL && strstr(ddata, "://")) {
11674 /* Assume a list of no files, and data has ://, is a remote link */
11675 gchar *tmpdata = g_strstrip(g_strdup(ddata));
11676 gchar *tmpfile = get_tmp_file();
11677 str_write_to_file(tmpdata, tmpfile, TRUE);
11679 compose_insert_file(compose, tmpfile);
11680 claws_unlink(tmpfile);
11682 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11683 compose_beautify_paragraph(compose, NULL, TRUE);
11686 switch (prefs_common.compose_dnd_mode) {
11687 case COMPOSE_DND_ASK:
11688 msg = g_strdup_printf(
11690 "Do you want to insert the contents of the file "
11691 "into the message body, or attach it to the email?",
11692 "Do you want to insert the contents of the %d files "
11693 "into the message body, or attach them to the email?",
11696 val = alertpanel_full(_("Insert or attach?"), msg,
11697 GTK_STOCK_CANCEL, _("_Insert"), _("_Attach"),
11699 TRUE, NULL, ALERT_QUESTION);
11702 case COMPOSE_DND_INSERT:
11703 val = G_ALERTALTERNATE;
11705 case COMPOSE_DND_ATTACH:
11706 val = G_ALERTOTHER;
11709 /* unexpected case */
11710 g_warning("error: unexpected compose_dnd_mode option value in compose_insert_drag_received_cb()");
11713 if (val & G_ALERTDISABLE) {
11714 val &= ~G_ALERTDISABLE;
11715 /* remember what action to perform by default, only if we don't click Cancel */
11716 if (val == G_ALERTALTERNATE)
11717 prefs_common.compose_dnd_mode = COMPOSE_DND_INSERT;
11718 else if (val == G_ALERTOTHER)
11719 prefs_common.compose_dnd_mode = COMPOSE_DND_ATTACH;
11722 if (val == G_ALERTDEFAULT || val == G_ALERTCANCEL) {
11723 gtk_drag_finish(drag_context, FALSE, FALSE, time);
11724 list_free_strings_full(list);
11726 } else if (val == G_ALERTOTHER) {
11727 compose_attach_drag_received_cb(widget, drag_context, x, y, data, info, time, user_data);
11728 list_free_strings_full(list);
11732 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11733 compose_insert_file(compose, (const gchar *)tmp->data);
11735 list_free_strings_full(list);
11736 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11741 static void compose_header_drag_received_cb (GtkWidget *widget,
11742 GdkDragContext *drag_context,
11745 GtkSelectionData *data,
11748 gpointer user_data)
11750 GtkEditable *entry = (GtkEditable *)user_data;
11751 const gchar *email = (const gchar *)gtk_selection_data_get_data(data);
11753 /* strangely, testing data->type == gdk_atom_intern("text/plain", TRUE)
11756 if (!strncmp(email, "mailto:", strlen("mailto:"))) {
11757 gchar *decoded=g_new(gchar, strlen(email));
11760 decode_uri(decoded, email + strlen("mailto:")); /* will fit */
11761 gtk_editable_delete_text(entry, 0, -1);
11762 gtk_editable_insert_text(entry, decoded, strlen(decoded), &start);
11763 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11767 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11770 static void compose_toggle_return_receipt_cb(GtkToggleAction *action, gpointer data)
11772 Compose *compose = (Compose *)data;
11774 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11775 compose->return_receipt = TRUE;
11777 compose->return_receipt = FALSE;
11780 static void compose_toggle_remove_refs_cb(GtkToggleAction *action, gpointer data)
11782 Compose *compose = (Compose *)data;
11784 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11785 compose->remove_references = TRUE;
11787 compose->remove_references = FALSE;
11790 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
11791 ComposeHeaderEntry *headerentry)
11793 gtk_entry_set_text(GTK_ENTRY(headerentry->entry), "");
11794 gtk_widget_modify_base(GTK_WIDGET(headerentry->entry), GTK_STATE_NORMAL, NULL);
11795 gtk_widget_modify_text(GTK_WIDGET(headerentry->entry), GTK_STATE_NORMAL, NULL);
11799 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
11800 GdkEventKey *event,
11801 ComposeHeaderEntry *headerentry)
11803 if ((g_slist_length(headerentry->compose->header_list) > 0) &&
11804 ((headerentry->headernum + 1) != headerentry->compose->header_nextrow) &&
11805 !(event->state & GDK_MODIFIER_MASK) &&
11806 (event->keyval == GDK_KEY_BackSpace) &&
11807 (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) == 0)) {
11808 gtk_container_remove
11809 (GTK_CONTAINER(headerentry->compose->header_table),
11810 headerentry->combo);
11811 gtk_container_remove
11812 (GTK_CONTAINER(headerentry->compose->header_table),
11813 headerentry->entry);
11814 headerentry->compose->header_list =
11815 g_slist_remove(headerentry->compose->header_list,
11817 g_free(headerentry);
11818 } else if (event->keyval == GDK_KEY_Tab) {
11819 if (headerentry->compose->header_last == headerentry) {
11820 /* Override default next focus, and give it to subject_entry
11821 * instead of notebook tabs
11823 g_signal_stop_emission_by_name(G_OBJECT(entry), "key-press-event");
11824 gtk_widget_grab_focus(headerentry->compose->subject_entry);
11831 static gboolean scroll_postpone(gpointer data)
11833 Compose *compose = (Compose *)data;
11835 if (compose->batch)
11838 GTK_EVENTS_FLUSH();
11839 compose_show_first_last_header(compose, FALSE);
11843 static void compose_headerentry_changed_cb(GtkWidget *entry,
11844 ComposeHeaderEntry *headerentry)
11846 if (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) != 0) {
11847 compose_create_header_entry(headerentry->compose);
11848 g_signal_handlers_disconnect_matched
11849 (G_OBJECT(entry), G_SIGNAL_MATCH_DATA,
11850 0, 0, NULL, NULL, headerentry);
11852 if (!headerentry->compose->batch)
11853 g_timeout_add(0, scroll_postpone, headerentry->compose);
11857 static gboolean compose_defer_auto_save_draft(Compose *compose)
11859 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
11860 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
11864 static void compose_show_first_last_header(Compose *compose, gboolean show_first)
11866 GtkAdjustment *vadj;
11868 cm_return_if_fail(compose);
11873 cm_return_if_fail(GTK_IS_WIDGET(compose->header_table));
11874 cm_return_if_fail(GTK_IS_VIEWPORT(gtk_widget_get_parent(compose->header_table)));
11875 vadj = gtk_viewport_get_vadjustment(GTK_VIEWPORT(
11876 gtk_widget_get_parent(compose->header_table)));
11877 gtk_adjustment_set_value(vadj, (show_first ?
11878 gtk_adjustment_get_lower(vadj) :
11879 (gtk_adjustment_get_upper(vadj) -
11880 gtk_adjustment_get_page_size(vadj))));
11881 gtk_adjustment_changed(vadj);
11884 static void text_inserted(GtkTextBuffer *buffer, GtkTextIter *iter,
11885 const gchar *text, gint len, Compose *compose)
11887 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data
11888 (G_OBJECT(compose->text), "paste_as_quotation"));
11891 cm_return_if_fail(text != NULL);
11893 g_signal_handlers_block_by_func(G_OBJECT(buffer),
11894 G_CALLBACK(text_inserted),
11896 if (paste_as_quotation) {
11898 const gchar *qmark;
11900 GtkTextIter start_iter;
11903 len = strlen(text);
11905 new_text = g_strndup(text, len);
11907 qmark = compose_quote_char_from_context(compose);
11909 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11910 gtk_text_buffer_place_cursor(buffer, iter);
11912 pos = gtk_text_iter_get_offset(iter);
11914 compose_quote_fmt(compose, NULL, "%Q", qmark, new_text, TRUE, FALSE,
11915 _("Quote format error at line %d."));
11916 quote_fmt_reset_vartable();
11918 g_object_set_data(G_OBJECT(compose->text), "paste_as_quotation",
11919 GINT_TO_POINTER(paste_as_quotation - 1));
11921 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11922 gtk_text_buffer_place_cursor(buffer, iter);
11923 gtk_text_buffer_delete_mark(buffer, mark);
11925 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, pos);
11926 mark = gtk_text_buffer_create_mark(buffer, NULL, &start_iter, FALSE);
11927 compose_beautify_paragraph(compose, &start_iter, FALSE);
11928 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark);
11929 gtk_text_buffer_delete_mark(buffer, mark);
11931 if (strcmp(text, "\n") || compose->automatic_break
11932 || gtk_text_iter_starts_line(iter)) {
11933 GtkTextIter before_ins;
11934 gtk_text_buffer_insert(buffer, iter, text, len);
11935 if (!strstr(text, "\n") && gtk_text_iter_has_tag(iter, compose->no_join_tag)) {
11936 before_ins = *iter;
11937 gtk_text_iter_backward_chars(&before_ins, len);
11938 gtk_text_buffer_remove_tag_by_name(buffer, "no_join", &before_ins, iter);
11941 /* check if the preceding is just whitespace or quote */
11942 GtkTextIter start_line;
11943 gchar *tmp = NULL, *quote = NULL;
11944 gint quote_len = 0, is_normal = 0;
11945 start_line = *iter;
11946 gtk_text_iter_set_line_offset(&start_line, 0);
11947 tmp = gtk_text_buffer_get_text(buffer, &start_line, iter, FALSE);
11950 if (*tmp == '\0') {
11953 quote = compose_get_quote_str(buffer, &start_line, "e_len);
11961 gtk_text_buffer_insert(buffer, iter, text, len);
11963 gtk_text_buffer_insert_with_tags_by_name(buffer,
11964 iter, text, len, "no_join", NULL);
11969 if (!paste_as_quotation) {
11970 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11971 compose_beautify_paragraph(compose, iter, FALSE);
11972 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11973 gtk_text_buffer_delete_mark(buffer, mark);
11976 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
11977 G_CALLBACK(text_inserted),
11979 g_signal_stop_emission_by_name(G_OBJECT(buffer), "insert-text");
11981 if (compose_can_autosave(compose) &&
11982 gtk_text_buffer_get_char_count(buffer) % prefs_common.autosave_length == 0 &&
11983 compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN /* disabled while loading */)
11984 compose->draft_timeout_tag = g_timeout_add
11985 (500, (GSourceFunc) compose_defer_auto_save_draft, compose);
11989 static void compose_check_all(GtkAction *action, gpointer data)
11991 Compose *compose = (Compose *)data;
11992 if (!compose->gtkaspell)
11995 if (gtk_widget_has_focus(compose->subject_entry))
11996 claws_spell_entry_check_all(
11997 CLAWS_SPELL_ENTRY(compose->subject_entry));
11999 gtkaspell_check_all(compose->gtkaspell);
12002 static void compose_highlight_all(GtkAction *action, gpointer data)
12004 Compose *compose = (Compose *)data;
12005 if (compose->gtkaspell) {
12006 claws_spell_entry_recheck_all(
12007 CLAWS_SPELL_ENTRY(compose->subject_entry));
12008 gtkaspell_highlight_all(compose->gtkaspell);
12012 static void compose_check_backwards(GtkAction *action, gpointer data)
12014 Compose *compose = (Compose *)data;
12015 if (!compose->gtkaspell) {
12016 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
12020 if (gtk_widget_has_focus(compose->subject_entry))
12021 claws_spell_entry_check_backwards(
12022 CLAWS_SPELL_ENTRY(compose->subject_entry));
12024 gtkaspell_check_backwards(compose->gtkaspell);
12027 static void compose_check_forwards_go(GtkAction *action, gpointer data)
12029 Compose *compose = (Compose *)data;
12030 if (!compose->gtkaspell) {
12031 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
12035 if (gtk_widget_has_focus(compose->subject_entry))
12036 claws_spell_entry_check_forwards_go(
12037 CLAWS_SPELL_ENTRY(compose->subject_entry));
12039 gtkaspell_check_forwards_go(compose->gtkaspell);
12044 *\brief Guess originating forward account from MsgInfo and several
12045 * "common preference" settings. Return NULL if no guess.
12047 static PrefsAccount *compose_find_account(MsgInfo *msginfo)
12049 PrefsAccount *account = NULL;
12051 cm_return_val_if_fail(msginfo, NULL);
12052 cm_return_val_if_fail(msginfo->folder, NULL);
12053 cm_return_val_if_fail(msginfo->folder->prefs, NULL);
12055 if (msginfo->folder->prefs->enable_default_account)
12056 account = account_find_from_id(msginfo->folder->prefs->default_account);
12058 if (!account && msginfo->to && prefs_common.forward_account_autosel) {
12060 Xstrdup_a(to, msginfo->to, return NULL);
12061 extract_address(to);
12062 account = account_find_from_address(to, FALSE);
12065 if (!account && prefs_common.forward_account_autosel) {
12067 if (!procheader_get_header_from_msginfo
12068 (msginfo, &cc, "Cc:")) {
12069 gchar *buf = cc + strlen("Cc:");
12070 extract_address(buf);
12071 account = account_find_from_address(buf, FALSE);
12076 if (!account && prefs_common.forward_account_autosel) {
12077 gchar *deliveredto = NULL;
12078 if (!procheader_get_header_from_msginfo
12079 (msginfo, &deliveredto, "Delivered-To:")) {
12080 gchar *buf = deliveredto + strlen("Delivered-To:");
12081 extract_address(buf);
12082 account = account_find_from_address(buf, FALSE);
12083 g_free(deliveredto);
12088 account = msginfo->folder->folder->account;
12093 gboolean compose_close(Compose *compose)
12097 cm_return_val_if_fail(compose, FALSE);
12099 if (!g_mutex_trylock(compose->mutex)) {
12100 /* we have to wait for the (possibly deferred by auto-save)
12101 * drafting to be done, before destroying the compose under
12103 debug_print("waiting for drafting to finish...\n");
12104 compose_allow_user_actions(compose, FALSE);
12105 if (compose->close_timeout_tag == 0) {
12106 compose->close_timeout_tag =
12107 g_timeout_add (500, (GSourceFunc) compose_close,
12113 if (compose->draft_timeout_tag >= 0) {
12114 g_source_remove(compose->draft_timeout_tag);
12115 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN;
12118 gtkut_widget_get_uposition(compose->window, &x, &y);
12119 if (!compose->batch) {
12120 prefs_common.compose_x = x;
12121 prefs_common.compose_y = y;
12123 g_mutex_unlock(compose->mutex);
12124 compose_destroy(compose);
12129 * Add entry field for each address in list.
12130 * \param compose E-Mail composition object.
12131 * \param listAddress List of (formatted) E-Mail addresses.
12133 static void compose_add_field_list( Compose *compose, GList *listAddress ) {
12136 node = listAddress;
12138 addr = ( gchar * ) node->data;
12139 compose_entry_append( compose, addr, COMPOSE_TO, PREF_NONE );
12140 node = g_list_next( node );
12144 static void compose_reply_from_messageview_real(MessageView *msgview, GSList *msginfo_list,
12145 guint action, gboolean opening_multiple)
12147 gchar *body = NULL;
12148 GSList *new_msglist = NULL;
12149 MsgInfo *tmp_msginfo = NULL;
12150 gboolean originally_enc = FALSE;
12151 gboolean originally_sig = FALSE;
12152 Compose *compose = NULL;
12153 gchar *s_system = NULL;
12155 cm_return_if_fail(msginfo_list != NULL);
12157 if (g_slist_length(msginfo_list) == 1 && !opening_multiple && msgview != NULL) {
12158 MimeInfo *mimeinfo = messageview_get_selected_mime_part(msgview);
12159 MsgInfo *orig_msginfo = (MsgInfo *)msginfo_list->data;
12161 if (mimeinfo != NULL && mimeinfo->type == MIMETYPE_MESSAGE &&
12162 !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
12163 tmp_msginfo = procmsg_msginfo_new_from_mimeinfo(
12164 orig_msginfo, mimeinfo);
12165 if (tmp_msginfo != NULL) {
12166 new_msglist = g_slist_append(NULL, tmp_msginfo);
12168 originally_enc = MSG_IS_ENCRYPTED(orig_msginfo->flags);
12169 privacy_msginfo_get_signed_state(orig_msginfo, &s_system);
12170 originally_sig = MSG_IS_SIGNED(orig_msginfo->flags);
12172 tmp_msginfo->folder = orig_msginfo->folder;
12173 tmp_msginfo->msgnum = orig_msginfo->msgnum;
12174 if (orig_msginfo->tags) {
12175 tmp_msginfo->tags = g_slist_copy(orig_msginfo->tags);
12176 tmp_msginfo->folder->tags_dirty = TRUE;
12182 if (!opening_multiple && msgview != NULL)
12183 body = messageview_get_selection(msgview);
12186 compose = compose_reply_mode((ComposeMode)action, new_msglist, body);
12187 procmsg_msginfo_free(&tmp_msginfo);
12188 g_slist_free(new_msglist);
12190 compose = compose_reply_mode((ComposeMode)action, msginfo_list, body);
12192 if (compose && originally_enc) {
12193 compose_force_encryption(compose, compose->account, FALSE, s_system);
12196 if (compose && originally_sig && compose->account->default_sign_reply) {
12197 compose_force_signing(compose, compose->account, s_system);
12201 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
12204 void compose_reply_from_messageview(MessageView *msgview, GSList *msginfo_list,
12207 if ((!prefs_common.forward_as_attachment || action != COMPOSE_FORWARD)
12208 && msginfo_list != NULL
12209 && action != COMPOSE_FORWARD_AS_ATTACH && g_slist_length(msginfo_list) > 1) {
12210 GSList *cur = msginfo_list;
12211 gchar *msg = g_strdup_printf(_("You are about to reply to %d "
12212 "messages. Opening the windows "
12213 "could take some time. Do you "
12214 "want to continue?"),
12215 g_slist_length(msginfo_list));
12216 if (g_slist_length(msginfo_list) > 9
12217 && alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, GTK_STOCK_YES, NULL,
12218 ALERTFOCUS_SECOND) != G_ALERTALTERNATE) {
12223 /* We'll open multiple compose windows */
12224 /* let the WM place the next windows */
12225 compose_force_window_origin = FALSE;
12226 for (; cur; cur = cur->next) {
12228 tmplist.data = cur->data;
12229 tmplist.next = NULL;
12230 compose_reply_from_messageview_real(msgview, &tmplist, action, TRUE);
12232 compose_force_window_origin = TRUE;
12234 /* forwarding multiple mails as attachments is done via a
12235 * single compose window */
12236 if (msginfo_list != NULL) {
12237 compose_reply_from_messageview_real(msgview, msginfo_list, action, FALSE);
12238 } else if (msgview != NULL) {
12240 tmplist.data = msgview->msginfo;
12241 tmplist.next = NULL;
12242 compose_reply_from_messageview_real(msgview, &tmplist, action, FALSE);
12244 debug_print("Nothing to reply to\n");
12249 void compose_check_for_email_account(Compose *compose)
12251 PrefsAccount *ac = NULL, *curr = NULL;
12257 if (compose->account && compose->account->protocol == A_NNTP) {
12258 ac = account_get_cur_account();
12259 if (ac->protocol == A_NNTP) {
12260 list = account_get_list();
12262 for( ; list != NULL ; list = g_list_next(list)) {
12263 curr = (PrefsAccount *) list->data;
12264 if (curr->protocol != A_NNTP) {
12270 combobox_select_by_data(GTK_COMBO_BOX(compose->account_combo),
12275 void compose_reply_to_address(MessageView *msgview, MsgInfo *msginfo,
12276 const gchar *address)
12278 GSList *msginfo_list = NULL;
12279 gchar *body = messageview_get_selection(msgview);
12282 msginfo_list = g_slist_prepend(msginfo_list, msginfo);
12284 compose = compose_reply_mode(COMPOSE_REPLY_TO_ADDRESS, msginfo_list, body);
12285 compose_check_for_email_account(compose);
12286 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
12287 compose_entry_append(compose, address, COMPOSE_TO, PREF_NONE);
12288 compose_reply_set_subject(compose, msginfo);
12291 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
12294 void compose_set_position(Compose *compose, gint pos)
12296 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
12298 gtkut_text_view_set_position(text, pos);
12301 gboolean compose_search_string(Compose *compose,
12302 const gchar *str, gboolean case_sens)
12304 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
12306 return gtkut_text_view_search_string(text, str, case_sens);
12309 gboolean compose_search_string_backward(Compose *compose,
12310 const gchar *str, gboolean case_sens)
12312 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
12314 return gtkut_text_view_search_string_backward(text, str, case_sens);
12317 /* allocate a msginfo structure and populate its data from a compose data structure */
12318 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose)
12320 MsgInfo *newmsginfo;
12322 gchar date[RFC822_DATE_BUFFSIZE];
12324 cm_return_val_if_fail( compose != NULL, NULL );
12326 newmsginfo = procmsg_msginfo_new();
12329 get_rfc822_date(date, sizeof(date));
12330 newmsginfo->date = g_strdup(date);
12333 if (compose->from_name) {
12334 newmsginfo->from = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
12335 newmsginfo->fromname = procheader_get_fromname(newmsginfo->from);
12339 if (compose->subject_entry)
12340 newmsginfo->subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
12342 /* to, cc, reply-to, newsgroups */
12343 for (list = compose->header_list; list; list = list->next) {
12344 gchar *header = gtk_editable_get_chars(
12346 gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
12347 gchar *entry = gtk_editable_get_chars(
12348 GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
12350 if ( strcasecmp(header, prefs_common_translated_header_name("To:")) == 0 ) {
12351 if ( newmsginfo->to == NULL ) {
12352 newmsginfo->to = g_strdup(entry);
12353 } else if (entry && *entry) {
12354 gchar *tmp = g_strconcat(newmsginfo->to, ", ", entry, NULL);
12355 g_free(newmsginfo->to);
12356 newmsginfo->to = tmp;
12359 if ( strcasecmp(header, prefs_common_translated_header_name("Cc:")) == 0 ) {
12360 if ( newmsginfo->cc == NULL ) {
12361 newmsginfo->cc = g_strdup(entry);
12362 } else if (entry && *entry) {
12363 gchar *tmp = g_strconcat(newmsginfo->cc, ", ", entry, NULL);
12364 g_free(newmsginfo->cc);
12365 newmsginfo->cc = tmp;
12368 if ( strcasecmp(header,
12369 prefs_common_translated_header_name("Newsgroups:")) == 0 ) {
12370 if ( newmsginfo->newsgroups == NULL ) {
12371 newmsginfo->newsgroups = g_strdup(entry);
12372 } else if (entry && *entry) {
12373 gchar *tmp = g_strconcat(newmsginfo->newsgroups, ", ", entry, NULL);
12374 g_free(newmsginfo->newsgroups);
12375 newmsginfo->newsgroups = tmp;
12383 /* other data is unset */
12389 /* update compose's dictionaries from folder dict settings */
12390 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
12391 FolderItem *folder_item)
12393 cm_return_if_fail(compose != NULL);
12395 if (compose->gtkaspell && folder_item && folder_item->prefs) {
12396 FolderItemPrefs *prefs = folder_item->prefs;
12398 if (prefs->enable_default_dictionary)
12399 gtkaspell_change_dict(compose->gtkaspell,
12400 prefs->default_dictionary, FALSE);
12401 if (folder_item->prefs->enable_default_alt_dictionary)
12402 gtkaspell_change_alt_dict(compose->gtkaspell,
12403 prefs->default_alt_dictionary);
12404 if (prefs->enable_default_dictionary
12405 || prefs->enable_default_alt_dictionary)
12406 compose_spell_menu_changed(compose);
12411 static void compose_subject_entry_activated(GtkWidget *widget, gpointer data)
12413 Compose *compose = (Compose *)data;
12415 cm_return_if_fail(compose != NULL);
12417 gtk_widget_grab_focus(compose->text);
12420 static void from_name_activate_cb(GtkWidget *widget, gpointer data)
12422 gtk_combo_box_popup(GTK_COMBO_BOX(data));