2 * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2018 the Claws Mail team and Hiroyuki Yamamoto
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 3 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
21 #include "claws-features.h"
26 #ifndef PANGO_ENABLE_ENGINE
27 # define PANGO_ENABLE_ENGINE
31 #include <glib/gi18n.h>
32 #include <gdk/gdkkeysyms.h>
35 #include <pango/pango-break.h>
40 #include <sys/types.h>
46 # include <sys/wait.h>
50 #ifndef G_OS_WIN32 /* fixme we should have a configure test. */
54 #if (HAVE_WCTYPE_H && HAVE_WCHAR_H)
61 #include "mainwindow.h"
63 #ifndef USE_ALT_ADDRBOOK
64 #include "addressbook.h"
66 #include "addressbook-dbus.h"
67 #include "addressadd.h"
69 #include "folderview.h"
72 #include "stock_pixmap.h"
73 #include "send_message.h"
76 #include "customheader.h"
77 #include "prefs_common.h"
78 #include "prefs_account.h"
82 #include "procheader.h"
84 #include "statusbar.h"
86 #include "quoted-printable.h"
90 #include "gtkshruler.h"
92 #include "alertpanel.h"
93 #include "manage_window.h"
95 #include "folder_item_prefs.h"
96 #include "addr_compl.h"
97 #include "quote_fmt.h"
99 #include "foldersel.h"
102 #include "message_search.h"
103 #include "combobox.h"
107 #include "autofaces.h"
108 #include "spell_entry.h"
110 #include "file-utils.h"
113 #include "password.h"
114 #include "ldapserver.h"
128 #define N_ATTACH_COLS (N_COL_COLUMNS)
132 COMPOSE_CALL_ADVANCED_ACTION_UNDEFINED = -1,
133 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE = 0,
134 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER,
135 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER,
136 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD,
137 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD,
138 COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE,
139 COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE,
140 COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE,
141 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER,
142 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER,
143 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD,
144 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD,
145 COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE,
146 COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END
147 } ComposeCallAdvancedAction;
151 PRIORITY_HIGHEST = 1,
160 COMPOSE_INSERT_SUCCESS,
161 COMPOSE_INSERT_READ_ERROR,
162 COMPOSE_INSERT_INVALID_CHARACTER,
163 COMPOSE_INSERT_NO_FILE
164 } ComposeInsertResult;
168 COMPOSE_WRITE_FOR_SEND,
169 COMPOSE_WRITE_FOR_STORE
174 COMPOSE_QUOTE_FORCED,
181 SUBJECT_FIELD_PRESENT,
186 #define B64_LINE_SIZE 57
187 #define B64_BUFFSIZE 77
189 #define MAX_REFERENCES_LEN 999
191 #define COMPOSE_DRAFT_TIMEOUT_UNSET -1
192 #define COMPOSE_DRAFT_TIMEOUT_FORBIDDEN -2
194 static GdkColor default_header_bgcolor = {
201 static GdkColor default_header_color = {
208 static GList *compose_list = NULL;
209 static GSList *extra_headers = NULL;
211 static Compose *compose_generic_new (PrefsAccount *account,
215 GList *listAddress );
217 static Compose *compose_create (PrefsAccount *account,
222 static void compose_entry_indicate (Compose *compose,
223 const gchar *address);
224 static Compose *compose_followup_and_reply_to (MsgInfo *msginfo,
225 ComposeQuoteMode quote_mode,
229 static Compose *compose_forward_multiple (PrefsAccount *account,
230 GSList *msginfo_list);
231 static Compose *compose_reply (MsgInfo *msginfo,
232 ComposeQuoteMode quote_mode,
237 static Compose *compose_reply_mode (ComposeMode mode,
238 GSList *msginfo_list,
240 static void compose_template_apply_fields(Compose *compose, Template *tmpl);
241 static void compose_update_privacy_systems_menu(Compose *compose);
243 static GtkWidget *compose_account_option_menu_create
245 static void compose_set_out_encoding (Compose *compose);
246 static void compose_set_template_menu (Compose *compose);
247 static void compose_destroy (Compose *compose);
249 static MailField compose_entries_set (Compose *compose,
251 ComposeEntryType to_type);
252 static gint compose_parse_header (Compose *compose,
254 static gint compose_parse_manual_headers (Compose *compose,
256 HeaderEntry *entries);
257 static gchar *compose_parse_references (const gchar *ref,
260 static gchar *compose_quote_fmt (Compose *compose,
266 gboolean need_unescape,
267 const gchar *err_msg);
269 static void compose_reply_set_entry (Compose *compose,
275 followup_and_reply_to);
276 static void compose_reedit_set_entry (Compose *compose,
279 static void compose_insert_sig (Compose *compose,
281 static ComposeInsertResult compose_insert_file (Compose *compose,
284 static gboolean compose_attach_append (Compose *compose,
287 const gchar *content_type,
288 const gchar *charset);
289 static void compose_attach_parts (Compose *compose,
292 static gboolean compose_beautify_paragraph (Compose *compose,
293 GtkTextIter *par_iter,
295 static void compose_wrap_all (Compose *compose);
296 static void compose_wrap_all_full (Compose *compose,
299 static void compose_set_title (Compose *compose);
300 static void compose_select_account (Compose *compose,
301 PrefsAccount *account,
304 static PrefsAccount *compose_current_mail_account(void);
305 /* static gint compose_send (Compose *compose); */
306 static gboolean compose_check_for_valid_recipient
308 static gboolean compose_check_entries (Compose *compose,
309 gboolean check_everything);
310 static gint compose_write_to_file (Compose *compose,
313 gboolean attach_parts);
314 static gint compose_write_body_to_file (Compose *compose,
316 static gint compose_remove_reedit_target (Compose *compose,
318 static void compose_remove_draft (Compose *compose);
319 static ComposeQueueResult compose_queue_sub (Compose *compose,
323 gboolean perform_checks,
324 gboolean remove_reedit_target);
325 static int compose_add_attachments (Compose *compose,
327 static gchar *compose_get_header (Compose *compose);
328 static gchar *compose_get_manual_headers_info (Compose *compose);
330 static void compose_convert_header (Compose *compose,
335 gboolean addr_field);
337 static void compose_attach_info_free (AttachInfo *ainfo);
338 static void compose_attach_remove_selected (GtkAction *action,
341 static void compose_template_apply (Compose *compose,
344 static void compose_attach_property (GtkAction *action,
346 static void compose_attach_property_create (gboolean *cancelled);
347 static void attach_property_ok (GtkWidget *widget,
348 gboolean *cancelled);
349 static void attach_property_cancel (GtkWidget *widget,
350 gboolean *cancelled);
351 static gint attach_property_delete_event (GtkWidget *widget,
353 gboolean *cancelled);
354 static gboolean attach_property_key_pressed (GtkWidget *widget,
356 gboolean *cancelled);
358 static void compose_exec_ext_editor (Compose *compose);
360 static gint compose_exec_ext_editor_real (const gchar *file,
361 GdkNativeWindow socket_wid);
362 static gboolean compose_ext_editor_kill (Compose *compose);
363 static gboolean compose_input_cb (GIOChannel *source,
364 GIOCondition condition,
366 static void compose_set_ext_editor_sensitive (Compose *compose,
368 static gboolean compose_get_ext_editor_cmd_valid();
369 static gboolean compose_get_ext_editor_uses_socket();
370 static gboolean compose_ext_editor_plug_removed_cb
373 #endif /* G_OS_UNIX */
375 static void compose_undo_state_changed (UndoMain *undostruct,
380 static void compose_create_header_entry (Compose *compose);
381 static void compose_add_header_entry (Compose *compose, const gchar *header,
382 gchar *text, ComposePrefType pref_type);
383 static void compose_remove_header_entries(Compose *compose);
385 static void compose_update_priority_menu_item(Compose * compose);
387 static void compose_spell_menu_changed (void *data);
388 static void compose_dict_changed (void *data);
390 static void compose_add_field_list ( Compose *compose,
391 GList *listAddress );
393 /* callback functions */
395 static void compose_notebook_size_alloc (GtkNotebook *notebook,
396 GtkAllocation *allocation,
398 static gboolean compose_edit_size_alloc (GtkEditable *widget,
399 GtkAllocation *allocation,
400 GtkSHRuler *shruler);
401 static void account_activated (GtkComboBox *optmenu,
403 static void attach_selected (GtkTreeView *tree_view,
404 GtkTreePath *tree_path,
405 GtkTreeViewColumn *column,
407 static gboolean attach_button_pressed (GtkWidget *widget,
408 GdkEventButton *event,
410 static gboolean attach_key_pressed (GtkWidget *widget,
413 static void compose_send_cb (GtkAction *action, gpointer data);
414 static void compose_send_later_cb (GtkAction *action, gpointer data);
416 static void compose_save_cb (GtkAction *action,
419 static void compose_attach_cb (GtkAction *action,
421 static void compose_insert_file_cb (GtkAction *action,
423 static void compose_insert_sig_cb (GtkAction *action,
425 static void compose_replace_sig_cb (GtkAction *action,
428 static void compose_close_cb (GtkAction *action,
430 static void compose_print_cb (GtkAction *action,
433 static void compose_set_encoding_cb (GtkAction *action, GtkRadioAction *current, gpointer data);
435 static void compose_address_cb (GtkAction *action,
437 static void about_show_cb (GtkAction *action,
439 static void compose_template_activate_cb(GtkWidget *widget,
442 static void compose_ext_editor_cb (GtkAction *action,
445 static gint compose_delete_cb (GtkWidget *widget,
449 static void compose_undo_cb (GtkAction *action,
451 static void compose_redo_cb (GtkAction *action,
453 static void compose_cut_cb (GtkAction *action,
455 static void compose_copy_cb (GtkAction *action,
457 static void compose_paste_cb (GtkAction *action,
459 static void compose_paste_as_quote_cb (GtkAction *action,
461 static void compose_paste_no_wrap_cb (GtkAction *action,
463 static void compose_paste_wrap_cb (GtkAction *action,
465 static void compose_allsel_cb (GtkAction *action,
468 static void compose_advanced_action_cb (GtkAction *action,
471 static void compose_grab_focus_cb (GtkWidget *widget,
474 static void compose_changed_cb (GtkTextBuffer *textbuf,
477 static void compose_wrap_cb (GtkAction *action,
479 static void compose_wrap_all_cb (GtkAction *action,
481 static void compose_find_cb (GtkAction *action,
483 static void compose_toggle_autowrap_cb (GtkToggleAction *action,
485 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
488 static void compose_toggle_ruler_cb (GtkToggleAction *action,
490 static void compose_toggle_sign_cb (GtkToggleAction *action,
492 static void compose_toggle_encrypt_cb (GtkToggleAction *action,
494 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data);
495 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn);
496 static void compose_activate_privacy_system (Compose *compose,
497 PrefsAccount *account,
499 static void compose_apply_folder_privacy_settings(Compose *compose, FolderItem *folder_item);
500 static void compose_toggle_return_receipt_cb(GtkToggleAction *action,
502 static void compose_toggle_remove_refs_cb(GtkToggleAction *action,
504 static void compose_set_priority_cb (GtkAction *action, GtkRadioAction *current, gpointer data);
505 static void compose_reply_change_mode (Compose *compose, ComposeMode action);
506 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data);
508 static void compose_attach_drag_received_cb (GtkWidget *widget,
509 GdkDragContext *drag_context,
512 GtkSelectionData *data,
516 static void compose_insert_drag_received_cb (GtkWidget *widget,
517 GdkDragContext *drag_context,
520 GtkSelectionData *data,
524 static void compose_header_drag_received_cb (GtkWidget *widget,
525 GdkDragContext *drag_context,
528 GtkSelectionData *data,
533 static gboolean compose_drag_drop (GtkWidget *widget,
534 GdkDragContext *drag_context,
536 guint time, gpointer user_data);
537 static gboolean completion_set_focus_to_subject
542 static void text_inserted (GtkTextBuffer *buffer,
547 static Compose *compose_generic_reply(MsgInfo *msginfo,
548 ComposeQuoteMode quote_mode,
552 gboolean followup_and_reply_to,
555 static void compose_headerentry_changed_cb (GtkWidget *entry,
556 ComposeHeaderEntry *headerentry);
557 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
559 ComposeHeaderEntry *headerentry);
560 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
561 ComposeHeaderEntry *headerentry);
563 static void compose_show_first_last_header (Compose *compose, gboolean show_first);
565 static void compose_allow_user_actions (Compose *compose, gboolean allow);
567 static void compose_nothing_cb (GtkAction *action, gpointer data)
573 static void compose_check_all (GtkAction *action, gpointer data);
574 static void compose_highlight_all (GtkAction *action, gpointer data);
575 static void compose_check_backwards (GtkAction *action, gpointer data);
576 static void compose_check_forwards_go (GtkAction *action, gpointer data);
579 static PrefsAccount *compose_find_account (MsgInfo *msginfo);
581 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose);
584 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
585 FolderItem *folder_item);
587 static void compose_attach_update_label(Compose *compose);
588 static void compose_set_folder_prefs(Compose *compose, FolderItem *folder,
589 gboolean respect_default_to);
590 static void compose_subject_entry_activated(GtkWidget *widget, gpointer data);
591 static void from_name_activate_cb(GtkWidget *widget, gpointer data);
593 static GtkActionEntry compose_popup_entries[] =
595 {"Compose", NULL, "Compose", NULL, NULL, NULL },
596 {"Compose/Add", NULL, N_("_Add..."), NULL, NULL, G_CALLBACK(compose_attach_cb) },
597 {"Compose/Remove", NULL, N_("_Remove"), NULL, NULL, G_CALLBACK(compose_attach_remove_selected) },
598 {"Compose/---", NULL, "---", NULL, NULL, NULL },
599 {"Compose/Properties", NULL, N_("_Properties..."), NULL, NULL, G_CALLBACK(compose_attach_property) },
602 static GtkActionEntry compose_entries[] =
604 {"Menu", NULL, "Menu", NULL, NULL, NULL },
606 {"Message", NULL, N_("_Message"), NULL, NULL, NULL },
607 {"Edit", NULL, N_("_Edit"), NULL, NULL, NULL },
609 {"Spelling", NULL, N_("_Spelling"), NULL, NULL, NULL },
611 {"Options", NULL, N_("_Options"), NULL, NULL, NULL },
612 {"Tools", NULL, N_("_Tools"), NULL, NULL, NULL },
613 {"Help", NULL, N_("_Help"), NULL, NULL, NULL },
615 {"Message/Send", NULL, N_("S_end"), "<control>Return", NULL, G_CALLBACK(compose_send_cb) },
616 {"Message/SendLater", NULL, N_("Send _later"), "<shift><control>S", NULL, G_CALLBACK(compose_send_later_cb) },
617 {"Message/---", NULL, "---", NULL, NULL, NULL },
619 {"Message/AttachFile", NULL, N_("_Attach file"), "<control>M", NULL, G_CALLBACK(compose_attach_cb) },
620 {"Message/InsertFile", NULL, N_("_Insert file"), "<control>I", NULL, G_CALLBACK(compose_insert_file_cb) },
621 {"Message/InsertSig", NULL, N_("Insert si_gnature"), "<control>G", NULL, G_CALLBACK(compose_insert_sig_cb) },
622 {"Message/ReplaceSig", NULL, N_("_Replace signature"), NULL, NULL, G_CALLBACK(compose_replace_sig_cb) },
623 /* {"Message/---", NULL, "---", NULL, NULL, NULL }, */
624 {"Message/Save", NULL, N_("_Save"), "<control>S", NULL, G_CALLBACK(compose_save_cb) }, /*COMPOSE_KEEP_EDITING*/
625 /* {"Message/---", NULL, "---", NULL, NULL, NULL }, */
626 {"Message/Print", NULL, N_("_Print"), NULL, NULL, G_CALLBACK(compose_print_cb) },
627 /* {"Message/---", NULL, "---", NULL, NULL, NULL }, */
628 {"Message/Close", NULL, N_("_Close"), "<control>W", NULL, G_CALLBACK(compose_close_cb) },
631 {"Edit/Undo", NULL, N_("_Undo"), "<control>Z", NULL, G_CALLBACK(compose_undo_cb) },
632 {"Edit/Redo", NULL, N_("_Redo"), "<control>Y", NULL, G_CALLBACK(compose_redo_cb) },
633 {"Edit/---", NULL, "---", NULL, NULL, NULL },
635 {"Edit/Cut", NULL, N_("Cu_t"), "<control>X", NULL, G_CALLBACK(compose_cut_cb) },
636 {"Edit/Copy", NULL, N_("_Copy"), "<control>C", NULL, G_CALLBACK(compose_copy_cb) },
637 {"Edit/Paste", NULL, N_("_Paste"), "<control>V", NULL, G_CALLBACK(compose_paste_cb) },
639 {"Edit/SpecialPaste", NULL, N_("_Special paste"), NULL, NULL, NULL },
640 {"Edit/SpecialPaste/AsQuotation", NULL, N_("As _quotation"), NULL, NULL, G_CALLBACK(compose_paste_as_quote_cb) },
641 {"Edit/SpecialPaste/Wrapped", NULL, N_("_Wrapped"), NULL, NULL, G_CALLBACK(compose_paste_wrap_cb) },
642 {"Edit/SpecialPaste/Unwrapped", NULL, N_("_Unwrapped"), NULL, NULL, G_CALLBACK(compose_paste_no_wrap_cb) },
644 {"Edit/SelectAll", NULL, N_("Select _all"), "<control>A", NULL, G_CALLBACK(compose_allsel_cb) },
646 {"Edit/Advanced", NULL, N_("A_dvanced"), NULL, NULL, NULL },
647 {"Edit/Advanced/BackChar", NULL, N_("Move a character backward"), "<shift><control>B", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER*/
648 {"Edit/Advanced/ForwChar", NULL, N_("Move a character forward"), "<shift><control>F", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER*/
649 {"Edit/Advanced/BackWord", NULL, N_("Move a word backward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD*/
650 {"Edit/Advanced/ForwWord", NULL, N_("Move a word forward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD*/
651 {"Edit/Advanced/BegLine", NULL, N_("Move to beginning of line"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE*/
652 {"Edit/Advanced/EndLine", NULL, N_("Move to end of line"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE*/
653 {"Edit/Advanced/PrevLine", NULL, N_("Move to previous line"), "<control>P", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE*/
654 {"Edit/Advanced/NextLine", NULL, N_("Move to next line"), "<control>N", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE*/
655 {"Edit/Advanced/DelBackChar", NULL, N_("Delete a character backward"), "<control>H", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER*/
656 {"Edit/Advanced/DelForwChar", NULL, N_("Delete a character forward"), "<control>D", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER*/
657 {"Edit/Advanced/DelBackWord", NULL, N_("Delete a word backward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD*/
658 {"Edit/Advanced/DelForwWord", NULL, N_("Delete a word forward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD*/
659 {"Edit/Advanced/DelLine", NULL, N_("Delete line"), "<control>U", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE*/
660 {"Edit/Advanced/DelEndLine", NULL, N_("Delete to end of line"), "<control>K", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END*/
662 /* {"Edit/---", NULL, "---", NULL, NULL, NULL }, */
663 {"Edit/Find", NULL, N_("_Find"), "<control>F", NULL, G_CALLBACK(compose_find_cb) },
665 /* {"Edit/---", NULL, "---", NULL, NULL, NULL }, */
666 {"Edit/WrapPara", NULL, N_("_Wrap current paragraph"), "<control>L", NULL, G_CALLBACK(compose_wrap_cb) }, /* 0 */
667 {"Edit/WrapAllLines", NULL, N_("Wrap all long _lines"), "<control><alt>L", NULL, G_CALLBACK(compose_wrap_all_cb) }, /* 1 */
668 /* {"Edit/---", NULL, "---", NULL, NULL, NULL }, */
669 {"Edit/ExtEditor", NULL, N_("Edit with e_xternal editor"), "<shift><control>X", NULL, G_CALLBACK(compose_ext_editor_cb) },
672 {"Spelling/CheckAllSel", NULL, N_("_Check all or check selection"), NULL, NULL, G_CALLBACK(compose_check_all) },
673 {"Spelling/HighlightAll", NULL, N_("_Highlight all misspelled words"), NULL, NULL, G_CALLBACK(compose_highlight_all) },
674 {"Spelling/CheckBackwards", NULL, N_("Check _backwards misspelled word"), NULL, NULL, G_CALLBACK(compose_check_backwards) },
675 {"Spelling/ForwardNext", NULL, N_("_Forward to next misspelled word"), NULL, NULL, G_CALLBACK(compose_check_forwards_go) },
677 {"Spelling/---", NULL, "---", NULL, NULL, NULL },
678 {"Spelling/Options", NULL, N_("_Options"), NULL, NULL, NULL },
682 {"Options/ReplyMode", NULL, N_("Reply _mode"), NULL, NULL, NULL },
683 {"Options/---", NULL, "---", NULL, NULL, NULL },
684 {"Options/PrivacySystem", NULL, N_("Privacy _System"), NULL, NULL, NULL },
685 {"Options/PrivacySystem/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
687 /* {"Options/---", NULL, "---", NULL, NULL, NULL }, */
688 {"Options/Priority", NULL, N_("_Priority"), NULL, NULL, NULL },
690 {"Options/Encoding", NULL, N_("Character _encoding"), NULL, NULL, NULL },
691 {"Options/Encoding/---", NULL, "---", NULL, NULL, NULL },
692 #define ENC_ACTION(cs_char,c_char,string) \
693 {"Options/Encoding/" cs_char, NULL, N_(string), NULL, NULL, c_char }
695 {"Options/Encoding/Western", NULL, N_("Western European"), NULL, NULL, NULL },
696 {"Options/Encoding/Baltic", NULL, N_("Baltic"), NULL, NULL, NULL },
697 {"Options/Encoding/Hebrew", NULL, N_("Hebrew"), NULL, NULL, NULL },
698 {"Options/Encoding/Arabic", NULL, N_("Arabic"), NULL, NULL, NULL },
699 {"Options/Encoding/Cyrillic", NULL, N_("Cyrillic"), NULL, NULL, NULL },
700 {"Options/Encoding/Japanese", NULL, N_("Japanese"), NULL, NULL, NULL },
701 {"Options/Encoding/Chinese", NULL, N_("Chinese"), NULL, NULL, NULL },
702 {"Options/Encoding/Korean", NULL, N_("Korean"), NULL, NULL, NULL },
703 {"Options/Encoding/Thai", NULL, N_("Thai"), NULL, NULL, NULL },
706 {"Tools/AddressBook", NULL, N_("_Address book"), NULL, NULL, G_CALLBACK(compose_address_cb) },
708 {"Tools/Template", NULL, N_("_Template"), NULL, NULL, NULL },
709 {"Tools/Template/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
710 {"Tools/Actions", NULL, N_("Actio_ns"), NULL, NULL, NULL },
711 {"Tools/Actions/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
714 {"Help/About", NULL, N_("_About"), NULL, NULL, G_CALLBACK(about_show_cb) },
717 static GtkToggleActionEntry compose_toggle_entries[] =
719 {"Edit/AutoWrap", NULL, N_("Aut_o wrapping"), "<shift><control>L", NULL, G_CALLBACK(compose_toggle_autowrap_cb), FALSE }, /* Toggle */
720 {"Edit/AutoIndent", NULL, N_("Auto _indent"), NULL, NULL, G_CALLBACK(compose_toggle_autoindent_cb), FALSE }, /* Toggle */
721 {"Options/Sign", NULL, N_("Si_gn"), NULL, NULL, G_CALLBACK(compose_toggle_sign_cb), FALSE }, /* Toggle */
722 {"Options/Encrypt", NULL, N_("_Encrypt"), NULL, NULL, G_CALLBACK(compose_toggle_encrypt_cb), FALSE }, /* Toggle */
723 {"Options/RequestRetRcpt", NULL, N_("_Request Return Receipt"), NULL, NULL, G_CALLBACK(compose_toggle_return_receipt_cb), FALSE }, /* Toggle */
724 {"Options/RemoveReferences", NULL, N_("Remo_ve references"), NULL, NULL, G_CALLBACK(compose_toggle_remove_refs_cb), FALSE }, /* Toggle */
725 {"Tools/ShowRuler", NULL, N_("Show _ruler"), NULL, NULL, G_CALLBACK(compose_toggle_ruler_cb), FALSE }, /* Toggle */
728 static GtkRadioActionEntry compose_radio_rm_entries[] =
730 {"Options/ReplyMode/Normal", NULL, N_("_Normal"), NULL, NULL, COMPOSE_REPLY }, /* RADIO compose_reply_change_mode_cb */
731 {"Options/ReplyMode/All", NULL, N_("_All"), NULL, NULL, COMPOSE_REPLY_TO_ALL }, /* RADIO compose_reply_change_mode_cb */
732 {"Options/ReplyMode/Sender", NULL, N_("_Sender"), NULL, NULL, COMPOSE_REPLY_TO_SENDER }, /* RADIO compose_reply_change_mode_cb */
733 {"Options/ReplyMode/List", NULL, N_("_Mailing-list"), NULL, NULL, COMPOSE_REPLY_TO_LIST }, /* RADIO compose_reply_change_mode_cb */
736 static GtkRadioActionEntry compose_radio_prio_entries[] =
738 {"Options/Priority/Highest", NULL, N_("_Highest"), NULL, NULL, PRIORITY_HIGHEST }, /* RADIO compose_set_priority_cb */
739 {"Options/Priority/High", NULL, N_("Hi_gh"), NULL, NULL, PRIORITY_HIGH }, /* RADIO compose_set_priority_cb */
740 {"Options/Priority/Normal", NULL, N_("_Normal"), NULL, NULL, PRIORITY_NORMAL }, /* RADIO compose_set_priority_cb */
741 {"Options/Priority/Low", NULL, N_("Lo_w"), NULL, NULL, PRIORITY_LOW }, /* RADIO compose_set_priority_cb */
742 {"Options/Priority/Lowest", NULL, N_("_Lowest"), NULL, NULL, PRIORITY_LOWEST }, /* RADIO compose_set_priority_cb */
745 static GtkRadioActionEntry compose_radio_enc_entries[] =
747 ENC_ACTION(CS_AUTO, C_AUTO, N_("_Automatic")), /* RADIO compose_set_encoding_cb */
748 ENC_ACTION(CS_US_ASCII, C_US_ASCII, N_("7bit ASCII (US-ASC_II)")), /* RADIO compose_set_encoding_cb */
749 ENC_ACTION(CS_UTF_8, C_UTF_8, N_("Unicode (_UTF-8)")), /* RADIO compose_set_encoding_cb */
750 ENC_ACTION("Western/"CS_ISO_8859_1, C_ISO_8859_1, "ISO-8859-_1"), /* RADIO compose_set_encoding_cb */
751 ENC_ACTION("Western/"CS_ISO_8859_15, C_ISO_8859_15, "ISO-8859-15"), /* RADIO compose_set_encoding_cb */
752 ENC_ACTION("Western/"CS_WINDOWS_1252, C_WINDOWS_1252, "Windows-1252"), /* RADIO compose_set_encoding_cb */
753 ENC_ACTION(CS_ISO_8859_2, C_ISO_8859_2, N_("Central European (ISO-8859-_2)")), /* RADIO compose_set_encoding_cb */
754 ENC_ACTION("Baltic/"CS_ISO_8859_13, C_ISO_8859_13, "ISO-8859-13"), /* RADIO compose_set_encoding_cb */
755 ENC_ACTION("Baltic/"CS_ISO_8859_4, C_ISO_8859_14, "ISO-8859-_4"), /* RADIO compose_set_encoding_cb */
756 ENC_ACTION(CS_ISO_8859_7, C_ISO_8859_7, N_("Greek (ISO-8859-_7)")), /* RADIO compose_set_encoding_cb */
757 ENC_ACTION("Hebrew/"CS_ISO_8859_8, C_ISO_8859_8, "ISO-8859-_8"), /* RADIO compose_set_encoding_cb */
758 ENC_ACTION("Hebrew/"CS_WINDOWS_1255, C_WINDOWS_1255, "Windows-1255"), /* RADIO compose_set_encoding_cb */
759 ENC_ACTION("Arabic/"CS_ISO_8859_6, C_ISO_8859_6, "ISO-8859-_6"), /* RADIO compose_set_encoding_cb */
760 ENC_ACTION("Arabic/"CS_WINDOWS_1256, C_WINDOWS_1256, "Windows-1256"), /* RADIO compose_set_encoding_cb */
761 ENC_ACTION(CS_ISO_8859_9, C_ISO_8859_9, N_("Turkish (ISO-8859-_9)")), /* RADIO compose_set_encoding_cb */
762 ENC_ACTION("Cyrillic/"CS_ISO_8859_5, C_ISO_8859_5, "ISO-8859-_5"), /* RADIO compose_set_encoding_cb */
763 ENC_ACTION("Cyrillic/"CS_KOI8_R, C_KOI8_R, "KOI8-_R"), /* RADIO compose_set_encoding_cb */
764 ENC_ACTION("Cyrillic/"CS_MACCYR, C_MACCYR, "_Mac-Cyrillic"), /* RADIO compose_set_encoding_cb */
765 ENC_ACTION("Cyrillic/"CS_KOI8_U, C_KOI8_U, "KOI8-_U"), /* RADIO compose_set_encoding_cb */
766 ENC_ACTION("Cyrillic/"CS_WINDOWS_1251, C_WINDOWS_1251, "Windows-1251"), /* RADIO compose_set_encoding_cb */
767 ENC_ACTION("Japanese/"CS_ISO_2022_JP, C_ISO_2022_JP, "ISO-2022-_JP"), /* RADIO compose_set_encoding_cb */
768 ENC_ACTION("Japanese/"CS_ISO_2022_JP_2, C_ISO_2022_JP_2, "ISO-2022-JP-_2"), /* RADIO compose_set_encoding_cb */
769 ENC_ACTION("Japanese/"CS_EUC_JP, C_EUC_JP, "_EUC-JP"), /* RADIO compose_set_encoding_cb */
770 ENC_ACTION("Japanese/"CS_SHIFT_JIS, C_SHIFT_JIS, "_Shift-JIS"), /* RADIO compose_set_encoding_cb */
771 ENC_ACTION("Chinese/"CS_GB18030, C_GB18030, "_GB18030"), /* RADIO compose_set_encoding_cb */
772 ENC_ACTION("Chinese/"CS_GB2312, C_GB2312, "_GB2312"), /* RADIO compose_set_encoding_cb */
773 ENC_ACTION("Chinese/"CS_GBK, C_GBK, "GB_K"), /* RADIO compose_set_encoding_cb */
774 ENC_ACTION("Chinese/"CS_BIG5, C_BIG5, "_Big5-JP"), /* RADIO compose_set_encoding_cb */
775 ENC_ACTION("Chinese/"CS_EUC_TW, C_EUC_TW, "EUC-_TW"), /* RADIO compose_set_encoding_cb */
776 ENC_ACTION("Korean/"CS_EUC_KR, C_EUC_KR, "_EUC-KR"), /* RADIO compose_set_encoding_cb */
777 ENC_ACTION("Korean/"CS_ISO_2022_KR, C_ISO_2022_KR, "_ISO-2022-KR"), /* RADIO compose_set_encoding_cb */
778 ENC_ACTION("Thai/"CS_TIS_620, C_TIS_620, "_TIS-620-KR"), /* RADIO compose_set_encoding_cb */
779 ENC_ACTION("Thai/"CS_WINDOWS_874, C_WINDOWS_874, "_Windows-874"), /* RADIO compose_set_encoding_cb */
782 static GtkTargetEntry compose_mime_types[] =
784 {"text/uri-list", 0, 0},
785 {"UTF8_STRING", 0, 0},
789 static gboolean compose_put_existing_to_front(MsgInfo *info)
791 const GList *compose_list = compose_get_compose_list();
792 const GList *elem = NULL;
795 for (elem = compose_list; elem != NULL && elem->data != NULL;
797 Compose *c = (Compose*)elem->data;
799 if (!c->targetinfo || !c->targetinfo->msgid ||
803 if (!strcmp(c->targetinfo->msgid, info->msgid)) {
804 gtkut_window_popup(c->window);
812 static GdkColor quote_color1 =
813 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
814 static GdkColor quote_color2 =
815 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
816 static GdkColor quote_color3 =
817 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
819 static GdkColor quote_bgcolor1 =
820 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
821 static GdkColor quote_bgcolor2 =
822 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
823 static GdkColor quote_bgcolor3 =
824 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
826 static GdkColor signature_color = {
833 static GdkColor uri_color = {
840 static void compose_create_tags(GtkTextView *text, Compose *compose)
842 GtkTextBuffer *buffer;
843 GdkColor black = {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
845 buffer = gtk_text_view_get_buffer(text);
847 if (prefs_common.enable_color) {
848 /* grab the quote colors, converting from an int to a GdkColor */
849 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_QUOTE_LEVEL1],
851 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_QUOTE_LEVEL2],
853 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_QUOTE_LEVEL3],
855 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_QUOTE_LEVEL1_BG],
857 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_QUOTE_LEVEL2_BG],
859 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_QUOTE_LEVEL3_BG],
861 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_SIGNATURE],
863 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_URI],
866 signature_color = quote_color1 = quote_color2 = quote_color3 =
867 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 = uri_color = black;
870 if (prefs_common.enable_color && prefs_common.enable_bgcolor) {
871 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
872 "foreground-gdk", "e_color1,
873 "paragraph-background-gdk", "e_bgcolor1,
875 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
876 "foreground-gdk", "e_color2,
877 "paragraph-background-gdk", "e_bgcolor2,
879 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
880 "foreground-gdk", "e_color3,
881 "paragraph-background-gdk", "e_bgcolor3,
884 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
885 "foreground-gdk", "e_color1,
887 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
888 "foreground-gdk", "e_color2,
890 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
891 "foreground-gdk", "e_color3,
895 compose->signature_tag = gtk_text_buffer_create_tag(buffer, "signature",
896 "foreground-gdk", &signature_color,
899 compose->uri_tag = gtk_text_buffer_create_tag(buffer, "link",
900 "foreground-gdk", &uri_color,
902 compose->no_wrap_tag = gtk_text_buffer_create_tag(buffer, "no_wrap", NULL);
903 compose->no_join_tag = gtk_text_buffer_create_tag(buffer, "no_join", NULL);
906 Compose *compose_new(PrefsAccount *account, const gchar *mailto,
909 return compose_generic_new(account, mailto, NULL, attach_files, NULL);
912 Compose *compose_new_with_folderitem(PrefsAccount *account, FolderItem *item, const gchar *mailto)
914 return compose_generic_new(account, mailto, item, NULL, NULL);
917 Compose *compose_new_with_list( PrefsAccount *account, GList *listAddress )
919 return compose_generic_new( account, NULL, NULL, NULL, listAddress );
922 #define SCROLL_TO_CURSOR(compose) { \
923 GtkTextMark *cmark = gtk_text_buffer_get_insert( \
924 gtk_text_view_get_buffer( \
925 GTK_TEXT_VIEW(compose->text))); \
926 gtk_text_view_scroll_mark_onscreen( \
927 GTK_TEXT_VIEW(compose->text), \
931 static void compose_set_save_to(Compose *compose, const gchar *folderidentifier)
934 if (folderidentifier) {
935 combobox_unset_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo));
936 prefs_common.compose_save_to_history = add_history(
937 prefs_common.compose_save_to_history, folderidentifier);
938 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo),
939 prefs_common.compose_save_to_history);
942 entry = GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(compose->savemsg_combo)));
943 if (folderidentifier)
944 gtk_entry_set_text(GTK_ENTRY(entry), folderidentifier);
946 gtk_entry_set_text(GTK_ENTRY(entry), "");
949 static gchar *compose_get_save_to(Compose *compose)
952 gchar *result = NULL;
953 entry = GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(compose->savemsg_combo)));
954 result = gtk_editable_get_chars(entry, 0, -1);
957 combobox_unset_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo));
958 prefs_common.compose_save_to_history = add_history(
959 prefs_common.compose_save_to_history, result);
960 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo),
961 prefs_common.compose_save_to_history);
966 Compose *compose_generic_new(PrefsAccount *account, const gchar *mailto, FolderItem *item,
967 GList *attach_files, GList *listAddress )
970 GtkTextView *textview;
971 GtkTextBuffer *textbuf;
973 const gchar *subject_format = NULL;
974 const gchar *body_format = NULL;
975 gchar *mailto_from = NULL;
976 PrefsAccount *mailto_account = NULL;
977 MsgInfo* dummyinfo = NULL;
978 gint cursor_pos = -1;
979 MailField mfield = NO_FIELD_PRESENT;
983 /* check if mailto defines a from */
984 if (mailto && *mailto != '\0') {
985 scan_mailto_url(mailto, &mailto_from, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
986 /* mailto defines a from, check if we can get account prefs from it,
987 if not, the account prefs will be guessed using other ways, but we'll keep
990 mailto_account = account_find_from_address(mailto_from, TRUE);
991 if (mailto_account == NULL) {
993 Xstrdup_a(tmp_from, mailto_from, return NULL);
994 extract_address(tmp_from);
995 mailto_account = account_find_from_address(tmp_from, TRUE);
999 account = mailto_account;
1002 /* if no account prefs set from mailto, set if from folder prefs (if any) */
1003 if (!mailto_account && item && item->prefs && item->prefs->enable_default_account)
1004 account = account_find_from_id(item->prefs->default_account);
1006 /* if no account prefs set, fallback to the current one */
1007 if (!account) account = cur_account;
1008 cm_return_val_if_fail(account != NULL, NULL);
1010 compose = compose_create(account, item, COMPOSE_NEW, FALSE);
1011 compose_apply_folder_privacy_settings(compose, item);
1013 if (privacy_system_can_sign(compose->privacy_system) == FALSE &&
1014 (account->default_encrypt || account->default_sign))
1015 alertpanel_error(_("You have opted to sign and/or encrypt this "
1016 "message but have not selected a privacy system.\n\n"
1017 "Signing and encrypting have been disabled for this "
1020 /* override from name if mailto asked for it */
1022 gtk_entry_set_text(GTK_ENTRY(compose->from_name), mailto_from);
1023 g_free(mailto_from);
1025 /* override from name according to folder properties */
1026 if (item && item->prefs &&
1027 item->prefs->compose_with_format &&
1028 item->prefs->compose_override_from_format &&
1029 *item->prefs->compose_override_from_format != '\0') {
1034 dummyinfo = compose_msginfo_new_from_compose(compose);
1036 /* decode \-escape sequences in the internal representation of the quote format */
1037 tmp = g_malloc(strlen(item->prefs->compose_override_from_format)+1);
1038 pref_get_unescaped_pref(tmp, item->prefs->compose_override_from_format);
1041 quote_fmt_init(dummyinfo, NULL, NULL, FALSE, compose->account, FALSE,
1042 compose->gtkaspell);
1044 quote_fmt_init(dummyinfo, NULL, NULL, FALSE, compose->account, FALSE);
1046 quote_fmt_scan_string(tmp);
1049 buf = quote_fmt_get_buffer();
1051 alertpanel_error(_("New message From format error."));
1053 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1054 quote_fmt_reset_vartable();
1055 quote_fmtlex_destroy();
1060 compose->replyinfo = NULL;
1061 compose->fwdinfo = NULL;
1063 textview = GTK_TEXT_VIEW(compose->text);
1064 textbuf = gtk_text_view_get_buffer(textview);
1065 compose_create_tags(textview, compose);
1067 undo_block(compose->undostruct);
1069 compose_set_dictionaries_from_folder_prefs(compose, item);
1072 if (account->auto_sig)
1073 compose_insert_sig(compose, FALSE);
1074 gtk_text_buffer_get_start_iter(textbuf, &iter);
1075 gtk_text_buffer_place_cursor(textbuf, &iter);
1077 if (account->protocol != A_NNTP) {
1078 if (mailto && *mailto != '\0') {
1079 mfield = compose_entries_set(compose, mailto, COMPOSE_TO);
1082 compose_set_folder_prefs(compose, item, TRUE);
1084 if (item && item->ret_rcpt) {
1085 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
1088 if (mailto && *mailto != '\0') {
1089 if (!strchr(mailto, '@'))
1090 mfield = compose_entries_set(compose, mailto, COMPOSE_NEWSGROUPS);
1092 mfield = compose_entries_set(compose, mailto, COMPOSE_TO);
1093 } else if (item && FOLDER_CLASS(item->folder) == news_get_class()) {
1094 compose_entry_append(compose, item->path, COMPOSE_NEWSGROUPS, PREF_FOLDER);
1095 mfield = TO_FIELD_PRESENT;
1098 * CLAWS: just don't allow return receipt request, even if the user
1099 * may want to send an email. simple but foolproof.
1101 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", FALSE);
1103 compose_add_field_list( compose, listAddress );
1105 if (item && item->prefs && item->prefs->compose_with_format) {
1106 subject_format = item->prefs->compose_subject_format;
1107 body_format = item->prefs->compose_body_format;
1108 } else if (account->compose_with_format) {
1109 subject_format = account->compose_subject_format;
1110 body_format = account->compose_body_format;
1111 } else if (prefs_common.compose_with_format) {
1112 subject_format = prefs_common.compose_subject_format;
1113 body_format = prefs_common.compose_body_format;
1116 if (subject_format || body_format) {
1119 && *subject_format != '\0' )
1121 gchar *subject = NULL;
1126 dummyinfo = compose_msginfo_new_from_compose(compose);
1128 /* decode \-escape sequences in the internal representation of the quote format */
1129 tmp = g_malloc(strlen(subject_format)+1);
1130 pref_get_unescaped_pref(tmp, subject_format);
1132 subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1134 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account, FALSE,
1135 compose->gtkaspell);
1137 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account, FALSE);
1139 quote_fmt_scan_string(tmp);
1142 buf = quote_fmt_get_buffer();
1144 alertpanel_error(_("New message subject format error."));
1146 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1147 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1148 quote_fmt_reset_vartable();
1149 quote_fmtlex_destroy();
1153 mfield = SUBJECT_FIELD_PRESENT;
1157 && *body_format != '\0' )
1160 GtkTextBuffer *buffer;
1161 GtkTextIter start, end;
1165 dummyinfo = compose_msginfo_new_from_compose(compose);
1167 text = GTK_TEXT_VIEW(compose->text);
1168 buffer = gtk_text_view_get_buffer(text);
1169 gtk_text_buffer_get_start_iter(buffer, &start);
1170 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
1171 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
1173 compose_quote_fmt(compose, dummyinfo,
1175 NULL, tmp, FALSE, TRUE,
1176 _("The body of the \"New message\" template has an error at line %d."));
1177 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1178 quote_fmt_reset_vartable();
1182 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1183 gtkaspell_highlight_all(compose->gtkaspell);
1185 mfield = BODY_FIELD_PRESENT;
1189 procmsg_msginfo_free( &dummyinfo );
1195 for (curr = attach_files ; curr != NULL ; curr = curr->next) {
1196 ainfo = (AttachInfo *) curr->data;
1198 compose_insert_file(compose, ainfo->file);
1200 compose_attach_append(compose, ainfo->file, ainfo->file,
1201 ainfo->content_type, ainfo->charset);
1205 compose_show_first_last_header(compose, TRUE);
1207 /* Set save folder */
1208 if (item && item->prefs && item->prefs->save_copy_to_folder) {
1209 gchar *folderidentifier;
1211 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1212 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo), TRUE);
1213 folderidentifier = folder_item_get_identifier(item);
1214 compose_set_save_to(compose, folderidentifier);
1215 g_free(folderidentifier);
1218 /* Place cursor according to provided input (mfield) */
1220 case NO_FIELD_PRESENT:
1221 if (compose->header_last)
1222 gtk_widget_grab_focus(compose->header_last->entry);
1224 case TO_FIELD_PRESENT:
1225 buf = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1227 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1230 gtk_widget_grab_focus(compose->subject_entry);
1232 case SUBJECT_FIELD_PRESENT:
1233 textview = GTK_TEXT_VIEW(compose->text);
1236 textbuf = gtk_text_view_get_buffer(textview);
1239 mark = gtk_text_buffer_get_insert(textbuf);
1240 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
1241 gtk_text_buffer_insert(textbuf, &iter, "", -1);
1243 * SUBJECT_FIELD_PRESENT and BODY_FIELD_PRESENT
1244 * only defers where it comes to the variable body
1245 * is not null. If no body is present compose->text
1246 * will be null in which case you cannot place the
1247 * cursor inside the component so. An empty component
1248 * is therefore created before placing the cursor
1250 case BODY_FIELD_PRESENT:
1251 cursor_pos = quote_fmt_get_cursor_pos();
1252 if (cursor_pos == -1)
1253 gtk_widget_grab_focus(compose->header_last->entry);
1255 gtk_widget_grab_focus(compose->text);
1259 undo_unblock(compose->undostruct);
1261 if (prefs_common.auto_exteditor)
1262 compose_exec_ext_editor(compose);
1264 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
1266 SCROLL_TO_CURSOR(compose);
1268 compose->modified = FALSE;
1269 compose_set_title(compose);
1271 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
1276 static void compose_force_encryption(Compose *compose, PrefsAccount *account,
1277 gboolean override_pref, const gchar *system)
1279 const gchar *privacy = NULL;
1281 cm_return_if_fail(compose != NULL);
1282 cm_return_if_fail(account != NULL);
1284 if (privacy_system_can_encrypt(compose->privacy_system) == FALSE ||
1285 (override_pref == FALSE && account->default_encrypt_reply == FALSE))
1288 if (account->default_privacy_system && strlen(account->default_privacy_system))
1289 privacy = account->default_privacy_system;
1293 GSList *privacy_avail = privacy_get_system_ids();
1294 if (privacy_avail && g_slist_length(privacy_avail)) {
1295 privacy = (gchar *)(privacy_avail->data);
1297 g_slist_free_full(privacy_avail, g_free);
1299 if (privacy != NULL) {
1301 g_free(compose->privacy_system);
1302 compose->privacy_system = NULL;
1303 g_free(compose->encdata);
1304 compose->encdata = NULL;
1306 if (compose->privacy_system == NULL)
1307 compose->privacy_system = g_strdup(privacy);
1308 else if (*(compose->privacy_system) == '\0') {
1309 g_free(compose->privacy_system);
1310 g_free(compose->encdata);
1311 compose->encdata = NULL;
1312 compose->privacy_system = g_strdup(privacy);
1314 compose_update_privacy_system_menu_item(compose, FALSE);
1315 compose_use_encryption(compose, TRUE);
1319 static void compose_force_signing(Compose *compose, PrefsAccount *account, const gchar *system)
1321 const gchar *privacy = NULL;
1322 if (privacy_system_can_sign(compose->privacy_system) == FALSE)
1325 if (account->default_privacy_system && strlen(account->default_privacy_system))
1326 privacy = account->default_privacy_system;
1330 GSList *privacy_avail = privacy_get_system_ids();
1331 if (privacy_avail && g_slist_length(privacy_avail)) {
1332 privacy = (gchar *)(privacy_avail->data);
1336 if (privacy != NULL) {
1338 g_free(compose->privacy_system);
1339 compose->privacy_system = NULL;
1340 g_free(compose->encdata);
1341 compose->encdata = NULL;
1343 if (compose->privacy_system == NULL)
1344 compose->privacy_system = g_strdup(privacy);
1345 compose_update_privacy_system_menu_item(compose, FALSE);
1346 compose_use_signing(compose, TRUE);
1350 static Compose *compose_reply_mode(ComposeMode mode, GSList *msginfo_list, gchar *body)
1354 Compose *compose = NULL;
1356 cm_return_val_if_fail(msginfo_list != NULL, NULL);
1358 msginfo = (MsgInfo*)g_slist_nth_data(msginfo_list, 0);
1359 cm_return_val_if_fail(msginfo != NULL, NULL);
1361 list_len = g_slist_length(msginfo_list);
1365 case COMPOSE_REPLY_TO_ADDRESS:
1366 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1367 FALSE, prefs_common.default_reply_list, FALSE, body);
1369 case COMPOSE_REPLY_WITH_QUOTE:
1370 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1371 FALSE, prefs_common.default_reply_list, FALSE, body);
1373 case COMPOSE_REPLY_WITHOUT_QUOTE:
1374 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1375 FALSE, prefs_common.default_reply_list, FALSE, NULL);
1377 case COMPOSE_REPLY_TO_SENDER:
1378 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1379 FALSE, FALSE, TRUE, body);
1381 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1382 compose = compose_followup_and_reply_to(msginfo,
1383 COMPOSE_QUOTE_CHECK,
1384 FALSE, FALSE, body);
1386 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1387 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1388 FALSE, FALSE, TRUE, body);
1390 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1391 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1392 FALSE, FALSE, TRUE, NULL);
1394 case COMPOSE_REPLY_TO_ALL:
1395 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1396 TRUE, FALSE, FALSE, body);
1398 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1399 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1400 TRUE, FALSE, FALSE, body);
1402 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1403 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1404 TRUE, FALSE, FALSE, NULL);
1406 case COMPOSE_REPLY_TO_LIST:
1407 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1408 FALSE, TRUE, FALSE, body);
1410 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1411 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1412 FALSE, TRUE, FALSE, body);
1414 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1415 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1416 FALSE, TRUE, FALSE, NULL);
1418 case COMPOSE_FORWARD:
1419 if (prefs_common.forward_as_attachment) {
1420 compose = compose_reply_mode(COMPOSE_FORWARD_AS_ATTACH, msginfo_list, body);
1423 compose = compose_reply_mode(COMPOSE_FORWARD_INLINE, msginfo_list, body);
1427 case COMPOSE_FORWARD_INLINE:
1428 /* check if we reply to more than one Message */
1429 if (list_len == 1) {
1430 compose = compose_forward(NULL, msginfo, FALSE, body, FALSE, FALSE);
1433 /* more messages FALL THROUGH */
1434 case COMPOSE_FORWARD_AS_ATTACH:
1435 compose = compose_forward_multiple(NULL, msginfo_list);
1437 case COMPOSE_REDIRECT:
1438 compose = compose_redirect(NULL, msginfo, FALSE);
1441 g_warning("compose_reply_mode(): invalid Compose Mode: %d", mode);
1444 if (compose == NULL) {
1445 alertpanel_error(_("Unable to reply. The original email probably doesn't exist."));
1449 compose->rmode = mode;
1450 switch (compose->rmode) {
1452 case COMPOSE_REPLY_WITH_QUOTE:
1453 case COMPOSE_REPLY_WITHOUT_QUOTE:
1454 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1455 debug_print("reply mode Normal\n");
1456 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/Normal", TRUE);
1457 compose_reply_change_mode(compose, COMPOSE_REPLY); /* force update */
1459 case COMPOSE_REPLY_TO_SENDER:
1460 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1461 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1462 debug_print("reply mode Sender\n");
1463 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/Sender", TRUE);
1465 case COMPOSE_REPLY_TO_ALL:
1466 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1467 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1468 debug_print("reply mode All\n");
1469 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/All", TRUE);
1471 case COMPOSE_REPLY_TO_LIST:
1472 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1473 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1474 debug_print("reply mode List\n");
1475 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/List", TRUE);
1477 case COMPOSE_REPLY_TO_ADDRESS:
1478 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", FALSE);
1486 static Compose *compose_reply(MsgInfo *msginfo,
1487 ComposeQuoteMode quote_mode,
1493 return compose_generic_reply(msginfo, quote_mode, to_all, to_ml,
1494 to_sender, FALSE, body);
1497 static Compose *compose_followup_and_reply_to(MsgInfo *msginfo,
1498 ComposeQuoteMode quote_mode,
1503 return compose_generic_reply(msginfo, quote_mode, to_all, FALSE,
1504 to_sender, TRUE, body);
1507 static void compose_extract_original_charset(Compose *compose)
1509 MsgInfo *info = NULL;
1510 if (compose->replyinfo) {
1511 info = compose->replyinfo;
1512 } else if (compose->fwdinfo) {
1513 info = compose->fwdinfo;
1514 } else if (compose->targetinfo) {
1515 info = compose->targetinfo;
1518 MimeInfo *mimeinfo = procmime_scan_message_short(info);
1519 MimeInfo *partinfo = mimeinfo;
1520 while (partinfo && partinfo->type != MIMETYPE_TEXT)
1521 partinfo = procmime_mimeinfo_next(partinfo);
1523 compose->orig_charset =
1524 g_strdup(procmime_mimeinfo_get_parameter(
1525 partinfo, "charset"));
1527 procmime_mimeinfo_free_all(&mimeinfo);
1531 #define SIGNAL_BLOCK(buffer) { \
1532 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1533 G_CALLBACK(compose_changed_cb), \
1535 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1536 G_CALLBACK(text_inserted), \
1540 #define SIGNAL_UNBLOCK(buffer) { \
1541 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1542 G_CALLBACK(compose_changed_cb), \
1544 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1545 G_CALLBACK(text_inserted), \
1549 static Compose *compose_generic_reply(MsgInfo *msginfo,
1550 ComposeQuoteMode quote_mode,
1551 gboolean to_all, gboolean to_ml,
1553 gboolean followup_and_reply_to,
1557 PrefsAccount *account = NULL;
1558 GtkTextView *textview;
1559 GtkTextBuffer *textbuf;
1560 gboolean quote = FALSE;
1561 const gchar *qmark = NULL;
1562 const gchar *body_fmt = NULL;
1563 gchar *s_system = NULL;
1565 cm_return_val_if_fail(msginfo != NULL, NULL);
1566 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
1568 account = account_get_reply_account(msginfo, prefs_common.reply_account_autosel);
1570 cm_return_val_if_fail(account != NULL, NULL);
1572 compose = compose_create(account, msginfo->folder, COMPOSE_REPLY, FALSE);
1573 compose_apply_folder_privacy_settings(compose, msginfo->folder);
1575 compose->updating = TRUE;
1577 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
1578 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", TRUE);
1580 compose->replyinfo = procmsg_msginfo_get_full_info(msginfo);
1581 if (!compose->replyinfo)
1582 compose->replyinfo = procmsg_msginfo_copy(msginfo);
1584 compose_extract_original_charset(compose);
1586 if (msginfo->folder && msginfo->folder->ret_rcpt)
1587 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
1589 /* Set save folder */
1590 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1591 gchar *folderidentifier;
1593 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1594 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo), TRUE);
1595 folderidentifier = folder_item_get_identifier(msginfo->folder);
1596 compose_set_save_to(compose, folderidentifier);
1597 g_free(folderidentifier);
1600 if (compose_parse_header(compose, msginfo) < 0) {
1601 compose->updating = FALSE;
1602 compose_destroy(compose);
1606 /* override from name according to folder properties */
1607 if (msginfo->folder && msginfo->folder->prefs &&
1608 msginfo->folder->prefs->reply_with_format &&
1609 msginfo->folder->prefs->reply_override_from_format &&
1610 *msginfo->folder->prefs->reply_override_from_format != '\0') {
1615 /* decode \-escape sequences in the internal representation of the quote format */
1616 tmp = g_malloc(strlen(msginfo->folder->prefs->reply_override_from_format)+1);
1617 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->reply_override_from_format);
1620 quote_fmt_init(compose->replyinfo, NULL, NULL, FALSE, compose->account, FALSE,
1621 compose->gtkaspell);
1623 quote_fmt_init(compose->replyinfo, NULL, NULL, FALSE, compose->account, FALSE);
1625 quote_fmt_scan_string(tmp);
1628 buf = quote_fmt_get_buffer();
1630 alertpanel_error(_("The \"From\" field of the \"Reply\" template contains an invalid email address."));
1632 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1633 quote_fmt_reset_vartable();
1634 quote_fmtlex_destroy();
1639 textview = (GTK_TEXT_VIEW(compose->text));
1640 textbuf = gtk_text_view_get_buffer(textview);
1641 compose_create_tags(textview, compose);
1643 undo_block(compose->undostruct);
1645 compose_set_dictionaries_from_folder_prefs(compose, msginfo->folder);
1646 gtkaspell_block_check(compose->gtkaspell);
1649 if (quote_mode == COMPOSE_QUOTE_FORCED ||
1650 (quote_mode == COMPOSE_QUOTE_CHECK && prefs_common.reply_with_quote)) {
1651 /* use the reply format of folder (if enabled), or the account's one
1652 (if enabled) or fallback to the global reply format, which is always
1653 enabled (even if empty), and use the relevant quotemark */
1655 if (msginfo->folder && msginfo->folder->prefs &&
1656 msginfo->folder->prefs->reply_with_format) {
1657 qmark = msginfo->folder->prefs->reply_quotemark;
1658 body_fmt = msginfo->folder->prefs->reply_body_format;
1660 } else if (account->reply_with_format) {
1661 qmark = account->reply_quotemark;
1662 body_fmt = account->reply_body_format;
1665 qmark = prefs_common.quotemark;
1666 if (prefs_common.quotefmt && *prefs_common.quotefmt)
1667 body_fmt = gettext(prefs_common.quotefmt);
1674 /* empty quotemark is not allowed */
1675 if (qmark == NULL || *qmark == '\0')
1677 compose_quote_fmt(compose, compose->replyinfo,
1678 body_fmt, qmark, body, FALSE, TRUE,
1679 _("The body of the \"Reply\" template has an error at line %d."));
1680 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1681 quote_fmt_reset_vartable();
1684 if (MSG_IS_ENCRYPTED(compose->replyinfo->flags)) {
1685 compose_force_encryption(compose, account, FALSE, s_system);
1688 privacy_msginfo_get_signed_state(compose->replyinfo, &s_system);
1689 if (MSG_IS_SIGNED(compose->replyinfo->flags) && account->default_sign_reply) {
1690 compose_force_signing(compose, account, s_system);
1694 if (privacy_system_can_sign(compose->privacy_system) == FALSE &&
1695 (account->default_encrypt_reply || account->default_sign_reply))
1696 alertpanel_error(_("You have opted to sign and/or encrypt this "
1697 "message but have not selected a privacy system.\n\n"
1698 "Signing and encrypting have been disabled for this "
1701 SIGNAL_BLOCK(textbuf);
1703 if (account->auto_sig)
1704 compose_insert_sig(compose, FALSE);
1706 compose_wrap_all(compose);
1709 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1710 gtkaspell_highlight_all(compose->gtkaspell);
1711 gtkaspell_unblock_check(compose->gtkaspell);
1713 SIGNAL_UNBLOCK(textbuf);
1715 gtk_widget_grab_focus(compose->text);
1717 undo_unblock(compose->undostruct);
1719 if (prefs_common.auto_exteditor)
1720 compose_exec_ext_editor(compose);
1722 compose->modified = FALSE;
1723 compose_set_title(compose);
1725 compose->updating = FALSE;
1726 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
1727 SCROLL_TO_CURSOR(compose);
1729 if (compose->deferred_destroy) {
1730 compose_destroy(compose);
1738 #define INSERT_FW_HEADER(var, hdr) \
1739 if (msginfo->var && *msginfo->var) { \
1740 gtk_stext_insert(text, NULL, NULL, NULL, hdr, -1); \
1741 gtk_stext_insert(text, NULL, NULL, NULL, msginfo->var, -1); \
1742 gtk_stext_insert(text, NULL, NULL, NULL, "\n", 1); \
1745 Compose *compose_forward(PrefsAccount *account, MsgInfo *msginfo,
1746 gboolean as_attach, const gchar *body,
1747 gboolean no_extedit,
1751 GtkTextView *textview;
1752 GtkTextBuffer *textbuf;
1753 gint cursor_pos = -1;
1756 cm_return_val_if_fail(msginfo != NULL, NULL);
1757 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
1759 if (!account && !(account = compose_find_account(msginfo)))
1760 account = cur_account;
1762 if (!prefs_common.forward_as_attachment)
1763 mode = COMPOSE_FORWARD_INLINE;
1765 mode = COMPOSE_FORWARD;
1766 compose = compose_create(account, msginfo->folder, mode, batch);
1767 compose_apply_folder_privacy_settings(compose, msginfo->folder);
1769 compose->updating = TRUE;
1770 compose->fwdinfo = procmsg_msginfo_get_full_info(msginfo);
1771 if (!compose->fwdinfo)
1772 compose->fwdinfo = procmsg_msginfo_copy(msginfo);
1774 compose_extract_original_charset(compose);
1776 if (msginfo->subject && *msginfo->subject) {
1777 gchar *buf, *buf2, *p;
1779 buf = p = g_strdup(msginfo->subject);
1780 p += subject_get_prefix_length(p);
1781 memmove(buf, p, strlen(p) + 1);
1783 buf2 = g_strdup_printf("Fw: %s", buf);
1784 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
1790 /* override from name according to folder properties */
1791 if (msginfo->folder && msginfo->folder->prefs &&
1792 msginfo->folder->prefs->forward_with_format &&
1793 msginfo->folder->prefs->forward_override_from_format &&
1794 *msginfo->folder->prefs->forward_override_from_format != '\0') {
1798 MsgInfo *full_msginfo = NULL;
1801 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1803 full_msginfo = procmsg_msginfo_copy(msginfo);
1805 /* decode \-escape sequences in the internal representation of the quote format */
1806 tmp = g_malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
1807 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
1810 gtkaspell_block_check(compose->gtkaspell);
1811 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE,
1812 compose->gtkaspell);
1814 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE);
1816 quote_fmt_scan_string(tmp);
1819 buf = quote_fmt_get_buffer();
1821 alertpanel_error(_("The \"From\" field of the \"Forward\" template contains an invalid email address."));
1823 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1824 quote_fmt_reset_vartable();
1825 quote_fmtlex_destroy();
1828 procmsg_msginfo_free(&full_msginfo);
1831 textview = GTK_TEXT_VIEW(compose->text);
1832 textbuf = gtk_text_view_get_buffer(textview);
1833 compose_create_tags(textview, compose);
1835 undo_block(compose->undostruct);
1839 msgfile = procmsg_get_message_file(msginfo);
1840 if (!is_file_exist(msgfile))
1841 g_warning("%s: file does not exist", msgfile);
1843 compose_attach_append(compose, msgfile, msgfile,
1844 "message/rfc822", NULL);
1848 const gchar *qmark = NULL;
1849 const gchar *body_fmt = NULL;
1850 MsgInfo *full_msginfo;
1852 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1854 full_msginfo = procmsg_msginfo_copy(msginfo);
1856 /* use the forward format of folder (if enabled), or the account's one
1857 (if enabled) or fallback to the global forward format, which is always
1858 enabled (even if empty), and use the relevant quotemark */
1859 if (msginfo->folder && msginfo->folder->prefs &&
1860 msginfo->folder->prefs->forward_with_format) {
1861 qmark = msginfo->folder->prefs->forward_quotemark;
1862 body_fmt = msginfo->folder->prefs->forward_body_format;
1864 } else if (account->forward_with_format) {
1865 qmark = account->forward_quotemark;
1866 body_fmt = account->forward_body_format;
1869 qmark = prefs_common.fw_quotemark;
1870 if (prefs_common.fw_quotefmt && *prefs_common.fw_quotefmt)
1871 body_fmt = gettext(prefs_common.fw_quotefmt);
1876 /* empty quotemark is not allowed */
1877 if (qmark == NULL || *qmark == '\0')
1880 compose_quote_fmt(compose, full_msginfo,
1881 body_fmt, qmark, body, FALSE, TRUE,
1882 _("The body of the \"Forward\" template has an error at line %d."));
1883 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1884 quote_fmt_reset_vartable();
1885 compose_attach_parts(compose, msginfo);
1887 procmsg_msginfo_free(&full_msginfo);
1890 SIGNAL_BLOCK(textbuf);
1892 if (account->auto_sig)
1893 compose_insert_sig(compose, FALSE);
1895 compose_wrap_all(compose);
1898 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1899 gtkaspell_highlight_all(compose->gtkaspell);
1900 gtkaspell_unblock_check(compose->gtkaspell);
1902 SIGNAL_UNBLOCK(textbuf);
1904 cursor_pos = quote_fmt_get_cursor_pos();
1905 if (cursor_pos == -1)
1906 gtk_widget_grab_focus(compose->header_last->entry);
1908 gtk_widget_grab_focus(compose->text);
1910 if (!no_extedit && prefs_common.auto_exteditor)
1911 compose_exec_ext_editor(compose);
1914 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1915 gchar *folderidentifier;
1917 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1918 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo), TRUE);
1919 folderidentifier = folder_item_get_identifier(msginfo->folder);
1920 compose_set_save_to(compose, folderidentifier);
1921 g_free(folderidentifier);
1924 undo_unblock(compose->undostruct);
1926 compose->modified = FALSE;
1927 compose_set_title(compose);
1929 compose->updating = FALSE;
1930 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
1931 SCROLL_TO_CURSOR(compose);
1933 if (compose->deferred_destroy) {
1934 compose_destroy(compose);
1938 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
1943 #undef INSERT_FW_HEADER
1945 static Compose *compose_forward_multiple(PrefsAccount *account, GSList *msginfo_list)
1948 GtkTextView *textview;
1949 GtkTextBuffer *textbuf;
1953 gboolean single_mail = TRUE;
1955 cm_return_val_if_fail(msginfo_list != NULL, NULL);
1957 if (g_slist_length(msginfo_list) > 1)
1958 single_mail = FALSE;
1960 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next)
1961 if (((MsgInfo *)msginfo->data)->folder == NULL)
1964 /* guess account from first selected message */
1966 !(account = compose_find_account(msginfo_list->data)))
1967 account = cur_account;
1969 cm_return_val_if_fail(account != NULL, NULL);
1971 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
1972 if (msginfo->data) {
1973 MSG_UNSET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_REPLIED);
1974 MSG_SET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_FORWARDED);
1978 if (msginfo_list == NULL || msginfo_list->data == NULL) {
1979 g_warning("no msginfo_list");
1983 compose = compose_create(account, ((MsgInfo *)msginfo_list->data)->folder, COMPOSE_FORWARD, FALSE);
1984 compose_apply_folder_privacy_settings(compose, ((MsgInfo *)msginfo_list->data)->folder);
1986 compose->updating = TRUE;
1988 /* override from name according to folder properties */
1989 if (msginfo_list->data) {
1990 MsgInfo *msginfo = msginfo_list->data;
1992 if (msginfo->folder && msginfo->folder->prefs &&
1993 msginfo->folder->prefs->forward_with_format &&
1994 msginfo->folder->prefs->forward_override_from_format &&
1995 *msginfo->folder->prefs->forward_override_from_format != '\0') {
2000 /* decode \-escape sequences in the internal representation of the quote format */
2001 tmp = g_malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
2002 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
2005 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
2006 compose->gtkaspell);
2008 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
2010 quote_fmt_scan_string(tmp);
2013 buf = quote_fmt_get_buffer();
2015 alertpanel_error(_("The \"From\" field of the \"Forward\" template contains an invalid email address."));
2017 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
2018 quote_fmt_reset_vartable();
2019 quote_fmtlex_destroy();
2025 textview = GTK_TEXT_VIEW(compose->text);
2026 textbuf = gtk_text_view_get_buffer(textview);
2027 compose_create_tags(textview, compose);
2029 undo_block(compose->undostruct);
2030 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
2031 msgfile = procmsg_get_message_file((MsgInfo *)msginfo->data);
2033 if (!is_file_exist(msgfile))
2034 g_warning("%s: file does not exist", msgfile);
2036 compose_attach_append(compose, msgfile, msgfile,
2037 "message/rfc822", NULL);
2042 MsgInfo *info = (MsgInfo *)msginfo_list->data;
2043 if (info->subject && *info->subject) {
2044 gchar *buf, *buf2, *p;
2046 buf = p = g_strdup(info->subject);
2047 p += subject_get_prefix_length(p);
2048 memmove(buf, p, strlen(p) + 1);
2050 buf2 = g_strdup_printf("Fw: %s", buf);
2051 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
2057 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2058 _("Fw: multiple emails"));
2061 SIGNAL_BLOCK(textbuf);
2063 if (account->auto_sig)
2064 compose_insert_sig(compose, FALSE);
2066 compose_wrap_all(compose);
2068 SIGNAL_UNBLOCK(textbuf);
2070 gtk_text_buffer_get_start_iter(textbuf, &iter);
2071 gtk_text_buffer_place_cursor(textbuf, &iter);
2073 if (prefs_common.auto_exteditor)
2074 compose_exec_ext_editor(compose);
2076 gtk_widget_grab_focus(compose->header_last->entry);
2077 undo_unblock(compose->undostruct);
2078 compose->modified = FALSE;
2079 compose_set_title(compose);
2081 compose->updating = FALSE;
2082 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2083 SCROLL_TO_CURSOR(compose);
2085 if (compose->deferred_destroy) {
2086 compose_destroy(compose);
2090 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2095 static gboolean compose_is_sig_separator(Compose *compose, GtkTextBuffer *textbuf, GtkTextIter *iter)
2097 GtkTextIter start = *iter;
2098 GtkTextIter end_iter;
2099 int start_pos = gtk_text_iter_get_offset(&start);
2101 if (!compose->account->sig_sep)
2104 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
2105 start_pos+strlen(compose->account->sig_sep));
2107 /* check sig separator */
2108 str = gtk_text_iter_get_text(&start, &end_iter);
2109 if (!strcmp(str, compose->account->sig_sep)) {
2111 /* check end of line (\n) */
2112 gtk_text_buffer_get_iter_at_offset(textbuf, &start,
2113 start_pos+strlen(compose->account->sig_sep));
2114 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
2115 start_pos+strlen(compose->account->sig_sep)+1);
2116 tmp = gtk_text_iter_get_text(&start, &end_iter);
2117 if (!strcmp(tmp,"\n")) {
2129 static gboolean compose_update_folder_hook(gpointer source, gpointer data)
2131 FolderUpdateData *hookdata = (FolderUpdateData *)source;
2132 Compose *compose = (Compose *)data;
2133 FolderItem *old_item = NULL;
2134 FolderItem *new_item = NULL;
2135 gchar *old_id, *new_id;
2137 if (!(hookdata->update_flags & FOLDER_REMOVE_FOLDERITEM)
2138 && !(hookdata->update_flags & FOLDER_MOVE_FOLDERITEM))
2141 old_item = hookdata->item;
2142 new_item = hookdata->item2;
2144 old_id = folder_item_get_identifier(old_item);
2145 new_id = new_item ? folder_item_get_identifier(new_item) : g_strdup("NULL");
2147 if (compose->targetinfo && compose->targetinfo->folder == old_item) {
2148 debug_print("updating targetinfo folder: %s -> %s\n", old_id, new_id);
2149 compose->targetinfo->folder = new_item;
2152 if (compose->replyinfo && compose->replyinfo->folder == old_item) {
2153 debug_print("updating replyinfo folder: %s -> %s\n", old_id, new_id);
2154 compose->replyinfo->folder = new_item;
2157 if (compose->fwdinfo && compose->fwdinfo->folder == old_item) {
2158 debug_print("updating fwdinfo folder: %s -> %s\n", old_id, new_id);
2159 compose->fwdinfo->folder = new_item;
2167 static void compose_colorize_signature(Compose *compose)
2169 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
2171 GtkTextIter end_iter;
2172 gtk_text_buffer_get_start_iter(buffer, &iter);
2173 while (gtk_text_iter_forward_line(&iter))
2174 if (compose_is_sig_separator(compose, buffer, &iter)) {
2175 gtk_text_buffer_get_end_iter(buffer, &end_iter);
2176 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &end_iter);
2180 #define BLOCK_WRAP() { \
2181 prev_autowrap = compose->autowrap; \
2182 buffer = gtk_text_view_get_buffer( \
2183 GTK_TEXT_VIEW(compose->text)); \
2184 compose->autowrap = FALSE; \
2186 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2187 G_CALLBACK(compose_changed_cb), \
2189 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2190 G_CALLBACK(text_inserted), \
2193 #define UNBLOCK_WRAP() { \
2194 compose->autowrap = prev_autowrap; \
2195 if (compose->autowrap) { \
2196 gint old = compose->draft_timeout_tag; \
2197 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN; \
2198 compose_wrap_all(compose); \
2199 compose->draft_timeout_tag = old; \
2202 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2203 G_CALLBACK(compose_changed_cb), \
2205 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2206 G_CALLBACK(text_inserted), \
2210 Compose *compose_reedit(MsgInfo *msginfo, gboolean batch)
2212 Compose *compose = NULL;
2213 PrefsAccount *account = NULL;
2214 GtkTextView *textview;
2215 GtkTextBuffer *textbuf;
2219 gboolean use_signing = FALSE;
2220 gboolean use_encryption = FALSE;
2221 gchar *privacy_system = NULL;
2222 int priority = PRIORITY_NORMAL;
2223 MsgInfo *replyinfo = NULL, *fwdinfo = NULL;
2224 gboolean autowrap = prefs_common.autowrap;
2225 gboolean autoindent = prefs_common.auto_indent;
2226 HeaderEntry *manual_headers = NULL;
2228 cm_return_val_if_fail(msginfo != NULL, NULL);
2229 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
2231 if (compose_put_existing_to_front(msginfo)) {
2235 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2236 folder_has_parent_of_type(msginfo->folder, F_DRAFT) ||
2237 folder_has_parent_of_type(msginfo->folder, F_OUTBOX)) {
2238 gchar *queueheader_buf = NULL;
2241 /* Select Account from queue headers */
2242 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2243 "X-Claws-Account-Id:")) {
2244 id = atoi(&queueheader_buf[strlen("X-Claws-Account-Id:")]);
2245 account = account_find_from_id(id);
2246 g_free(queueheader_buf);
2248 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2249 "X-Sylpheed-Account-Id:")) {
2250 id = atoi(&queueheader_buf[strlen("X-Sylpheed-Account-Id:")]);
2251 account = account_find_from_id(id);
2252 g_free(queueheader_buf);
2254 if (!account && !procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2256 id = atoi(&queueheader_buf[strlen("NAID:")]);
2257 account = account_find_from_id(id);
2258 g_free(queueheader_buf);
2260 if (!account && !procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2262 id = atoi(&queueheader_buf[strlen("MAID:")]);
2263 account = account_find_from_id(id);
2264 g_free(queueheader_buf);
2266 if (!account && !procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2268 account = account_find_from_address(queueheader_buf, FALSE);
2269 g_free(queueheader_buf);
2271 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2273 param = atoi(&queueheader_buf[strlen("X-Claws-Sign:")]);
2274 use_signing = param;
2275 g_free(queueheader_buf);
2277 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2278 "X-Sylpheed-Sign:")) {
2279 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Sign:")]);
2280 use_signing = param;
2281 g_free(queueheader_buf);
2283 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2284 "X-Claws-Encrypt:")) {
2285 param = atoi(&queueheader_buf[strlen("X-Claws-Encrypt:")]);
2286 use_encryption = param;
2287 g_free(queueheader_buf);
2289 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2290 "X-Sylpheed-Encrypt:")) {
2291 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Encrypt:")]);
2292 use_encryption = param;
2293 g_free(queueheader_buf);
2295 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2296 "X-Claws-Auto-Wrapping:")) {
2297 param = atoi(&queueheader_buf[strlen("X-Claws-Auto-Wrapping:")]);
2299 g_free(queueheader_buf);
2301 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2302 "X-Claws-Auto-Indent:")) {
2303 param = atoi(&queueheader_buf[strlen("X-Claws-Auto-Indent:")]);
2305 g_free(queueheader_buf);
2307 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2308 "X-Claws-Privacy-System:")) {
2309 privacy_system = g_strdup(&queueheader_buf[strlen("X-Claws-Privacy-System:")]);
2310 g_free(queueheader_buf);
2312 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2313 "X-Sylpheed-Privacy-System:")) {
2314 privacy_system = g_strdup(&queueheader_buf[strlen("X-Sylpheed-Privacy-System:")]);
2315 g_free(queueheader_buf);
2317 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2319 param = atoi(&queueheader_buf[strlen("X-Priority: ")]); /* mind the space */
2321 g_free(queueheader_buf);
2323 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2325 gchar **tokens = g_strsplit(&queueheader_buf[strlen("RMID:")], "\t", 0);
2326 if (tokens && tokens[0] && tokens[1] && tokens[2]) {
2327 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2328 if (orig_item != NULL) {
2329 replyinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2333 g_free(queueheader_buf);
2335 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2337 gchar **tokens = g_strsplit(&queueheader_buf[strlen("FMID:")], "\t", 0);
2338 if (tokens && tokens[0] && tokens[1] && tokens[2]) {
2339 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2340 if (orig_item != NULL) {
2341 fwdinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2345 g_free(queueheader_buf);
2347 /* Get manual headers */
2348 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2349 "X-Claws-Manual-Headers:")) {
2350 gchar *listmh = g_strdup(&queueheader_buf[strlen("X-Claws-Manual-Headers:")]);
2351 if (listmh && *listmh != '\0') {
2352 debug_print("Got manual headers: %s\n", listmh);
2353 manual_headers = procheader_entries_from_str(listmh);
2356 g_free(queueheader_buf);
2359 account = msginfo->folder->folder->account;
2362 if (!account && prefs_common.reedit_account_autosel) {
2364 if (!procheader_get_header_from_msginfo(msginfo, &from, "FROM:")) {
2365 extract_address(from);
2366 account = account_find_from_address(from, FALSE);
2371 account = cur_account;
2373 cm_return_val_if_fail(account != NULL, NULL);
2375 compose = compose_create(account, msginfo->folder, COMPOSE_REEDIT, batch);
2377 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", autowrap);
2378 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", autoindent);
2379 compose->autowrap = autowrap;
2380 compose->replyinfo = replyinfo;
2381 compose->fwdinfo = fwdinfo;
2383 compose->updating = TRUE;
2384 compose->priority = priority;
2386 if (privacy_system != NULL) {
2387 compose->privacy_system = privacy_system;
2388 compose_use_signing(compose, use_signing);
2389 compose_use_encryption(compose, use_encryption);
2390 compose_update_privacy_system_menu_item(compose, FALSE);
2392 compose_activate_privacy_system(compose, account, FALSE);
2394 compose_apply_folder_privacy_settings(compose, msginfo->folder);
2396 compose->targetinfo = procmsg_msginfo_copy(msginfo);
2397 compose->targetinfo->tags = g_slist_copy(msginfo->tags);
2399 compose_extract_original_charset(compose);
2401 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2402 folder_has_parent_of_type(msginfo->folder, F_DRAFT) ||
2403 folder_has_parent_of_type(msginfo->folder, F_OUTBOX)) {
2404 gchar *queueheader_buf = NULL;
2406 /* Set message save folder */
2407 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf, "SCF:")) {
2408 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
2409 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo), TRUE);
2410 compose_set_save_to(compose, &queueheader_buf[4]);
2411 g_free(queueheader_buf);
2413 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf, "RRCPT:")) {
2414 gint active = atoi(&queueheader_buf[strlen("RRCPT:")]);
2416 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
2418 g_free(queueheader_buf);
2422 if (compose_parse_header(compose, msginfo) < 0) {
2423 compose->updating = FALSE;
2424 compose_destroy(compose);
2427 compose_reedit_set_entry(compose, msginfo);
2429 textview = GTK_TEXT_VIEW(compose->text);
2430 textbuf = gtk_text_view_get_buffer(textview);
2431 compose_create_tags(textview, compose);
2433 mark = gtk_text_buffer_get_insert(textbuf);
2434 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
2436 g_signal_handlers_block_by_func(G_OBJECT(textbuf),
2437 G_CALLBACK(compose_changed_cb),
2440 if (MSG_IS_ENCRYPTED(msginfo->flags)) {
2441 fp = procmime_get_first_encrypted_text_content(msginfo);
2443 compose_force_encryption(compose, account, TRUE, NULL);
2446 fp = procmime_get_first_text_content(msginfo);
2449 g_warning("Can't get text part");
2453 gchar buf[BUFFSIZE];
2454 gboolean prev_autowrap;
2455 GtkTextBuffer *buffer;
2457 while (claws_fgets(buf, sizeof(buf), fp) != NULL) {
2459 gtk_text_buffer_insert(textbuf, &iter, buf, -1);
2465 compose_attach_parts(compose, msginfo);
2467 compose_colorize_signature(compose);
2469 g_signal_handlers_unblock_by_func(G_OBJECT(textbuf),
2470 G_CALLBACK(compose_changed_cb),
2473 if (manual_headers != NULL) {
2474 if (compose_parse_manual_headers(compose, msginfo, manual_headers) < 0) {
2475 procheader_entries_free(manual_headers);
2476 compose->updating = FALSE;
2477 compose_destroy(compose);
2480 procheader_entries_free(manual_headers);
2483 gtk_widget_grab_focus(compose->text);
2485 if (prefs_common.auto_exteditor) {
2486 compose_exec_ext_editor(compose);
2488 compose->modified = FALSE;
2489 compose_set_title(compose);
2491 compose->updating = FALSE;
2492 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2493 SCROLL_TO_CURSOR(compose);
2495 if (compose->deferred_destroy) {
2496 compose_destroy(compose);
2500 compose->sig_str = account_get_signature_str(compose->account);
2502 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2507 Compose *compose_redirect(PrefsAccount *account, MsgInfo *msginfo,
2514 cm_return_val_if_fail(msginfo != NULL, NULL);
2517 account = account_get_reply_account(msginfo,
2518 prefs_common.reply_account_autosel);
2519 cm_return_val_if_fail(account != NULL, NULL);
2521 compose = compose_create(account, msginfo->folder, COMPOSE_REDIRECT, batch);
2522 compose_apply_folder_privacy_settings(compose, msginfo->folder);
2524 compose->updating = TRUE;
2526 compose_create_tags(GTK_TEXT_VIEW(compose->text), compose);
2527 compose->replyinfo = NULL;
2528 compose->fwdinfo = NULL;
2530 compose_show_first_last_header(compose, TRUE);
2532 gtk_widget_grab_focus(compose->header_last->entry);
2534 filename = procmsg_get_message_file(msginfo);
2536 if (filename == NULL) {
2537 compose->updating = FALSE;
2538 compose_destroy(compose);
2543 compose->redirect_filename = filename;
2545 /* Set save folder */
2546 item = msginfo->folder;
2547 if (item && item->prefs && item->prefs->save_copy_to_folder) {
2548 gchar *folderidentifier;
2550 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
2551 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo), TRUE);
2552 folderidentifier = folder_item_get_identifier(item);
2553 compose_set_save_to(compose, folderidentifier);
2554 g_free(folderidentifier);
2557 compose_attach_parts(compose, msginfo);
2559 if (msginfo->subject)
2560 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2562 gtk_editable_set_editable(GTK_EDITABLE(compose->subject_entry), FALSE);
2564 compose_quote_fmt(compose, msginfo, "%M", NULL, NULL, FALSE, FALSE,
2565 _("The body of the \"Redirect\" template has an error at line %d."));
2566 quote_fmt_reset_vartable();
2567 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), FALSE);
2569 compose_colorize_signature(compose);
2572 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Add", FALSE);
2573 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", FALSE);
2574 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", FALSE);
2576 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/Save", FALSE);
2577 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertFile", FALSE);
2578 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/AttachFile", FALSE);
2579 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertSig", FALSE);
2580 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/ReplaceSig", FALSE);
2581 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", FALSE);
2582 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", FALSE);
2583 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/ShowRuler", FALSE);
2584 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Actions", FALSE);
2586 if (compose->toolbar->draft_btn)
2587 gtk_widget_set_sensitive(compose->toolbar->draft_btn, FALSE);
2588 if (compose->toolbar->insert_btn)
2589 gtk_widget_set_sensitive(compose->toolbar->insert_btn, FALSE);
2590 if (compose->toolbar->attach_btn)
2591 gtk_widget_set_sensitive(compose->toolbar->attach_btn, FALSE);
2592 if (compose->toolbar->sig_btn)
2593 gtk_widget_set_sensitive(compose->toolbar->sig_btn, FALSE);
2594 if (compose->toolbar->exteditor_btn)
2595 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, FALSE);
2596 if (compose->toolbar->linewrap_current_btn)
2597 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, FALSE);
2598 if (compose->toolbar->linewrap_all_btn)
2599 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, FALSE);
2601 compose->modified = FALSE;
2602 compose_set_title(compose);
2603 compose->updating = FALSE;
2604 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2605 SCROLL_TO_CURSOR(compose);
2607 if (compose->deferred_destroy) {
2608 compose_destroy(compose);
2612 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2617 const GList *compose_get_compose_list(void)
2619 return compose_list;
2622 void compose_entry_append(Compose *compose, const gchar *address,
2623 ComposeEntryType type, ComposePrefType pref_type)
2625 const gchar *header;
2627 gboolean in_quote = FALSE;
2628 if (!address || *address == '\0') return;
2635 header = N_("Bcc:");
2637 case COMPOSE_REPLYTO:
2638 header = N_("Reply-To:");
2640 case COMPOSE_NEWSGROUPS:
2641 header = N_("Newsgroups:");
2643 case COMPOSE_FOLLOWUPTO:
2644 header = N_( "Followup-To:");
2646 case COMPOSE_INREPLYTO:
2647 header = N_( "In-Reply-To:");
2654 header = prefs_common_translated_header_name(header);
2656 cur = begin = (gchar *)address;
2658 /* we separate the line by commas, but not if we're inside a quoted
2660 while (*cur != '\0') {
2662 in_quote = !in_quote;
2663 if (*cur == ',' && !in_quote) {
2664 gchar *tmp = g_strdup(begin);
2666 tmp[cur-begin]='\0';
2669 while (*tmp == ' ' || *tmp == '\t')
2671 compose_add_header_entry(compose, header, tmp, pref_type);
2672 compose_entry_indicate(compose, tmp);
2679 gchar *tmp = g_strdup(begin);
2681 tmp[cur-begin]='\0';
2682 while (*tmp == ' ' || *tmp == '\t')
2684 compose_add_header_entry(compose, header, tmp, pref_type);
2685 compose_entry_indicate(compose, tmp);
2690 static void compose_entry_indicate(Compose *compose, const gchar *mailto)
2695 for (h_list = compose->header_list; h_list != NULL; h_list = h_list->next) {
2696 entry = GTK_ENTRY(((ComposeHeaderEntry *)h_list->data)->entry);
2697 if (gtk_entry_get_text(entry) &&
2698 !g_utf8_collate(gtk_entry_get_text(entry), mailto)) {
2699 gtk_widget_modify_base(
2700 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2701 GTK_STATE_NORMAL, &default_header_bgcolor);
2702 gtk_widget_modify_text(
2703 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2704 GTK_STATE_NORMAL, &default_header_color);
2709 void compose_toolbar_cb(gint action, gpointer data)
2711 ToolbarItem *toolbar_item = (ToolbarItem*)data;
2712 Compose *compose = (Compose*)toolbar_item->parent;
2714 cm_return_if_fail(compose != NULL);
2718 compose_send_cb(NULL, compose);
2721 compose_send_later_cb(NULL, compose);
2724 compose_draft(compose, COMPOSE_QUIT_EDITING);
2727 compose_insert_file_cb(NULL, compose);
2730 compose_attach_cb(NULL, compose);
2733 compose_insert_sig(compose, FALSE);
2736 compose_insert_sig(compose, TRUE);
2739 compose_ext_editor_cb(NULL, compose);
2741 case A_LINEWRAP_CURRENT:
2742 compose_beautify_paragraph(compose, NULL, TRUE);
2744 case A_LINEWRAP_ALL:
2745 compose_wrap_all_full(compose, TRUE);
2748 compose_address_cb(NULL, compose);
2751 case A_CHECK_SPELLING:
2752 compose_check_all(NULL, compose);
2755 case A_PRIVACY_SIGN:
2757 case A_PRIVACY_ENCRYPT:
2764 static MailField compose_entries_set(Compose *compose, const gchar *mailto, ComposeEntryType to_type)
2769 gchar *subject = NULL;
2773 gchar **attach = NULL;
2774 gchar *inreplyto = NULL;
2775 MailField mfield = NO_FIELD_PRESENT;
2777 /* get mailto parts but skip from */
2778 scan_mailto_url(mailto, NULL, &to, &cc, &bcc, &subject, &body, &attach, &inreplyto);
2781 compose_entry_append(compose, to, to_type, PREF_MAILTO);
2782 mfield = TO_FIELD_PRESENT;
2785 compose_entry_append(compose, cc, COMPOSE_CC, PREF_MAILTO);
2787 compose_entry_append(compose, bcc, COMPOSE_BCC, PREF_MAILTO);
2789 if (!g_utf8_validate (subject, -1, NULL)) {
2790 temp = g_locale_to_utf8 (subject, -1, NULL, &len, NULL);
2791 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), temp);
2794 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), subject);
2796 mfield = SUBJECT_FIELD_PRESENT;
2799 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2800 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2803 gboolean prev_autowrap = compose->autowrap;
2805 compose->autowrap = FALSE;
2807 mark = gtk_text_buffer_get_insert(buffer);
2808 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2810 if (!g_utf8_validate (body, -1, NULL)) {
2811 temp = g_locale_to_utf8 (body, -1, NULL, &len, NULL);
2812 gtk_text_buffer_insert(buffer, &iter, temp, -1);
2815 gtk_text_buffer_insert(buffer, &iter, body, -1);
2817 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
2819 compose->autowrap = prev_autowrap;
2820 if (compose->autowrap)
2821 compose_wrap_all(compose);
2822 mfield = BODY_FIELD_PRESENT;
2826 gint i = 0, att = 0;
2827 gchar *warn_files = NULL;
2828 while (attach[i] != NULL) {
2829 gchar *utf8_filename = conv_filename_to_utf8(attach[i]);
2830 if (utf8_filename) {
2831 if (compose_attach_append(compose, attach[i], utf8_filename, NULL, NULL)) {
2832 gchar *tmp = g_strdup_printf("%s%s\n",
2833 warn_files?warn_files:"",
2839 g_free(utf8_filename);
2841 alertpanel_error(_("Couldn't attach a file (charset conversion failed)."));
2846 alertpanel_notice(ngettext(
2847 "The following file has been attached: \n%s",
2848 "The following files have been attached: \n%s", att), warn_files);
2853 compose_entry_append(compose, inreplyto, COMPOSE_INREPLYTO, PREF_MAILTO);
2866 static gint compose_parse_header(Compose *compose, MsgInfo *msginfo)
2868 static HeaderEntry hentry[] = {
2869 {"Reply-To:", NULL, TRUE },
2870 {"Cc:", NULL, TRUE },
2871 {"References:", NULL, FALSE },
2872 {"Bcc:", NULL, TRUE },
2873 {"Newsgroups:", NULL, TRUE },
2874 {"Followup-To:", NULL, TRUE },
2875 {"List-Post:", NULL, FALSE },
2876 {"X-Priority:", NULL, FALSE },
2877 {NULL, NULL, FALSE }
2894 cm_return_val_if_fail(msginfo != NULL, -1);
2896 if ((fp = procmsg_open_message(msginfo)) == NULL) return -1;
2897 procheader_get_header_fields(fp, hentry);
2900 if (hentry[H_REPLY_TO].body != NULL) {
2901 if (hentry[H_REPLY_TO].body[0] != '\0') {
2903 conv_unmime_header(hentry[H_REPLY_TO].body,
2906 g_free(hentry[H_REPLY_TO].body);
2907 hentry[H_REPLY_TO].body = NULL;
2909 if (hentry[H_CC].body != NULL) {
2910 compose->cc = conv_unmime_header(hentry[H_CC].body, NULL, TRUE);
2911 g_free(hentry[H_CC].body);
2912 hentry[H_CC].body = NULL;
2914 if (hentry[H_REFERENCES].body != NULL) {
2915 if (compose->mode == COMPOSE_REEDIT)
2916 compose->references = hentry[H_REFERENCES].body;
2918 compose->references = compose_parse_references
2919 (hentry[H_REFERENCES].body, msginfo->msgid);
2920 g_free(hentry[H_REFERENCES].body);
2922 hentry[H_REFERENCES].body = NULL;
2924 if (hentry[H_BCC].body != NULL) {
2925 if (compose->mode == COMPOSE_REEDIT)
2927 conv_unmime_header(hentry[H_BCC].body, NULL, TRUE);
2928 g_free(hentry[H_BCC].body);
2929 hentry[H_BCC].body = NULL;
2931 if (hentry[H_NEWSGROUPS].body != NULL) {
2932 compose->newsgroups = hentry[H_NEWSGROUPS].body;
2933 hentry[H_NEWSGROUPS].body = NULL;
2935 if (hentry[H_FOLLOWUP_TO].body != NULL) {
2936 if (hentry[H_FOLLOWUP_TO].body[0] != '\0') {
2937 compose->followup_to =
2938 conv_unmime_header(hentry[H_FOLLOWUP_TO].body,
2941 g_free(hentry[H_FOLLOWUP_TO].body);
2942 hentry[H_FOLLOWUP_TO].body = NULL;
2944 if (hentry[H_LIST_POST].body != NULL) {
2945 gchar *to = NULL, *start = NULL;
2947 extract_address(hentry[H_LIST_POST].body);
2948 if (hentry[H_LIST_POST].body[0] != '\0') {
2949 start = strstr(hentry[H_LIST_POST].body, "mailto:");
2951 scan_mailto_url(start ? start : hentry[H_LIST_POST].body,
2952 NULL, &to, NULL, NULL, NULL, NULL, NULL, NULL);
2955 g_free(compose->ml_post);
2956 compose->ml_post = to;
2959 g_free(hentry[H_LIST_POST].body);
2960 hentry[H_LIST_POST].body = NULL;
2963 /* CLAWS - X-Priority */
2964 if (compose->mode == COMPOSE_REEDIT)
2965 if (hentry[H_X_PRIORITY].body != NULL) {
2968 priority = atoi(hentry[H_X_PRIORITY].body);
2969 g_free(hentry[H_X_PRIORITY].body);
2971 hentry[H_X_PRIORITY].body = NULL;
2973 if (priority < PRIORITY_HIGHEST ||
2974 priority > PRIORITY_LOWEST)
2975 priority = PRIORITY_NORMAL;
2977 compose->priority = priority;
2980 if (compose->mode == COMPOSE_REEDIT) {
2981 if (msginfo->inreplyto && *msginfo->inreplyto)
2982 compose->inreplyto = g_strdup(msginfo->inreplyto);
2984 if (msginfo->msgid && *msginfo->msgid &&
2985 compose->folder != NULL &&
2986 compose->folder->stype == F_DRAFT)
2987 compose->msgid = g_strdup(msginfo->msgid);
2989 if (msginfo->msgid && *msginfo->msgid)
2990 compose->inreplyto = g_strdup(msginfo->msgid);
2992 if (!compose->references) {
2993 if (msginfo->msgid && *msginfo->msgid) {
2994 if (msginfo->inreplyto && *msginfo->inreplyto)
2995 compose->references =
2996 g_strdup_printf("<%s>\n\t<%s>",
3000 compose->references =
3001 g_strconcat("<", msginfo->msgid, ">",
3003 } else if (msginfo->inreplyto && *msginfo->inreplyto) {
3004 compose->references =
3005 g_strconcat("<", msginfo->inreplyto, ">",
3014 static gint compose_parse_manual_headers(Compose *compose, MsgInfo *msginfo, HeaderEntry *entries)
3019 cm_return_val_if_fail(msginfo != NULL, -1);
3021 if ((fp = procmsg_open_message(msginfo)) == NULL) return -1;
3022 procheader_get_header_fields(fp, entries);
3026 while (he != NULL && he->name != NULL) {
3028 GtkListStore *model = NULL;
3030 debug_print("Adding manual header: %s with value %s\n", he->name, he->body);
3031 model = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(compose->header_last->combo)));
3032 COMBOBOX_ADD(model, he->name, COMPOSE_TO);
3033 gtk_combo_box_set_active_iter(GTK_COMBO_BOX(compose->header_last->combo), &iter);
3034 gtk_entry_set_text(GTK_ENTRY(compose->header_last->entry), he->body);
3041 static gchar *compose_parse_references(const gchar *ref, const gchar *msgid)
3043 GSList *ref_id_list, *cur;
3047 ref_id_list = references_list_append(NULL, ref);
3048 if (!ref_id_list) return NULL;
3049 if (msgid && *msgid)
3050 ref_id_list = g_slist_append(ref_id_list, g_strdup(msgid));
3055 for (cur = ref_id_list; cur != NULL; cur = cur->next)
3056 /* "<" + Message-ID + ">" + CR+LF+TAB */
3057 len += strlen((gchar *)cur->data) + 5;
3059 if (len > MAX_REFERENCES_LEN) {
3060 /* remove second message-ID */
3061 if (ref_id_list && ref_id_list->next &&
3062 ref_id_list->next->next) {
3063 g_free(ref_id_list->next->data);
3064 ref_id_list = g_slist_remove
3065 (ref_id_list, ref_id_list->next->data);
3067 slist_free_strings_full(ref_id_list);
3074 new_ref = g_string_new("");
3075 for (cur = ref_id_list; cur != NULL; cur = cur->next) {
3076 if (new_ref->len > 0)
3077 g_string_append(new_ref, "\n\t");
3078 g_string_append_printf(new_ref, "<%s>", (gchar *)cur->data);
3081 slist_free_strings_full(ref_id_list);
3083 new_ref_str = new_ref->str;
3084 g_string_free(new_ref, FALSE);
3089 static gchar *compose_quote_fmt(Compose *compose, MsgInfo *msginfo,
3090 const gchar *fmt, const gchar *qmark,
3091 const gchar *body, gboolean rewrap,
3092 gboolean need_unescape,
3093 const gchar *err_msg)
3095 MsgInfo* dummyinfo = NULL;
3096 gchar *quote_str = NULL;
3098 gboolean prev_autowrap;
3099 const gchar *trimmed_body = body;
3100 gint cursor_pos = -1;
3101 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3102 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3107 SIGNAL_BLOCK(buffer);
3110 dummyinfo = compose_msginfo_new_from_compose(compose);
3111 msginfo = dummyinfo;
3114 if (qmark != NULL) {
3116 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
3117 compose->gtkaspell);
3119 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
3121 quote_fmt_scan_string(qmark);
3124 buf = quote_fmt_get_buffer();
3127 alertpanel_error(_("The \"Quotation mark\" of the template is invalid."));
3129 Xstrdup_a(quote_str, buf, goto error)
3132 if (fmt && *fmt != '\0') {
3135 while (*trimmed_body == '\n')
3139 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE,
3140 compose->gtkaspell);
3142 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE);
3144 if (need_unescape) {
3147 /* decode \-escape sequences in the internal representation of the quote format */
3148 tmp = g_malloc(strlen(fmt)+1);
3149 pref_get_unescaped_pref(tmp, fmt);
3150 quote_fmt_scan_string(tmp);
3154 quote_fmt_scan_string(fmt);
3158 buf = quote_fmt_get_buffer();
3161 gint line = quote_fmt_get_line();
3162 alertpanel_error(err_msg, line);
3170 prev_autowrap = compose->autowrap;
3171 compose->autowrap = FALSE;
3173 mark = gtk_text_buffer_get_insert(buffer);
3174 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3175 if (g_utf8_validate(buf, -1, NULL)) {
3176 gtk_text_buffer_insert(buffer, &iter, buf, -1);
3178 gchar *tmpout = NULL;
3179 tmpout = conv_codeset_strdup
3180 (buf, conv_get_locale_charset_str_no_utf8(),
3182 if (!tmpout || !g_utf8_validate(tmpout, -1, NULL)) {
3184 tmpout = g_malloc(strlen(buf)*2+1);
3185 conv_localetodisp(tmpout, strlen(buf)*2+1, buf);
3187 gtk_text_buffer_insert(buffer, &iter, tmpout, -1);
3191 cursor_pos = quote_fmt_get_cursor_pos();
3192 if (cursor_pos == -1)
3193 cursor_pos = gtk_text_iter_get_offset(&iter);
3194 compose->set_cursor_pos = cursor_pos;
3196 gtk_text_buffer_get_start_iter(buffer, &iter);
3197 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
3198 gtk_text_buffer_place_cursor(buffer, &iter);
3200 compose->autowrap = prev_autowrap;
3201 if (compose->autowrap && rewrap)
3202 compose_wrap_all(compose);
3209 SIGNAL_UNBLOCK(buffer);
3211 procmsg_msginfo_free( &dummyinfo );
3216 /* if ml_post is of type addr@host and from is of type
3217 * addr-anything@host, return TRUE
3219 static gboolean is_subscription(const gchar *ml_post, const gchar *from)
3221 gchar *left_ml = NULL;
3222 gchar *right_ml = NULL;
3223 gchar *left_from = NULL;
3224 gchar *right_from = NULL;
3225 gboolean result = FALSE;
3227 if (!ml_post || !from)
3230 left_ml = g_strdup(ml_post);
3231 if (strstr(left_ml, "@")) {
3232 right_ml = strstr(left_ml, "@")+1;
3233 *(strstr(left_ml, "@")) = '\0';
3236 left_from = g_strdup(from);
3237 if (strstr(left_from, "@")) {
3238 right_from = strstr(left_from, "@")+1;
3239 *(strstr(left_from, "@")) = '\0';
3242 if (right_ml && right_from
3243 && !strncmp(left_from, left_ml, strlen(left_ml))
3244 && !strcmp(right_from, right_ml)) {
3253 static void compose_set_folder_prefs(Compose *compose, FolderItem *folder,
3254 gboolean respect_default_to)
3258 if (!folder || !folder->prefs)
3261 if (respect_default_to && folder->prefs->enable_default_to) {
3262 compose_entry_append(compose, folder->prefs->default_to,
3263 COMPOSE_TO, PREF_FOLDER);
3264 compose_entry_indicate(compose, folder->prefs->default_to);
3266 if (folder->prefs->enable_default_cc) {
3267 compose_entry_append(compose, folder->prefs->default_cc,
3268 COMPOSE_CC, PREF_FOLDER);
3269 compose_entry_indicate(compose, folder->prefs->default_cc);
3271 if (folder->prefs->enable_default_bcc) {
3272 compose_entry_append(compose, folder->prefs->default_bcc,
3273 COMPOSE_BCC, PREF_FOLDER);
3274 compose_entry_indicate(compose, folder->prefs->default_bcc);
3276 if (folder->prefs->enable_default_replyto) {
3277 compose_entry_append(compose, folder->prefs->default_replyto,
3278 COMPOSE_REPLYTO, PREF_FOLDER);
3279 compose_entry_indicate(compose, folder->prefs->default_replyto);
3283 static void compose_reply_set_subject(Compose *compose, MsgInfo *msginfo)
3288 if (!compose || !msginfo)
3291 if (msginfo->subject && *msginfo->subject) {
3292 buf = p = g_strdup(msginfo->subject);
3293 p += subject_get_prefix_length(p);
3294 memmove(buf, p, strlen(p) + 1);
3296 buf2 = g_strdup_printf("Re: %s", buf);
3297 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
3302 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), "Re: ");
3305 static void compose_reply_set_entry(Compose *compose, MsgInfo *msginfo,
3306 gboolean to_all, gboolean to_ml,
3308 gboolean followup_and_reply_to)
3310 GSList *cc_list = NULL;
3313 gchar *replyto = NULL;
3314 gchar *ac_email = NULL;
3316 gboolean reply_to_ml = FALSE;
3317 gboolean default_reply_to = FALSE;
3319 cm_return_if_fail(compose->account != NULL);
3320 cm_return_if_fail(msginfo != NULL);
3322 reply_to_ml = to_ml && compose->ml_post;
3324 default_reply_to = msginfo->folder &&
3325 msginfo->folder->prefs->enable_default_reply_to;
3327 if (compose->account->protocol != A_NNTP) {
3328 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
3330 if (reply_to_ml && !default_reply_to) {
3332 gboolean is_subscr = is_subscription(compose->ml_post,
3335 /* normal answer to ml post with a reply-to */
3336 compose_entry_append(compose,
3338 COMPOSE_TO, PREF_ML);
3339 if (compose->replyto)
3340 compose_entry_append(compose,
3342 COMPOSE_CC, PREF_ML);
3344 /* answer to subscription confirmation */
3345 if (compose->replyto)
3346 compose_entry_append(compose,
3348 COMPOSE_TO, PREF_ML);
3349 else if (msginfo->from)
3350 compose_entry_append(compose,
3352 COMPOSE_TO, PREF_ML);
3355 else if (!(to_all || to_sender) && default_reply_to) {
3356 compose_entry_append(compose,
3357 msginfo->folder->prefs->default_reply_to,
3358 COMPOSE_TO, PREF_FOLDER);
3359 compose_entry_indicate(compose,
3360 msginfo->folder->prefs->default_reply_to);
3366 compose_entry_append(compose, msginfo->from,
3367 COMPOSE_TO, PREF_NONE);
3369 Xstrdup_a(tmp1, msginfo->from, return);
3370 extract_address(tmp1);
3371 compose_entry_append(compose,
3372 (!account_find_from_address(tmp1, FALSE))
3375 COMPOSE_TO, PREF_NONE);
3376 if (compose->replyto)
3377 compose_entry_append(compose,
3379 COMPOSE_CC, PREF_NONE);
3381 if (!folder_has_parent_of_type(msginfo->folder, F_QUEUE) &&
3382 !folder_has_parent_of_type(msginfo->folder, F_OUTBOX) &&
3383 !folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
3384 if (compose->replyto) {
3385 compose_entry_append(compose,
3387 COMPOSE_TO, PREF_NONE);
3389 compose_entry_append(compose,
3390 msginfo->from ? msginfo->from : "",
3391 COMPOSE_TO, PREF_NONE);
3394 /* replying to own mail, use original recp */
3395 compose_entry_append(compose,
3396 msginfo->to ? msginfo->to : "",
3397 COMPOSE_TO, PREF_NONE);
3398 compose_entry_append(compose,
3399 msginfo->cc ? msginfo->cc : "",
3400 COMPOSE_CC, PREF_NONE);
3405 if (to_sender || (compose->followup_to &&
3406 !strncmp(compose->followup_to, "poster", 6)))
3407 compose_entry_append
3409 (compose->replyto ? compose->replyto :
3410 msginfo->from ? msginfo->from : ""),
3411 COMPOSE_TO, PREF_NONE);
3413 else if (followup_and_reply_to || to_all) {
3414 compose_entry_append
3416 (compose->replyto ? compose->replyto :
3417 msginfo->from ? msginfo->from : ""),
3418 COMPOSE_TO, PREF_NONE);
3420 compose_entry_append
3422 compose->followup_to ? compose->followup_to :
3423 compose->newsgroups ? compose->newsgroups : "",
3424 COMPOSE_NEWSGROUPS, PREF_NONE);
3426 compose_entry_append
3428 msginfo->cc ? msginfo->cc : "",
3429 COMPOSE_CC, PREF_NONE);
3432 compose_entry_append
3434 compose->followup_to ? compose->followup_to :
3435 compose->newsgroups ? compose->newsgroups : "",
3436 COMPOSE_NEWSGROUPS, PREF_NONE);
3438 compose_reply_set_subject(compose, msginfo);
3440 if (to_ml && compose->ml_post) return;
3441 if (!to_all || compose->account->protocol == A_NNTP) return;
3443 if (compose->replyto) {
3444 Xstrdup_a(replyto, compose->replyto, return);
3445 extract_address(replyto);
3447 if (msginfo->from) {
3448 Xstrdup_a(from, msginfo->from, return);
3449 extract_address(from);
3452 if (replyto && from)
3453 cc_list = address_list_append_with_comments(cc_list, from);
3454 if (to_all && msginfo->folder &&
3455 msginfo->folder->prefs->enable_default_reply_to)
3456 cc_list = address_list_append_with_comments(cc_list,
3457 msginfo->folder->prefs->default_reply_to);
3458 cc_list = address_list_append_with_comments(cc_list, msginfo->to);
3459 cc_list = address_list_append_with_comments(cc_list, compose->cc);
3461 ac_email = g_utf8_strdown(compose->account->address, -1);
3464 for (cur = cc_list; cur != NULL; cur = cur->next) {
3465 gchar *addr = g_utf8_strdown(cur->data, -1);
3466 extract_address(addr);
3468 if (strcmp(ac_email, addr))
3469 compose_entry_append(compose, (gchar *)cur->data,
3470 COMPOSE_CC, PREF_NONE);
3472 debug_print("Cc address same as compose account's, ignoring\n");
3477 slist_free_strings_full(cc_list);
3483 #define SET_ENTRY(entry, str) \
3486 gtk_entry_set_text(GTK_ENTRY(compose->entry), str); \
3489 #define SET_ADDRESS(type, str) \
3492 compose_entry_append(compose, str, type, PREF_NONE); \
3495 static void compose_reedit_set_entry(Compose *compose, MsgInfo *msginfo)
3497 cm_return_if_fail(msginfo != NULL);
3499 SET_ENTRY(subject_entry, msginfo->subject);
3500 SET_ENTRY(from_name, msginfo->from);
3501 SET_ADDRESS(COMPOSE_TO, msginfo->to);
3502 SET_ADDRESS(COMPOSE_CC, compose->cc);
3503 SET_ADDRESS(COMPOSE_BCC, compose->bcc);
3504 SET_ADDRESS(COMPOSE_REPLYTO, compose->replyto);
3505 SET_ADDRESS(COMPOSE_NEWSGROUPS, compose->newsgroups);
3506 SET_ADDRESS(COMPOSE_FOLLOWUPTO, compose->followup_to);
3508 compose_update_priority_menu_item(compose);
3509 compose_update_privacy_system_menu_item(compose, FALSE);
3510 compose_show_first_last_header(compose, TRUE);
3516 static void compose_insert_sig(Compose *compose, gboolean replace)
3518 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3519 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3521 GtkTextIter iter, iter_end;
3522 gint cur_pos, ins_pos;
3523 gboolean prev_autowrap;
3524 gboolean found = FALSE;
3525 gboolean exists = FALSE;
3527 cm_return_if_fail(compose->account != NULL);
3531 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3532 G_CALLBACK(compose_changed_cb),
3535 mark = gtk_text_buffer_get_insert(buffer);
3536 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3537 cur_pos = gtk_text_iter_get_offset (&iter);
3540 gtk_text_buffer_get_end_iter(buffer, &iter);
3542 exists = (compose->sig_str != NULL);
3545 GtkTextIter first_iter, start_iter, end_iter;
3547 gtk_text_buffer_get_start_iter(buffer, &first_iter);
3549 if (!exists || compose->sig_str[0] == '\0')
3552 found = gtk_text_iter_forward_to_tag_toggle(&first_iter,
3553 compose->signature_tag);
3556 /* include previous \n\n */
3557 gtk_text_iter_backward_chars(&first_iter, 1);
3558 start_iter = first_iter;
3559 end_iter = first_iter;
3561 found = gtk_text_iter_forward_to_tag_toggle(&end_iter,
3562 compose->signature_tag);
3563 found &= gtk_text_iter_forward_to_tag_toggle(&end_iter,
3564 compose->signature_tag);
3566 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
3572 g_free(compose->sig_str);
3573 compose->sig_str = account_get_signature_str(compose->account);
3575 cur_pos = gtk_text_iter_get_offset(&iter);
3577 if (!compose->sig_str || (replace && !compose->account->auto_sig)) {
3578 g_free(compose->sig_str);
3579 compose->sig_str = NULL;
3581 if (compose->sig_inserted == FALSE)
3582 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
3583 compose->sig_inserted = TRUE;
3585 cur_pos = gtk_text_iter_get_offset(&iter);
3586 gtk_text_buffer_insert(buffer, &iter, compose->sig_str, -1);
3588 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cur_pos);
3589 gtk_text_iter_forward_chars(&iter, 1);
3590 gtk_text_buffer_get_end_iter(buffer, &iter_end);
3591 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &iter_end);
3593 if (cur_pos > gtk_text_buffer_get_char_count (buffer))
3594 cur_pos = gtk_text_buffer_get_char_count (buffer);
3597 /* put the cursor where it should be
3598 * either where the quote_fmt says, either where it was */
3599 if (compose->set_cursor_pos < 0)
3600 gtk_text_buffer_get_iter_at_offset(buffer, &iter, ins_pos);
3602 gtk_text_buffer_get_iter_at_offset(buffer, &iter,
3603 compose->set_cursor_pos);
3605 compose->set_cursor_pos = -1;
3606 gtk_text_buffer_place_cursor(buffer, &iter);
3607 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3608 G_CALLBACK(compose_changed_cb),
3614 static ComposeInsertResult compose_insert_file(Compose *compose, const gchar *file)
3617 GtkTextBuffer *buffer;
3620 const gchar *cur_encoding;
3621 gchar buf[BUFFSIZE];
3624 gboolean prev_autowrap;
3628 GError *error = NULL;
3634 GString *file_contents = NULL;
3635 ComposeInsertResult result = COMPOSE_INSERT_SUCCESS;
3637 cm_return_val_if_fail(file != NULL, COMPOSE_INSERT_NO_FILE);
3639 /* get the size of the file we are about to insert */
3641 f = g_file_new_for_path(file);
3642 fi = g_file_query_info(f, "standard::size",
3643 G_FILE_QUERY_INFO_NONE, NULL, &error);
3645 if (error != NULL) {
3646 g_warning(error->message);
3648 g_error_free(error);
3652 ret = g_stat(file, &file_stat);
3655 gchar *shortfile = g_path_get_basename(file);
3656 alertpanel_error(_("Could not get size of file '%s'."), shortfile);
3658 return COMPOSE_INSERT_NO_FILE;
3659 } else if (prefs_common.warn_large_insert == TRUE) {
3661 size = g_file_info_get_size(fi);
3665 size = file_stat.st_size;
3668 /* ask user for confirmation if the file is large */
3669 if (prefs_common.warn_large_insert_size < 0 ||
3670 size > ((goffset) prefs_common.warn_large_insert_size * 1024)) {
3674 msg = g_strdup_printf(_("You are about to insert a file of %s "
3675 "in the message body. Are you sure you want to do that?"),
3676 to_human_readable(size));
3677 aval = alertpanel_full(_("Are you sure?"), msg, GTK_STOCK_CANCEL,
3678 _("_Insert"), NULL, ALERTFOCUS_SECOND, TRUE,
3679 NULL, ALERT_QUESTION);
3682 /* do we ask for confirmation next time? */
3683 if (aval & G_ALERTDISABLE) {
3684 /* no confirmation next time, disable feature in preferences */
3685 aval &= ~G_ALERTDISABLE;
3686 prefs_common.warn_large_insert = FALSE;
3689 /* abort file insertion if user canceled action */
3690 if (aval != G_ALERTALTERNATE) {
3691 return COMPOSE_INSERT_NO_FILE;
3697 if ((fp = claws_fopen(file, "rb")) == NULL) {
3698 FILE_OP_ERROR(file, "claws_fopen");
3699 return COMPOSE_INSERT_READ_ERROR;
3702 prev_autowrap = compose->autowrap;
3703 compose->autowrap = FALSE;
3705 text = GTK_TEXT_VIEW(compose->text);
3706 buffer = gtk_text_view_get_buffer(text);
3707 mark = gtk_text_buffer_get_insert(buffer);
3708 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3710 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3711 G_CALLBACK(text_inserted),
3714 cur_encoding = conv_get_locale_charset_str_no_utf8();
3716 file_contents = g_string_new("");
3717 while (claws_fgets(buf, sizeof(buf), fp) != NULL) {
3720 if (g_utf8_validate(buf, -1, NULL) == TRUE)
3721 str = g_strdup(buf);
3723 codeconv_set_strict(TRUE);
3724 str = conv_codeset_strdup
3725 (buf, cur_encoding, CS_INTERNAL);
3726 codeconv_set_strict(FALSE);
3729 result = COMPOSE_INSERT_INVALID_CHARACTER;
3735 /* strip <CR> if DOS/Windows file,
3736 replace <CR> with <LF> if Macintosh file. */
3739 if (len > 0 && str[len - 1] != '\n') {
3741 if (str[len] == '\r') str[len] = '\n';
3744 file_contents = g_string_append(file_contents, str);
3748 if (result == COMPOSE_INSERT_SUCCESS) {
3749 gtk_text_buffer_insert(buffer, &iter, file_contents->str, -1);
3751 compose_changed_cb(NULL, compose);
3752 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3753 G_CALLBACK(text_inserted),
3755 compose->autowrap = prev_autowrap;
3756 if (compose->autowrap)
3757 compose_wrap_all(compose);
3760 g_string_free(file_contents, TRUE);
3766 static gboolean compose_attach_append(Compose *compose, const gchar *file,
3767 const gchar *filename,
3768 const gchar *content_type,
3769 const gchar *charset)
3777 GtkListStore *store;
3779 gboolean has_binary = FALSE;
3781 if (!is_file_exist(file)) {
3782 gchar *file_from_uri = g_filename_from_uri(file, NULL, NULL);
3783 gboolean result = FALSE;
3784 if (file_from_uri && is_file_exist(file_from_uri)) {
3785 result = compose_attach_append(
3786 compose, file_from_uri,
3787 filename, content_type,
3790 g_free(file_from_uri);
3793 alertpanel_error("File %s doesn't exist or permission denied\n", filename);
3796 if ((size = get_file_size(file)) < 0) {
3797 alertpanel_error("Can't get file size of %s\n", filename);
3801 /* In batch mode, we allow 0-length files to be attached no questions asked */
3802 if (size == 0 && !compose->batch) {
3803 gchar * msg = g_strdup_printf(_("File %s is empty."), filename);
3804 AlertValue aval = alertpanel_full(_("Empty file"), msg,
3805 GTK_STOCK_CANCEL, _("_Attach anyway"), NULL,
3806 ALERTFOCUS_SECOND, FALSE, NULL, ALERT_WARNING);
3809 if (aval != G_ALERTALTERNATE) {
3813 if ((fp = claws_fopen(file, "rb")) == NULL) {
3814 alertpanel_error(_("Can't read %s."), filename);
3819 ainfo = g_new0(AttachInfo, 1);
3820 auto_ainfo = g_auto_pointer_new_with_free
3821 (ainfo, (GFreeFunc) compose_attach_info_free);
3822 ainfo->file = g_strdup(file);
3825 ainfo->content_type = g_strdup(content_type);
3826 if (!g_ascii_strcasecmp(content_type, "message/rfc822")) {
3828 MsgFlags flags = {0, 0};
3830 if (procmime_get_encoding_for_text_file(file, &has_binary) == ENC_7BIT)
3831 ainfo->encoding = ENC_7BIT;
3833 ainfo->encoding = ENC_8BIT;
3835 msginfo = procheader_parse_file(file, flags, FALSE, FALSE);
3836 if (msginfo && msginfo->subject)
3837 name = g_strdup(msginfo->subject);
3839 name = g_path_get_basename(filename ? filename : file);
3841 ainfo->name = g_strdup_printf(_("Message: %s"), name);
3843 procmsg_msginfo_free(&msginfo);
3845 if (!g_ascii_strncasecmp(content_type, "text/", 5)) {
3846 ainfo->charset = g_strdup(charset);
3847 ainfo->encoding = procmime_get_encoding_for_text_file(file, &has_binary);
3849 ainfo->encoding = ENC_BASE64;
3851 name = g_path_get_basename(filename ? filename : file);
3852 ainfo->name = g_strdup(name);
3856 ainfo->content_type = procmime_get_mime_type(file);
3857 if (!ainfo->content_type) {
3858 ainfo->content_type =
3859 g_strdup("application/octet-stream");
3860 ainfo->encoding = ENC_BASE64;
3861 } else if (!g_ascii_strncasecmp(ainfo->content_type, "text/", 5))
3863 procmime_get_encoding_for_text_file(file, &has_binary);
3865 ainfo->encoding = ENC_BASE64;
3866 name = g_path_get_basename(filename ? filename : file);
3867 ainfo->name = g_strdup(name);
3871 if (ainfo->name != NULL
3872 && !strcmp(ainfo->name, ".")) {
3873 g_free(ainfo->name);
3877 if (!strcmp(ainfo->content_type, "unknown") || has_binary) {
3878 g_free(ainfo->content_type);
3879 ainfo->content_type = g_strdup("application/octet-stream");
3880 g_free(ainfo->charset);
3881 ainfo->charset = NULL;
3884 ainfo->size = (goffset)size;
3885 size_text = to_human_readable((goffset)size);
3887 store = GTK_LIST_STORE(gtk_tree_view_get_model
3888 (GTK_TREE_VIEW(compose->attach_clist)));
3890 gtk_list_store_append(store, &iter);
3891 gtk_list_store_set(store, &iter,
3892 COL_MIMETYPE, ainfo->content_type,
3893 COL_SIZE, size_text,
3894 COL_NAME, ainfo->name,
3895 COL_CHARSET, ainfo->charset,
3897 COL_AUTODATA, auto_ainfo,
3900 g_auto_pointer_free(auto_ainfo);
3901 compose_attach_update_label(compose);
3905 void compose_use_signing(Compose *compose, gboolean use_signing)
3907 compose->use_signing = use_signing;
3908 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", use_signing);
3911 void compose_use_encryption(Compose *compose, gboolean use_encryption)
3913 compose->use_encryption = use_encryption;
3914 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", use_encryption);
3917 #define NEXT_PART_NOT_CHILD(info) \
3919 node = info->node; \
3920 while (node->children) \
3921 node = g_node_last_child(node); \
3922 info = procmime_mimeinfo_next((MimeInfo *)node->data); \
3925 static void compose_attach_parts(Compose *compose, MsgInfo *msginfo)
3929 MimeInfo *firsttext = NULL;
3930 MimeInfo *encrypted = NULL;
3933 const gchar *partname = NULL;
3935 mimeinfo = procmime_scan_message(msginfo);
3936 if (!mimeinfo) return;
3938 if (mimeinfo->node->children == NULL) {
3939 procmime_mimeinfo_free_all(&mimeinfo);
3943 /* find first content part */
3944 child = (MimeInfo *) mimeinfo->node->children->data;
3945 while (child && child->node->children && (child->type == MIMETYPE_MULTIPART))
3946 child = (MimeInfo *)child->node->children->data;
3949 if (child->type == MIMETYPE_TEXT) {
3951 debug_print("First text part found\n");
3952 } else if (compose->mode == COMPOSE_REEDIT &&
3953 child->type == MIMETYPE_APPLICATION &&
3954 !g_ascii_strcasecmp(child->subtype, "pgp-encrypted")) {
3955 encrypted = (MimeInfo *)child->node->parent->data;
3958 child = (MimeInfo *) mimeinfo->node->children->data;
3959 while (child != NULL) {
3962 if (child == encrypted) {
3963 /* skip this part of tree */
3964 NEXT_PART_NOT_CHILD(child);
3968 if (child->type == MIMETYPE_MULTIPART) {
3969 /* get the actual content */
3970 child = procmime_mimeinfo_next(child);
3974 if (child == firsttext) {
3975 child = procmime_mimeinfo_next(child);
3979 outfile = procmime_get_tmp_file_name(child);
3980 if ((err = procmime_get_part(outfile, child)) < 0)
3981 g_warning("Can't get the part of multipart message. (%s)", g_strerror(-err));
3983 gchar *content_type;
3985 content_type = procmime_get_content_type_str(child->type, child->subtype);
3987 /* if we meet a pgp signature, we don't attach it, but
3988 * we force signing. */
3989 if ((strcmp(content_type, "application/pgp-signature") &&
3990 strcmp(content_type, "application/pkcs7-signature") &&
3991 strcmp(content_type, "application/x-pkcs7-signature"))
3992 || compose->mode == COMPOSE_REDIRECT) {
3993 partname = procmime_mimeinfo_get_parameter(child, "filename");
3994 if (partname == NULL)
3995 partname = procmime_mimeinfo_get_parameter(child, "name");
3996 if (partname == NULL)
3998 compose_attach_append(compose, outfile,
3999 partname, content_type,
4000 procmime_mimeinfo_get_parameter(child, "charset"));
4002 compose_force_signing(compose, compose->account, NULL);
4004 g_free(content_type);
4007 NEXT_PART_NOT_CHILD(child);
4009 procmime_mimeinfo_free_all(&mimeinfo);
4012 #undef NEXT_PART_NOT_CHILD
4017 WAIT_FOR_INDENT_CHAR,
4018 WAIT_FOR_INDENT_CHAR_OR_SPACE,
4021 /* return indent length, we allow:
4022 indent characters followed by indent characters or spaces/tabs,
4023 alphabets and numbers immediately followed by indent characters,
4024 and the repeating sequences of the above
4025 If quote ends with multiple spaces, only the first one is included. */
4026 static gchar *compose_get_quote_str(GtkTextBuffer *buffer,
4027 const GtkTextIter *start, gint *len)
4029 GtkTextIter iter = *start;
4033 IndentState state = WAIT_FOR_INDENT_CHAR;
4036 gint alnum_count = 0;
4037 gint space_count = 0;
4040 if (prefs_common.quote_chars == NULL) {
4044 while (!gtk_text_iter_ends_line(&iter)) {
4045 wc = gtk_text_iter_get_char(&iter);
4046 if (g_unichar_iswide(wc))
4048 clen = g_unichar_to_utf8(wc, ch);
4052 is_indent = strchr(prefs_common.quote_chars, ch[0]) ? TRUE : FALSE;
4053 is_space = g_unichar_isspace(wc);
4055 if (state == WAIT_FOR_INDENT_CHAR) {
4056 if (!is_indent && !g_unichar_isalnum(wc))
4059 quote_len += alnum_count + space_count + 1;
4060 alnum_count = space_count = 0;
4061 state = WAIT_FOR_INDENT_CHAR_OR_SPACE;
4064 } else if (state == WAIT_FOR_INDENT_CHAR_OR_SPACE) {
4065 if (!is_indent && !is_space && !g_unichar_isalnum(wc))
4069 else if (is_indent) {
4070 quote_len += alnum_count + space_count + 1;
4071 alnum_count = space_count = 0;
4074 state = WAIT_FOR_INDENT_CHAR;
4078 gtk_text_iter_forward_char(&iter);
4081 if (quote_len > 0 && space_count > 0)
4087 if (quote_len > 0) {
4089 gtk_text_iter_forward_chars(&iter, quote_len);
4090 return gtk_text_buffer_get_text(buffer, start, &iter, FALSE);
4096 /* return >0 if the line is itemized */
4097 static int compose_itemized_length(GtkTextBuffer *buffer,
4098 const GtkTextIter *start)
4100 GtkTextIter iter = *start;
4105 if (gtk_text_iter_ends_line(&iter))
4110 wc = gtk_text_iter_get_char(&iter);
4111 if (!g_unichar_isspace(wc))
4113 gtk_text_iter_forward_char(&iter);
4114 if (gtk_text_iter_ends_line(&iter))
4118 clen = g_unichar_to_utf8(wc, ch);
4119 if (!((clen == 1 && strchr("*-+", ch[0])) ||
4121 wc == 0x2022 || /* BULLET */
4122 wc == 0x2023 || /* TRIANGULAR BULLET */
4123 wc == 0x2043 || /* HYPHEN BULLET */
4124 wc == 0x204c || /* BLACK LEFTWARDS BULLET */
4125 wc == 0x204d || /* BLACK RIGHTWARDS BULLET */
4126 wc == 0x2219 || /* BULLET OPERATOR */
4127 wc == 0x25d8 || /* INVERSE BULLET */
4128 wc == 0x25e6 || /* WHITE BULLET */
4129 wc == 0x2619 || /* REVERSED ROTATED FLORAL HEART BULLET */
4130 wc == 0x2765 || /* ROTATED HEAVY BLACK HEART BULLET */
4131 wc == 0x2767 || /* ROTATED FLORAL HEART BULLET */
4132 wc == 0x29be || /* CIRCLED WHITE BULLET */
4133 wc == 0x29bf /* CIRCLED BULLET */
4137 gtk_text_iter_forward_char(&iter);
4138 if (gtk_text_iter_ends_line(&iter))
4140 wc = gtk_text_iter_get_char(&iter);
4141 if (g_unichar_isspace(wc)) {
4147 /* return the string at the start of the itemization */
4148 static gchar * compose_get_itemized_chars(GtkTextBuffer *buffer,
4149 const GtkTextIter *start)
4151 GtkTextIter iter = *start;
4154 GString *item_chars = g_string_new("");
4157 if (gtk_text_iter_ends_line(&iter))
4162 wc = gtk_text_iter_get_char(&iter);
4163 if (!g_unichar_isspace(wc))
4165 gtk_text_iter_forward_char(&iter);
4166 if (gtk_text_iter_ends_line(&iter))
4168 g_string_append_unichar(item_chars, wc);
4171 str = item_chars->str;
4172 g_string_free(item_chars, FALSE);
4176 /* return the number of spaces at a line's start */
4177 static int compose_left_offset_length(GtkTextBuffer *buffer,
4178 const GtkTextIter *start)
4180 GtkTextIter iter = *start;
4183 if (gtk_text_iter_ends_line(&iter))
4187 wc = gtk_text_iter_get_char(&iter);
4188 if (!g_unichar_isspace(wc))
4191 gtk_text_iter_forward_char(&iter);
4192 if (gtk_text_iter_ends_line(&iter))
4196 gtk_text_iter_forward_char(&iter);
4197 if (gtk_text_iter_ends_line(&iter))
4202 static gboolean compose_get_line_break_pos(GtkTextBuffer *buffer,
4203 const GtkTextIter *start,
4204 GtkTextIter *break_pos,
4208 GtkTextIter iter = *start, line_end = *start;
4209 PangoLogAttr *attrs;
4216 gboolean can_break = FALSE;
4217 gboolean do_break = FALSE;
4218 gboolean was_white = FALSE;
4219 gboolean prev_dont_break = FALSE;
4221 gtk_text_iter_forward_to_line_end(&line_end);
4222 str = gtk_text_buffer_get_text(buffer, &iter, &line_end, FALSE);
4223 len = g_utf8_strlen(str, -1);
4227 g_warning("compose_get_line_break_pos: len = 0!");
4231 /* g_print("breaking line: %d: %s (len = %d)\n",
4232 gtk_text_iter_get_line(&iter), str, len); */
4234 attrs = g_new(PangoLogAttr, len + 1);
4236 pango_default_break(str, -1, NULL, attrs, len + 1);
4240 /* skip quote and leading spaces */
4241 for (i = 0; *p != '\0' && i < len; i++) {
4244 wc = g_utf8_get_char(p);
4245 if (i >= quote_len && !g_unichar_isspace(wc))
4247 if (g_unichar_iswide(wc))
4249 else if (*p == '\t')
4253 p = g_utf8_next_char(p);
4256 for (; *p != '\0' && i < len; i++) {
4257 PangoLogAttr *attr = attrs + i;
4261 if (attr->is_line_break && can_break && was_white && !prev_dont_break)
4264 was_white = attr->is_white;
4266 /* don't wrap URI */
4267 if ((uri_len = get_uri_len(p)) > 0) {
4269 if (pos > 0 && col > max_col) {
4279 wc = g_utf8_get_char(p);
4280 if (g_unichar_iswide(wc)) {
4282 if (prev_dont_break && can_break && attr->is_line_break)
4284 } else if (*p == '\t')
4288 if (pos > 0 && col > max_col) {
4293 if (*p == '-' || *p == '/')
4294 prev_dont_break = TRUE;
4296 prev_dont_break = FALSE;
4298 p = g_utf8_next_char(p);
4302 /* debug_print("compose_get_line_break_pos(): do_break = %d, pos = %d, col = %d\n", do_break, pos, col); */
4307 *break_pos = *start;
4308 gtk_text_iter_set_line_offset(break_pos, pos);
4313 static gboolean compose_join_next_line(Compose *compose,
4314 GtkTextBuffer *buffer,
4316 const gchar *quote_str)
4318 GtkTextIter iter_ = *iter, cur, prev, next, end;
4319 PangoLogAttr attrs[3];
4321 gchar *next_quote_str;
4324 gboolean keep_cursor = FALSE;
4326 if (!gtk_text_iter_forward_line(&iter_) ||
4327 gtk_text_iter_ends_line(&iter_)) {
4330 next_quote_str = compose_get_quote_str(buffer, &iter_, "e_len);
4332 if ((quote_str || next_quote_str) &&
4333 strcmp2(quote_str, next_quote_str) != 0) {
4334 g_free(next_quote_str);
4337 g_free(next_quote_str);
4340 if (quote_len > 0) {
4341 gtk_text_iter_forward_chars(&end, quote_len);
4342 if (gtk_text_iter_ends_line(&end)) {
4347 /* don't join itemized lines */
4348 if (compose_itemized_length(buffer, &end) > 0) {
4352 /* don't join signature separator */
4353 if (compose_is_sig_separator(compose, buffer, &iter_)) {
4356 /* delete quote str */
4358 gtk_text_buffer_delete(buffer, &iter_, &end);
4360 /* don't join line breaks put by the user */
4362 gtk_text_iter_backward_char(&cur);
4363 if (gtk_text_iter_has_tag(&cur, compose->no_join_tag)) {
4364 gtk_text_iter_forward_char(&cur);
4368 gtk_text_iter_forward_char(&cur);
4369 /* delete linebreak and extra spaces */
4370 while (gtk_text_iter_backward_char(&cur)) {
4371 wc1 = gtk_text_iter_get_char(&cur);
4372 if (!g_unichar_isspace(wc1))
4377 while (!gtk_text_iter_ends_line(&cur)) {
4378 wc1 = gtk_text_iter_get_char(&cur);
4379 if (!g_unichar_isspace(wc1))
4381 gtk_text_iter_forward_char(&cur);
4384 if (!gtk_text_iter_equal(&prev, &next)) {
4387 mark = gtk_text_buffer_get_insert(buffer);
4388 gtk_text_buffer_get_iter_at_mark(buffer, &cur, mark);
4389 if (gtk_text_iter_equal(&prev, &cur))
4391 gtk_text_buffer_delete(buffer, &prev, &next);
4395 /* insert space if required */
4396 gtk_text_iter_backward_char(&prev);
4397 wc1 = gtk_text_iter_get_char(&prev);
4398 wc2 = gtk_text_iter_get_char(&next);
4399 gtk_text_iter_forward_char(&next);
4400 str = gtk_text_buffer_get_text(buffer, &prev, &next, FALSE);
4401 pango_default_break(str, -1, NULL, attrs, 3);
4402 if (!attrs[1].is_line_break ||
4403 (!g_unichar_iswide(wc1) || !g_unichar_iswide(wc2))) {
4404 gtk_text_buffer_insert(buffer, &iter_, " ", 1);
4406 gtk_text_iter_backward_char(&iter_);
4407 gtk_text_buffer_place_cursor(buffer, &iter_);
4416 #define ADD_TXT_POS(bp_, ep_, pti_) \
4417 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
4418 last = last->next; \
4419 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
4420 last->next = NULL; \
4422 g_warning("alloc error scanning URIs"); \
4425 static gboolean compose_beautify_paragraph(Compose *compose, GtkTextIter *par_iter, gboolean force)
4427 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4428 GtkTextBuffer *buffer;
4429 GtkTextIter iter, break_pos, end_of_line;
4430 gchar *quote_str = NULL;
4432 gboolean wrap_quote = force || prefs_common.linewrap_quote;
4433 gboolean prev_autowrap = compose->autowrap;
4434 gint startq_offset = -1, noq_offset = -1;
4435 gint uri_start = -1, uri_stop = -1;
4436 gint nouri_start = -1, nouri_stop = -1;
4437 gint num_blocks = 0;
4438 gint quotelevel = -1;
4439 gboolean modified = force;
4440 gboolean removed = FALSE;
4441 gboolean modified_before_remove = FALSE;
4443 gboolean start = TRUE;
4444 gint itemized_len = 0, rem_item_len = 0;
4445 gchar *itemized_chars = NULL;
4446 gboolean item_continuation = FALSE;
4451 if (compose->draft_timeout_tag == COMPOSE_DRAFT_TIMEOUT_FORBIDDEN) {
4455 compose->autowrap = FALSE;
4457 buffer = gtk_text_view_get_buffer(text);
4458 undo_wrapping(compose->undostruct, TRUE);
4463 mark = gtk_text_buffer_get_insert(buffer);
4464 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
4468 if (compose->draft_timeout_tag == COMPOSE_DRAFT_TIMEOUT_FORBIDDEN) {
4469 if (gtk_text_iter_ends_line(&iter)) {
4470 while (gtk_text_iter_ends_line(&iter) &&
4471 gtk_text_iter_forward_line(&iter))
4474 while (gtk_text_iter_backward_line(&iter)) {
4475 if (gtk_text_iter_ends_line(&iter)) {
4476 gtk_text_iter_forward_line(&iter);
4482 /* move to line start */
4483 gtk_text_iter_set_line_offset(&iter, 0);
4486 itemized_len = compose_itemized_length(buffer, &iter);
4488 if (!itemized_len) {
4489 itemized_len = compose_left_offset_length(buffer, &iter);
4490 item_continuation = TRUE;
4494 itemized_chars = compose_get_itemized_chars(buffer, &iter);
4496 /* go until paragraph end (empty line) */
4497 while (start || !gtk_text_iter_ends_line(&iter)) {
4498 gchar *scanpos = NULL;
4499 /* parse table - in order of priority */
4501 const gchar *needle; /* token */
4503 /* token search function */
4504 gchar *(*search) (const gchar *haystack,
4505 const gchar *needle);
4506 /* part parsing function */
4507 gboolean (*parse) (const gchar *start,
4508 const gchar *scanpos,
4512 /* part to URI function */
4513 gchar *(*build_uri) (const gchar *bp,
4517 static struct table parser[] = {
4518 {"http://", strcasestr, get_uri_part, make_uri_string},
4519 {"https://", strcasestr, get_uri_part, make_uri_string},
4520 {"ftp://", strcasestr, get_uri_part, make_uri_string},
4521 {"sftp://", strcasestr, get_uri_part, make_uri_string},
4522 {"gopher://",strcasestr, get_uri_part, make_uri_string},
4523 {"www.", strcasestr, get_uri_part, make_http_string},
4524 {"mailto:", strcasestr, get_uri_part, make_uri_string},
4525 {"@", strcasestr, get_email_part, make_email_string}
4527 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
4528 gint last_index = PARSE_ELEMS;
4530 gchar *o_walk = NULL, *walk = NULL, *bp = NULL, *ep = NULL;
4534 if (!prev_autowrap && num_blocks == 0) {
4536 g_signal_handlers_block_by_func(G_OBJECT(buffer),
4537 G_CALLBACK(text_inserted),
4540 if (gtk_text_iter_has_tag(&iter, compose->no_wrap_tag) && !force)
4543 uri_start = uri_stop = -1;
4545 quote_str = compose_get_quote_str(buffer, &iter, "e_len);
4548 /* debug_print("compose_beautify_paragraph(): quote_str = '%s'\n", quote_str); */
4549 if (startq_offset == -1)
4550 startq_offset = gtk_text_iter_get_offset(&iter);
4551 quotelevel = get_quote_level(quote_str, prefs_common.quote_chars);
4552 if (quotelevel > 2) {
4553 /* recycle colors */
4554 if (prefs_common.recycle_quote_colors)
4563 if (startq_offset == -1)
4564 noq_offset = gtk_text_iter_get_offset(&iter);
4568 if (prev_autowrap == FALSE && !force && !wrap_quote) {
4571 if (gtk_text_iter_ends_line(&iter)) {
4573 } else if (compose_get_line_break_pos(buffer, &iter, &break_pos,
4574 prefs_common.linewrap_len,
4576 GtkTextIter prev, next, cur;
4577 if (prev_autowrap != FALSE || force) {
4578 compose->automatic_break = TRUE;
4580 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4581 compose->automatic_break = FALSE;
4582 if (itemized_len && compose->autoindent) {
4583 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4584 if (!item_continuation)
4585 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4587 } else if (quote_str && wrap_quote) {
4588 compose->automatic_break = TRUE;
4590 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4591 compose->automatic_break = FALSE;
4592 if (itemized_len && compose->autoindent) {
4593 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4594 if (!item_continuation)
4595 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4599 /* remove trailing spaces */
4601 rem_item_len = itemized_len;
4602 while (compose->autoindent && rem_item_len-- > 0)
4603 gtk_text_iter_backward_char(&cur);
4604 gtk_text_iter_backward_char(&cur);
4607 while (!gtk_text_iter_starts_line(&cur)) {
4610 gtk_text_iter_backward_char(&cur);
4611 wc = gtk_text_iter_get_char(&cur);
4612 if (!g_unichar_isspace(wc))
4616 if (!gtk_text_iter_equal(&prev, &next)) {
4617 gtk_text_buffer_delete(buffer, &prev, &next);
4619 gtk_text_iter_forward_char(&break_pos);
4623 gtk_text_buffer_insert(buffer, &break_pos,
4627 modified |= compose_join_next_line(compose, buffer, &iter, quote_str);
4629 /* move iter to current line start */
4630 gtk_text_iter_set_line_offset(&iter, 0);
4637 /* move iter to next line start */
4643 if (!prev_autowrap && num_blocks > 0) {
4645 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
4646 G_CALLBACK(text_inserted),
4650 while (!gtk_text_iter_ends_line(&end_of_line)) {
4651 gtk_text_iter_forward_char(&end_of_line);
4653 o_walk = walk = gtk_text_buffer_get_text(buffer, &iter, &end_of_line, FALSE);
4655 nouri_start = gtk_text_iter_get_offset(&iter);
4656 nouri_stop = gtk_text_iter_get_offset(&end_of_line);
4658 walk_pos = gtk_text_iter_get_offset(&iter);
4659 /* FIXME: this looks phony. scanning for anything in the parse table */
4660 for (n = 0; n < PARSE_ELEMS; n++) {
4663 tmp = parser[n].search(walk, parser[n].needle);
4665 if (scanpos == NULL || tmp < scanpos) {
4674 /* check if URI can be parsed */
4675 if (parser[last_index].parse(walk, scanpos, (const gchar **)&bp,
4676 (const gchar **)&ep, FALSE)
4677 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
4681 strlen(parser[last_index].needle);
4684 uri_start = walk_pos + (bp - o_walk);
4685 uri_stop = walk_pos + (ep - o_walk);
4689 gtk_text_iter_forward_line(&iter);
4692 if (startq_offset != -1) {
4693 GtkTextIter startquote, endquote;
4694 gtk_text_buffer_get_iter_at_offset(
4695 buffer, &startquote, startq_offset);
4698 switch (quotelevel) {
4700 if (!gtk_text_iter_has_tag(&startquote, compose->quote0_tag) ||
4701 !gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) {
4702 gtk_text_buffer_apply_tag_by_name(
4703 buffer, "quote0", &startquote, &endquote);
4704 gtk_text_buffer_remove_tag_by_name(
4705 buffer, "quote1", &startquote, &endquote);
4706 gtk_text_buffer_remove_tag_by_name(
4707 buffer, "quote2", &startquote, &endquote);
4712 if (!gtk_text_iter_has_tag(&startquote, compose->quote1_tag) ||
4713 !gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) {
4714 gtk_text_buffer_apply_tag_by_name(
4715 buffer, "quote1", &startquote, &endquote);
4716 gtk_text_buffer_remove_tag_by_name(
4717 buffer, "quote0", &startquote, &endquote);
4718 gtk_text_buffer_remove_tag_by_name(
4719 buffer, "quote2", &startquote, &endquote);
4724 if (!gtk_text_iter_has_tag(&startquote, compose->quote2_tag) ||
4725 !gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag)) {
4726 gtk_text_buffer_apply_tag_by_name(
4727 buffer, "quote2", &startquote, &endquote);
4728 gtk_text_buffer_remove_tag_by_name(
4729 buffer, "quote0", &startquote, &endquote);
4730 gtk_text_buffer_remove_tag_by_name(
4731 buffer, "quote1", &startquote, &endquote);
4737 } else if (noq_offset != -1) {
4738 GtkTextIter startnoquote, endnoquote;
4739 gtk_text_buffer_get_iter_at_offset(
4740 buffer, &startnoquote, noq_offset);
4743 if ((gtk_text_iter_has_tag(&startnoquote, compose->quote0_tag)
4744 && gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) ||
4745 (gtk_text_iter_has_tag(&startnoquote, compose->quote1_tag)
4746 && gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) ||
4747 (gtk_text_iter_has_tag(&startnoquote, compose->quote2_tag)
4748 && gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag))) {
4749 gtk_text_buffer_remove_tag_by_name(
4750 buffer, "quote0", &startnoquote, &endnoquote);
4751 gtk_text_buffer_remove_tag_by_name(
4752 buffer, "quote1", &startnoquote, &endnoquote);
4753 gtk_text_buffer_remove_tag_by_name(
4754 buffer, "quote2", &startnoquote, &endnoquote);
4760 if (uri_start != nouri_start && uri_stop != nouri_stop) {
4761 GtkTextIter nouri_start_iter, nouri_end_iter;
4762 gtk_text_buffer_get_iter_at_offset(
4763 buffer, &nouri_start_iter, nouri_start);
4764 gtk_text_buffer_get_iter_at_offset(
4765 buffer, &nouri_end_iter, nouri_stop);
4766 if (gtk_text_iter_has_tag(&nouri_start_iter, compose->uri_tag) &&
4767 gtk_text_iter_has_tag(&nouri_end_iter, compose->uri_tag)) {
4768 gtk_text_buffer_remove_tag_by_name(
4769 buffer, "link", &nouri_start_iter, &nouri_end_iter);
4770 modified_before_remove = modified;
4775 if (uri_start >= 0 && uri_stop > 0) {
4776 GtkTextIter uri_start_iter, uri_end_iter, back;
4777 gtk_text_buffer_get_iter_at_offset(
4778 buffer, &uri_start_iter, uri_start);
4779 gtk_text_buffer_get_iter_at_offset(
4780 buffer, &uri_end_iter, uri_stop);
4781 back = uri_end_iter;
4782 gtk_text_iter_backward_char(&back);
4783 if (!gtk_text_iter_has_tag(&uri_start_iter, compose->uri_tag) ||
4784 !gtk_text_iter_has_tag(&back, compose->uri_tag)) {
4785 gtk_text_buffer_apply_tag_by_name(
4786 buffer, "link", &uri_start_iter, &uri_end_iter);
4788 if (removed && !modified_before_remove) {
4794 /* debug_print("not modified, out after %d lines\n", lines); */
4798 /* debug_print("modified, out after %d lines\n", lines); */
4800 g_free(itemized_chars);
4803 undo_wrapping(compose->undostruct, FALSE);
4804 compose->autowrap = prev_autowrap;
4809 void compose_action_cb(void *data)
4811 Compose *compose = (Compose *)data;
4812 compose_wrap_all(compose);
4815 static void compose_wrap_all(Compose *compose)
4817 compose_wrap_all_full(compose, FALSE);
4820 static void compose_wrap_all_full(Compose *compose, gboolean force)
4822 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4823 GtkTextBuffer *buffer;
4825 gboolean modified = TRUE;
4827 buffer = gtk_text_view_get_buffer(text);
4829 gtk_text_buffer_get_start_iter(buffer, &iter);
4831 undo_wrapping(compose->undostruct, TRUE);
4833 while (!gtk_text_iter_is_end(&iter) && modified)
4834 modified = compose_beautify_paragraph(compose, &iter, force);
4836 undo_wrapping(compose->undostruct, FALSE);
4840 static void compose_set_title(Compose *compose)
4846 edited = compose->modified ? _(" [Edited]") : "";
4848 subject = gtk_editable_get_chars(
4849 GTK_EDITABLE(compose->subject_entry), 0, -1);
4851 #ifndef GENERIC_UMPC
4852 if (subject && strlen(subject))
4853 str = g_strdup_printf(_("%s - Compose message%s"),
4856 str = g_strdup_printf(_("[no subject] - Compose message%s"), edited);
4858 str = g_strdup(_("Compose message"));
4861 gtk_window_set_title(GTK_WINDOW(compose->window), str);
4867 * compose_current_mail_account:
4869 * Find a current mail account (the currently selected account, or the
4870 * default account, if a news account is currently selected). If a
4871 * mail account cannot be found, display an error message.
4873 * Return value: Mail account, or NULL if not found.
4875 static PrefsAccount *
4876 compose_current_mail_account(void)
4880 if (cur_account && cur_account->protocol != A_NNTP)
4883 ac = account_get_default();
4884 if (!ac || ac->protocol == A_NNTP) {
4885 alertpanel_error(_("Account for sending mail is not specified.\n"
4886 "Please select a mail account before sending."));
4893 #define QUOTE_IF_REQUIRED(out, str) \
4895 if (*str != '"' && strpbrk(str, ",.:;[]<>()@\\\"")) { \
4899 len = strlen(str) + 3; \
4900 if ((__tmp = alloca(len)) == NULL) { \
4901 g_warning("can't allocate memory"); \
4902 g_string_free(header, TRUE); \
4905 g_snprintf(__tmp, len, "\"%s\"", str); \
4910 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4911 g_warning("can't allocate memory"); \
4912 g_string_free(header, TRUE); \
4915 strcpy(__tmp, str); \
4921 #define QUOTE_IF_REQUIRED_NORMAL(out, str, errret) \
4923 if (*str != '"' && strpbrk(str, ",.:;[]<>()@\\\"")) { \
4927 len = strlen(str) + 3; \
4928 if ((__tmp = alloca(len)) == NULL) { \
4929 g_warning("can't allocate memory"); \
4932 g_snprintf(__tmp, len, "\"%s\"", str); \
4937 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4938 g_warning("can't allocate memory"); \
4941 strcpy(__tmp, str); \
4947 static void compose_select_account(Compose *compose, PrefsAccount *account,
4950 gchar *from = NULL, *header = NULL;
4951 ComposeHeaderEntry *header_entry;
4954 cm_return_if_fail(account != NULL);
4956 compose->account = account;
4957 if (account->name && *account->name) {
4959 QUOTE_IF_REQUIRED_NORMAL(buf, account->name, return);
4960 qbuf = escape_internal_quotes(buf, '"');
4961 from = g_strdup_printf("%s <%s>",
4962 qbuf, account->address);
4965 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4967 from = g_strdup_printf("<%s>",
4969 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4974 compose_set_title(compose);
4976 compose_activate_privacy_system(compose, account, FALSE);
4978 if (account->default_sign && privacy_system_can_sign(compose->privacy_system) &&
4979 compose->mode != COMPOSE_REDIRECT)
4980 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", TRUE);
4982 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", FALSE);
4983 if (account->default_encrypt && privacy_system_can_encrypt(compose->privacy_system) &&
4984 compose->mode != COMPOSE_REDIRECT)
4985 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", TRUE);
4987 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", FALSE);
4989 if (!init && compose->mode != COMPOSE_REDIRECT) {
4990 undo_block(compose->undostruct);
4991 compose_insert_sig(compose, TRUE);
4992 undo_unblock(compose->undostruct);
4995 header_entry = (ComposeHeaderEntry *) compose->header_list->data;
4996 if (gtk_combo_box_get_active_iter(GTK_COMBO_BOX(header_entry->combo), &iter))
4997 gtk_tree_model_get(gtk_combo_box_get_model(GTK_COMBO_BOX(
4998 header_entry->combo)), &iter, COMBOBOX_TEXT, &header, -1);
5000 if (header && !strlen(gtk_entry_get_text(GTK_ENTRY(header_entry->entry)))) {
5001 if (account->protocol == A_NNTP) {
5002 if (!strcmp(header, _("To:")))
5003 combobox_select_by_text(
5004 GTK_COMBO_BOX(header_entry->combo),
5007 if (!strcmp(header, _("Newsgroups:")))
5008 combobox_select_by_text(
5009 GTK_COMBO_BOX(header_entry->combo),
5017 /* use account's dict info if set */
5018 if (compose->gtkaspell) {
5019 if (account->enable_default_dictionary)
5020 gtkaspell_change_dict(compose->gtkaspell,
5021 account->default_dictionary, FALSE);
5022 if (account->enable_default_alt_dictionary)
5023 gtkaspell_change_alt_dict(compose->gtkaspell,
5024 account->default_alt_dictionary);
5025 if (account->enable_default_dictionary
5026 || account->enable_default_alt_dictionary)
5027 compose_spell_menu_changed(compose);
5032 gboolean compose_check_for_valid_recipient(Compose *compose) {
5033 gchar *recipient_headers_mail[] = {"To:", "Cc:", "Bcc:", NULL};
5034 gchar *recipient_headers_news[] = {"Newsgroups:", NULL};
5035 gboolean recipient_found = FALSE;
5039 /* free to and newsgroup list */
5040 slist_free_strings_full(compose->to_list);
5041 compose->to_list = NULL;
5043 slist_free_strings_full(compose->newsgroup_list);
5044 compose->newsgroup_list = NULL;
5046 /* search header entries for to and newsgroup entries */
5047 for (list = compose->header_list; list; list = list->next) {
5050 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5051 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5054 if (entry[0] != '\0') {
5055 for (strptr = recipient_headers_mail; *strptr != NULL; strptr++) {
5056 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
5057 compose->to_list = address_list_append(compose->to_list, entry);
5058 recipient_found = TRUE;
5061 for (strptr = recipient_headers_news; *strptr != NULL; strptr++) {
5062 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
5063 compose->newsgroup_list = newsgroup_list_append(compose->newsgroup_list, entry);
5064 recipient_found = TRUE;
5071 return recipient_found;
5074 static gboolean compose_check_for_set_recipients(Compose *compose)
5076 if (compose->account->set_autocc && compose->account->auto_cc) {
5077 gboolean found_other = FALSE;
5079 /* search header entries for to and newsgroup entries */
5080 for (list = compose->header_list; list; list = list->next) {
5083 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5084 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5087 if (strcmp(entry, compose->account->auto_cc)
5088 || strcmp(header, prefs_common_translated_header_name("Cc:"))) {
5099 if (compose->batch) {
5100 gtk_widget_show_all(compose->window);
5102 text = g_strdup_printf(_("The only recipient is the default '%s' address. Send anyway?"),
5103 prefs_common_translated_header_name("Cc"));
5104 aval = alertpanel(_("Send"),
5106 GTK_STOCK_CANCEL, _("_Send"), NULL, ALERTFOCUS_SECOND);
5108 if (aval != G_ALERTALTERNATE)
5112 if (compose->account->set_autobcc && compose->account->auto_bcc) {
5113 gboolean found_other = FALSE;
5115 /* search header entries for to and newsgroup entries */
5116 for (list = compose->header_list; list; list = list->next) {
5119 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5120 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5123 if (strcmp(entry, compose->account->auto_bcc)
5124 || strcmp(header, prefs_common_translated_header_name("Bcc:"))) {
5136 if (compose->batch) {
5137 gtk_widget_show_all(compose->window);
5139 text = g_strdup_printf(_("The only recipient is the default '%s' address. Send anyway?"),
5140 prefs_common_translated_header_name("Bcc"));
5141 aval = alertpanel(_("Send"),
5143 GTK_STOCK_CANCEL, _("_Send"), NULL, ALERTFOCUS_SECOND);
5145 if (aval != G_ALERTALTERNATE)
5152 static gboolean compose_check_entries(Compose *compose, gboolean check_everything)
5156 if (compose_check_for_valid_recipient(compose) == FALSE) {
5157 if (compose->batch) {
5158 gtk_widget_show_all(compose->window);
5160 alertpanel_error(_("Recipient is not specified."));
5164 if (compose_check_for_set_recipients(compose) == FALSE) {
5168 if (!compose->batch && prefs_common.warn_empty_subj == TRUE) {
5169 str = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5170 if (*str == '\0' && check_everything == TRUE &&
5171 compose->mode != COMPOSE_REDIRECT) {
5175 message = g_strdup_printf(_("Subject is empty. %s"),
5176 compose->sending?_("Send it anyway?"):
5177 _("Queue it anyway?"));
5179 aval = alertpanel_full(compose->sending?_("Send"):_("Send later"), message,
5180 GTK_STOCK_CANCEL, compose->sending?_("_Send"):_("_Queue"), NULL,
5181 ALERTFOCUS_FIRST, TRUE, NULL, ALERT_QUESTION);
5183 if (aval & G_ALERTDISABLE) {
5184 aval &= ~G_ALERTDISABLE;
5185 prefs_common.warn_empty_subj = FALSE;
5187 if (aval != G_ALERTALTERNATE)
5192 if (!compose->batch && prefs_common.warn_sending_many_recipients_num > 0
5193 && check_everything == TRUE) {
5197 /* count To and Cc recipients */
5198 for (list = compose->header_list; list; list = list->next) {
5202 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5203 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5206 if ((entry[0] != '\0') &&
5207 (!strcmp(header, prefs_common_translated_header_name("To:")) ||
5208 !strcmp(header, prefs_common_translated_header_name("Cc:")))) {
5214 if (cnt > prefs_common.warn_sending_many_recipients_num) {
5218 message = g_strdup_printf(_("Sending to %d recipients. %s"), cnt,
5219 compose->sending?_("Send it anyway?"):
5220 _("Queue it anyway?"));
5222 aval = alertpanel_full(compose->sending?_("Send"):_("Send later"), message,
5223 GTK_STOCK_CANCEL, compose->sending?_("_Send"):_("_Queue"), NULL,
5224 ALERTFOCUS_FIRST, TRUE, NULL, ALERT_QUESTION);
5226 if (aval & G_ALERTDISABLE) {
5227 aval &= ~G_ALERTDISABLE;
5228 prefs_common.warn_sending_many_recipients_num = 0;
5230 if (aval != G_ALERTALTERNATE)
5235 if (check_everything && hooks_invoke(COMPOSE_CHECK_BEFORE_SEND_HOOKLIST, compose))
5241 static void _display_queue_error(ComposeQueueResult val)
5244 case COMPOSE_QUEUE_SUCCESS:
5246 case COMPOSE_QUEUE_ERROR_NO_MSG:
5247 alertpanel_error(_("Could not queue message."));
5249 case COMPOSE_QUEUE_ERROR_WITH_ERRNO:
5250 alertpanel_error(_("Could not queue message:\n\n%s."),
5253 case COMPOSE_QUEUE_ERROR_SIGNING_FAILED:
5254 alertpanel_error(_("Could not queue message for sending:\n\n"
5255 "Signature failed: %s"),
5256 privacy_peek_error() ? privacy_get_error() : _("Unknown error"));
5258 case COMPOSE_QUEUE_ERROR_ENCRYPT_FAILED:
5259 alertpanel_error(_("Could not queue message for sending:\n\n"
5260 "Encryption failed: %s"),
5261 privacy_peek_error() ? privacy_get_error() : _("Unknown error"));
5263 case COMPOSE_QUEUE_ERROR_CHAR_CONVERSION:
5264 alertpanel_error(_("Could not queue message for sending:\n\n"
5265 "Charset conversion failed."));
5267 case COMPOSE_QUEUE_ERROR_NO_ENCRYPTION_KEY:
5268 alertpanel_error(_("Could not queue message for sending:\n\n"
5269 "Couldn't get recipient encryption key."));
5272 /* unhandled error */
5273 debug_print("oops, unhandled compose_queue() return value %d\n",
5279 gint compose_send(Compose *compose)
5282 FolderItem *folder = NULL;
5283 ComposeQueueResult val = COMPOSE_QUEUE_ERROR_NO_MSG;
5284 gchar *msgpath = NULL;
5285 gboolean discard_window = FALSE;
5286 gchar *errstr = NULL;
5287 gchar *tmsgid = NULL;
5288 MainWindow *mainwin = mainwindow_get_mainwindow();
5289 gboolean queued_removed = FALSE;
5291 if (prefs_common.send_dialog_invisible
5292 || compose->batch == TRUE)
5293 discard_window = TRUE;
5295 compose_allow_user_actions (compose, FALSE);
5296 compose->sending = TRUE;
5298 if (compose_check_entries(compose, TRUE) == FALSE) {
5299 if (compose->batch) {
5300 gtk_widget_show_all(compose->window);
5306 val = compose_queue(compose, &msgnum, &folder, &msgpath, TRUE);
5308 if (val != COMPOSE_QUEUE_SUCCESS) {
5309 if (compose->batch) {
5310 gtk_widget_show_all(compose->window);
5313 _display_queue_error(val);
5318 tmsgid = compose->msgid ? g_strdup(compose->msgid) : NULL;
5319 if (discard_window) {
5320 compose->sending = FALSE;
5321 compose_close(compose);
5322 /* No more compose access in the normal codepath
5323 * after this point! */
5328 alertpanel_error(_("The message was queued but could not be "
5329 "sent.\nUse \"Send queued messages\" from "
5330 "the main window to retry."));
5331 if (!discard_window) {
5338 if (msgpath == NULL) {
5339 msgpath = folder_item_fetch_msg(folder, msgnum);
5340 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5343 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5344 claws_unlink(msgpath);
5347 if (!discard_window) {
5349 if (!queued_removed)
5350 folder_item_remove_msg(folder, msgnum);
5351 folder_item_scan(folder);
5353 /* make sure we delete that */
5354 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5356 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5357 folder_item_remove_msg(folder, tmp->msgnum);
5358 procmsg_msginfo_free(&tmp);
5365 if (!queued_removed)
5366 folder_item_remove_msg(folder, msgnum);
5367 folder_item_scan(folder);
5369 /* make sure we delete that */
5370 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5372 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5373 folder_item_remove_msg(folder, tmp->msgnum);
5374 procmsg_msginfo_free(&tmp);
5377 if (!discard_window) {
5378 compose->sending = FALSE;
5379 compose_allow_user_actions (compose, TRUE);
5380 compose_close(compose);
5384 alertpanel_error_log(_("%s\nUse \"Send queued messages\" from "
5385 "the main window to retry."), errstr);
5388 alertpanel_error_log(_("The message was queued but could not be "
5389 "sent.\nUse \"Send queued messages\" from "
5390 "the main window to retry."));
5392 if (!discard_window) {
5401 toolbar_main_set_sensitive(mainwin);
5402 main_window_set_menu_sensitive(mainwin);
5408 compose_allow_user_actions (compose, TRUE);
5409 compose->sending = FALSE;
5410 compose->modified = TRUE;
5411 toolbar_main_set_sensitive(mainwin);
5412 main_window_set_menu_sensitive(mainwin);
5417 static gboolean compose_use_attach(Compose *compose)
5419 GtkTreeModel *model = gtk_tree_view_get_model
5420 (GTK_TREE_VIEW(compose->attach_clist));
5421 return gtk_tree_model_iter_n_children(model, NULL) > 0;
5424 static gint compose_redirect_write_headers_from_headerlist(Compose *compose,
5427 gchar buf[BUFFSIZE];
5429 gboolean first_to_address;
5430 gboolean first_cc_address;
5432 ComposeHeaderEntry *headerentry;
5433 const gchar *headerentryname;
5434 const gchar *cc_hdr;
5435 const gchar *to_hdr;
5436 gboolean err = FALSE;
5438 debug_print("Writing redirect header\n");
5440 cc_hdr = prefs_common_translated_header_name("Cc:");
5441 to_hdr = prefs_common_translated_header_name("To:");
5443 first_to_address = TRUE;
5444 for (list = compose->header_list; list; list = list->next) {
5445 headerentry = ((ComposeHeaderEntry *)list->data);
5446 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5448 if (g_utf8_collate(headerentryname, to_hdr) == 0) {
5449 const gchar *entstr = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5450 Xstrdup_a(str, entstr, return -1);
5452 if (str[0] != '\0') {
5453 compose_convert_header
5454 (compose, buf, sizeof(buf), str,
5455 strlen("Resent-To") + 2, TRUE);
5457 if (first_to_address) {
5458 err |= (fprintf(fp, "Resent-To: ") < 0);
5459 first_to_address = FALSE;
5461 err |= (fprintf(fp, ",") < 0);
5463 err |= (fprintf(fp, "%s", buf) < 0);
5467 if (!first_to_address) {
5468 err |= (fprintf(fp, "\n") < 0);
5471 first_cc_address = TRUE;
5472 for (list = compose->header_list; list; list = list->next) {
5473 headerentry = ((ComposeHeaderEntry *)list->data);
5474 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5476 if (g_utf8_collate(headerentryname, cc_hdr) == 0) {
5477 const gchar *strg = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5478 Xstrdup_a(str, strg, return -1);
5480 if (str[0] != '\0') {
5481 compose_convert_header
5482 (compose, buf, sizeof(buf), str,
5483 strlen("Resent-Cc") + 2, TRUE);
5485 if (first_cc_address) {
5486 err |= (fprintf(fp, "Resent-Cc: ") < 0);
5487 first_cc_address = FALSE;
5489 err |= (fprintf(fp, ",") < 0);
5491 err |= (fprintf(fp, "%s", buf) < 0);
5495 if (!first_cc_address) {
5496 err |= (fprintf(fp, "\n") < 0);
5499 return (err ? -1:0);
5502 static gint compose_redirect_write_headers(Compose *compose, FILE *fp)
5504 gchar date[RFC822_DATE_BUFFSIZE];
5505 gchar buf[BUFFSIZE];
5507 const gchar *entstr;
5508 /* struct utsname utsbuf; */
5509 gboolean err = FALSE;
5511 cm_return_val_if_fail(fp != NULL, -1);
5512 cm_return_val_if_fail(compose->account != NULL, -1);
5513 cm_return_val_if_fail(compose->account->address != NULL, -1);
5516 if (prefs_common.hide_timezone)
5517 get_rfc822_date_hide_tz(date, sizeof(date));
5519 get_rfc822_date(date, sizeof(date));
5520 err |= (fprintf(fp, "Resent-Date: %s\n", date) < 0);
5523 if (compose->account->name && *compose->account->name) {
5524 compose_convert_header
5525 (compose, buf, sizeof(buf), compose->account->name,
5526 strlen("From: "), TRUE);
5527 err |= (fprintf(fp, "Resent-From: %s <%s>\n",
5528 buf, compose->account->address) < 0);
5530 err |= (fprintf(fp, "Resent-From: %s\n", compose->account->address) < 0);
5533 entstr = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5534 if (*entstr != '\0') {
5535 Xstrdup_a(str, entstr, return -1);
5538 compose_convert_header(compose, buf, sizeof(buf), str,
5539 strlen("Subject: "), FALSE);
5540 err |= (fprintf(fp, "Subject: %s\n", buf) < 0);
5544 /* Resent-Message-ID */
5545 if (compose->account->gen_msgid) {
5546 gchar *addr = prefs_account_generate_msgid(compose->account);
5547 err |= (fprintf(fp, "Resent-Message-ID: <%s>\n", addr) < 0);
5549 g_free(compose->msgid);
5550 compose->msgid = addr;
5552 compose->msgid = NULL;
5555 if (compose_redirect_write_headers_from_headerlist(compose, fp))
5558 /* separator between header and body */
5559 err |= (claws_fputs("\n", fp) == EOF);
5561 return (err ? -1:0);
5564 static gint compose_redirect_write_to_file(Compose *compose, FILE *fdest)
5569 gchar rewrite_buf[BUFFSIZE];
5571 gboolean skip = FALSE;
5572 gboolean err = FALSE;
5573 gchar *not_included[]={
5574 "Return-Path:", "Delivered-To:", "Received:",
5575 "Subject:", "X-UIDL:", "AF:",
5576 "NF:", "PS:", "SRH:",
5577 "SFN:", "DSR:", "MID:",
5578 "CFG:", "PT:", "S:",
5579 "RQ:", "SSV:", "NSV:",
5580 "SSH:", "R:", "MAID:",
5581 "NAID:", "RMID:", "FMID:",
5582 "SCF:", "RRCPT:", "NG:",
5583 "X-Claws-Privacy", "X-Claws-Sign:", "X-Claws-Encrypt",
5584 "X-Claws-End-Special-Headers:", "X-Claws-Account-Id:",
5585 "X-Sylpheed-Privacy", "X-Sylpheed-Sign:", "X-Sylpheed-Encrypt",
5586 "X-Sylpheed-End-Special-Headers:", "X-Sylpheed-Account-Id:",
5587 "X-Claws-Auto-Wrapping:", "X-Claws-Auto-Indent:",
5592 if ((fp = claws_fopen(compose->redirect_filename, "rb")) == NULL) {
5593 FILE_OP_ERROR(compose->redirect_filename, "claws_fopen");
5597 while ((ret = procheader_get_one_field_asis(&buf, fp)) != -1) {
5599 for (i = 0; not_included[i] != NULL; i++) {
5600 if (g_ascii_strncasecmp(buf, not_included[i],
5601 strlen(not_included[i])) == 0) {
5611 if (claws_fputs(buf, fdest) == -1) {
5617 if (!prefs_common.redirect_keep_from) {
5618 if (g_ascii_strncasecmp(buf, "From:",
5619 strlen("From:")) == 0) {
5620 err |= (claws_fputs(" (by way of ", fdest) == EOF);
5621 if (compose->account->name
5622 && *compose->account->name) {
5623 gchar buffer[BUFFSIZE];
5625 compose_convert_header
5626 (compose, buffer, sizeof(buffer),
5627 compose->account->name,
5630 err |= (fprintf(fdest, "%s <%s>",
5632 compose->account->address) < 0);
5634 err |= (fprintf(fdest, "%s",
5635 compose->account->address) < 0);
5636 err |= (claws_fputs(")", fdest) == EOF);
5642 if (claws_fputs("\n", fdest) == -1)
5649 if (compose_redirect_write_headers(compose, fdest))
5652 while ((len = claws_fread(rewrite_buf, sizeof(gchar), sizeof(rewrite_buf), fp)) > 0) {
5653 if (claws_fwrite(rewrite_buf, sizeof(gchar), len, fdest) != len)
5667 static gint compose_write_to_file(Compose *compose, FILE *fp, gint action, gboolean attach_parts)
5669 GtkTextBuffer *buffer;
5670 GtkTextIter start, end, tmp;
5671 gchar *chars, *tmp_enc_file, *content;
5673 const gchar *out_codeset;
5674 EncodingType encoding = ENC_UNKNOWN;
5675 MimeInfo *mimemsg, *mimetext;
5677 const gchar *src_codeset = CS_INTERNAL;
5678 gchar *from_addr = NULL;
5679 gchar *from_name = NULL;
5682 if (action == COMPOSE_WRITE_FOR_SEND) {
5683 attach_parts = TRUE;
5685 /* We're sending the message, generate a Message-ID
5687 if (compose->msgid == NULL &&
5688 compose->account->gen_msgid) {
5689 compose->msgid = prefs_account_generate_msgid(compose->account);
5693 /* create message MimeInfo */
5694 mimemsg = procmime_mimeinfo_new();
5695 mimemsg->type = MIMETYPE_MESSAGE;
5696 mimemsg->subtype = g_strdup("rfc822");
5697 mimemsg->content = MIMECONTENT_MEM;
5698 mimemsg->tmp = TRUE; /* must free content later */
5699 mimemsg->data.mem = compose_get_header(compose);
5701 /* Create text part MimeInfo */
5702 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5703 gtk_text_buffer_get_end_iter(buffer, &end);
5706 /* We make sure that there is a newline at the end. */
5707 if (action == COMPOSE_WRITE_FOR_SEND && gtk_text_iter_backward_char(&tmp)) {
5708 chars = gtk_text_buffer_get_text(buffer, &tmp, &end, FALSE);
5709 if (*chars != '\n') {
5710 gtk_text_buffer_insert(buffer, &end, "\n", 1);
5715 /* get all composed text */
5716 gtk_text_buffer_get_start_iter(buffer, &start);
5717 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5719 out_codeset = conv_get_charset_str(compose->out_encoding);
5721 if (!out_codeset && is_ascii_str(chars)) {
5722 out_codeset = CS_US_ASCII;
5723 } else if (prefs_common.outgoing_fallback_to_ascii &&
5724 is_ascii_str(chars)) {
5725 out_codeset = CS_US_ASCII;
5726 encoding = ENC_7BIT;
5730 gchar *test_conv_global_out = NULL;
5731 gchar *test_conv_reply = NULL;
5733 /* automatic mode. be automatic. */
5734 codeconv_set_strict(TRUE);
5736 out_codeset = conv_get_outgoing_charset_str();
5738 debug_print("trying to convert to %s\n", out_codeset);
5739 test_conv_global_out = conv_codeset_strdup(chars, src_codeset, out_codeset);
5742 if (!test_conv_global_out && compose->orig_charset
5743 && strcmp(compose->orig_charset, CS_US_ASCII)) {
5744 out_codeset = compose->orig_charset;
5745 debug_print("failure; trying to convert to %s\n", out_codeset);
5746 test_conv_reply = conv_codeset_strdup(chars, src_codeset, out_codeset);
5749 if (!test_conv_global_out && !test_conv_reply) {
5751 out_codeset = CS_INTERNAL;
5752 debug_print("failure; finally using %s\n", out_codeset);
5754 g_free(test_conv_global_out);
5755 g_free(test_conv_reply);
5756 codeconv_set_strict(FALSE);
5759 if (encoding == ENC_UNKNOWN) {
5760 if (prefs_common.encoding_method == CTE_BASE64)
5761 encoding = ENC_BASE64;
5762 else if (prefs_common.encoding_method == CTE_QUOTED_PRINTABLE)
5763 encoding = ENC_QUOTED_PRINTABLE;
5764 else if (prefs_common.encoding_method == CTE_8BIT)
5765 encoding = ENC_8BIT;
5767 encoding = procmime_get_encoding_for_charset(out_codeset);
5770 debug_print("src encoding = %s, out encoding = %s, transfer encoding = %s\n",
5771 src_codeset, out_codeset, procmime_get_encoding_str(encoding));
5773 if (action == COMPOSE_WRITE_FOR_SEND) {
5774 codeconv_set_strict(TRUE);
5775 buf = conv_codeset_strdup(chars, src_codeset, out_codeset);
5776 codeconv_set_strict(FALSE);
5781 msg = g_strdup_printf(_("Can't convert the character encoding of the message \n"
5782 "to the specified %s charset.\n"
5783 "Send it as %s?"), out_codeset, src_codeset);
5784 aval = alertpanel_full(_("Error"), msg, GTK_STOCK_CANCEL,
5785 _("_Send"), NULL, ALERTFOCUS_SECOND, FALSE,
5789 if (aval != G_ALERTALTERNATE) {
5791 return COMPOSE_QUEUE_ERROR_CHAR_CONVERSION;
5794 out_codeset = src_codeset;
5800 out_codeset = src_codeset;
5805 if (prefs_common.rewrite_first_from && (encoding == ENC_8BIT || encoding == ENC_7BIT)) {
5806 if (!strncmp(buf, "From ", sizeof("From ")-1) ||
5807 strstr(buf, "\nFrom ") != NULL) {
5808 encoding = ENC_QUOTED_PRINTABLE;
5812 mimetext = procmime_mimeinfo_new();
5813 mimetext->content = MIMECONTENT_MEM;
5814 mimetext->tmp = TRUE; /* must free content later */
5815 /* dup'ed because procmime_encode_content can turn it into a tmpfile
5816 * and free the data, which we need later. */
5817 mimetext->data.mem = g_strdup(buf);
5818 mimetext->type = MIMETYPE_TEXT;
5819 mimetext->subtype = g_strdup("plain");
5820 g_hash_table_insert(mimetext->typeparameters, g_strdup("charset"),
5821 g_strdup(out_codeset));
5823 /* protect trailing spaces when signing message */
5824 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5825 privacy_system_can_sign(compose->privacy_system)) {
5826 encoding = ENC_QUOTED_PRINTABLE;
5830 debug_print("main text: %Id bytes encoded as %s in %d\n",
5832 debug_print("main text: %zd bytes encoded as %s in %d\n",
5834 strlen(buf), out_codeset, encoding);
5836 /* check for line length limit */
5837 if (action == COMPOSE_WRITE_FOR_SEND &&
5838 encoding != ENC_QUOTED_PRINTABLE && encoding != ENC_BASE64 &&
5839 check_line_length(buf, 1000, &line) < 0) {
5842 msg = g_strdup_printf
5843 (_("Line %d exceeds the line length limit (998 bytes).\n"
5844 "The contents of the message might be broken on the way to the delivery.\n"
5846 "Send it anyway?"), line + 1);
5847 aval = alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, GTK_STOCK_OK, NULL,
5850 if (aval != G_ALERTALTERNATE) {
5852 return COMPOSE_QUEUE_ERROR_NO_MSG;
5856 if (encoding != ENC_UNKNOWN)
5857 procmime_encode_content(mimetext, encoding);
5859 /* append attachment parts */
5860 if (compose_use_attach(compose) && attach_parts) {
5861 MimeInfo *mimempart;
5862 gchar *boundary = NULL;
5863 mimempart = procmime_mimeinfo_new();
5864 mimempart->content = MIMECONTENT_EMPTY;
5865 mimempart->type = MIMETYPE_MULTIPART;
5866 mimempart->subtype = g_strdup("mixed");
5870 boundary = generate_mime_boundary(NULL);
5871 } while (strstr(buf, boundary) != NULL);
5873 g_hash_table_insert(mimempart->typeparameters, g_strdup("boundary"),
5876 mimetext->disposition = DISPOSITIONTYPE_INLINE;
5878 g_node_append(mimempart->node, mimetext->node);
5879 g_node_append(mimemsg->node, mimempart->node);
5881 if (compose_add_attachments(compose, mimempart) < 0)
5882 return COMPOSE_QUEUE_ERROR_NO_MSG;
5884 g_node_append(mimemsg->node, mimetext->node);
5888 if (strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) != 0) {
5889 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
5890 /* extract name and address */
5891 if (strstr(spec, " <") && strstr(spec, ">")) {
5892 from_addr = g_strdup(strrchr(spec, '<')+1);
5893 *(strrchr(from_addr, '>')) = '\0';
5894 from_name = g_strdup(spec);
5895 *(strrchr(from_name, '<')) = '\0';
5902 /* sign message if sending */
5903 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5904 privacy_system_can_sign(compose->privacy_system))
5905 if (!privacy_sign(compose->privacy_system, mimemsg,
5906 compose->account, from_addr)) {
5909 return COMPOSE_QUEUE_ERROR_SIGNING_FAILED;
5914 if (compose->use_encryption) {
5915 if (compose->encdata != NULL &&
5916 strcmp(compose->encdata, "_DONT_ENCRYPT_")) {
5918 /* First, write an unencrypted copy and save it to outbox, if
5919 * user wants that. */
5920 if (compose->account->save_encrypted_as_clear_text) {
5921 debug_print("saving sent message unencrypted...\n");
5922 FILE *tmpfp = get_tmpfile_in_dir(get_mime_tmp_dir(), &tmp_enc_file);
5924 claws_fclose(tmpfp);
5926 /* fp now points to a file with headers written,
5927 * let's make a copy. */
5929 content = file_read_stream_to_str(fp);
5931 str_write_to_file(content, tmp_enc_file, TRUE);
5934 /* Now write the unencrypted body. */
5935 if ((tmpfp = claws_fopen(tmp_enc_file, "a")) != NULL) {
5936 procmime_write_mimeinfo(mimemsg, tmpfp);
5937 claws_fclose(tmpfp);
5939 outbox = folder_find_item_from_identifier(compose_get_save_to(compose));
5941 outbox = folder_get_default_outbox();
5943 procmsg_save_to_outbox(outbox, tmp_enc_file, TRUE);
5944 claws_unlink(tmp_enc_file);
5946 g_warning("Can't open file '%s'", tmp_enc_file);
5949 g_warning("couldn't get tempfile");
5952 if (!privacy_encrypt(compose->privacy_system, mimemsg, compose->encdata)) {
5953 debug_print("Couldn't encrypt mime structure: %s.\n",
5954 privacy_get_error());
5955 return COMPOSE_QUEUE_ERROR_ENCRYPT_FAILED;
5960 procmime_write_mimeinfo(mimemsg, fp);
5962 procmime_mimeinfo_free_all(&mimemsg);
5967 static gint compose_write_body_to_file(Compose *compose, const gchar *file)
5969 GtkTextBuffer *buffer;
5970 GtkTextIter start, end;
5975 if ((fp = claws_fopen(file, "wb")) == NULL) {
5976 FILE_OP_ERROR(file, "claws_fopen");
5980 /* chmod for security */
5981 if (change_file_mode_rw(fp, file) < 0) {
5982 FILE_OP_ERROR(file, "chmod");
5983 g_warning("can't change file mode");
5986 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5987 gtk_text_buffer_get_start_iter(buffer, &start);
5988 gtk_text_buffer_get_end_iter(buffer, &end);
5989 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5991 chars = conv_codeset_strdup
5992 (tmp, CS_INTERNAL, conv_get_locale_charset_str());
6001 len = strlen(chars);
6002 if (claws_fwrite(chars, sizeof(gchar), len, fp) != len) {
6003 FILE_OP_ERROR(file, "claws_fwrite");
6012 if (claws_safe_fclose(fp) == EOF) {
6013 FILE_OP_ERROR(file, "claws_fclose");
6020 static gint compose_remove_reedit_target(Compose *compose, gboolean force)
6023 MsgInfo *msginfo = compose->targetinfo;
6025 cm_return_val_if_fail(compose->mode == COMPOSE_REEDIT, -1);
6026 if (!msginfo) return -1;
6028 if (!force && MSG_IS_LOCKED(msginfo->flags))
6031 item = msginfo->folder;
6032 cm_return_val_if_fail(item != NULL, -1);
6034 if (procmsg_msg_exist(msginfo) &&
6035 (folder_has_parent_of_type(item, F_QUEUE) ||
6036 folder_has_parent_of_type(item, F_DRAFT)
6037 || msginfo == compose->autosaved_draft)) {
6038 if (folder_item_remove_msg(item, msginfo->msgnum) < 0) {
6039 g_warning("can't remove the old message");
6042 debug_print("removed reedit target %d\n", msginfo->msgnum);
6049 static void compose_remove_draft(Compose *compose)
6052 MsgInfo *msginfo = compose->targetinfo;
6053 drafts = account_get_special_folder(compose->account, F_DRAFT);
6055 if (procmsg_msg_exist(msginfo)) {
6056 folder_item_remove_msg(drafts, msginfo->msgnum);
6061 ComposeQueueResult compose_queue(Compose *compose, gint *msgnum, FolderItem **item, gchar **msgpath,
6062 gboolean remove_reedit_target)
6064 return compose_queue_sub (compose, msgnum, item, msgpath, FALSE, remove_reedit_target);
6067 static gboolean compose_warn_encryption(Compose *compose)
6069 const gchar *warning = privacy_get_encrypt_warning(compose->privacy_system);
6070 AlertValue val = G_ALERTALTERNATE;
6072 if (warning == NULL)
6075 val = alertpanel_full(_("Encryption warning"), warning,
6076 GTK_STOCK_CANCEL, _("C_ontinue"), NULL, ALERTFOCUS_SECOND,
6077 TRUE, NULL, ALERT_WARNING);
6078 if (val & G_ALERTDISABLE) {
6079 val &= ~G_ALERTDISABLE;
6080 if (val == G_ALERTALTERNATE)
6081 privacy_inhibit_encrypt_warning(compose->privacy_system,
6085 if (val == G_ALERTALTERNATE) {
6092 static ComposeQueueResult compose_queue_sub(Compose *compose, gint *msgnum, FolderItem **item,
6093 gchar **msgpath, gboolean perform_checks,
6094 gboolean remove_reedit_target)
6101 PrefsAccount *mailac = NULL, *newsac = NULL;
6102 gboolean err = FALSE;
6104 debug_print("queueing message...\n");
6105 cm_return_val_if_fail(compose->account != NULL, -1);
6107 if (compose_check_entries(compose, perform_checks) == FALSE) {
6108 if (compose->batch) {
6109 gtk_widget_show_all(compose->window);
6111 return COMPOSE_QUEUE_ERROR_NO_MSG;
6114 if (!compose->to_list && !compose->newsgroup_list) {
6115 g_warning("can't get recipient list.");
6116 return COMPOSE_QUEUE_ERROR_NO_MSG;
6119 if (compose->to_list) {
6120 if (compose->account->protocol != A_NNTP)
6121 mailac = compose->account;
6122 else if (cur_account && cur_account->protocol != A_NNTP)
6123 mailac = cur_account;
6124 else if (!(mailac = compose_current_mail_account())) {
6125 alertpanel_error(_("No account for sending mails available!"));
6126 return COMPOSE_QUEUE_ERROR_NO_MSG;
6130 if (compose->newsgroup_list) {
6131 if (compose->account->protocol == A_NNTP)
6132 newsac = compose->account;
6134 alertpanel_error(_("Selected account isn't NNTP: Posting is impossible."));
6135 return COMPOSE_QUEUE_ERROR_NO_MSG;
6139 /* write queue header */
6140 tmp = g_strdup_printf("%s%cqueue.%p%08x", get_tmp_dir(),
6141 G_DIR_SEPARATOR, compose, (guint) rand());
6142 debug_print("queuing to %s\n", tmp);
6143 if ((fp = claws_fopen(tmp, "w+b")) == NULL) {
6144 FILE_OP_ERROR(tmp, "claws_fopen");
6146 return COMPOSE_QUEUE_ERROR_WITH_ERRNO;
6149 if (change_file_mode_rw(fp, tmp) < 0) {
6150 FILE_OP_ERROR(tmp, "chmod");
6151 g_warning("can't change file mode");
6154 /* queueing variables */
6155 err |= (fprintf(fp, "AF:\n") < 0);
6156 err |= (fprintf(fp, "NF:0\n") < 0);
6157 err |= (fprintf(fp, "PS:10\n") < 0);
6158 err |= (fprintf(fp, "SRH:1\n") < 0);
6159 err |= (fprintf(fp, "SFN:\n") < 0);
6160 err |= (fprintf(fp, "DSR:\n") < 0);
6162 err |= (fprintf(fp, "MID:<%s>\n", compose->msgid) < 0);
6164 err |= (fprintf(fp, "MID:\n") < 0);
6165 err |= (fprintf(fp, "CFG:\n") < 0);
6166 err |= (fprintf(fp, "PT:0\n") < 0);
6167 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
6168 err |= (fprintf(fp, "RQ:\n") < 0);
6170 err |= (fprintf(fp, "SSV:%s\n", mailac->smtp_server) < 0);
6172 err |= (fprintf(fp, "SSV:\n") < 0);
6174 err |= (fprintf(fp, "NSV:%s\n", newsac->nntp_server) < 0);
6176 err |= (fprintf(fp, "NSV:\n") < 0);
6177 err |= (fprintf(fp, "SSH:\n") < 0);
6178 /* write recipient list */
6179 if (compose->to_list) {
6180 err |= (fprintf(fp, "R:<%s>", (gchar *)compose->to_list->data) < 0);
6181 for (cur = compose->to_list->next; cur != NULL;
6183 err |= (fprintf(fp, ",<%s>", (gchar *)cur->data) < 0);
6184 err |= (fprintf(fp, "\n") < 0);
6186 /* write newsgroup list */
6187 if (compose->newsgroup_list) {
6188 err |= (fprintf(fp, "NG:") < 0);
6189 err |= (fprintf(fp, "%s", (gchar *)compose->newsgroup_list->data) < 0);
6190 for (cur = compose->newsgroup_list->next; cur != NULL; cur = cur->next)
6191 err |= (fprintf(fp, ",%s", (gchar *)cur->data) < 0);
6192 err |= (fprintf(fp, "\n") < 0);
6196 err |= (fprintf(fp, "MAID:%d\n", mailac->account_id) < 0);
6198 err |= (fprintf(fp, "NAID:%d\n", newsac->account_id) < 0);
6201 if (compose->privacy_system != NULL) {
6202 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
6203 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
6204 if (compose->use_encryption) {
6205 if (!compose_warn_encryption(compose)) {
6209 return COMPOSE_QUEUE_ERROR_NO_MSG;
6211 if (mailac && mailac->encrypt_to_self) {
6212 GSList *tmp_list = g_slist_copy(compose->to_list);
6213 tmp_list = g_slist_append(tmp_list, compose->account->address);
6214 compose->encdata = privacy_get_encrypt_data(compose->privacy_system, tmp_list);
6215 g_slist_free(tmp_list);
6217 compose->encdata = privacy_get_encrypt_data(compose->privacy_system, compose->to_list);
6219 if (compose->encdata != NULL) {
6220 if (strcmp(compose->encdata, "_DONT_ENCRYPT_")) {
6221 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
6222 err |= (fprintf(fp, "X-Claws-Encrypt-Data:%s\n",
6223 compose->encdata) < 0);
6224 } /* else we finally dont want to encrypt */
6226 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
6227 /* and if encdata was null, it means there's been a problem in
6230 g_warning("failed to write queue message");
6234 return COMPOSE_QUEUE_ERROR_NO_ENCRYPTION_KEY;
6239 /* Save copy folder */
6240 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
6241 gchar *savefolderid;
6243 savefolderid = compose_get_save_to(compose);
6244 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
6245 g_free(savefolderid);
6247 /* Save copy folder */
6248 if (compose->return_receipt) {
6249 err |= (fprintf(fp, "RRCPT:1\n") < 0);
6251 /* Message-ID of message replying to */
6252 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
6253 gchar *folderid = NULL;
6255 if (compose->replyinfo->folder)
6256 folderid = folder_item_get_identifier(compose->replyinfo->folder);
6257 if (folderid == NULL)
6258 folderid = g_strdup("NULL");
6260 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
6263 /* Message-ID of message forwarding to */
6264 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
6265 gchar *folderid = NULL;
6267 if (compose->fwdinfo->folder)
6268 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
6269 if (folderid == NULL)
6270 folderid = g_strdup("NULL");
6272 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
6276 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
6277 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
6279 /* end of headers */
6280 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
6282 if (compose->redirect_filename != NULL) {
6283 if (compose_redirect_write_to_file(compose, fp) < 0) {
6287 return COMPOSE_QUEUE_ERROR_WITH_ERRNO;
6291 if ((result = compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_SEND, TRUE)) < 0) {
6299 g_warning("failed to write queue message");
6303 return COMPOSE_QUEUE_ERROR_WITH_ERRNO;
6305 if (claws_safe_fclose(fp) == EOF) {
6306 FILE_OP_ERROR(tmp, "claws_fclose");
6309 return COMPOSE_QUEUE_ERROR_WITH_ERRNO;
6312 if (item && *item) {
6315 queue = account_get_special_folder(compose->account, F_QUEUE);
6318 g_warning("can't find queue folder");
6321 return COMPOSE_QUEUE_ERROR_NO_MSG;
6323 folder_item_scan(queue);
6324 if ((num = folder_item_add_msg(queue, tmp, NULL, FALSE)) < 0) {
6325 g_warning("can't queue the message");
6328 return COMPOSE_QUEUE_ERROR_NO_MSG;
6331 if (msgpath == NULL) {
6337 if (compose->mode == COMPOSE_REEDIT && compose->targetinfo) {
6338 MsgInfo *mi = folder_item_get_msginfo(queue, num);
6340 procmsg_msginfo_change_flags(mi,
6341 compose->targetinfo->flags.perm_flags,
6342 compose->targetinfo->flags.tmp_flags & ~(MSG_COPY | MSG_MOVE | MSG_MOVE_DONE),
6345 g_slist_free(mi->tags);
6346 mi->tags = g_slist_copy(compose->targetinfo->tags);
6347 procmsg_msginfo_free(&mi);
6351 if (compose->mode == COMPOSE_REEDIT && remove_reedit_target) {
6352 compose_remove_reedit_target(compose, FALSE);
6355 if ((msgnum != NULL) && (item != NULL)) {
6360 return COMPOSE_QUEUE_SUCCESS;
6363 static int compose_add_attachments(Compose *compose, MimeInfo *parent)
6366 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
6371 GError *error = NULL;
6376 gchar *type, *subtype;
6377 GtkTreeModel *model;
6380 model = gtk_tree_view_get_model(tree_view);
6382 if (!gtk_tree_model_get_iter_first(model, &iter))
6385 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
6387 if (!is_file_exist(ainfo->file)) {
6388 gchar *msg = g_strdup_printf(_("Attachment %s doesn't exist anymore. Ignore?"), ainfo->file);
6389 AlertValue val = alertpanel_full(_("Warning"), msg,
6390 _("Cancel sending"), _("Ignore attachment"), NULL,
6391 ALERTFOCUS_FIRST, FALSE, NULL, ALERT_WARNING);
6393 if (val == G_ALERTDEFAULT) {
6399 f = g_file_new_for_path(ainfo->file);
6400 fi = g_file_query_info(f, "standard::size",
6401 G_FILE_QUERY_INFO_NONE, NULL, &error);
6402 if (error != NULL) {
6403 g_warning(error->message);
6404 g_error_free(error);
6408 size = g_file_info_get_size(fi);
6412 if (g_stat(ainfo->file, &statbuf) < 0)
6414 size = statbuf.st_size;
6417 mimepart = procmime_mimeinfo_new();
6418 mimepart->content = MIMECONTENT_FILE;
6419 mimepart->data.filename = g_strdup(ainfo->file);
6420 mimepart->tmp = FALSE; /* or we destroy our attachment */
6421 mimepart->offset = 0;
6422 mimepart->length = size;
6424 type = g_strdup(ainfo->content_type);
6426 if (!strchr(type, '/')) {
6428 type = g_strdup("application/octet-stream");
6431 subtype = strchr(type, '/') + 1;
6432 *(subtype - 1) = '\0';
6433 mimepart->type = procmime_get_media_type(type);
6434 mimepart->subtype = g_strdup(subtype);
6437 if (mimepart->type == MIMETYPE_MESSAGE &&
6438 !g_ascii_strcasecmp(mimepart->subtype, "rfc822")) {
6439 mimepart->disposition = DISPOSITIONTYPE_INLINE;
6440 } else if (mimepart->type == MIMETYPE_TEXT) {
6441 if (!ainfo->name && g_ascii_strcasecmp(mimepart->subtype, "plain")) {
6442 /* Text parts with no name come from multipart/alternative
6443 * forwards. Make sure the recipient won't look at the
6444 * original HTML part by mistake. */
6445 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6446 ainfo->name = g_strdup_printf(_("Original %s part"),
6450 g_hash_table_insert(mimepart->typeparameters,
6451 g_strdup("charset"), g_strdup(ainfo->charset));
6453 if (ainfo->name && mimepart->type != MIMETYPE_MESSAGE) {
6454 if (mimepart->type == MIMETYPE_APPLICATION &&
6455 !strcmp2(mimepart->subtype, "octet-stream"))
6456 g_hash_table_insert(mimepart->typeparameters,
6457 g_strdup("name"), g_strdup(ainfo->name));
6458 g_hash_table_insert(mimepart->dispositionparameters,
6459 g_strdup("filename"), g_strdup(ainfo->name));
6460 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6463 if (mimepart->type == MIMETYPE_MESSAGE
6464 || mimepart->type == MIMETYPE_MULTIPART)
6465 ainfo->encoding = ENC_BINARY;
6466 else if (compose->use_signing) {
6467 if (ainfo->encoding == ENC_7BIT)
6468 ainfo->encoding = ENC_QUOTED_PRINTABLE;
6469 else if (ainfo->encoding == ENC_8BIT)
6470 ainfo->encoding = ENC_BASE64;
6473 procmime_encode_content(mimepart, ainfo->encoding);
6475 g_node_append(parent->node, mimepart->node);
6476 } while (gtk_tree_model_iter_next(model, &iter));
6481 static gchar *compose_quote_list_of_addresses(gchar *str)
6483 GSList *list = NULL, *item = NULL;
6484 gchar *qname = NULL, *faddr = NULL, *result = NULL;
6486 list = address_list_append_with_comments(list, str);
6487 for (item = list; item != NULL; item = item->next) {
6488 gchar *spec = item->data;
6489 gchar *endofname = strstr(spec, " <");
6490 if (endofname != NULL) {
6493 QUOTE_IF_REQUIRED_NORMAL(qname, spec, return NULL);
6494 qqname = escape_internal_quotes(qname, '"');
6496 if (*qname != *spec || qqname != qname) { /* has been quoted, compute new */
6497 gchar *addr = g_strdup(endofname);
6498 gchar *name = (qqname != qname)? qqname: g_strdup(qname);
6499 faddr = g_strconcat(name, addr, NULL);
6502 debug_print("new auto-quoted address: '%s'\n", faddr);
6506 result = g_strdup((faddr != NULL)? faddr: spec);
6508 result = g_strconcat(result,
6510 (faddr != NULL)? faddr: spec,
6513 if (faddr != NULL) {
6518 slist_free_strings_full(list);
6523 #define IS_IN_CUSTOM_HEADER(header) \
6524 (compose->account->add_customhdr && \
6525 custom_header_find(compose->account->customhdr_list, header) != NULL)
6527 static const gchar *compose_untranslated_header_name(gchar *header_name)
6529 /* return the untranslated header name, if header_name is a known
6530 header name, in either its translated or untranslated form, with
6531 or without trailing colon. otherwise, returns header_name. */
6532 gchar *translated_header_name;
6533 gchar *translated_header_name_wcolon;
6534 const gchar *untranslated_header_name;
6535 const gchar *untranslated_header_name_wcolon;
6538 cm_return_val_if_fail(header_name != NULL, NULL);
6540 for (i = 0; HEADERS[i].header_name != NULL; i++) {
6541 untranslated_header_name = HEADERS[i].header_name;
6542 untranslated_header_name_wcolon = HEADERS[i].header_name_w_colon;
6544 translated_header_name = gettext(untranslated_header_name);
6545 translated_header_name_wcolon = gettext(untranslated_header_name_wcolon);
6547 if (!strcmp(header_name, untranslated_header_name) ||
6548 !strcmp(header_name, translated_header_name)) {
6549 return untranslated_header_name;
6551 if (!strcmp(header_name, untranslated_header_name_wcolon) ||
6552 !strcmp(header_name, translated_header_name_wcolon)) {
6553 return untranslated_header_name_wcolon;
6557 debug_print("compose_untranslated_header_name: unknown header '%s'\n", header_name);
6561 static void compose_add_headerfield_from_headerlist(Compose *compose,
6563 const gchar *fieldname,
6564 const gchar *seperator)
6566 gchar *str, *fieldname_w_colon;
6567 gboolean add_field = FALSE;
6569 ComposeHeaderEntry *headerentry;
6570 const gchar *headerentryname;
6571 const gchar *trans_fieldname;
6574 if (IS_IN_CUSTOM_HEADER(fieldname))
6577 debug_print("Adding %s-fields\n", fieldname);
6579 fieldstr = g_string_sized_new(64);
6581 fieldname_w_colon = g_strconcat(fieldname, ":", NULL);
6582 trans_fieldname = prefs_common_translated_header_name(fieldname_w_colon);
6584 for (list = compose->header_list; list; list = list->next) {
6585 headerentry = ((ComposeHeaderEntry *)list->data);
6586 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
6588 if (!g_utf8_collate(trans_fieldname, headerentryname)) {
6589 gchar * ustr = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
6591 str = compose_quote_list_of_addresses(ustr);
6593 if (str != NULL && str[0] != '\0') {
6595 g_string_append(fieldstr, seperator);
6596 g_string_append(fieldstr, str);
6605 buf = g_new0(gchar, fieldstr->len * 4 + 256);
6606 compose_convert_header
6607 (compose, buf, fieldstr->len * 4 + 256, fieldstr->str,
6608 strlen(fieldname) + 2, TRUE);
6609 g_string_append_printf(header, "%s: %s\n", fieldname, buf);
6613 g_free(fieldname_w_colon);
6614 g_string_free(fieldstr, TRUE);
6619 static gchar *compose_get_manual_headers_info(Compose *compose)
6621 GString *sh_header = g_string_new(" ");
6623 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6625 for (list = compose->header_list; list; list = list->next) {
6626 ComposeHeaderEntry *headerentry;
6629 gchar *headername_wcolon;
6630 const gchar *headername_trans;
6632 gboolean standard_header = FALSE;
6634 headerentry = ((ComposeHeaderEntry *)list->data);
6636 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6638 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6643 if (!strstr(tmp, ":")) {
6644 headername_wcolon = g_strconcat(tmp, ":", NULL);
6645 headername = g_strdup(tmp);
6647 headername_wcolon = g_strdup(tmp);
6648 headername = g_strdup(strtok(tmp, ":"));
6652 string = std_headers;
6653 while (*string != NULL) {
6654 headername_trans = prefs_common_translated_header_name(*string);
6655 if (!strcmp(headername_trans, headername_wcolon))
6656 standard_header = TRUE;
6659 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
6660 g_string_append_printf(sh_header, "%s ", headername);
6662 g_free(headername_wcolon);
6664 g_string_truncate(sh_header, strlen(sh_header->str) - 1); /* remove last space */
6665 return g_string_free(sh_header, FALSE);
6668 static gchar *compose_get_header(Compose *compose)
6670 gchar date[RFC822_DATE_BUFFSIZE];
6671 gchar buf[BUFFSIZE];
6672 const gchar *entry_str;
6676 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6678 gchar *from_name = NULL, *from_address = NULL;
6681 cm_return_val_if_fail(compose->account != NULL, NULL);
6682 cm_return_val_if_fail(compose->account->address != NULL, NULL);
6684 header = g_string_sized_new(64);
6687 if (prefs_common.hide_timezone)
6688 get_rfc822_date_hide_tz(date, sizeof(date));
6690 get_rfc822_date(date, sizeof(date));
6691 g_string_append_printf(header, "Date: %s\n", date);
6695 if (compose->account->name && *compose->account->name) {
6697 QUOTE_IF_REQUIRED(buf, compose->account->name);
6698 tmp = g_strdup_printf("%s <%s>",
6699 buf, compose->account->address);
6701 tmp = g_strdup_printf("%s",
6702 compose->account->address);
6704 if (!strcmp(gtk_entry_get_text(GTK_ENTRY(compose->from_name)), tmp)
6705 || strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) == 0) {
6707 from_name = compose->account->name ? g_strdup(compose->account->name):NULL;
6708 from_address = g_strdup(compose->account->address);
6710 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
6711 /* extract name and address */
6712 if (strstr(spec, " <") && strstr(spec, ">")) {
6713 from_address = g_strdup(strrchr(spec, '<')+1);
6714 *(strrchr(from_address, '>')) = '\0';
6715 from_name = g_strdup(spec);
6716 *(strrchr(from_name, '<')) = '\0';
6719 from_address = g_strdup(spec);
6726 if (from_name && *from_name) {
6728 compose_convert_header
6729 (compose, buf, sizeof(buf), from_name,
6730 strlen("From: "), TRUE);
6731 QUOTE_IF_REQUIRED(name, buf);
6732 qname = escape_internal_quotes(name, '"');
6734 g_string_append_printf(header, "From: %s <%s>\n",
6735 qname, from_address);
6736 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To") &&
6737 compose->return_receipt) {
6738 compose_convert_header(compose, buf, sizeof(buf), from_name,
6739 strlen("Disposition-Notification-To: "),
6741 g_string_append_printf(header, "Disposition-Notification-To: %s <%s>\n", buf, from_address);
6746 g_string_append_printf(header, "From: %s\n", from_address);
6747 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To") &&
6748 compose->return_receipt)
6749 g_string_append_printf(header, "Disposition-Notification-To: %s\n", from_address);
6753 g_free(from_address);
6756 compose_add_headerfield_from_headerlist(compose, header, "To", ", ");
6759 compose_add_headerfield_from_headerlist(compose, header, "Newsgroups", ",");
6762 compose_add_headerfield_from_headerlist(compose, header, "Cc", ", ");
6766 * If this account is a NNTP account remove Bcc header from
6767 * message body since it otherwise will be publicly shown
6769 if (compose->account->protocol != A_NNTP)
6770 compose_add_headerfield_from_headerlist(compose, header, "Bcc", ", ");
6773 str = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
6775 if (*str != '\0' && !IS_IN_CUSTOM_HEADER("Subject")) {
6778 compose_convert_header(compose, buf, sizeof(buf), str,
6779 strlen("Subject: "), FALSE);
6780 g_string_append_printf(header, "Subject: %s\n", buf);
6786 if (compose->msgid != NULL && strlen(compose->msgid) > 0) {
6787 g_string_append_printf(header, "Message-ID: <%s>\n",
6791 if (compose->remove_references == FALSE) {
6793 if (compose->inreplyto && compose->to_list)
6794 g_string_append_printf(header, "In-Reply-To: <%s>\n", compose->inreplyto);
6797 if (compose->references)
6798 g_string_append_printf(header, "References: %s\n", compose->references);
6802 compose_add_headerfield_from_headerlist(compose, header, "Followup-To", ",");
6805 compose_add_headerfield_from_headerlist(compose, header, "Reply-To", ", ");
6808 if (compose->account->organization &&
6809 strlen(compose->account->organization) &&
6810 !IS_IN_CUSTOM_HEADER("Organization")) {
6811 compose_convert_header(compose, buf, sizeof(buf),
6812 compose->account->organization,
6813 strlen("Organization: "), FALSE);
6814 g_string_append_printf(header, "Organization: %s\n", buf);
6817 /* Program version and system info */
6818 if (compose->account->gen_xmailer &&
6819 g_slist_length(compose->to_list) && !IS_IN_CUSTOM_HEADER("X-Mailer") &&
6820 !compose->newsgroup_list) {
6821 g_string_append_printf(header, "X-Mailer: %s (GTK+ %d.%d.%d; %s)\n",
6823 gtk_major_version, gtk_minor_version, gtk_micro_version,
6826 if (compose->account->gen_xmailer &&
6827 g_slist_length(compose->newsgroup_list) && !IS_IN_CUSTOM_HEADER("X-Newsreader")) {
6828 g_string_append_printf(header, "X-Newsreader: %s (GTK+ %d.%d.%d; %s)\n",
6830 gtk_major_version, gtk_minor_version, gtk_micro_version,
6834 /* custom headers */
6835 if (compose->account->add_customhdr) {
6838 for (cur = compose->account->customhdr_list; cur != NULL;
6840 CustomHeader *chdr = (CustomHeader *)cur->data;
6842 if (custom_header_is_allowed(chdr->name)
6843 && chdr->value != NULL
6844 && *(chdr->value) != '\0') {
6845 compose_convert_header
6846 (compose, buf, sizeof(buf),
6848 strlen(chdr->name) + 2, FALSE);
6849 g_string_append_printf(header, "%s: %s\n", chdr->name, buf);
6854 /* Automatic Faces and X-Faces */
6855 if (get_account_xface (buf, sizeof(buf), compose->account->account_name) == 0) {
6856 g_string_append_printf(header, "X-Face: %s\n", buf);
6858 else if (get_default_xface (buf, sizeof(buf)) == 0) {
6859 g_string_append_printf(header, "X-Face: %s\n", buf);
6861 if (get_account_face (buf, sizeof(buf), compose->account->account_name) == 0) {
6862 g_string_append_printf(header, "Face: %s\n", buf);
6864 else if (get_default_face (buf, sizeof(buf)) == 0) {
6865 g_string_append_printf(header, "Face: %s\n", buf);
6869 switch (compose->priority) {
6870 case PRIORITY_HIGHEST: g_string_append_printf(header, "Importance: high\n"
6871 "X-Priority: 1 (Highest)\n");
6873 case PRIORITY_HIGH: g_string_append_printf(header, "Importance: high\n"
6874 "X-Priority: 2 (High)\n");
6876 case PRIORITY_NORMAL: break;
6877 case PRIORITY_LOW: g_string_append_printf(header, "Importance: low\n"
6878 "X-Priority: 4 (Low)\n");
6880 case PRIORITY_LOWEST: g_string_append_printf(header, "Importance: low\n"
6881 "X-Priority: 5 (Lowest)\n");
6883 default: debug_print("compose: priority unknown : %d\n",
6887 /* get special headers */
6888 for (list = compose->header_list; list; list = list->next) {
6889 ComposeHeaderEntry *headerentry;
6892 gchar *headername_wcolon;
6893 const gchar *headername_trans;
6896 gboolean standard_header = FALSE;
6898 headerentry = ((ComposeHeaderEntry *)list->data);
6900 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6902 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6907 if (!strstr(tmp, ":")) {
6908 headername_wcolon = g_strconcat(tmp, ":", NULL);
6909 headername = g_strdup(tmp);
6911 headername_wcolon = g_strdup(tmp);
6912 headername = g_strdup(strtok(tmp, ":"));
6916 entry_str = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
6917 Xstrdup_a(headervalue, entry_str, return NULL);
6918 subst_char(headervalue, '\r', ' ');
6919 subst_char(headervalue, '\n', ' ');
6920 g_strstrip(headervalue);
6921 if (*headervalue != '\0') {
6922 string = std_headers;
6923 while (*string != NULL && !standard_header) {
6924 headername_trans = prefs_common_translated_header_name(*string);
6925 /* support mixed translated and untranslated headers */
6926 if (!strcmp(headername_trans, headername_wcolon) || !strcmp(*string, headername_wcolon))
6927 standard_header = TRUE;
6930 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername)) {
6931 /* store untranslated header name */
6932 g_string_append_printf(header, "%s %s\n",
6933 compose_untranslated_header_name(headername_wcolon), headervalue);
6937 g_free(headername_wcolon);
6941 g_string_free(header, FALSE);
6946 #undef IS_IN_CUSTOM_HEADER
6948 static void compose_convert_header(Compose *compose, gchar *dest, gint len, gchar *src,
6949 gint header_len, gboolean addr_field)
6951 gchar *tmpstr = NULL;
6952 const gchar *out_codeset = NULL;
6954 cm_return_if_fail(src != NULL);
6955 cm_return_if_fail(dest != NULL);
6957 if (len < 1) return;
6959 tmpstr = g_strdup(src);
6961 subst_char(tmpstr, '\n', ' ');
6962 subst_char(tmpstr, '\r', ' ');
6965 if (!g_utf8_validate(tmpstr, -1, NULL)) {
6966 gchar *mybuf = g_malloc(strlen(tmpstr)*2 +1);
6967 conv_localetodisp(mybuf, strlen(tmpstr)*2 +1, tmpstr);
6972 codeconv_set_strict(TRUE);
6973 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6974 conv_get_charset_str(compose->out_encoding));
6975 codeconv_set_strict(FALSE);
6977 if (!dest || *dest == '\0') {
6978 gchar *test_conv_global_out = NULL;
6979 gchar *test_conv_reply = NULL;
6981 /* automatic mode. be automatic. */
6982 codeconv_set_strict(TRUE);
6984 out_codeset = conv_get_outgoing_charset_str();
6986 debug_print("trying to convert to %s\n", out_codeset);
6987 test_conv_global_out = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6990 if (!test_conv_global_out && compose->orig_charset
6991 && strcmp(compose->orig_charset, CS_US_ASCII)) {
6992 out_codeset = compose->orig_charset;
6993 debug_print("failure; trying to convert to %s\n", out_codeset);
6994 test_conv_reply = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6997 if (!test_conv_global_out && !test_conv_reply) {
6999 out_codeset = CS_INTERNAL;
7000 debug_print("finally using %s\n", out_codeset);
7002 g_free(test_conv_global_out);
7003 g_free(test_conv_reply);
7004 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
7006 codeconv_set_strict(FALSE);
7011 static void compose_add_to_addressbook_cb(GtkMenuItem *menuitem, gpointer user_data)
7015 cm_return_if_fail(user_data != NULL);
7017 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(user_data)));
7018 g_strstrip(address);
7019 if (*address != '\0') {
7020 gchar *name = procheader_get_fromname(address);
7021 extract_address(address);
7022 #ifndef USE_ALT_ADDRBOOK
7023 addressbook_add_contact(name, address, NULL, NULL);
7025 debug_print("%s: %s\n", name, address);
7026 if (addressadd_selection(name, address, NULL, NULL)) {
7027 debug_print( "addressbook_add_contact - added\n" );
7034 static void compose_entry_popup_extend(GtkEntry *entry, GtkMenu *menu, gpointer user_data)
7036 GtkWidget *menuitem;
7039 cm_return_if_fail(menu != NULL);
7040 cm_return_if_fail(GTK_IS_MENU_SHELL(menu));
7042 menuitem = gtk_separator_menu_item_new();
7043 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
7044 gtk_widget_show(menuitem);
7046 menuitem = gtk_menu_item_new_with_mnemonic(_("Add to address _book"));
7047 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
7049 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
7050 g_strstrip(address);
7051 if (*address == '\0') {
7052 gtk_widget_set_sensitive(GTK_WIDGET(menuitem), FALSE);
7055 g_signal_connect(G_OBJECT(menuitem), "activate",
7056 G_CALLBACK(compose_add_to_addressbook_cb), entry);
7057 gtk_widget_show(menuitem);
7060 void compose_add_extra_header(gchar *header, GtkListStore *model)
7063 if (strcmp(header, "")) {
7064 COMBOBOX_ADD(model, header, COMPOSE_TO);
7068 void compose_add_extra_header_entries(GtkListStore *model)
7072 gchar buf[BUFFSIZE];
7075 if (extra_headers == NULL) {
7076 exhrc = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, "extraheaderrc", NULL);
7077 if ((exh = claws_fopen(exhrc, "rb")) == NULL) {
7078 debug_print("extra headers file not found\n");
7079 goto extra_headers_done;
7081 while (claws_fgets(buf, BUFFSIZE, exh) != NULL) {
7082 lastc = strlen(buf) - 1; /* remove trailing control chars */
7083 while (lastc >= 0 && buf[lastc] != ':')
7084 buf[lastc--] = '\0';
7085 if (lastc > 0 && buf[0] != '#' && buf[lastc] == ':') {
7086 buf[lastc] = '\0'; /* remove trailing : for comparison */
7087 if (custom_header_is_allowed(buf)) {
7089 extra_headers = g_slist_prepend(extra_headers, g_strdup(buf));
7092 g_message("disallowed extra header line: %s\n", buf);
7096 g_message("invalid extra header line: %s\n", buf);
7102 extra_headers = g_slist_prepend(extra_headers, g_strdup("")); /* end of list */
7103 extra_headers = g_slist_reverse(extra_headers);
7105 g_slist_foreach(extra_headers, (GFunc)compose_add_extra_header, (gpointer)model);
7109 static void _ldap_srv_func(gpointer data, gpointer user_data)
7111 LdapServer *server = (LdapServer *)data;
7112 gboolean *enable = (gboolean *)user_data;
7114 debug_print("%s server '%s'\n", (*enable == TRUE ? "enabling" : "disabling"), server->control->hostName);
7115 server->searchFlag = *enable;
7119 static void compose_create_header_entry(Compose *compose)
7121 gchar *headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
7128 const gchar *header = NULL;
7129 ComposeHeaderEntry *headerentry;
7130 gboolean standard_header = FALSE;
7131 GtkListStore *model;
7134 headerentry = g_new0(ComposeHeaderEntry, 1);
7136 /* Combo box model */
7137 model = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_BOOLEAN);
7138 COMBOBOX_ADD(model, prefs_common_translated_header_name("To:"),
7140 COMBOBOX_ADD(model, prefs_common_translated_header_name("Cc:"),
7142 COMBOBOX_ADD(model, prefs_common_translated_header_name("Bcc:"),
7144 COMBOBOX_ADD(model, prefs_common_translated_header_name("Newsgroups:"),
7145 COMPOSE_NEWSGROUPS);
7146 COMBOBOX_ADD(model, prefs_common_translated_header_name("Reply-To:"),
7148 COMBOBOX_ADD(model, prefs_common_translated_header_name("Followup-To:"),
7149 COMPOSE_FOLLOWUPTO);
7150 compose_add_extra_header_entries(model);
7153 combo = gtk_combo_box_new_with_model_and_entry(GTK_TREE_MODEL(model));
7154 GtkCellRenderer *cell = gtk_cell_renderer_text_new();
7155 gtk_cell_renderer_set_alignment(cell, 0.0, 0.5);
7156 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), cell, TRUE);
7157 gtk_combo_box_set_entry_text_column(GTK_COMBO_BOX(combo), 0);
7158 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
7159 g_signal_connect(G_OBJECT(gtk_bin_get_child(GTK_BIN(combo))), "grab_focus",
7160 G_CALLBACK(compose_grab_focus_cb), compose);
7161 gtk_widget_show(combo);
7163 /* Putting only the combobox child into focus chain of its parent causes
7164 * the parent to be skipped when changing focus via Tab or Shift+Tab.
7165 * This eliminates need to pres Tab twice in order to really get from the
7166 * combobox to next widget. */
7168 l = g_list_prepend(l, gtk_bin_get_child(GTK_BIN(combo)));
7169 gtk_container_set_focus_chain(GTK_CONTAINER(combo), l);
7172 gtk_table_attach(GTK_TABLE(compose->header_table), combo, 0, 1,
7173 compose->header_nextrow, compose->header_nextrow+1,
7174 GTK_SHRINK, GTK_FILL, 0, 0);
7175 if (compose->header_last && (compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN)) {
7176 const gchar *last_header_entry = gtk_entry_get_text(
7177 GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
7179 while (*string != NULL) {
7180 if (!strcmp(prefs_common_translated_header_name(*string), last_header_entry))
7181 standard_header = TRUE;
7184 if (standard_header)
7185 header = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
7187 if (!compose->header_last || !standard_header) {
7188 switch(compose->account->protocol) {
7190 header = prefs_common_translated_header_name("Newsgroups:");
7193 header = prefs_common_translated_header_name("To:");
7198 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((combo)))), header);
7200 gtk_editable_set_editable(
7201 GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((combo)))),
7202 prefs_common.type_any_header);
7204 g_signal_connect_after(G_OBJECT(gtk_bin_get_child(GTK_BIN((combo)))), "grab_focus",
7205 G_CALLBACK(compose_grab_focus_cb), compose);
7207 /* Entry field with cleanup button */
7208 button = gtk_button_new();
7209 gtk_button_set_image(GTK_BUTTON(button),
7210 gtk_image_new_from_stock(GTK_STOCK_CLEAR, GTK_ICON_SIZE_MENU));
7211 gtk_widget_show(button);
7212 CLAWS_SET_TIP(button,
7213 _("Delete entry contents"));
7214 entry = gtk_entry_new();
7215 gtk_widget_show(entry);
7216 CLAWS_SET_TIP(entry,
7217 _("Use <tab> to autocomplete from addressbook"));
7218 hbox = gtk_hbox_new (FALSE, 0);
7219 gtk_widget_show(hbox);
7220 gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, TRUE, 0);
7221 gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
7222 gtk_table_attach(GTK_TABLE(compose->header_table), hbox, 1, 2,
7223 compose->header_nextrow, compose->header_nextrow+1,
7224 GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
7226 g_signal_connect(G_OBJECT(entry), "key-press-event",
7227 G_CALLBACK(compose_headerentry_key_press_event_cb),
7229 g_signal_connect(G_OBJECT(entry), "changed",
7230 G_CALLBACK(compose_headerentry_changed_cb),
7232 g_signal_connect_after(G_OBJECT(entry), "grab_focus",
7233 G_CALLBACK(compose_grab_focus_cb), compose);
7235 g_signal_connect(G_OBJECT(button), "clicked",
7236 G_CALLBACK(compose_headerentry_button_clicked_cb),
7240 gtk_drag_dest_set(entry, GTK_DEST_DEFAULT_ALL, compose_mime_types,
7241 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7242 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7243 g_signal_connect(G_OBJECT(entry), "drag_data_received",
7244 G_CALLBACK(compose_header_drag_received_cb),
7246 g_signal_connect(G_OBJECT(entry), "drag-drop",
7247 G_CALLBACK(compose_drag_drop),
7249 g_signal_connect(G_OBJECT(entry), "populate-popup",
7250 G_CALLBACK(compose_entry_popup_extend),
7254 #ifndef PASSWORD_CRYPTO_OLD
7255 GSList *pwd_servers = addrindex_get_password_protected_ldap_servers();
7256 if (pwd_servers != NULL && master_passphrase() == NULL) {
7257 gboolean enable = FALSE;
7258 debug_print("Master passphrase not available, disabling password-protected LDAP servers for this compose window.\n");
7259 /* Temporarily disable password-protected LDAP servers,
7260 * because user did not provide a master passphrase.
7261 * We can safely enable searchFlag on all servers in this list
7262 * later, since addrindex_get_password_protected_ldap_servers()
7263 * includes servers which have it enabled initially. */
7264 g_slist_foreach(pwd_servers, _ldap_srv_func, &enable);
7265 compose->passworded_ldap_servers = pwd_servers;
7267 #endif /* PASSWORD_CRYPTO_OLD */
7268 #endif /* USE_LDAP */
7270 address_completion_register_entry(GTK_ENTRY(entry), TRUE);
7272 headerentry->compose = compose;
7273 headerentry->combo = combo;
7274 headerentry->entry = entry;
7275 headerentry->button = button;
7276 headerentry->hbox = hbox;
7277 headerentry->headernum = compose->header_nextrow;
7278 headerentry->type = PREF_NONE;
7280 compose->header_nextrow++;
7281 compose->header_last = headerentry;
7282 compose->header_list =
7283 g_slist_append(compose->header_list,
7287 static void compose_add_header_entry(Compose *compose, const gchar *header,
7288 gchar *text, ComposePrefType pref_type)
7290 ComposeHeaderEntry *last_header = compose->header_last;
7291 gchar *tmp = g_strdup(text), *email;
7292 gboolean replyto_hdr;
7294 replyto_hdr = (!strcasecmp(header,
7295 prefs_common_translated_header_name("Reply-To:")) ||
7297 prefs_common_translated_header_name("Followup-To:")) ||
7299 prefs_common_translated_header_name("In-Reply-To:")));
7301 extract_address(tmp);
7302 email = g_utf8_strdown(tmp, -1);
7304 if (replyto_hdr == FALSE &&
7305 g_hash_table_lookup(compose->email_hashtable, email) != NULL)
7307 debug_print("Ignoring duplicate address - %s %s, pref_type: %d\n",
7308 header, text, (gint) pref_type);
7314 if (!strcasecmp(header, prefs_common_translated_header_name("In-Reply-To:")))
7315 gtk_entry_set_text(GTK_ENTRY(
7316 gtk_bin_get_child(GTK_BIN(last_header->combo))), header);
7318 combobox_select_by_text(GTK_COMBO_BOX(last_header->combo), header);
7319 gtk_entry_set_text(GTK_ENTRY(last_header->entry), text);
7320 last_header->type = pref_type;
7322 if (replyto_hdr == FALSE)
7323 g_hash_table_insert(compose->email_hashtable, email,
7324 GUINT_TO_POINTER(1));
7331 static void compose_destroy_headerentry(Compose *compose,
7332 ComposeHeaderEntry *headerentry)
7334 gchar *text = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
7337 extract_address(text);
7338 email = g_utf8_strdown(text, -1);
7339 g_hash_table_remove(compose->email_hashtable, email);
7343 gtk_widget_destroy(headerentry->combo);
7344 gtk_widget_destroy(headerentry->entry);
7345 gtk_widget_destroy(headerentry->button);
7346 gtk_widget_destroy(headerentry->hbox);
7347 g_free(headerentry);
7350 static void compose_remove_header_entries(Compose *compose)
7353 for (list = compose->header_list; list; list = list->next)
7354 compose_destroy_headerentry(compose, (ComposeHeaderEntry *)list->data);
7356 compose->header_last = NULL;
7357 g_slist_free(compose->header_list);
7358 compose->header_list = NULL;
7359 compose->header_nextrow = 1;
7360 compose_create_header_entry(compose);
7363 static GtkWidget *compose_create_header(Compose *compose)
7365 GtkWidget *from_optmenu_hbox;
7366 GtkWidget *header_table_main;
7367 GtkWidget *header_scrolledwin;
7368 GtkWidget *header_table;
7370 /* parent with account selection and from header */
7371 header_table_main = gtk_table_new(2, 2, FALSE);
7372 gtk_widget_show(header_table_main);
7373 gtk_container_set_border_width(GTK_CONTAINER(header_table_main), BORDER_WIDTH);
7375 from_optmenu_hbox = compose_account_option_menu_create(compose);
7376 gtk_table_attach(GTK_TABLE(header_table_main), from_optmenu_hbox,
7377 0, 2, 0, 1, GTK_EXPAND | GTK_FILL, GTK_SHRINK, 0, 0);
7379 /* child with header labels and entries */
7380 header_scrolledwin = gtk_scrolled_window_new(NULL, NULL);
7381 gtk_widget_show(header_scrolledwin);
7382 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
7384 header_table = gtk_table_new(2, 2, FALSE);
7385 gtk_widget_show(header_table);
7386 gtk_container_set_border_width(GTK_CONTAINER(header_table), 0);
7387 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin), header_table);
7388 gtk_container_set_focus_vadjustment(GTK_CONTAINER(header_table),
7389 gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(header_scrolledwin)));
7390 gtk_viewport_set_shadow_type(GTK_VIEWPORT(gtk_bin_get_child(GTK_BIN(header_scrolledwin))), GTK_SHADOW_NONE);
7392 gtk_table_attach(GTK_TABLE(header_table_main), header_scrolledwin,
7393 0, 2, 1, 2, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 2);
7395 compose->header_table = header_table;
7396 compose->header_list = NULL;
7397 compose->header_nextrow = 0;
7399 compose_create_header_entry(compose);
7401 compose->table = NULL;
7403 return header_table_main;
7406 static gboolean popup_attach_button_pressed(GtkWidget *widget, gpointer data)
7408 Compose *compose = (Compose *)data;
7409 GdkEventButton event;
7412 event.time = gtk_get_current_event_time();
7414 return attach_button_pressed(compose->attach_clist, &event, compose);
7417 static GtkWidget *compose_create_attach(Compose *compose)
7419 GtkWidget *attach_scrwin;
7420 GtkWidget *attach_clist;
7422 GtkListStore *store;
7423 GtkCellRenderer *renderer;
7424 GtkTreeViewColumn *column;
7425 GtkTreeSelection *selection;
7427 /* attachment list */
7428 attach_scrwin = gtk_scrolled_window_new(NULL, NULL);
7429 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(attach_scrwin),
7430 GTK_POLICY_AUTOMATIC,
7431 GTK_POLICY_AUTOMATIC);
7432 gtk_widget_set_size_request(attach_scrwin, -1, 80);
7434 store = gtk_list_store_new(N_ATTACH_COLS,
7440 G_TYPE_AUTO_POINTER,
7442 attach_clist = GTK_WIDGET(gtk_tree_view_new_with_model
7443 (GTK_TREE_MODEL(store)));
7444 gtk_container_add(GTK_CONTAINER(attach_scrwin), attach_clist);
7445 g_object_unref(store);
7447 renderer = gtk_cell_renderer_text_new();
7448 column = gtk_tree_view_column_new_with_attributes
7449 (_("Mime type"), renderer, "text",
7450 COL_MIMETYPE, NULL);
7451 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7453 renderer = gtk_cell_renderer_text_new();
7454 column = gtk_tree_view_column_new_with_attributes
7455 (_("Size"), renderer, "text",
7457 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7459 renderer = gtk_cell_renderer_text_new();
7460 column = gtk_tree_view_column_new_with_attributes
7461 (_("Name"), renderer, "text",
7463 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7465 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(attach_clist),
7466 prefs_common.use_stripes_everywhere);
7467 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(attach_clist));
7468 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
7470 g_signal_connect(G_OBJECT(attach_clist), "row_activated",
7471 G_CALLBACK(attach_selected), compose);
7472 g_signal_connect(G_OBJECT(attach_clist), "button_press_event",
7473 G_CALLBACK(attach_button_pressed), compose);
7474 g_signal_connect(G_OBJECT(attach_clist), "popup-menu",
7475 G_CALLBACK(popup_attach_button_pressed), compose);
7476 g_signal_connect(G_OBJECT(attach_clist), "key_press_event",
7477 G_CALLBACK(attach_key_pressed), compose);
7480 gtk_drag_dest_set(attach_clist,
7481 GTK_DEST_DEFAULT_ALL, compose_mime_types,
7482 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7483 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7484 g_signal_connect(G_OBJECT(attach_clist), "drag_data_received",
7485 G_CALLBACK(compose_attach_drag_received_cb),
7487 g_signal_connect(G_OBJECT(attach_clist), "drag-drop",
7488 G_CALLBACK(compose_drag_drop),
7491 compose->attach_scrwin = attach_scrwin;
7492 compose->attach_clist = attach_clist;
7494 return attach_scrwin;
7497 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose);
7499 static GtkWidget *compose_create_others(Compose *compose)
7502 GtkWidget *savemsg_checkbtn;
7503 GtkWidget *savemsg_combo;
7504 GtkWidget *savemsg_select;
7507 gchar *folderidentifier;
7509 /* Table for settings */
7510 table = gtk_table_new(3, 1, FALSE);
7511 gtk_container_set_border_width(GTK_CONTAINER(table), BORDER_WIDTH);
7512 gtk_widget_show(table);
7513 gtk_table_set_row_spacings(GTK_TABLE(table), VSPACING_NARROW);
7516 /* Save Message to folder */
7517 savemsg_checkbtn = gtk_check_button_new_with_label(_("Save Message to "));
7518 gtk_widget_show(savemsg_checkbtn);
7519 gtk_table_attach(GTK_TABLE(table), savemsg_checkbtn, 0, 1, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7520 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7521 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), prefs_common.savemsg);
7524 savemsg_combo = gtk_combo_box_text_new_with_entry();
7525 compose->savemsg_checkbtn = savemsg_checkbtn;
7526 compose->savemsg_combo = savemsg_combo;
7527 gtk_widget_show(savemsg_combo);
7529 if (prefs_common.compose_save_to_history)
7530 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(savemsg_combo),
7531 prefs_common.compose_save_to_history);
7532 gtk_table_attach(GTK_TABLE(table), savemsg_combo, 1, 2, rowcount, rowcount + 1, GTK_FILL|GTK_EXPAND, GTK_SHRINK, 0, 0);
7533 gtk_widget_set_sensitive(GTK_WIDGET(savemsg_combo), prefs_common.savemsg);
7534 g_signal_connect_after(G_OBJECT(savemsg_combo), "grab_focus",
7535 G_CALLBACK(compose_grab_focus_cb), compose);
7536 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7537 if (compose->account->set_sent_folder)
7538 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), TRUE);
7540 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), FALSE);
7541 gtk_widget_set_sensitive(GTK_WIDGET(savemsg_combo), TRUE);
7542 folderidentifier = folder_item_get_identifier(account_get_special_folder
7543 (compose->account, F_OUTBOX));
7544 compose_set_save_to(compose, folderidentifier);
7545 g_free(folderidentifier);
7548 savemsg_select = gtkut_get_browse_file_btn(_("_Browse"));
7549 gtk_widget_show(savemsg_select);
7550 gtk_table_attach(GTK_TABLE(table), savemsg_select, 2, 3, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7551 g_signal_connect(G_OBJECT(savemsg_select), "clicked",
7552 G_CALLBACK(compose_savemsg_select_cb),
7558 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose)
7563 dest = foldersel_folder_sel(NULL, FOLDER_SEL_COPY, NULL, FALSE,
7564 _("Select folder to save message to"));
7567 path = folder_item_get_identifier(dest);
7569 compose_set_save_to(compose, path);
7573 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry, gboolean wrap,
7574 GdkAtom clip, GtkTextIter *insert_place);
7577 static gboolean text_clicked(GtkWidget *text, GdkEventButton *event,
7581 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
7583 if (event->button == 3) {
7585 GtkTextIter sel_start, sel_end;
7586 gboolean stuff_selected;
7588 /* move the cursor to allow GtkAspell to check the word
7589 * under the mouse */
7590 if (event->x && event->y) {
7591 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7592 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7594 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7597 GtkTextMark *mark = gtk_text_buffer_get_insert(buffer);
7598 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7601 stuff_selected = gtk_text_buffer_get_selection_bounds(
7603 &sel_start, &sel_end);
7605 gtk_text_buffer_place_cursor (buffer, &iter);
7606 /* reselect stuff */
7608 && gtk_text_iter_in_range(&iter, &sel_start, &sel_end)) {
7609 gtk_text_buffer_select_range(buffer,
7610 &sel_start, &sel_end);
7612 return FALSE; /* pass the event so that the right-click goes through */
7615 if (event->button == 2) {
7620 /* get the middle-click position to paste at the correct place */
7621 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7622 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7624 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7627 entry_paste_clipboard(compose, text,
7628 prefs_common.linewrap_pastes,
7629 GDK_SELECTION_PRIMARY, &iter);
7637 static void compose_spell_menu_changed(void *data)
7639 Compose *compose = (Compose *)data;
7641 GtkWidget *menuitem;
7642 GtkWidget *parent_item;
7643 GtkMenu *menu = GTK_MENU(gtk_menu_new());
7646 if (compose->gtkaspell == NULL)
7649 parent_item = gtk_ui_manager_get_widget(compose->ui_manager,
7650 "/Menu/Spelling/Options");
7652 /* setting the submenu removes /Spelling/Options from the factory
7653 * so we need to save it */
7655 if (parent_item == NULL) {
7656 parent_item = compose->aspell_options_menu;
7657 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), NULL);
7659 compose->aspell_options_menu = parent_item;
7661 spell_menu = gtkaspell_make_config_menu(compose->gtkaspell);
7663 spell_menu = g_slist_reverse(spell_menu);
7664 for (items = spell_menu;
7665 items; items = items->next) {
7666 menuitem = GTK_WIDGET(GTK_MENU_ITEM(items->data));
7667 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), GTK_WIDGET(menuitem));
7668 gtk_widget_show(GTK_WIDGET(menuitem));
7670 g_slist_free(spell_menu);
7672 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), GTK_WIDGET(menu));
7673 gtk_widget_show(parent_item);
7676 static void compose_dict_changed(void *data)
7678 Compose *compose = (Compose *) data;
7680 if(!compose->gtkaspell)
7682 if(compose->gtkaspell->recheck_when_changing_dict == FALSE)
7685 gtkaspell_highlight_all(compose->gtkaspell);
7686 claws_spell_entry_recheck_all(CLAWS_SPELL_ENTRY(compose->subject_entry));
7690 static gboolean compose_popup_menu(GtkWidget *widget, gpointer data)
7692 Compose *compose = (Compose *)data;
7693 GdkEventButton event;
7696 event.time = gtk_get_current_event_time();
7700 return text_clicked(compose->text, &event, compose);
7703 static gboolean compose_force_window_origin = TRUE;
7704 static Compose *compose_create(PrefsAccount *account,
7713 GtkWidget *handlebox;
7715 GtkWidget *notebook;
7717 GtkWidget *attach_hbox;
7718 GtkWidget *attach_lab1;
7719 GtkWidget *attach_lab2;
7724 GtkWidget *subject_hbox;
7725 GtkWidget *subject_frame;
7726 GtkWidget *subject_entry;
7730 GtkWidget *edit_vbox;
7731 GtkWidget *ruler_hbox;
7733 GtkWidget *scrolledwin;
7735 GtkTextBuffer *buffer;
7736 GtkClipboard *clipboard;
7738 UndoMain *undostruct;
7740 GtkWidget *popupmenu;
7741 GtkWidget *tmpl_menu;
7742 GtkActionGroup *action_group = NULL;
7745 GtkAspell * gtkaspell = NULL;
7748 static GdkGeometry geometry;
7750 cm_return_val_if_fail(account != NULL, NULL);
7752 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_DEFAULT_HEADER_BG],
7753 &default_header_bgcolor);
7754 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_DEFAULT_HEADER],
7755 &default_header_color);
7757 debug_print("Creating compose window...\n");
7758 compose = g_new0(Compose, 1);
7760 compose->batch = batch;
7761 compose->account = account;
7762 compose->folder = folder;
7764 compose->mutex = cm_mutex_new();
7765 compose->set_cursor_pos = -1;
7767 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose");
7769 gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
7770 gtk_widget_set_size_request(window, prefs_common.compose_width,
7771 prefs_common.compose_height);
7773 if (!geometry.max_width) {
7774 geometry.max_width = gdk_screen_width();
7775 geometry.max_height = gdk_screen_height();
7778 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7779 &geometry, GDK_HINT_MAX_SIZE);
7780 if (!geometry.min_width) {
7781 geometry.min_width = 600;
7782 geometry.min_height = 440;
7784 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7785 &geometry, GDK_HINT_MIN_SIZE);
7787 #ifndef GENERIC_UMPC
7788 if (compose_force_window_origin)
7789 gtk_window_move(GTK_WINDOW(window), prefs_common.compose_x,
7790 prefs_common.compose_y);
7792 g_signal_connect(G_OBJECT(window), "delete_event",
7793 G_CALLBACK(compose_delete_cb), compose);
7794 MANAGE_WINDOW_SIGNALS_CONNECT(window);
7795 gtk_widget_realize(window);
7797 gtkut_widget_set_composer_icon(window);
7799 vbox = gtk_vbox_new(FALSE, 0);
7800 gtk_container_add(GTK_CONTAINER(window), vbox);
7802 compose->ui_manager = gtk_ui_manager_new();
7803 action_group = cm_menu_create_action_group_full(compose->ui_manager,"Menu", compose_entries,
7804 G_N_ELEMENTS(compose_entries), (gpointer)compose);
7805 gtk_action_group_add_toggle_actions(action_group, compose_toggle_entries,
7806 G_N_ELEMENTS(compose_toggle_entries), (gpointer)compose);
7807 gtk_action_group_add_radio_actions(action_group, compose_radio_rm_entries,
7808 G_N_ELEMENTS(compose_radio_rm_entries), COMPOSE_REPLY, G_CALLBACK(compose_reply_change_mode_cb), (gpointer)compose);
7809 gtk_action_group_add_radio_actions(action_group, compose_radio_prio_entries,
7810 G_N_ELEMENTS(compose_radio_prio_entries), PRIORITY_NORMAL, G_CALLBACK(compose_set_priority_cb), (gpointer)compose);
7811 gtk_action_group_add_radio_actions(action_group, compose_radio_enc_entries,
7812 G_N_ELEMENTS(compose_radio_enc_entries), C_AUTO, G_CALLBACK(compose_set_encoding_cb), (gpointer)compose);
7814 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Menu", NULL, GTK_UI_MANAGER_MENUBAR)
7816 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Message", "Message", GTK_UI_MANAGER_MENU)
7817 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Edit", "Edit", GTK_UI_MANAGER_MENU)
7819 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Spelling", "Spelling", GTK_UI_MANAGER_MENU)
7821 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Options", "Options", GTK_UI_MANAGER_MENU)
7822 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Tools", "Tools", GTK_UI_MANAGER_MENU)
7823 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Help", "Help", GTK_UI_MANAGER_MENU)
7826 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Send", "Message/Send", GTK_UI_MANAGER_MENUITEM)
7827 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "SendLater", "Message/SendLater", GTK_UI_MANAGER_MENUITEM)
7828 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator1", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7829 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "AttachFile", "Message/AttachFile", GTK_UI_MANAGER_MENUITEM)
7830 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertFile", "Message/InsertFile", GTK_UI_MANAGER_MENUITEM)
7831 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertSig", "Message/InsertSig", GTK_UI_MANAGER_MENUITEM)
7832 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "ReplaceSig", "Message/ReplaceSig", GTK_UI_MANAGER_MENUITEM)
7833 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator2", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7834 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Save", "Message/Save", GTK_UI_MANAGER_MENUITEM)
7835 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator3", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7836 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Print", "Message/Print", GTK_UI_MANAGER_MENUITEM)
7837 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator4", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7838 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Close", "Message/Close", GTK_UI_MANAGER_MENUITEM)
7841 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Undo", "Edit/Undo", GTK_UI_MANAGER_MENUITEM)
7842 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Redo", "Edit/Redo", GTK_UI_MANAGER_MENUITEM)
7843 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator1", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7845 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Cut", "Edit/Cut", GTK_UI_MANAGER_MENUITEM)
7846 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Copy", "Edit/Copy", GTK_UI_MANAGER_MENUITEM)
7847 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Paste", "Edit/Paste", GTK_UI_MANAGER_MENUITEM)
7849 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SpecialPaste", "Edit/SpecialPaste", GTK_UI_MANAGER_MENU)
7850 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "AsQuotation", "Edit/SpecialPaste/AsQuotation", GTK_UI_MANAGER_MENUITEM)
7851 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Wrapped", "Edit/SpecialPaste/Wrapped", GTK_UI_MANAGER_MENUITEM)
7852 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Unwrapped", "Edit/SpecialPaste/Unwrapped", GTK_UI_MANAGER_MENUITEM)
7854 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SelectAll", "Edit/SelectAll", GTK_UI_MANAGER_MENUITEM)
7856 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Advanced", "Edit/Advanced", GTK_UI_MANAGER_MENU)
7857 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackChar", "Edit/Advanced/BackChar", GTK_UI_MANAGER_MENUITEM)
7858 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwChar", "Edit/Advanced/ForwChar", GTK_UI_MANAGER_MENUITEM)
7859 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackWord", "Edit/Advanced/BackWord", GTK_UI_MANAGER_MENUITEM)
7860 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwWord", "Edit/Advanced/ForwWord", GTK_UI_MANAGER_MENUITEM)
7861 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BegLine", "Edit/Advanced/BegLine", GTK_UI_MANAGER_MENUITEM)
7862 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "EndLine", "Edit/Advanced/EndLine", GTK_UI_MANAGER_MENUITEM)
7863 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "PrevLine", "Edit/Advanced/PrevLine", GTK_UI_MANAGER_MENUITEM)
7864 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "NextLine", "Edit/Advanced/NextLine", GTK_UI_MANAGER_MENUITEM)
7865 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackChar", "Edit/Advanced/DelBackChar", GTK_UI_MANAGER_MENUITEM)
7866 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwChar", "Edit/Advanced/DelForwChar", GTK_UI_MANAGER_MENUITEM)
7867 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackWord", "Edit/Advanced/DelBackWord", GTK_UI_MANAGER_MENUITEM)
7868 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwWord", "Edit/Advanced/DelForwWord", GTK_UI_MANAGER_MENUITEM)
7869 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelLine", "Edit/Advanced/DelLine", GTK_UI_MANAGER_MENUITEM)
7870 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelEndLine", "Edit/Advanced/DelEndLine", GTK_UI_MANAGER_MENUITEM)
7872 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator2", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7874 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Find", "Edit/Find", GTK_UI_MANAGER_MENUITEM)
7875 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapPara", "Edit/WrapPara", GTK_UI_MANAGER_MENUITEM)
7876 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapAllLines", "Edit/WrapAllLines", GTK_UI_MANAGER_MENUITEM)
7877 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoWrap", "Edit/AutoWrap", GTK_UI_MANAGER_MENUITEM)
7878 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoIndent", "Edit/AutoIndent", GTK_UI_MANAGER_MENUITEM)
7880 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator3", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7882 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "ExtEditor", "Edit/ExtEditor", GTK_UI_MANAGER_MENUITEM)
7886 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckAllSel", "Spelling/CheckAllSel", GTK_UI_MANAGER_MENUITEM)
7887 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "HighlightAll", "Spelling/HighlightAll", GTK_UI_MANAGER_MENUITEM)
7888 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckBackwards", "Spelling/CheckBackwards", GTK_UI_MANAGER_MENUITEM)
7889 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "ForwardNext", "Spelling/ForwardNext", GTK_UI_MANAGER_MENUITEM)
7890 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Separator1", "Spelling/---", GTK_UI_MANAGER_SEPARATOR)
7891 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Options", "Spelling/Options", GTK_UI_MANAGER_MENU)
7895 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "ReplyMode", "Options/ReplyMode", GTK_UI_MANAGER_MENU)
7896 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Normal", "Options/ReplyMode/Normal", GTK_UI_MANAGER_MENUITEM)
7897 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "All", "Options/ReplyMode/All", GTK_UI_MANAGER_MENUITEM)
7898 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Sender", "Options/ReplyMode/Sender", GTK_UI_MANAGER_MENUITEM)
7899 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "List", "Options/ReplyMode/List", GTK_UI_MANAGER_MENUITEM)
7901 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator1", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7902 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "PrivacySystem", "Options/PrivacySystem", GTK_UI_MANAGER_MENU)
7903 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/PrivacySystem", "PlaceHolder", "Options/PrivacySystem/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7904 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Sign", "Options/Sign", GTK_UI_MANAGER_MENUITEM)
7905 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encrypt", "Options/Encrypt", GTK_UI_MANAGER_MENUITEM)
7908 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator2", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7909 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Priority", "Options/Priority", GTK_UI_MANAGER_MENU)
7910 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Highest", "Options/Priority/Highest", GTK_UI_MANAGER_MENUITEM)
7911 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "High", "Options/Priority/High", GTK_UI_MANAGER_MENUITEM)
7912 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Normal", "Options/Priority/Normal", GTK_UI_MANAGER_MENUITEM)
7913 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Low", "Options/Priority/Low", GTK_UI_MANAGER_MENUITEM)
7914 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Lowest", "Options/Priority/Lowest", GTK_UI_MANAGER_MENUITEM)
7916 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator3", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7917 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RequestRetRcpt", "Options/RequestRetRcpt", GTK_UI_MANAGER_MENUITEM)
7918 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator4", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7919 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RemoveReferences", "Options/RemoveReferences", GTK_UI_MANAGER_MENUITEM)
7920 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator5", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7922 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encoding", "Options/Encoding", GTK_UI_MANAGER_MENU)
7924 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_AUTO, "Options/Encoding/"CS_AUTO, GTK_UI_MANAGER_MENUITEM)
7925 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator1", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7926 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_US_ASCII, "Options/Encoding/"CS_US_ASCII, GTK_UI_MANAGER_MENUITEM)
7927 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_UTF_8, "Options/Encoding/"CS_UTF_8, GTK_UI_MANAGER_MENUITEM)
7928 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator2", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7930 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Western", "Options/Encoding/Western", GTK_UI_MANAGER_MENU)
7931 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)
7932 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)
7933 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Western", CS_WINDOWS_1252, "Options/Encoding/Western/"CS_WINDOWS_1252, GTK_UI_MANAGER_MENUITEM)
7935 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_2, "Options/Encoding/"CS_ISO_8859_2, GTK_UI_MANAGER_MENUITEM)
7937 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Baltic", "Options/Encoding/Baltic", GTK_UI_MANAGER_MENU)
7938 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)
7939 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)
7941 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_7, "Options/Encoding/"CS_ISO_8859_7, GTK_UI_MANAGER_MENUITEM)
7943 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Hebrew", "Options/Encoding/Hebrew", GTK_UI_MANAGER_MENU)
7944 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)
7945 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Hebrew", CS_WINDOWS_1255, "Options/Encoding/Hebrew/"CS_WINDOWS_1255, GTK_UI_MANAGER_MENUITEM)
7947 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Arabic", "Options/Encoding/Arabic", GTK_UI_MANAGER_MENU)
7948 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)
7949 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Arabic", CS_WINDOWS_1256, "Options/Encoding/Arabic/"CS_WINDOWS_1256, GTK_UI_MANAGER_MENUITEM)
7951 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_9, "Options/Encoding/"CS_ISO_8859_9, GTK_UI_MANAGER_MENUITEM)
7953 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Cyrillic", "Options/Encoding/Cyrillic", GTK_UI_MANAGER_MENU)
7954 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)
7955 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_R, "Options/Encoding/Cyrillic/"CS_KOI8_R, GTK_UI_MANAGER_MENUITEM)
7956 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_MACCYR, "Options/Encoding/Cyrillic/"CS_MACCYR, GTK_UI_MANAGER_MENUITEM)
7957 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_U, "Options/Encoding/Cyrillic/"CS_KOI8_U, GTK_UI_MANAGER_MENUITEM)
7958 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_WINDOWS_1251, "Options/Encoding/Cyrillic/"CS_WINDOWS_1251, GTK_UI_MANAGER_MENUITEM)
7960 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Japanese", "Options/Encoding/Japanese", GTK_UI_MANAGER_MENU)
7961 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)
7962 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)
7963 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_EUC_JP, "Options/Encoding/Japanese/"CS_EUC_JP, GTK_UI_MANAGER_MENUITEM)
7964 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_SHIFT_JIS, "Options/Encoding/Japanese/"CS_SHIFT_JIS, GTK_UI_MANAGER_MENUITEM)
7966 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Chinese", "Options/Encoding/Chinese", GTK_UI_MANAGER_MENU)
7967 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB18030, "Options/Encoding/Chinese/"CS_GB18030, GTK_UI_MANAGER_MENUITEM)
7968 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB2312, "Options/Encoding/Chinese/"CS_GB2312, GTK_UI_MANAGER_MENUITEM)
7969 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GBK, "Options/Encoding/Chinese/"CS_GBK, GTK_UI_MANAGER_MENUITEM)
7970 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_BIG5, "Options/Encoding/Chinese/"CS_BIG5, GTK_UI_MANAGER_MENUITEM)
7971 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_EUC_TW, "Options/Encoding/Chinese/"CS_EUC_TW, GTK_UI_MANAGER_MENUITEM)
7973 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Korean", "Options/Encoding/Korean", GTK_UI_MANAGER_MENU)
7974 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Korean", CS_EUC_KR, "Options/Encoding/Korean/"CS_EUC_KR, GTK_UI_MANAGER_MENUITEM)
7975 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)
7977 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Thai", "Options/Encoding/Thai", GTK_UI_MANAGER_MENU)
7978 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_TIS_620, "Options/Encoding/Thai/"CS_TIS_620, GTK_UI_MANAGER_MENUITEM)
7979 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_WINDOWS_874, "Options/Encoding/Thai/"CS_WINDOWS_874, GTK_UI_MANAGER_MENUITEM)
7983 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "ShowRuler", "Tools/ShowRuler", GTK_UI_MANAGER_MENUITEM)
7984 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "AddressBook", "Tools/AddressBook", GTK_UI_MANAGER_MENUITEM)
7985 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Template", "Tools/Template", GTK_UI_MANAGER_MENU)
7986 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Template", "PlaceHolder", "Tools/Template/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7987 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Actions", "Tools/Actions", GTK_UI_MANAGER_MENU)
7988 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Actions", "PlaceHolder", "Tools/Actions/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7991 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Help", "About", "Help/About", GTK_UI_MANAGER_MENUITEM)
7993 menubar = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu");
7994 gtk_widget_show_all(menubar);
7996 gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(compose->ui_manager));
7997 gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, TRUE, 0);
7999 if (prefs_common.toolbar_detachable) {
8000 handlebox = gtk_handle_box_new();
8002 handlebox = gtk_hbox_new(FALSE, 0);
8004 gtk_box_pack_start(GTK_BOX(vbox), handlebox, FALSE, FALSE, 0);
8006 gtk_widget_realize(handlebox);
8007 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, handlebox,
8010 vbox2 = gtk_vbox_new(FALSE, 2);
8011 gtk_box_pack_start(GTK_BOX(vbox), vbox2, TRUE, TRUE, 0);
8012 gtk_container_set_border_width(GTK_CONTAINER(vbox2), 0);
8015 notebook = gtk_notebook_new();
8016 gtk_widget_show(notebook);
8018 /* header labels and entries */
8019 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
8020 compose_create_header(compose),
8021 gtk_label_new_with_mnemonic(_("Hea_der")));
8022 /* attachment list */
8023 attach_hbox = gtk_hbox_new(FALSE, 0);
8024 gtk_widget_show(attach_hbox);
8026 attach_lab1 = gtk_label_new_with_mnemonic(_("_Attachments"));
8027 gtk_widget_show(attach_lab1);
8028 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab1, TRUE, TRUE, 0);
8030 attach_lab2 = gtk_label_new("");
8031 gtk_widget_show(attach_lab2);
8032 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab2, FALSE, FALSE, 0);
8034 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
8035 compose_create_attach(compose),
8038 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
8039 compose_create_others(compose),
8040 gtk_label_new_with_mnemonic(_("Othe_rs")));
8043 subject_hbox = gtk_hbox_new(FALSE, 0);
8044 gtk_widget_show(subject_hbox);
8046 subject_frame = gtk_frame_new(NULL);
8047 gtk_frame_set_shadow_type(GTK_FRAME(subject_frame), GTK_SHADOW_NONE);
8048 gtk_box_pack_start(GTK_BOX(subject_hbox), subject_frame, TRUE, TRUE, 0);
8049 gtk_widget_show(subject_frame);
8051 subject = gtk_hbox_new(FALSE, HSPACING_NARROW);
8052 gtk_container_set_border_width(GTK_CONTAINER(subject), 0);
8053 gtk_widget_show(subject);
8055 label = gtk_label_new_with_mnemonic(_("S_ubject:"));
8056 gtk_box_pack_start(GTK_BOX(subject), label, FALSE, FALSE, 0);
8057 gtk_widget_show(label);
8060 subject_entry = claws_spell_entry_new();
8062 subject_entry = gtk_entry_new();
8064 gtk_box_pack_start(GTK_BOX(subject), subject_entry, TRUE, TRUE, 0);
8065 g_signal_connect_after(G_OBJECT(subject_entry), "grab_focus",
8066 G_CALLBACK(compose_grab_focus_cb), compose);
8067 gtk_label_set_mnemonic_widget(GTK_LABEL(label), subject_entry);
8068 gtk_widget_show(subject_entry);
8069 compose->subject_entry = subject_entry;
8070 gtk_container_add(GTK_CONTAINER(subject_frame), subject);
8072 edit_vbox = gtk_vbox_new(FALSE, 0);
8074 gtk_box_pack_start(GTK_BOX(edit_vbox), subject_hbox, FALSE, FALSE, 0);
8077 ruler_hbox = gtk_hbox_new(FALSE, 0);
8078 gtk_box_pack_start(GTK_BOX(edit_vbox), ruler_hbox, FALSE, FALSE, 0);
8080 ruler = gtk_shruler_new(GTK_ORIENTATION_HORIZONTAL);
8081 gtk_shruler_set_range(GTK_SHRULER(ruler), 0.0, 100.0, 1.0);
8082 gtk_box_pack_start(GTK_BOX(ruler_hbox), ruler, TRUE, TRUE,
8086 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
8087 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
8088 GTK_POLICY_AUTOMATIC,
8089 GTK_POLICY_AUTOMATIC);
8090 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
8092 gtk_box_pack_start(GTK_BOX(edit_vbox), scrolledwin, TRUE, TRUE, 0);
8094 text = gtk_text_view_new();
8095 if (prefs_common.show_compose_margin) {
8096 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text), 6);
8097 gtk_text_view_set_right_margin(GTK_TEXT_VIEW(text), 6);
8099 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
8100 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
8101 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), TRUE);
8102 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
8103 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
8105 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
8106 g_signal_connect_after(G_OBJECT(text), "size_allocate",
8107 G_CALLBACK(compose_edit_size_alloc),
8109 g_signal_connect(G_OBJECT(buffer), "changed",
8110 G_CALLBACK(compose_changed_cb), compose);
8111 g_signal_connect(G_OBJECT(text), "grab_focus",
8112 G_CALLBACK(compose_grab_focus_cb), compose);
8113 g_signal_connect(G_OBJECT(buffer), "insert_text",
8114 G_CALLBACK(text_inserted), compose);
8115 g_signal_connect(G_OBJECT(text), "button_press_event",
8116 G_CALLBACK(text_clicked), compose);
8117 g_signal_connect(G_OBJECT(text), "popup-menu",
8118 G_CALLBACK(compose_popup_menu), compose);
8119 g_signal_connect(G_OBJECT(subject_entry), "changed",
8120 G_CALLBACK(compose_changed_cb), compose);
8121 g_signal_connect(G_OBJECT(subject_entry), "activate",
8122 G_CALLBACK(compose_subject_entry_activated), compose);
8125 gtk_drag_dest_set(text, GTK_DEST_DEFAULT_ALL, compose_mime_types,
8126 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
8127 GDK_ACTION_COPY | GDK_ACTION_MOVE);
8128 g_signal_connect(G_OBJECT(text), "drag_data_received",
8129 G_CALLBACK(compose_insert_drag_received_cb),
8131 g_signal_connect(G_OBJECT(text), "drag-drop",
8132 G_CALLBACK(compose_drag_drop),
8134 g_signal_connect(G_OBJECT(text), "key-press-event",
8135 G_CALLBACK(completion_set_focus_to_subject),
8137 gtk_widget_show_all(vbox);
8139 /* pane between attach clist and text */
8140 paned = gtk_vpaned_new();
8141 gtk_container_add(GTK_CONTAINER(vbox2), paned);
8142 gtk_paned_pack1(GTK_PANED(paned), notebook, FALSE, FALSE);
8143 gtk_paned_pack2(GTK_PANED(paned), edit_vbox, TRUE, FALSE);
8144 gtk_paned_set_position(GTK_PANED(paned), prefs_common.compose_notebook_height);
8145 g_signal_connect(G_OBJECT(notebook), "size_allocate",
8146 G_CALLBACK(compose_notebook_size_alloc), paned);
8148 gtk_widget_show_all(paned);
8151 if (prefs_common.textfont) {
8152 PangoFontDescription *font_desc;
8154 font_desc = pango_font_description_from_string
8155 (prefs_common.textfont);
8157 gtk_widget_modify_font(text, font_desc);
8158 pango_font_description_free(font_desc);
8162 gtk_action_group_add_actions(action_group, compose_popup_entries,
8163 G_N_ELEMENTS(compose_popup_entries), (gpointer)compose);
8164 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Popup", NULL, GTK_UI_MANAGER_MENUBAR)
8165 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup", "Compose", "Compose", GTK_UI_MANAGER_MENU)
8166 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Add", "Compose/Add", GTK_UI_MANAGER_MENUITEM)
8167 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Remove", "Compose/Remove", GTK_UI_MANAGER_MENUITEM)
8168 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Separator1", "Compose/---", GTK_UI_MANAGER_SEPARATOR)
8169 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Properties", "Compose/Properties", GTK_UI_MANAGER_MENUITEM)
8171 popupmenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(gtk_ui_manager_get_widget(compose->ui_manager, "/Popup/Compose")));
8173 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
8174 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
8175 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
8177 tmpl_menu = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu/Tools/Template");
8179 undostruct = undo_init(text);
8180 undo_set_change_state_func(undostruct, &compose_undo_state_changed,
8183 address_completion_start(window);
8185 compose->window = window;
8186 compose->vbox = vbox;
8187 compose->menubar = menubar;
8188 compose->handlebox = handlebox;
8190 compose->vbox2 = vbox2;
8192 compose->paned = paned;
8194 compose->attach_label = attach_lab2;
8196 compose->notebook = notebook;
8197 compose->edit_vbox = edit_vbox;
8198 compose->ruler_hbox = ruler_hbox;
8199 compose->ruler = ruler;
8200 compose->scrolledwin = scrolledwin;
8201 compose->text = text;
8203 compose->focused_editable = NULL;
8205 compose->popupmenu = popupmenu;
8207 compose->tmpl_menu = tmpl_menu;
8209 compose->mode = mode;
8210 compose->rmode = mode;
8212 compose->targetinfo = NULL;
8213 compose->replyinfo = NULL;
8214 compose->fwdinfo = NULL;
8216 compose->email_hashtable = g_hash_table_new_full(g_str_hash,
8217 g_str_equal, (GDestroyNotify) g_free, NULL);
8219 compose->replyto = NULL;
8221 compose->bcc = NULL;
8222 compose->followup_to = NULL;
8224 compose->ml_post = NULL;
8226 compose->inreplyto = NULL;
8227 compose->references = NULL;
8228 compose->msgid = NULL;
8229 compose->boundary = NULL;
8231 compose->autowrap = prefs_common.autowrap;
8232 compose->autoindent = prefs_common.auto_indent;
8233 compose->use_signing = FALSE;
8234 compose->use_encryption = FALSE;
8235 compose->privacy_system = NULL;
8236 compose->encdata = NULL;
8238 compose->modified = FALSE;
8240 compose->return_receipt = FALSE;
8242 compose->to_list = NULL;
8243 compose->newsgroup_list = NULL;
8245 compose->undostruct = undostruct;
8247 compose->sig_str = NULL;
8249 compose->exteditor_file = NULL;
8250 compose->exteditor_pid = -1;
8251 compose->exteditor_tag = -1;
8252 compose->exteditor_socket = NULL;
8253 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN; /* inhibit auto-drafting while loading */
8255 compose->folder_update_callback_id =
8256 hooks_register_hook(FOLDER_UPDATE_HOOKLIST,
8257 compose_update_folder_hook,
8258 (gpointer) compose);
8261 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
8262 if (mode != COMPOSE_REDIRECT) {
8263 if (prefs_common.enable_aspell && prefs_common.dictionary &&
8264 strcmp(prefs_common.dictionary, "")) {
8265 gtkaspell = gtkaspell_new(prefs_common.dictionary,
8266 prefs_common.alt_dictionary,
8267 conv_get_locale_charset_str(),
8268 prefs_common.color[COL_MISSPELLED],
8269 prefs_common.check_while_typing,
8270 prefs_common.recheck_when_changing_dict,
8271 prefs_common.use_alternate,
8272 prefs_common.use_both_dicts,
8273 GTK_TEXT_VIEW(text),
8274 GTK_WINDOW(compose->window),
8275 compose_dict_changed,
8276 compose_spell_menu_changed,
8279 alertpanel_error(_("Spell checker could not "
8281 gtkaspell_checkers_strerror());
8282 gtkaspell_checkers_reset_error();
8284 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", TRUE);
8288 compose->gtkaspell = gtkaspell;
8289 compose_spell_menu_changed(compose);
8290 claws_spell_entry_set_gtkaspell(CLAWS_SPELL_ENTRY(subject_entry), gtkaspell);
8293 compose_select_account(compose, account, TRUE);
8295 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", prefs_common.autowrap);
8296 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", prefs_common.auto_indent);
8298 if (account->set_autocc && account->auto_cc && mode != COMPOSE_REEDIT)
8299 compose_entry_append(compose, account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8301 if (account->set_autobcc && account->auto_bcc && mode != COMPOSE_REEDIT)
8302 compose_entry_append(compose, account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8304 if (account->set_autoreplyto && account->auto_replyto && mode != COMPOSE_REEDIT)
8305 compose_entry_append(compose, account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8307 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", compose->mode == COMPOSE_REPLY);
8308 if (account->protocol != A_NNTP)
8309 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
8310 prefs_common_translated_header_name("To:"));
8312 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
8313 prefs_common_translated_header_name("Newsgroups:"));
8315 #ifndef USE_ALT_ADDRBOOK
8316 addressbook_set_target_compose(compose);
8318 if (mode != COMPOSE_REDIRECT)
8319 compose_set_template_menu(compose);
8321 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Template", FALSE);
8324 compose_list = g_list_append(compose_list, compose);
8326 if (!prefs_common.show_ruler)
8327 gtk_widget_hide(ruler_hbox);
8329 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Tools/ShowRuler", prefs_common.show_ruler);
8332 compose->priority = PRIORITY_NORMAL;
8333 compose_update_priority_menu_item(compose);
8335 compose_set_out_encoding(compose);
8338 compose_update_actions_menu(compose);
8340 /* Privacy Systems menu */
8341 compose_update_privacy_systems_menu(compose);
8342 compose_activate_privacy_system(compose, account, TRUE);
8344 toolbar_set_style(compose->toolbar->toolbar, compose->handlebox, prefs_common.toolbar_style);
8346 gtk_widget_realize(window);
8348 gtk_widget_show(window);
8354 static GtkWidget *compose_account_option_menu_create(Compose *compose)
8359 GtkWidget *optmenubox;
8360 GtkWidget *fromlabel;
8363 GtkWidget *from_name = NULL;
8365 gint num = 0, def_menu = 0;
8367 accounts = account_get_list();
8368 cm_return_val_if_fail(accounts != NULL, NULL);
8370 optmenubox = gtk_event_box_new();
8371 optmenu = gtkut_sc_combobox_create(optmenubox, FALSE);
8372 menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
8374 hbox = gtk_hbox_new(FALSE, 4);
8375 from_name = gtk_entry_new();
8377 g_signal_connect_after(G_OBJECT(from_name), "grab_focus",
8378 G_CALLBACK(compose_grab_focus_cb), compose);
8379 g_signal_connect_after(G_OBJECT(from_name), "activate",
8380 G_CALLBACK(from_name_activate_cb), optmenu);
8382 for (; accounts != NULL; accounts = accounts->next, num++) {
8383 PrefsAccount *ac = (PrefsAccount *)accounts->data;
8384 gchar *name, *from = NULL;
8386 if (ac == compose->account) def_menu = num;
8388 name = g_markup_printf_escaped("<i>%s</i>",
8391 if (ac == compose->account) {
8392 if (ac->name && *ac->name) {
8394 QUOTE_IF_REQUIRED_NORMAL(buf, ac->name, return NULL);
8395 from = g_strdup_printf("%s <%s>",
8397 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8399 from = g_strdup_printf("%s",
8401 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8403 if (cur_account != compose->account) {
8404 gtk_widget_modify_base(
8405 GTK_WIDGET(from_name),
8406 GTK_STATE_NORMAL, &default_header_bgcolor);
8407 gtk_widget_modify_text(
8408 GTK_WIDGET(from_name),
8409 GTK_STATE_NORMAL, &default_header_color);
8412 COMBOBOX_ADD(menu, name, ac->account_id);
8417 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), def_menu);
8419 g_signal_connect(G_OBJECT(optmenu), "changed",
8420 G_CALLBACK(account_activated),
8422 g_signal_connect(G_OBJECT(from_name), "populate-popup",
8423 G_CALLBACK(compose_entry_popup_extend),
8426 fromlabel = gtk_label_new_with_mnemonic(_("_From:"));
8427 gtk_label_set_mnemonic_widget(GTK_LABEL(fromlabel), from_name);
8429 gtk_box_pack_start(GTK_BOX(hbox), fromlabel, FALSE, FALSE, 4);
8430 gtk_box_pack_start(GTK_BOX(hbox), optmenubox, FALSE, FALSE, 0);
8431 gtk_box_pack_start(GTK_BOX(hbox), from_name, TRUE, TRUE, 0);
8433 /* Putting only the GtkEntry into focus chain of parent hbox causes
8434 * the account selector combobox next to it to be unreachable when
8435 * navigating widgets in GtkTable with up/down arrow keys.
8436 * Note: gtk_widget_set_can_focus() was not enough. */
8438 l = g_list_prepend(l, from_name);
8439 gtk_container_set_focus_chain(GTK_CONTAINER(hbox), l);
8442 CLAWS_SET_TIP(optmenubox,
8443 _("Account to use for this email"));
8444 CLAWS_SET_TIP(from_name,
8445 _("Sender address to be used"));
8447 compose->account_combo = optmenu;
8448 compose->from_name = from_name;
8453 static void compose_set_priority_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8455 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8456 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8457 Compose *compose = (Compose *) data;
8459 compose->priority = value;
8463 static void compose_reply_change_mode(Compose *compose,
8466 gboolean was_modified = compose->modified;
8468 gboolean all = FALSE, ml = FALSE, sender = FALSE, followup = FALSE;
8470 cm_return_if_fail(compose->replyinfo != NULL);
8472 if (action == COMPOSE_REPLY && prefs_common.default_reply_list)
8474 if (action == COMPOSE_REPLY && compose->rmode == COMPOSE_FOLLOWUP_AND_REPLY_TO)
8476 if (action == COMPOSE_REPLY_TO_ALL)
8478 if (action == COMPOSE_REPLY_TO_SENDER)
8480 if (action == COMPOSE_REPLY_TO_LIST)
8483 compose_remove_header_entries(compose);
8484 compose_reply_set_entry(compose, compose->replyinfo, all, ml, sender, followup);
8485 if (compose->account->set_autocc && compose->account->auto_cc)
8486 compose_entry_append(compose, compose->account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8488 if (compose->account->set_autobcc && compose->account->auto_bcc)
8489 compose_entry_append(compose, compose->account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8491 if (compose->account->set_autoreplyto && compose->account->auto_replyto)
8492 compose_entry_append(compose, compose->account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8493 compose_show_first_last_header(compose, TRUE);
8494 compose->modified = was_modified;
8495 compose_set_title(compose);
8498 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8500 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8501 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8502 Compose *compose = (Compose *) data;
8505 compose_reply_change_mode(compose, value);
8508 static void compose_update_priority_menu_item(Compose * compose)
8510 GtkWidget *menuitem = NULL;
8511 switch (compose->priority) {
8512 case PRIORITY_HIGHEST:
8513 menuitem = gtk_ui_manager_get_widget
8514 (compose->ui_manager, "/Menu/Options/Priority/Highest");
8517 menuitem = gtk_ui_manager_get_widget
8518 (compose->ui_manager, "/Menu/Options/Priority/High");
8520 case PRIORITY_NORMAL:
8521 menuitem = gtk_ui_manager_get_widget
8522 (compose->ui_manager, "/Menu/Options/Priority/Normal");
8525 menuitem = gtk_ui_manager_get_widget
8526 (compose->ui_manager, "/Menu/Options/Priority/Low");
8528 case PRIORITY_LOWEST:
8529 menuitem = gtk_ui_manager_get_widget
8530 (compose->ui_manager, "/Menu/Options/Priority/Lowest");
8533 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8536 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data)
8538 Compose *compose = (Compose *) data;
8540 gboolean can_sign = FALSE, can_encrypt = FALSE;
8542 cm_return_if_fail(GTK_IS_CHECK_MENU_ITEM(widget));
8544 if (!gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget)))
8547 systemid = g_object_get_data(G_OBJECT(widget), "privacy_system");
8548 g_free(compose->privacy_system);
8549 compose->privacy_system = NULL;
8550 g_free(compose->encdata);
8551 compose->encdata = NULL;
8552 if (systemid != NULL) {
8553 compose->privacy_system = g_strdup(systemid);
8555 can_sign = privacy_system_can_sign(systemid);
8556 can_encrypt = privacy_system_can_encrypt(systemid);
8559 debug_print("activated privacy system: %s\n", systemid != NULL ? systemid : "None");
8561 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8562 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8563 if (compose->toolbar->privacy_sign_btn != NULL) {
8564 gtk_widget_set_sensitive(
8565 GTK_WIDGET(compose->toolbar->privacy_sign_btn),
8567 gtk_toggle_tool_button_set_active(
8568 GTK_TOGGLE_TOOL_BUTTON(compose->toolbar->privacy_sign_btn),
8569 can_sign ? compose->use_signing : FALSE);
8571 if (compose->toolbar->privacy_encrypt_btn != NULL) {
8572 gtk_widget_set_sensitive(
8573 GTK_WIDGET(compose->toolbar->privacy_encrypt_btn),
8575 gtk_toggle_tool_button_set_active(
8576 GTK_TOGGLE_TOOL_BUTTON(compose->toolbar->privacy_encrypt_btn),
8577 can_encrypt ? compose->use_encryption : FALSE);
8581 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn)
8583 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8584 GtkWidget *menuitem = NULL;
8585 GList *children, *amenu;
8586 gboolean can_sign = FALSE, can_encrypt = FALSE;
8587 gboolean found = FALSE;
8589 if (compose->privacy_system != NULL) {
8591 menuitem = gtk_menu_item_get_submenu(GTK_MENU_ITEM(
8592 gtk_ui_manager_get_widget(compose->ui_manager, branch_path)));
8593 cm_return_if_fail(menuitem != NULL);
8595 children = gtk_container_get_children(GTK_CONTAINER(GTK_MENU_SHELL(menuitem)));
8598 while (amenu != NULL) {
8599 systemid = g_object_get_data(G_OBJECT(amenu->data), "privacy_system");
8600 if (systemid != NULL) {
8601 if (strcmp(systemid, compose->privacy_system) == 0 &&
8602 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8603 menuitem = GTK_WIDGET(amenu->data);
8605 can_sign = privacy_system_can_sign(systemid);
8606 can_encrypt = privacy_system_can_encrypt(systemid);
8610 } else if (strlen(compose->privacy_system) == 0 &&
8611 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8612 menuitem = GTK_WIDGET(amenu->data);
8615 can_encrypt = FALSE;
8620 amenu = amenu->next;
8622 g_list_free(children);
8623 if (menuitem != NULL)
8624 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8626 if (warn && !found && strlen(compose->privacy_system)) {
8627 alertpanel_warning(_("The privacy system '%s' cannot be loaded. You "
8628 "will not be able to sign or encrypt this message."),
8629 compose->privacy_system);
8633 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8634 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8635 if (compose->toolbar->privacy_sign_btn != NULL) {
8636 gtk_widget_set_sensitive(
8637 GTK_WIDGET(compose->toolbar->privacy_sign_btn),
8640 if (compose->toolbar->privacy_encrypt_btn != NULL) {
8641 gtk_widget_set_sensitive(
8642 GTK_WIDGET(compose->toolbar->privacy_encrypt_btn),
8647 static void compose_set_out_encoding(Compose *compose)
8649 CharSet out_encoding;
8650 const gchar *branch = NULL;
8651 out_encoding = conv_get_charset_from_str(prefs_common.outgoing_charset);
8653 switch(out_encoding) {
8654 case C_AUTO: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8655 case C_US_ASCII: branch = "Menu/Options/Encoding/" CS_US_ASCII; break;
8656 case C_UTF_8: branch = "Menu/Options/Encoding/" CS_UTF_8; break;
8657 case C_ISO_8859_2: branch = "Menu/Options/Encoding/" CS_ISO_8859_2; break;
8658 case C_ISO_8859_7: branch = "Menu/Options/Encoding/" CS_ISO_8859_7; break;
8659 case C_ISO_8859_9: branch = "Menu/Options/Encoding/" CS_ISO_8859_9; break;
8660 case C_ISO_8859_1: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_1; break;
8661 case C_ISO_8859_15: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_15; break;
8662 case C_WINDOWS_1252: branch = "Menu/Options/Encoding/Western/" CS_WINDOWS_1252; break;
8663 case C_ISO_8859_13: branch = "Menu/Options/Encoding/Baltic/" CS_ISO_8859_13; break;
8664 case C_ISO_8859_4: branch = "Menu/Options/Encoding/Baltic" CS_ISO_8859_4; break;
8665 case C_ISO_8859_8: branch = "Menu/Options/Encoding/Hebrew/" CS_ISO_8859_8; break;
8666 case C_WINDOWS_1255: branch = "Menu/Options/Encoding/Hebrew/" CS_WINDOWS_1255; break;
8667 case C_ISO_8859_6: branch = "Menu/Options/Encoding/Arabic/" CS_ISO_8859_6; break;
8668 case C_WINDOWS_1256: branch = "Menu/Options/Encoding/Arabic/" CS_WINDOWS_1256; break;
8669 case C_ISO_8859_5: branch = "Menu/Options/Encoding/Cyrillic/" CS_ISO_8859_5; break;
8670 case C_KOI8_R: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_R; break;
8671 case C_MACCYR: branch = "Menu/Options/Encoding/Cyrillic/" CS_MACCYR; break;
8672 case C_KOI8_U: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_U; break;
8673 case C_WINDOWS_1251: branch = "Menu/Options/Encoding/Cyrillic/" CS_WINDOWS_1251; break;
8674 case C_ISO_2022_JP: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP; break;
8675 case C_ISO_2022_JP_2: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP_2; break;
8676 case C_EUC_JP: branch = "Menu/Options/Encoding/Japanese/" CS_EUC_JP; break;
8677 case C_SHIFT_JIS: branch = "Menu/Options/Encoding/Japanese/" CS_SHIFT_JIS; break;
8678 case C_GB18030: branch = "Menu/Options/Encoding/Chinese/" CS_GB18030; break;
8679 case C_GB2312: branch = "Menu/Options/Encoding/Chinese/" CS_GB2312; break;
8680 case C_GBK: branch = "Menu/Options/Encoding/Chinese/" CS_GBK; break;
8681 case C_BIG5: branch = "Menu/Options/Encoding/Chinese/" CS_BIG5; break;
8682 case C_EUC_TW: branch = "Menu/Options/Encoding/Chinese/" CS_EUC_TW; break;
8683 case C_EUC_KR: branch = "Menu/Options/Encoding/Korean/" CS_EUC_KR; break;
8684 case C_ISO_2022_KR: branch = "Menu/Options/Encoding/Korean/" CS_ISO_2022_KR; break;
8685 case C_TIS_620: branch = "Menu/Options/Encoding/Thai/" CS_TIS_620; break;
8686 case C_WINDOWS_874: branch = "Menu/Options/Encoding/Thai/" CS_WINDOWS_874; break;
8687 default: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8689 cm_toggle_menu_set_active_full(compose->ui_manager, (gchar *)branch, TRUE);
8692 static void compose_set_template_menu(Compose *compose)
8694 GSList *tmpl_list, *cur;
8698 tmpl_list = template_get_config();
8700 menu = gtk_menu_new();
8702 gtk_menu_set_accel_group (GTK_MENU (menu),
8703 gtk_ui_manager_get_accel_group(compose->ui_manager));
8704 for (cur = tmpl_list; cur != NULL; cur = cur->next) {
8705 Template *tmpl = (Template *)cur->data;
8706 gchar *accel_path = NULL;
8707 item = gtk_menu_item_new_with_label(tmpl->name);
8708 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
8709 g_signal_connect(G_OBJECT(item), "activate",
8710 G_CALLBACK(compose_template_activate_cb),
8712 g_object_set_data(G_OBJECT(item), "template", tmpl);
8713 gtk_widget_show(item);
8714 accel_path = g_strconcat("<ComposeTemplates>" , "/", tmpl->name, NULL);
8715 gtk_menu_item_set_accel_path(GTK_MENU_ITEM(item), accel_path);
8719 gtk_widget_show(menu);
8720 gtk_menu_item_set_submenu(GTK_MENU_ITEM(compose->tmpl_menu), menu);
8723 void compose_update_actions_menu(Compose *compose)
8725 action_update_compose_menu(compose->ui_manager, "/Menu/Tools/Actions", compose);
8728 static void compose_update_privacy_systems_menu(Compose *compose)
8730 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8731 GSList *systems, *cur;
8733 GtkWidget *system_none;
8735 GtkWidget *privacy_menuitem = gtk_ui_manager_get_widget(compose->ui_manager, branch_path);
8736 GtkWidget *privacy_menu = gtk_menu_new();
8738 system_none = gtk_radio_menu_item_new_with_mnemonic(NULL, _("_None"));
8739 g_object_set_data_full(G_OBJECT(system_none), "privacy_system", NULL, NULL);
8741 g_signal_connect(G_OBJECT(system_none), "activate",
8742 G_CALLBACK(compose_set_privacy_system_cb), compose);
8744 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), system_none);
8745 gtk_widget_show(system_none);
8747 systems = privacy_get_system_ids();
8748 for (cur = systems; cur != NULL; cur = g_slist_next(cur)) {
8749 gchar *systemid = cur->data;
8751 group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(system_none));
8752 widget = gtk_radio_menu_item_new_with_label(group,
8753 privacy_system_get_name(systemid));
8754 g_object_set_data_full(G_OBJECT(widget), "privacy_system",
8755 g_strdup(systemid), g_free);
8756 g_signal_connect(G_OBJECT(widget), "activate",
8757 G_CALLBACK(compose_set_privacy_system_cb), compose);
8759 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), widget);
8760 gtk_widget_show(widget);
8763 g_slist_free(systems);
8764 gtk_menu_item_set_submenu(GTK_MENU_ITEM(privacy_menuitem), privacy_menu);
8765 gtk_widget_show_all(privacy_menu);
8766 gtk_widget_show_all(privacy_menuitem);
8769 void compose_reflect_prefs_all(void)
8774 for (cur = compose_list; cur != NULL; cur = cur->next) {
8775 compose = (Compose *)cur->data;
8776 compose_set_template_menu(compose);
8780 void compose_reflect_prefs_pixmap_theme(void)
8785 for (cur = compose_list; cur != NULL; cur = cur->next) {
8786 compose = (Compose *)cur->data;
8787 toolbar_update(TOOLBAR_COMPOSE, compose);
8791 static const gchar *compose_quote_char_from_context(Compose *compose)
8793 const gchar *qmark = NULL;
8795 cm_return_val_if_fail(compose != NULL, NULL);
8797 switch (compose->mode) {
8798 /* use forward-specific quote char */
8799 case COMPOSE_FORWARD:
8800 case COMPOSE_FORWARD_AS_ATTACH:
8801 case COMPOSE_FORWARD_INLINE:
8802 if (compose->folder && compose->folder->prefs &&
8803 compose->folder->prefs->forward_with_format)
8804 qmark = compose->folder->prefs->forward_quotemark;
8805 else if (compose->account->forward_with_format)
8806 qmark = compose->account->forward_quotemark;
8808 qmark = prefs_common.fw_quotemark;
8811 /* use reply-specific quote char in all other modes */
8813 if (compose->folder && compose->folder->prefs &&
8814 compose->folder->prefs->reply_with_format)
8815 qmark = compose->folder->prefs->reply_quotemark;
8816 else if (compose->account->reply_with_format)
8817 qmark = compose->account->reply_quotemark;
8819 qmark = prefs_common.quotemark;
8823 if (qmark == NULL || *qmark == '\0')
8829 static void compose_template_apply(Compose *compose, Template *tmpl,
8833 GtkTextBuffer *buffer;
8837 gchar *parsed_str = NULL;
8838 gint cursor_pos = 0;
8839 const gchar *err_msg = _("The body of the template has an error at line %d.");
8842 /* process the body */
8844 text = GTK_TEXT_VIEW(compose->text);
8845 buffer = gtk_text_view_get_buffer(text);
8848 qmark = compose_quote_char_from_context(compose);
8850 if (compose->replyinfo != NULL) {
8853 gtk_text_buffer_set_text(buffer, "", -1);
8854 mark = gtk_text_buffer_get_insert(buffer);
8855 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8857 parsed_str = compose_quote_fmt(compose, compose->replyinfo,
8858 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8860 } else if (compose->fwdinfo != NULL) {
8863 gtk_text_buffer_set_text(buffer, "", -1);
8864 mark = gtk_text_buffer_get_insert(buffer);
8865 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8867 parsed_str = compose_quote_fmt(compose, compose->fwdinfo,
8868 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8871 MsgInfo* dummyinfo = compose_msginfo_new_from_compose(compose);
8873 GtkTextIter start, end;
8876 gtk_text_buffer_get_start_iter(buffer, &start);
8877 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
8878 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
8880 /* clear the buffer now */
8882 gtk_text_buffer_set_text(buffer, "", -1);
8884 parsed_str = compose_quote_fmt(compose, dummyinfo,
8885 tmpl->value, qmark, tmp, FALSE, FALSE, err_msg);
8886 procmsg_msginfo_free( &dummyinfo );
8892 gtk_text_buffer_set_text(buffer, "", -1);
8893 mark = gtk_text_buffer_get_insert(buffer);
8894 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8897 if (replace && parsed_str && compose->account->auto_sig)
8898 compose_insert_sig(compose, FALSE);
8900 if (replace && parsed_str) {
8901 gtk_text_buffer_get_start_iter(buffer, &iter);
8902 gtk_text_buffer_place_cursor(buffer, &iter);
8906 cursor_pos = quote_fmt_get_cursor_pos();
8907 compose->set_cursor_pos = cursor_pos;
8908 if (cursor_pos == -1)
8910 gtk_text_buffer_get_start_iter(buffer, &iter);
8911 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
8912 gtk_text_buffer_place_cursor(buffer, &iter);
8915 /* process the other fields */
8917 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
8918 compose_template_apply_fields(compose, tmpl);
8919 quote_fmt_reset_vartable();
8920 quote_fmtlex_destroy();
8922 compose_changed_cb(NULL, compose);
8925 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
8926 gtkaspell_highlight_all(compose->gtkaspell);
8930 static void compose_template_apply_fields_error(const gchar *header)
8935 tr = g_strdup(C_("'%s' stands for a header name",
8936 "Template '%s' format error."));
8937 text = g_strdup_printf(tr, prefs_common_translated_header_name(header));
8938 alertpanel_error("%s", text);
8944 static void compose_template_apply_fields(Compose *compose, Template *tmpl)
8946 MsgInfo* dummyinfo = NULL;
8947 MsgInfo *msginfo = NULL;
8950 if (compose->replyinfo != NULL)
8951 msginfo = compose->replyinfo;
8952 else if (compose->fwdinfo != NULL)
8953 msginfo = compose->fwdinfo;
8955 dummyinfo = compose_msginfo_new_from_compose(compose);
8956 msginfo = dummyinfo;
8959 if (tmpl->from && *tmpl->from != '\0') {
8961 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8962 compose->gtkaspell);
8964 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8966 quote_fmt_scan_string(tmpl->from);
8969 buf = quote_fmt_get_buffer();
8971 compose_template_apply_fields_error("From");
8973 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
8976 quote_fmt_reset_vartable();
8977 quote_fmtlex_destroy();
8980 if (tmpl->to && *tmpl->to != '\0') {
8982 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8983 compose->gtkaspell);
8985 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8987 quote_fmt_scan_string(tmpl->to);
8990 buf = quote_fmt_get_buffer();
8992 compose_template_apply_fields_error("To");
8994 compose_entry_append(compose, buf, COMPOSE_TO, PREF_TEMPLATE);
8997 quote_fmt_reset_vartable();
8998 quote_fmtlex_destroy();
9001 if (tmpl->cc && *tmpl->cc != '\0') {
9003 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
9004 compose->gtkaspell);
9006 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
9008 quote_fmt_scan_string(tmpl->cc);
9011 buf = quote_fmt_get_buffer();
9013 compose_template_apply_fields_error("Cc");
9015 compose_entry_append(compose, buf, COMPOSE_CC, PREF_TEMPLATE);
9018 quote_fmt_reset_vartable();
9019 quote_fmtlex_destroy();
9022 if (tmpl->bcc && *tmpl->bcc != '\0') {
9024 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
9025 compose->gtkaspell);
9027 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
9029 quote_fmt_scan_string(tmpl->bcc);
9032 buf = quote_fmt_get_buffer();
9034 compose_template_apply_fields_error("Bcc");
9036 compose_entry_append(compose, buf, COMPOSE_BCC, PREF_TEMPLATE);
9039 quote_fmt_reset_vartable();
9040 quote_fmtlex_destroy();
9043 if (tmpl->replyto && *tmpl->replyto != '\0') {
9045 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
9046 compose->gtkaspell);
9048 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
9050 quote_fmt_scan_string(tmpl->replyto);
9053 buf = quote_fmt_get_buffer();
9055 compose_template_apply_fields_error("Reply-To");
9057 compose_entry_append(compose, buf, COMPOSE_REPLYTO, PREF_TEMPLATE);
9060 quote_fmt_reset_vartable();
9061 quote_fmtlex_destroy();
9064 /* process the subject */
9065 if (tmpl->subject && *tmpl->subject != '\0') {
9067 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
9068 compose->gtkaspell);
9070 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
9072 quote_fmt_scan_string(tmpl->subject);
9075 buf = quote_fmt_get_buffer();
9077 compose_template_apply_fields_error("Subject");
9079 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
9082 quote_fmt_reset_vartable();
9083 quote_fmtlex_destroy();
9086 procmsg_msginfo_free( &dummyinfo );
9089 static void compose_destroy(Compose *compose)
9091 GtkAllocation allocation;
9092 GtkTextBuffer *buffer;
9093 GtkClipboard *clipboard;
9095 compose_list = g_list_remove(compose_list, compose);
9098 gboolean enable = TRUE;
9099 g_slist_foreach(compose->passworded_ldap_servers,
9100 _ldap_srv_func, &enable);
9101 g_slist_free(compose->passworded_ldap_servers);
9104 if (compose->updating) {
9105 debug_print("danger, not destroying anything now\n");
9106 compose->deferred_destroy = TRUE;
9110 /* NOTE: address_completion_end() does nothing with the window
9111 * however this may change. */
9112 address_completion_end(compose->window);
9114 slist_free_strings_full(compose->to_list);
9115 slist_free_strings_full(compose->newsgroup_list);
9116 slist_free_strings_full(compose->header_list);
9118 slist_free_strings_full(extra_headers);
9119 extra_headers = NULL;
9121 compose->header_list = compose->newsgroup_list = compose->to_list = NULL;
9123 g_hash_table_destroy(compose->email_hashtable);
9125 hooks_unregister_hook(FOLDER_UPDATE_HOOKLIST,
9126 compose->folder_update_callback_id);
9128 procmsg_msginfo_free(&(compose->targetinfo));
9129 procmsg_msginfo_free(&(compose->replyinfo));
9130 procmsg_msginfo_free(&(compose->fwdinfo));
9132 g_free(compose->replyto);
9133 g_free(compose->cc);
9134 g_free(compose->bcc);
9135 g_free(compose->newsgroups);
9136 g_free(compose->followup_to);
9138 g_free(compose->ml_post);
9140 g_free(compose->inreplyto);
9141 g_free(compose->references);
9142 g_free(compose->msgid);
9143 g_free(compose->boundary);
9145 g_free(compose->redirect_filename);
9146 if (compose->undostruct)
9147 undo_destroy(compose->undostruct);
9149 g_free(compose->sig_str);
9151 g_free(compose->exteditor_file);
9153 g_free(compose->orig_charset);
9155 g_free(compose->privacy_system);
9156 g_free(compose->encdata);
9158 #ifndef USE_ALT_ADDRBOOK
9159 if (addressbook_get_target_compose() == compose)
9160 addressbook_set_target_compose(NULL);
9163 if (compose->gtkaspell) {
9164 gtkaspell_delete(compose->gtkaspell);
9165 compose->gtkaspell = NULL;
9169 if (!compose->batch) {
9170 gtk_widget_get_allocation(compose->window, &allocation);
9171 prefs_common.compose_width = allocation.width;
9172 prefs_common.compose_height = allocation.height;
9175 if (!gtk_widget_get_parent(compose->paned))
9176 gtk_widget_destroy(compose->paned);
9177 gtk_widget_destroy(compose->popupmenu);
9179 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
9180 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
9181 gtk_text_buffer_remove_selection_clipboard(buffer, clipboard);
9183 message_search_close(compose);
9184 gtk_widget_destroy(compose->window);
9185 toolbar_destroy(compose->toolbar);
9186 g_free(compose->toolbar);
9187 cm_mutex_free(compose->mutex);
9191 static void compose_attach_info_free(AttachInfo *ainfo)
9193 g_free(ainfo->file);
9194 g_free(ainfo->content_type);
9195 g_free(ainfo->name);
9196 g_free(ainfo->charset);
9200 static void compose_attach_update_label(Compose *compose)
9205 GtkTreeModel *model;
9209 if (compose == NULL)
9212 model = gtk_tree_view_get_model(GTK_TREE_VIEW(compose->attach_clist));
9213 if (!gtk_tree_model_get_iter_first(model, &iter)) {
9214 gtk_label_set_text(GTK_LABEL(compose->attach_label), "");
9218 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
9219 total_size = ainfo->size;
9220 while(gtk_tree_model_iter_next(model, &iter)) {
9221 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
9222 total_size += ainfo->size;
9225 text = g_strdup_printf(" (%d/%s)", i, to_human_readable(total_size));
9226 gtk_label_set_text(GTK_LABEL(compose->attach_label), text);
9230 static void compose_attach_remove_selected(GtkAction *action, gpointer data)
9232 Compose *compose = (Compose *)data;
9233 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
9234 GtkTreeSelection *selection;
9236 GtkTreeModel *model;
9238 selection = gtk_tree_view_get_selection(tree_view);
9239 sel = gtk_tree_selection_get_selected_rows(selection, &model);
9240 cm_return_if_fail(sel);
9242 for (cur = sel; cur != NULL; cur = cur->next) {
9243 GtkTreePath *path = cur->data;
9244 GtkTreeRowReference *ref = gtk_tree_row_reference_new
9247 gtk_tree_path_free(path);
9250 for (cur = sel; cur != NULL; cur = cur->next) {
9251 GtkTreeRowReference *ref = cur->data;
9252 GtkTreePath *path = gtk_tree_row_reference_get_path(ref);
9255 if (gtk_tree_model_get_iter(model, &iter, path))
9256 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
9258 gtk_tree_path_free(path);
9259 gtk_tree_row_reference_free(ref);
9263 compose_attach_update_label(compose);
9266 static struct _AttachProperty
9269 GtkWidget *mimetype_entry;
9270 GtkWidget *encoding_optmenu;
9271 GtkWidget *path_entry;
9272 GtkWidget *filename_entry;
9274 GtkWidget *cancel_btn;
9277 static void gtk_tree_path_free_(gpointer ptr, gpointer data)
9279 gtk_tree_path_free((GtkTreePath *)ptr);
9282 static void compose_attach_property(GtkAction *action, gpointer data)
9284 Compose *compose = (Compose *)data;
9285 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
9287 GtkComboBox *optmenu;
9288 GtkTreeSelection *selection;
9290 GtkTreeModel *model;
9293 static gboolean cancelled;
9295 /* only if one selected */
9296 selection = gtk_tree_view_get_selection(tree_view);
9297 if (gtk_tree_selection_count_selected_rows(selection) != 1)
9300 sel = gtk_tree_selection_get_selected_rows(selection, &model);
9301 cm_return_if_fail(sel);
9303 path = (GtkTreePath *) sel->data;
9304 gtk_tree_model_get_iter(model, &iter, path);
9305 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
9308 g_list_foreach(sel, gtk_tree_path_free_, NULL);
9314 if (!attach_prop.window)
9315 compose_attach_property_create(&cancelled);
9316 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), TRUE);
9317 gtk_widget_grab_focus(attach_prop.ok_btn);
9318 gtk_widget_show(attach_prop.window);
9319 gtk_window_set_transient_for(GTK_WINDOW(attach_prop.window),
9320 GTK_WINDOW(compose->window));
9322 optmenu = GTK_COMBO_BOX(attach_prop.encoding_optmenu);
9323 if (ainfo->encoding == ENC_UNKNOWN)
9324 combobox_select_by_data(optmenu, ENC_BASE64);
9326 combobox_select_by_data(optmenu, ainfo->encoding);
9328 gtk_entry_set_text(GTK_ENTRY(attach_prop.mimetype_entry),
9329 ainfo->content_type ? ainfo->content_type : "");
9330 gtk_entry_set_text(GTK_ENTRY(attach_prop.path_entry),
9331 ainfo->file ? ainfo->file : "");
9332 gtk_entry_set_text(GTK_ENTRY(attach_prop.filename_entry),
9333 ainfo->name ? ainfo->name : "");
9336 const gchar *entry_text;
9338 gchar *cnttype = NULL;
9345 gtk_widget_hide(attach_prop.window);
9346 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), FALSE);
9351 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.mimetype_entry));
9352 if (*entry_text != '\0') {
9355 text = g_strstrip(g_strdup(entry_text));
9356 if ((p = strchr(text, '/')) && !strchr(p + 1, '/')) {
9357 cnttype = g_strdup(text);
9360 alertpanel_error(_("Invalid MIME type."));
9366 ainfo->encoding = combobox_get_active_data(optmenu);
9368 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.path_entry));
9369 if (*entry_text != '\0') {
9370 if (is_file_exist(entry_text) &&
9371 (size = get_file_size(entry_text)) > 0)
9372 file = g_strdup(entry_text);
9375 (_("File doesn't exist or is empty."));
9381 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.filename_entry));
9382 if (*entry_text != '\0') {
9383 g_free(ainfo->name);
9384 ainfo->name = g_strdup(entry_text);
9388 g_free(ainfo->content_type);
9389 ainfo->content_type = cnttype;
9392 g_free(ainfo->file);
9396 ainfo->size = (goffset)size;
9398 /* update tree store */
9399 text = to_human_readable(ainfo->size);
9400 gtk_tree_model_get_iter(model, &iter, path);
9401 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
9402 COL_MIMETYPE, ainfo->content_type,
9404 COL_NAME, ainfo->name,
9405 COL_CHARSET, ainfo->charset,
9411 gtk_tree_path_free(path);
9414 #define SET_LABEL_AND_ENTRY(str, entry, top) \
9416 label = gtk_label_new(str); \
9417 gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), \
9418 GTK_FILL, 0, 0, 0); \
9419 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); \
9421 entry = gtk_entry_new(); \
9422 gtk_table_attach(GTK_TABLE(table), entry, 1, 2, top, (top + 1), \
9423 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); \
9426 static void compose_attach_property_create(gboolean *cancelled)
9432 GtkWidget *mimetype_entry;
9435 GtkListStore *optmenu_menu;
9436 GtkWidget *path_entry;
9437 GtkWidget *filename_entry;
9440 GtkWidget *cancel_btn;
9441 GList *mime_type_list, *strlist;
9444 debug_print("Creating attach_property window...\n");
9446 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose_attach_property");
9447 gtk_widget_set_size_request(window, 480, -1);
9448 gtk_container_set_border_width(GTK_CONTAINER(window), 8);
9449 gtk_window_set_title(GTK_WINDOW(window), _("Properties"));
9450 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
9451 g_signal_connect(G_OBJECT(window), "delete_event",
9452 G_CALLBACK(attach_property_delete_event),
9454 g_signal_connect(G_OBJECT(window), "key_press_event",
9455 G_CALLBACK(attach_property_key_pressed),
9458 vbox = gtk_vbox_new(FALSE, 8);
9459 gtk_container_add(GTK_CONTAINER(window), vbox);
9461 table = gtk_table_new(4, 2, FALSE);
9462 gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
9463 gtk_table_set_row_spacings(GTK_TABLE(table), 8);
9464 gtk_table_set_col_spacings(GTK_TABLE(table), 8);
9466 label = gtk_label_new(_("MIME type"));
9467 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, (0 + 1),
9469 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
9470 mimetype_entry = gtk_combo_box_text_new_with_entry();
9471 gtk_table_attach(GTK_TABLE(table), mimetype_entry, 1, 2, 0, (0 + 1),
9472 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
9474 /* stuff with list */
9475 mime_type_list = procmime_get_mime_type_list();
9477 for (; mime_type_list != NULL; mime_type_list = mime_type_list->next) {
9478 MimeType *type = (MimeType *) mime_type_list->data;
9481 tmp = g_strdup_printf("%s/%s", type->type, type->sub_type);
9483 if (g_list_find_custom(strlist, tmp, (GCompareFunc)strcmp2))
9486 strlist = g_list_insert_sorted(strlist, (gpointer)tmp,
9487 (GCompareFunc)strcmp2);
9490 for (mime_type_list = strlist; mime_type_list != NULL;
9491 mime_type_list = mime_type_list->next) {
9492 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(mimetype_entry), mime_type_list->data);
9493 g_free(mime_type_list->data);
9495 g_list_free(strlist);
9496 gtk_combo_box_set_active(GTK_COMBO_BOX(mimetype_entry), 0);
9497 mimetype_entry = gtk_bin_get_child(GTK_BIN((mimetype_entry)));
9499 label = gtk_label_new(_("Encoding"));
9500 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2,
9502 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
9504 hbox = gtk_hbox_new(FALSE, 0);
9505 gtk_table_attach(GTK_TABLE(table), hbox, 1, 2, 1, 2,
9506 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
9508 optmenu = gtkut_sc_combobox_create(NULL, TRUE);
9509 optmenu_menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
9511 COMBOBOX_ADD(optmenu_menu, "7bit", ENC_7BIT);
9512 COMBOBOX_ADD(optmenu_menu, "8bit", ENC_8BIT);
9513 COMBOBOX_ADD(optmenu_menu, "quoted-printable", ENC_QUOTED_PRINTABLE);
9514 COMBOBOX_ADD(optmenu_menu, "base64", ENC_BASE64);
9515 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), 0);
9517 gtk_box_pack_start(GTK_BOX(hbox), optmenu, TRUE, TRUE, 0);
9519 SET_LABEL_AND_ENTRY(_("Path"), path_entry, 2);
9520 SET_LABEL_AND_ENTRY(_("File name"), filename_entry, 3);
9522 gtkut_stock_button_set_create(&hbbox, &cancel_btn, GTK_STOCK_CANCEL,
9523 &ok_btn, GTK_STOCK_OK,
9525 gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
9526 gtk_widget_grab_default(ok_btn);
9528 g_signal_connect(G_OBJECT(ok_btn), "clicked",
9529 G_CALLBACK(attach_property_ok),
9531 g_signal_connect(G_OBJECT(cancel_btn), "clicked",
9532 G_CALLBACK(attach_property_cancel),
9535 gtk_widget_show_all(vbox);
9537 attach_prop.window = window;
9538 attach_prop.mimetype_entry = mimetype_entry;
9539 attach_prop.encoding_optmenu = optmenu;
9540 attach_prop.path_entry = path_entry;
9541 attach_prop.filename_entry = filename_entry;
9542 attach_prop.ok_btn = ok_btn;
9543 attach_prop.cancel_btn = cancel_btn;
9546 #undef SET_LABEL_AND_ENTRY
9548 static void attach_property_ok(GtkWidget *widget, gboolean *cancelled)
9554 static void attach_property_cancel(GtkWidget *widget, gboolean *cancelled)
9560 static gint attach_property_delete_event(GtkWidget *widget, GdkEventAny *event,
9561 gboolean *cancelled)
9569 static gboolean attach_property_key_pressed(GtkWidget *widget,
9571 gboolean *cancelled)
9573 if (event && event->keyval == GDK_KEY_Escape) {
9577 if (event && event->keyval == GDK_KEY_Return) {
9585 static void compose_exec_ext_editor(Compose *compose)
9590 GdkNativeWindow socket_wid = 0;
9594 tmp = g_strdup_printf("%s%ctmpmsg.%p", get_tmp_dir(),
9595 G_DIR_SEPARATOR, compose);
9597 if (compose_get_ext_editor_uses_socket()) {
9598 /* Only allow one socket */
9599 if (compose->exteditor_socket != NULL) {
9600 if (gtk_widget_is_focus(compose->exteditor_socket)) {
9601 /* Move the focus off of the socket */
9602 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9607 /* Create the receiving GtkSocket */
9608 socket = gtk_socket_new ();
9609 g_signal_connect (GTK_OBJECT(socket), "plug-removed",
9610 G_CALLBACK(compose_ext_editor_plug_removed_cb),
9612 gtk_box_pack_start(GTK_BOX(compose->edit_vbox), socket, TRUE, TRUE, 0);
9613 gtk_widget_set_size_request(socket, prefs_common.compose_width, -1);
9614 /* Realize the socket so that we can use its ID */
9615 gtk_widget_realize(socket);
9616 socket_wid = gtk_socket_get_id(GTK_SOCKET (socket));
9617 compose->exteditor_socket = socket;
9620 if (pipe(pipe_fds) < 0) {
9626 if ((pid = fork()) < 0) {
9633 /* close the write side of the pipe */
9636 compose->exteditor_file = g_strdup(tmp);
9637 compose->exteditor_pid = pid;
9639 compose_set_ext_editor_sensitive(compose, FALSE);
9642 compose->exteditor_ch = g_io_channel_unix_new(pipe_fds[0]);
9644 compose->exteditor_ch = g_io_channel_win32_new_fd(pipe_fds[0]);
9646 compose->exteditor_tag = g_io_add_watch(compose->exteditor_ch,
9650 } else { /* process-monitoring process */
9656 /* close the read side of the pipe */
9659 if (compose_write_body_to_file(compose, tmp) < 0) {
9660 fd_write_all(pipe_fds[1], "2\n", 2);
9664 pid_ed = compose_exec_ext_editor_real(tmp, socket_wid);
9666 fd_write_all(pipe_fds[1], "1\n", 2);
9670 /* wait until editor is terminated */
9671 waitpid(pid_ed, NULL, 0);
9673 fd_write_all(pipe_fds[1], "0\n", 2);
9680 #endif /* G_OS_UNIX */
9683 static gboolean compose_can_autosave(Compose *compose)
9685 if (compose->privacy_system && compose->use_encryption)
9686 return prefs_common.autosave && prefs_common.autosave_encrypted;
9688 return prefs_common.autosave;
9692 static gboolean compose_get_ext_editor_cmd_valid()
9694 gboolean has_s = FALSE;
9695 gboolean has_w = FALSE;
9696 const gchar *p = prefs_common_get_ext_editor_cmd();
9699 while ((p = strchr(p, '%'))) {
9705 } else if (*p == 'w') {
9716 static gint compose_exec_ext_editor_real(const gchar *file, GdkNativeWindow socket_wid)
9723 cm_return_val_if_fail(file != NULL, -1);
9725 if ((pid = fork()) < 0) {
9730 if (pid != 0) return pid;
9732 /* grandchild process */
9734 if (setpgid(0, getppid()))
9737 if (compose_get_ext_editor_cmd_valid()) {
9738 if (compose_get_ext_editor_uses_socket()) {
9739 p = g_strdup(prefs_common_get_ext_editor_cmd());
9740 s = strstr(p, "%w");
9742 if (strstr(p, "%s") < s)
9743 buf = g_strdup_printf(p, file, socket_wid);
9745 buf = g_strdup_printf(p, socket_wid, file);
9748 buf = g_strdup_printf(prefs_common_get_ext_editor_cmd(), file);
9751 if (prefs_common_get_ext_editor_cmd())
9752 g_warning("External editor command-line is invalid: '%s'",
9753 prefs_common_get_ext_editor_cmd());
9754 buf = g_strdup_printf(DEFAULT_EDITOR_CMD, file);
9757 cmdline = strsplit_with_quote(buf, " ", 0);
9759 execvp(cmdline[0], cmdline);
9762 g_strfreev(cmdline);
9767 static gboolean compose_ext_editor_kill(Compose *compose)
9769 pid_t pgid = compose->exteditor_pid * -1;
9772 ret = kill(pgid, 0);
9774 if (ret == 0 || (ret == -1 && EPERM == errno)) {
9778 msg = g_strdup_printf
9779 (_("The external editor is still working.\n"
9780 "Force terminating the process?\n"
9781 "process group id: %d"), -pgid);
9782 val = alertpanel_full(_("Notice"), msg, GTK_STOCK_NO, GTK_STOCK_YES,
9783 NULL, ALERTFOCUS_FIRST, FALSE, NULL,
9788 if (val == G_ALERTALTERNATE) {
9789 g_source_remove(compose->exteditor_tag);
9790 g_io_channel_shutdown(compose->exteditor_ch,
9792 g_io_channel_unref(compose->exteditor_ch);
9794 if (kill(pgid, SIGTERM) < 0) perror("kill");
9795 waitpid(compose->exteditor_pid, NULL, 0);
9797 g_warning("Terminated process group id: %d. "
9798 "Temporary file: %s", -pgid, compose->exteditor_file);
9800 compose_set_ext_editor_sensitive(compose, TRUE);
9802 g_free(compose->exteditor_file);
9803 compose->exteditor_file = NULL;
9804 compose->exteditor_pid = -1;
9805 compose->exteditor_ch = NULL;
9806 compose->exteditor_tag = -1;
9814 static gboolean compose_input_cb(GIOChannel *source, GIOCondition condition,
9818 Compose *compose = (Compose *)data;
9821 debug_print("Compose: input from monitoring process\n");
9823 if (g_io_channel_read_chars(source, buf, sizeof(buf), &bytes_read, NULL) != G_IO_STATUS_NORMAL) {
9828 g_io_channel_shutdown(source, FALSE, NULL);
9829 g_io_channel_unref(source);
9831 waitpid(compose->exteditor_pid, NULL, 0);
9833 if (buf[0] == '0') { /* success */
9834 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9835 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
9836 GtkTextIter start, end;
9839 gtk_text_buffer_set_text(buffer, "", -1);
9840 compose_insert_file(compose, compose->exteditor_file);
9841 compose_changed_cb(NULL, compose);
9843 /* Check if we should save the draft or not */
9844 if (compose_can_autosave(compose))
9845 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
9847 if (claws_unlink(compose->exteditor_file) < 0)
9848 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9850 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
9851 gtk_text_buffer_get_start_iter(buffer, &start);
9852 gtk_text_buffer_get_end_iter(buffer, &end);
9853 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
9854 if (chars && strlen(chars) > 0)
9855 compose->modified = TRUE;
9857 } else if (buf[0] == '1') { /* failed */
9858 g_warning("Couldn't exec external editor");
9859 if (claws_unlink(compose->exteditor_file) < 0)
9860 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9861 } else if (buf[0] == '2') {
9862 g_warning("Couldn't write to file");
9863 } else if (buf[0] == '3') {
9864 g_warning("Pipe read failed");
9867 compose_set_ext_editor_sensitive(compose, TRUE);
9869 g_free(compose->exteditor_file);
9870 compose->exteditor_file = NULL;
9871 compose->exteditor_pid = -1;
9872 compose->exteditor_ch = NULL;
9873 compose->exteditor_tag = -1;
9874 if (compose->exteditor_socket) {
9875 gtk_widget_destroy(compose->exteditor_socket);
9876 compose->exteditor_socket = NULL;
9883 static char *ext_editor_menu_entries[] = {
9884 "Menu/Message/Send",
9885 "Menu/Message/SendLater",
9886 "Menu/Message/InsertFile",
9887 "Menu/Message/InsertSig",
9888 "Menu/Message/ReplaceSig",
9889 "Menu/Message/Save",
9890 "Menu/Message/Print",
9895 "Menu/Tools/ShowRuler",
9896 "Menu/Tools/Actions",
9901 static void compose_set_ext_editor_sensitive(Compose *compose,
9906 for (i = 0; ext_editor_menu_entries[i]; ++i) {
9907 cm_menu_set_sensitive_full(compose->ui_manager,
9908 ext_editor_menu_entries[i], sensitive);
9911 if (compose_get_ext_editor_uses_socket()) {
9913 if (compose->exteditor_socket)
9914 gtk_widget_hide(compose->exteditor_socket);
9915 gtk_widget_show(compose->scrolledwin);
9916 if (prefs_common.show_ruler)
9917 gtk_widget_show(compose->ruler_hbox);
9918 /* Fix the focus, as it doesn't go anywhere when the
9919 * socket is hidden or destroyed */
9920 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9922 g_assert (compose->exteditor_socket != NULL);
9923 /* Fix the focus, as it doesn't go anywhere when the
9924 * edit box is hidden */
9925 if (gtk_widget_is_focus(compose->text))
9926 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9927 gtk_widget_hide(compose->scrolledwin);
9928 gtk_widget_hide(compose->ruler_hbox);
9929 gtk_widget_show(compose->exteditor_socket);
9932 gtk_widget_set_sensitive(compose->text, sensitive);
9934 if (compose->toolbar->send_btn)
9935 gtk_widget_set_sensitive(compose->toolbar->send_btn, sensitive);
9936 if (compose->toolbar->sendl_btn)
9937 gtk_widget_set_sensitive(compose->toolbar->sendl_btn, sensitive);
9938 if (compose->toolbar->draft_btn)
9939 gtk_widget_set_sensitive(compose->toolbar->draft_btn, sensitive);
9940 if (compose->toolbar->insert_btn)
9941 gtk_widget_set_sensitive(compose->toolbar->insert_btn, sensitive);
9942 if (compose->toolbar->sig_btn)
9943 gtk_widget_set_sensitive(compose->toolbar->sig_btn, sensitive);
9944 if (compose->toolbar->exteditor_btn)
9945 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, sensitive);
9946 if (compose->toolbar->linewrap_current_btn)
9947 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, sensitive);
9948 if (compose->toolbar->linewrap_all_btn)
9949 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, sensitive);
9952 static gboolean compose_get_ext_editor_uses_socket()
9954 return (prefs_common_get_ext_editor_cmd() &&
9955 strstr(prefs_common_get_ext_editor_cmd(), "%w"));
9958 static gboolean compose_ext_editor_plug_removed_cb(GtkSocket *socket, Compose *compose)
9960 compose->exteditor_socket = NULL;
9961 /* returning FALSE allows destruction of the socket */
9964 #endif /* G_OS_UNIX */
9967 * compose_undo_state_changed:
9969 * Change the sensivity of the menuentries undo and redo
9971 static void compose_undo_state_changed(UndoMain *undostruct, gint undo_state,
9972 gint redo_state, gpointer data)
9974 Compose *compose = (Compose *)data;
9976 switch (undo_state) {
9977 case UNDO_STATE_TRUE:
9978 if (!undostruct->undo_state) {
9979 undostruct->undo_state = TRUE;
9980 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", TRUE);
9983 case UNDO_STATE_FALSE:
9984 if (undostruct->undo_state) {
9985 undostruct->undo_state = FALSE;
9986 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
9989 case UNDO_STATE_UNCHANGED:
9991 case UNDO_STATE_REFRESH:
9992 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", undostruct->undo_state);
9995 g_warning("Undo state not recognized");
9999 switch (redo_state) {
10000 case UNDO_STATE_TRUE:
10001 if (!undostruct->redo_state) {
10002 undostruct->redo_state = TRUE;
10003 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", TRUE);
10006 case UNDO_STATE_FALSE:
10007 if (undostruct->redo_state) {
10008 undostruct->redo_state = FALSE;
10009 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
10012 case UNDO_STATE_UNCHANGED:
10014 case UNDO_STATE_REFRESH:
10015 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", undostruct->redo_state);
10018 g_warning("Redo state not recognized");
10023 /* callback functions */
10025 static void compose_notebook_size_alloc(GtkNotebook *notebook,
10026 GtkAllocation *allocation,
10029 prefs_common.compose_notebook_height = gtk_paned_get_position(paned);
10032 /* compose_edit_size_alloc() - called when resized. don't know whether Gtk
10033 * includes "non-client" (windows-izm) in calculation, so this calculation
10034 * may not be accurate.
10036 static gboolean compose_edit_size_alloc(GtkEditable *widget,
10037 GtkAllocation *allocation,
10038 GtkSHRuler *shruler)
10040 if (prefs_common.show_ruler) {
10041 gint char_width = 0, char_height = 0;
10042 gint line_width_in_chars;
10044 gtkut_get_font_size(GTK_WIDGET(widget),
10045 &char_width, &char_height);
10046 line_width_in_chars =
10047 (allocation->width - allocation->x) / char_width;
10049 /* got the maximum */
10050 gtk_shruler_set_range(GTK_SHRULER(shruler),
10051 0.0, line_width_in_chars, 0);
10060 ComposePrefType type;
10061 gboolean entry_marked;
10062 } HeaderEntryState;
10064 static void account_activated(GtkComboBox *optmenu, gpointer data)
10066 Compose *compose = (Compose *)data;
10069 gchar *folderidentifier;
10070 gint account_id = 0;
10071 GtkTreeModel *menu;
10073 GSList *list, *saved_list = NULL;
10074 HeaderEntryState *state;
10076 /* Get ID of active account in the combo box */
10077 menu = gtk_combo_box_get_model(optmenu);
10078 cm_return_if_fail(gtk_combo_box_get_active_iter(optmenu, &iter));
10079 gtk_tree_model_get(menu, &iter, 1, &account_id, -1);
10081 ac = account_find_from_id(account_id);
10082 cm_return_if_fail(ac != NULL);
10084 if (ac != compose->account) {
10085 compose_select_account(compose, ac, FALSE);
10087 for (list = compose->header_list; list; list = list->next) {
10088 ComposeHeaderEntry *hentry=(ComposeHeaderEntry *)list->data;
10090 if (hentry->type == PREF_ACCOUNT || !list->next) {
10091 compose_destroy_headerentry(compose, hentry);
10094 state = g_malloc0(sizeof(HeaderEntryState));
10095 state->header = gtk_editable_get_chars(GTK_EDITABLE(
10096 gtk_bin_get_child(GTK_BIN(hentry->combo))), 0, -1);
10097 state->entry = gtk_editable_get_chars(
10098 GTK_EDITABLE(hentry->entry), 0, -1);
10099 state->type = hentry->type;
10101 saved_list = g_slist_append(saved_list, state);
10102 compose_destroy_headerentry(compose, hentry);
10105 compose->header_last = NULL;
10106 g_slist_free(compose->header_list);
10107 compose->header_list = NULL;
10108 compose->header_nextrow = 1;
10109 compose_create_header_entry(compose);
10111 if (ac->set_autocc && ac->auto_cc)
10112 compose_entry_append(compose, ac->auto_cc,
10113 COMPOSE_CC, PREF_ACCOUNT);
10114 if (ac->set_autobcc && ac->auto_bcc)
10115 compose_entry_append(compose, ac->auto_bcc,
10116 COMPOSE_BCC, PREF_ACCOUNT);
10117 if (ac->set_autoreplyto && ac->auto_replyto)
10118 compose_entry_append(compose, ac->auto_replyto,
10119 COMPOSE_REPLYTO, PREF_ACCOUNT);
10121 for (list = saved_list; list; list = list->next) {
10122 state = (HeaderEntryState *) list->data;
10124 compose_add_header_entry(compose, state->header,
10125 state->entry, state->type);
10127 g_free(state->header);
10128 g_free(state->entry);
10131 g_slist_free(saved_list);
10133 combobox_select_by_data(GTK_COMBO_BOX(compose->header_last->combo),
10134 (ac->protocol == A_NNTP) ?
10135 COMPOSE_NEWSGROUPS : COMPOSE_TO);
10138 /* Set message save folder */
10139 compose_set_save_to(compose, NULL);
10140 if (account_get_special_folder(compose->account, F_OUTBOX)) {
10141 if (compose->account->set_sent_folder)
10142 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
10144 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), FALSE);
10145 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo), TRUE);
10146 folderidentifier = folder_item_get_identifier(account_get_special_folder
10147 (compose->account, F_OUTBOX));
10148 compose_set_save_to(compose, folderidentifier);
10149 g_free(folderidentifier);
10153 static void attach_selected(GtkTreeView *tree_view, GtkTreePath *tree_path,
10154 GtkTreeViewColumn *column, Compose *compose)
10156 compose_attach_property(NULL, compose);
10159 static gboolean attach_button_pressed(GtkWidget *widget, GdkEventButton *event,
10162 Compose *compose = (Compose *)data;
10163 GtkTreeSelection *attach_selection;
10164 gint attach_nr_selected;
10167 if (!event) return FALSE;
10169 if (event->button == 3) {
10170 attach_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
10171 attach_nr_selected = gtk_tree_selection_count_selected_rows(attach_selection);
10173 /* If no rows, or just one row is selected, right-click should
10174 * open menu relevant to the row being right-clicked on. We
10175 * achieve that by selecting the clicked row first. If more
10176 * than one row is selected, we shouldn't modify the selection,
10177 * as user may want to remove selected rows (attachments). */
10178 if (attach_nr_selected < 2) {
10179 gtk_tree_selection_unselect_all(attach_selection);
10180 attach_nr_selected = 0;
10181 gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget),
10182 event->x, event->y, &path, NULL, NULL, NULL);
10183 if (path != NULL) {
10184 gtk_tree_selection_select_path(attach_selection, path);
10185 gtk_tree_path_free(path);
10186 attach_nr_selected++;
10190 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", (attach_nr_selected > 0));
10191 /* Properties menu item makes no sense with more than one row
10192 * selected, the properties dialog can only edit one attachment. */
10193 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", (attach_nr_selected == 1));
10195 gtk_menu_popup(GTK_MENU(compose->popupmenu), NULL, NULL,
10196 NULL, NULL, event->button, event->time);
10203 static gboolean attach_key_pressed(GtkWidget *widget, GdkEventKey *event,
10206 Compose *compose = (Compose *)data;
10208 if (!event) return FALSE;
10210 switch (event->keyval) {
10211 case GDK_KEY_Delete:
10212 compose_attach_remove_selected(NULL, compose);
10218 static void compose_allow_user_actions (Compose *compose, gboolean allow)
10220 toolbar_comp_set_sensitive(compose, allow);
10221 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message", allow);
10222 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", allow);
10224 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", allow);
10226 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", allow);
10227 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools", allow);
10228 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Help", allow);
10230 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), allow);
10234 static void compose_send_cb(GtkAction *action, gpointer data)
10236 Compose *compose = (Compose *)data;
10239 if (compose->exteditor_tag != -1) {
10240 debug_print("ignoring send: external editor still open\n");
10244 if (prefs_common.work_offline &&
10245 !inc_offline_should_override(TRUE,
10246 _("Claws Mail needs network access in order "
10247 "to send this email.")))
10250 if (compose->draft_timeout_tag >= 0) { /* CLAWS: disable draft timeout */
10251 g_source_remove(compose->draft_timeout_tag);
10252 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
10255 compose_send(compose);
10258 static void compose_send_later_cb(GtkAction *action, gpointer data)
10260 Compose *compose = (Compose *)data;
10261 ComposeQueueResult val;
10264 compose_allow_user_actions(compose, FALSE);
10265 val = compose_queue_sub(compose, NULL, NULL, NULL, TRUE, TRUE);
10266 compose_allow_user_actions(compose, TRUE);
10269 if (val == COMPOSE_QUEUE_SUCCESS) {
10270 compose_close(compose);
10272 _display_queue_error(val);
10275 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
10278 #define DRAFTED_AT_EXIT "drafted_at_exit"
10279 static void compose_register_draft(MsgInfo *info)
10281 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10282 DRAFTED_AT_EXIT, NULL);
10283 FILE *fp = claws_fopen(filepath, "ab");
10286 fprintf(fp, "%s\t%d\n", folder_item_get_identifier(info->folder),
10294 gboolean compose_draft (gpointer data, guint action)
10296 Compose *compose = (Compose *)data;
10301 MsgFlags flag = {0, 0};
10302 static gboolean lock = FALSE;
10303 MsgInfo *newmsginfo;
10305 gboolean target_locked = FALSE;
10306 gboolean err = FALSE;
10308 if (lock) return FALSE;
10310 if (compose->sending)
10313 draft = account_get_special_folder(compose->account, F_DRAFT);
10314 cm_return_val_if_fail(draft != NULL, FALSE);
10316 if (!g_mutex_trylock(compose->mutex)) {
10317 /* we don't want to lock the mutex once it's available,
10318 * because as the only other part of compose.c locking
10319 * it is compose_close - which means once unlocked,
10320 * the compose struct will be freed */
10321 debug_print("couldn't lock mutex, probably sending\n");
10327 tmp = g_strdup_printf("%s%cdraft.%p", get_tmp_dir(),
10328 G_DIR_SEPARATOR, compose);
10329 if ((fp = claws_fopen(tmp, "wb")) == NULL) {
10330 FILE_OP_ERROR(tmp, "claws_fopen");
10334 /* chmod for security */
10335 if (change_file_mode_rw(fp, tmp) < 0) {
10336 FILE_OP_ERROR(tmp, "chmod");
10337 g_warning("can't change file mode");
10340 /* Save draft infos */
10341 err |= (fprintf(fp, "X-Claws-Account-Id:%d\n", compose->account->account_id) < 0);
10342 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
10344 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
10345 gchar *savefolderid;
10347 savefolderid = compose_get_save_to(compose);
10348 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
10349 g_free(savefolderid);
10351 if (compose->return_receipt) {
10352 err |= (fprintf(fp, "RRCPT:1\n") < 0);
10354 if (compose->privacy_system) {
10355 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
10356 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
10357 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
10360 /* Message-ID of message replying to */
10361 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
10362 gchar *folderid = NULL;
10364 if (compose->replyinfo->folder)
10365 folderid = folder_item_get_identifier(compose->replyinfo->folder);
10366 if (folderid == NULL)
10367 folderid = g_strdup("NULL");
10369 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
10372 /* Message-ID of message forwarding to */
10373 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
10374 gchar *folderid = NULL;
10376 if (compose->fwdinfo->folder)
10377 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
10378 if (folderid == NULL)
10379 folderid = g_strdup("NULL");
10381 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
10385 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
10386 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
10388 sheaders = compose_get_manual_headers_info(compose);
10389 err |= (fprintf(fp, "X-Claws-Manual-Headers:%s\n", sheaders) < 0);
10392 /* end of headers */
10393 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
10400 if (compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_STORE, action != COMPOSE_AUTO_SAVE) < 0) {
10404 if (claws_safe_fclose(fp) == EOF) {
10408 flag.perm_flags = MSG_NEW|MSG_UNREAD;
10409 if (compose->targetinfo) {
10410 target_locked = MSG_IS_LOCKED(compose->targetinfo->flags);
10412 flag.perm_flags |= MSG_LOCKED;
10414 flag.tmp_flags = MSG_DRAFT;
10416 folder_item_scan(draft);
10417 if ((msgnum = folder_item_add_msg(draft, tmp, &flag, TRUE)) < 0) {
10418 MsgInfo *tmpinfo = NULL;
10419 debug_print("didn't get msgnum after adding draft [%s]\n", compose->msgid?compose->msgid:"no msgid");
10420 if (compose->msgid) {
10421 tmpinfo = folder_item_get_msginfo_by_msgid(draft, compose->msgid);
10424 msgnum = tmpinfo->msgnum;
10425 procmsg_msginfo_free(&tmpinfo);
10426 debug_print("got draft msgnum %d from scanning\n", msgnum);
10428 debug_print("didn't get draft msgnum after scanning\n");
10431 debug_print("got draft msgnum %d from adding\n", msgnum);
10437 if (action != COMPOSE_AUTO_SAVE) {
10438 if (action != COMPOSE_DRAFT_FOR_EXIT)
10439 alertpanel_error(_("Could not save draft."));
10442 gtkut_window_popup(compose->window);
10443 val = alertpanel_full(_("Could not save draft"),
10444 _("Could not save draft.\n"
10445 "Do you want to cancel exit or discard this email?"),
10446 _("_Cancel exit"), _("_Discard email"), NULL, ALERTFOCUS_FIRST,
10447 FALSE, NULL, ALERT_QUESTION);
10448 if (val == G_ALERTALTERNATE) {
10450 g_mutex_unlock(compose->mutex); /* must be done before closing */
10451 compose_close(compose);
10455 g_mutex_unlock(compose->mutex); /* must be done before closing */
10464 if (compose->mode == COMPOSE_REEDIT) {
10465 compose_remove_reedit_target(compose, TRUE);
10468 newmsginfo = folder_item_get_msginfo(draft, msgnum);
10471 procmsg_msginfo_unset_flags(newmsginfo, ~0, ~0);
10473 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD|MSG_LOCKED, MSG_DRAFT);
10475 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD, MSG_DRAFT);
10476 if (compose_use_attach(compose) && action != COMPOSE_AUTO_SAVE)
10477 procmsg_msginfo_set_flags(newmsginfo, 0,
10478 MSG_HAS_ATTACHMENT);
10480 if (action == COMPOSE_DRAFT_FOR_EXIT) {
10481 compose_register_draft(newmsginfo);
10483 procmsg_msginfo_free(&newmsginfo);
10486 folder_item_scan(draft);
10488 if (action == COMPOSE_QUIT_EDITING || action == COMPOSE_DRAFT_FOR_EXIT) {
10490 g_mutex_unlock(compose->mutex); /* must be done before closing */
10491 compose_close(compose);
10498 GError *error = NULL;
10503 goffset size, mtime;
10505 path = folder_item_fetch_msg(draft, msgnum);
10506 if (path == NULL) {
10507 debug_print("can't fetch %s:%d\n", draft->path, msgnum);
10511 f = g_file_new_for_path(path);
10512 fi = g_file_query_info(f, "standard::size,time::modified",
10513 G_FILE_QUERY_INFO_NONE, NULL, &error);
10514 if (error != NULL) {
10515 debug_print("couldn't query file info for '%s': %s\n",
10516 path, error->message);
10517 g_error_free(error);
10522 size = g_file_info_get_size(fi);
10523 g_file_info_get_modification_time(fi, &tv);
10525 g_object_unref(fi);
10528 if (g_stat(path, &s) < 0) {
10529 FILE_OP_ERROR(path, "stat");
10534 mtime = s.st_mtime;
10538 procmsg_msginfo_free(&(compose->targetinfo));
10539 compose->targetinfo = procmsg_msginfo_new();
10540 compose->targetinfo->msgnum = msgnum;
10541 compose->targetinfo->size = size;
10542 compose->targetinfo->mtime = mtime;
10543 compose->targetinfo->folder = draft;
10545 procmsg_msginfo_set_flags(compose->targetinfo, MSG_LOCKED, 0);
10546 compose->mode = COMPOSE_REEDIT;
10548 if (action == COMPOSE_AUTO_SAVE) {
10549 compose->autosaved_draft = compose->targetinfo;
10551 compose->modified = FALSE;
10552 compose_set_title(compose);
10556 g_mutex_unlock(compose->mutex);
10560 void compose_clear_exit_drafts(void)
10562 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10563 DRAFTED_AT_EXIT, NULL);
10564 if (is_file_exist(filepath))
10565 claws_unlink(filepath);
10570 void compose_reopen_exit_drafts(void)
10572 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10573 DRAFTED_AT_EXIT, NULL);
10574 FILE *fp = claws_fopen(filepath, "rb");
10578 while (claws_fgets(buf, sizeof(buf), fp)) {
10579 gchar **parts = g_strsplit(buf, "\t", 2);
10580 const gchar *folder = parts[0];
10581 int msgnum = parts[1] ? atoi(parts[1]):-1;
10583 if (folder && *folder && msgnum > -1) {
10584 FolderItem *item = folder_find_item_from_identifier(folder);
10585 MsgInfo *info = folder_item_get_msginfo(item, msgnum);
10587 compose_reedit(info, FALSE);
10594 compose_clear_exit_drafts();
10597 static void compose_save_cb(GtkAction *action, gpointer data)
10599 Compose *compose = (Compose *)data;
10600 compose_draft(compose, COMPOSE_KEEP_EDITING);
10601 compose->rmode = COMPOSE_REEDIT;
10604 void compose_attach_from_list(Compose *compose, GList *file_list, gboolean free_data)
10606 if (compose && file_list) {
10609 for ( tmp = file_list; tmp; tmp = tmp->next) {
10610 gchar *file = (gchar *) tmp->data;
10611 gchar *utf8_filename = conv_filename_to_utf8(file);
10612 compose_attach_append(compose, file, utf8_filename, NULL, NULL);
10613 compose_changed_cb(NULL, compose);
10618 g_free(utf8_filename);
10623 static void compose_attach_cb(GtkAction *action, gpointer data)
10625 Compose *compose = (Compose *)data;
10628 if (compose->redirect_filename != NULL)
10631 /* Set focus_window properly, in case we were called via popup menu,
10632 * which unsets it (via focus_out_event callback on compose window). */
10633 manage_window_focus_in(compose->window, NULL, NULL);
10635 file_list = filesel_select_multiple_files_open(_("Select file"), NULL);
10638 compose_attach_from_list(compose, file_list, TRUE);
10639 g_list_free(file_list);
10643 static void compose_insert_file_cb(GtkAction *action, gpointer data)
10645 Compose *compose = (Compose *)data;
10647 gint files_inserted = 0;
10649 file_list = filesel_select_multiple_files_open(_("Select file"), NULL);
10654 for ( tmp = file_list; tmp; tmp = tmp->next) {
10655 gchar *file = (gchar *) tmp->data;
10656 gchar *filedup = g_strdup(file);
10657 gchar *shortfile = g_path_get_basename(filedup);
10658 ComposeInsertResult res;
10659 /* insert the file if the file is short or if the user confirmed that
10660 he/she wants to insert the large file */
10661 res = compose_insert_file(compose, file);
10662 if (res == COMPOSE_INSERT_READ_ERROR) {
10663 alertpanel_error(_("File '%s' could not be read."), shortfile);
10664 } else if (res == COMPOSE_INSERT_INVALID_CHARACTER) {
10665 alertpanel_error(_("File '%s' contained invalid characters\n"
10666 "for the current encoding, insertion may be incorrect."),
10668 } else if (res == COMPOSE_INSERT_SUCCESS)
10675 g_list_free(file_list);
10679 if (files_inserted > 0 && compose->gtkaspell &&
10680 compose->gtkaspell->check_while_typing)
10681 gtkaspell_highlight_all(compose->gtkaspell);
10685 static void compose_insert_sig_cb(GtkAction *action, gpointer data)
10687 Compose *compose = (Compose *)data;
10689 compose_insert_sig(compose, FALSE);
10692 static void compose_replace_sig_cb(GtkAction *action, gpointer data)
10694 Compose *compose = (Compose *)data;
10696 compose_insert_sig(compose, TRUE);
10699 static gint compose_delete_cb(GtkWidget *widget, GdkEventAny *event,
10703 Compose *compose = (Compose *)data;
10705 gtkut_widget_get_uposition(widget, &x, &y);
10706 if (!compose->batch) {
10707 prefs_common.compose_x = x;
10708 prefs_common.compose_y = y;
10710 if (compose->sending || compose->updating)
10712 compose_close_cb(NULL, compose);
10716 void compose_close_toolbar(Compose *compose)
10718 compose_close_cb(NULL, compose);
10721 static void compose_close_cb(GtkAction *action, gpointer data)
10723 Compose *compose = (Compose *)data;
10727 if (compose->exteditor_tag != -1) {
10728 if (!compose_ext_editor_kill(compose))
10733 if (compose->modified) {
10734 gboolean reedit = (compose->rmode == COMPOSE_REEDIT);
10735 if (!g_mutex_trylock(compose->mutex)) {
10736 /* we don't want to lock the mutex once it's available,
10737 * because as the only other part of compose.c locking
10738 * it is compose_close - which means once unlocked,
10739 * the compose struct will be freed */
10740 debug_print("couldn't lock mutex, probably sending\n");
10743 if (!reedit || compose->folder->stype == F_DRAFT) {
10744 val = alertpanel(_("Discard message"),
10745 _("This message has been modified. Discard it?"),
10746 _("_Discard"), _("_Save to Drafts"), GTK_STOCK_CANCEL,
10749 val = alertpanel(_("Save changes"),
10750 _("This message has been modified. Save the latest changes?"),
10751 _("_Don't save"), _("_Save to Drafts"), GTK_STOCK_CANCEL,
10752 ALERTFOCUS_SECOND);
10754 g_mutex_unlock(compose->mutex);
10756 case G_ALERTDEFAULT:
10757 if (compose_can_autosave(compose) && !reedit)
10758 compose_remove_draft(compose);
10760 case G_ALERTALTERNATE:
10761 compose_draft(data, COMPOSE_QUIT_EDITING);
10768 compose_close(compose);
10771 static void compose_print_cb(GtkAction *action, gpointer data)
10773 Compose *compose = (Compose *) data;
10775 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
10776 if (compose->targetinfo)
10777 messageview_print(compose->targetinfo, FALSE, -1, -1, 0);
10780 static void compose_set_encoding_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
10782 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
10783 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
10784 Compose *compose = (Compose *) data;
10787 compose->out_encoding = (CharSet)value;
10790 static void compose_address_cb(GtkAction *action, gpointer data)
10792 Compose *compose = (Compose *)data;
10794 #ifndef USE_ALT_ADDRBOOK
10795 addressbook_open(compose);
10797 GError* error = NULL;
10798 addressbook_connect_signals(compose);
10799 addressbook_dbus_open(TRUE, &error);
10801 g_warning("%s", error->message);
10802 g_error_free(error);
10807 static void about_show_cb(GtkAction *action, gpointer data)
10812 static void compose_template_activate_cb(GtkWidget *widget, gpointer data)
10814 Compose *compose = (Compose *)data;
10819 tmpl = g_object_get_data(G_OBJECT(widget), "template");
10820 cm_return_if_fail(tmpl != NULL);
10822 msg = g_strdup_printf(_("Do you want to apply the template '%s'?"),
10824 val = alertpanel(_("Apply template"), msg,
10825 _("_Replace"), _("_Insert"), GTK_STOCK_CANCEL, ALERTFOCUS_FIRST);
10828 if (val == G_ALERTDEFAULT)
10829 compose_template_apply(compose, tmpl, TRUE);
10830 else if (val == G_ALERTALTERNATE)
10831 compose_template_apply(compose, tmpl, FALSE);
10834 static void compose_ext_editor_cb(GtkAction *action, gpointer data)
10836 Compose *compose = (Compose *)data;
10839 if (compose->exteditor_tag != -1) {
10840 debug_print("ignoring open external editor: external editor still open\n");
10844 compose_exec_ext_editor(compose);
10847 static void compose_undo_cb(GtkAction *action, gpointer data)
10849 Compose *compose = (Compose *)data;
10850 gboolean prev_autowrap = compose->autowrap;
10852 compose->autowrap = FALSE;
10853 undo_undo(compose->undostruct);
10854 compose->autowrap = prev_autowrap;
10857 static void compose_redo_cb(GtkAction *action, gpointer data)
10859 Compose *compose = (Compose *)data;
10860 gboolean prev_autowrap = compose->autowrap;
10862 compose->autowrap = FALSE;
10863 undo_redo(compose->undostruct);
10864 compose->autowrap = prev_autowrap;
10867 static void entry_cut_clipboard(GtkWidget *entry)
10869 if (GTK_IS_EDITABLE(entry))
10870 gtk_editable_cut_clipboard (GTK_EDITABLE(entry));
10871 else if (GTK_IS_TEXT_VIEW(entry))
10872 gtk_text_buffer_cut_clipboard(
10873 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10874 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
10878 static void entry_copy_clipboard(GtkWidget *entry)
10880 if (GTK_IS_EDITABLE(entry))
10881 gtk_editable_copy_clipboard (GTK_EDITABLE(entry));
10882 else if (GTK_IS_TEXT_VIEW(entry))
10883 gtk_text_buffer_copy_clipboard(
10884 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10885 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
10888 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry,
10889 gboolean wrap, GdkAtom clip, GtkTextIter *insert_place)
10891 if (GTK_IS_TEXT_VIEW(entry)) {
10892 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10893 GtkTextMark *mark_start = gtk_text_buffer_get_insert(buffer);
10894 GtkTextIter start_iter, end_iter;
10896 gchar *contents = gtk_clipboard_wait_for_text(gtk_clipboard_get(clip));
10898 if (contents == NULL)
10901 /* we shouldn't delete the selection when middle-click-pasting, or we
10902 * can't mid-click-paste our own selection */
10903 if (clip != GDK_SELECTION_PRIMARY) {
10904 undo_paste_clipboard(GTK_TEXT_VIEW(compose->text), compose->undostruct);
10905 gtk_text_buffer_delete_selection(buffer, FALSE, TRUE);
10908 if (insert_place == NULL) {
10909 /* if insert_place isn't specified, insert at the cursor.
10910 * used for Ctrl-V pasting */
10911 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10912 start = gtk_text_iter_get_offset(&start_iter);
10913 gtk_text_buffer_insert(buffer, &start_iter, contents, strlen(contents));
10915 /* if insert_place is specified, paste here.
10916 * used for mid-click-pasting */
10917 start = gtk_text_iter_get_offset(insert_place);
10918 gtk_text_buffer_insert(buffer, insert_place, contents, strlen(contents));
10919 if (prefs_common.primary_paste_unselects)
10920 gtk_text_buffer_select_range(buffer, insert_place, insert_place);
10924 /* paste unwrapped: mark the paste so it's not wrapped later */
10925 end = start + strlen(contents);
10926 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, start);
10927 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, end);
10928 gtk_text_buffer_apply_tag_by_name(buffer, "no_wrap", &start_iter, &end_iter);
10929 } else if (wrap && clip == GDK_SELECTION_PRIMARY) {
10930 /* rewrap paragraph now (after a mid-click-paste) */
10931 mark_start = gtk_text_buffer_get_insert(buffer);
10932 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10933 gtk_text_iter_backward_char(&start_iter);
10934 compose_beautify_paragraph(compose, &start_iter, TRUE);
10936 } else if (GTK_IS_EDITABLE(entry))
10937 gtk_editable_paste_clipboard (GTK_EDITABLE(entry));
10939 compose->modified = TRUE;
10942 static void entry_allsel(GtkWidget *entry)
10944 if (GTK_IS_EDITABLE(entry))
10945 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
10946 else if (GTK_IS_TEXT_VIEW(entry)) {
10947 GtkTextIter startiter, enditer;
10948 GtkTextBuffer *textbuf;
10950 textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10951 gtk_text_buffer_get_start_iter(textbuf, &startiter);
10952 gtk_text_buffer_get_end_iter(textbuf, &enditer);
10954 gtk_text_buffer_move_mark_by_name(textbuf,
10955 "selection_bound", &startiter);
10956 gtk_text_buffer_move_mark_by_name(textbuf,
10957 "insert", &enditer);
10961 static void compose_cut_cb(GtkAction *action, gpointer data)
10963 Compose *compose = (Compose *)data;
10964 if (compose->focused_editable
10965 #ifndef GENERIC_UMPC
10966 && gtk_widget_has_focus(compose->focused_editable)
10969 entry_cut_clipboard(compose->focused_editable);
10972 static void compose_copy_cb(GtkAction *action, gpointer data)
10974 Compose *compose = (Compose *)data;
10975 if (compose->focused_editable
10976 #ifndef GENERIC_UMPC
10977 && gtk_widget_has_focus(compose->focused_editable)
10980 entry_copy_clipboard(compose->focused_editable);
10983 static void compose_paste_cb(GtkAction *action, gpointer data)
10985 Compose *compose = (Compose *)data;
10986 gint prev_autowrap;
10987 GtkTextBuffer *buffer;
10989 if (compose->focused_editable
10990 #ifndef GENERIC_UMPC
10991 && gtk_widget_has_focus(compose->focused_editable)
10994 entry_paste_clipboard(compose, compose->focused_editable,
10995 prefs_common.linewrap_pastes,
10996 GDK_SELECTION_CLIPBOARD, NULL);
11001 #ifndef GENERIC_UMPC
11002 gtk_widget_has_focus(compose->text) &&
11004 compose->gtkaspell &&
11005 compose->gtkaspell->check_while_typing)
11006 gtkaspell_highlight_all(compose->gtkaspell);
11010 static void compose_paste_as_quote_cb(GtkAction *action, gpointer data)
11012 Compose *compose = (Compose *)data;
11013 gint wrap_quote = prefs_common.linewrap_quote;
11014 if (compose->focused_editable
11015 #ifndef GENERIC_UMPC
11016 && gtk_widget_has_focus(compose->focused_editable)
11019 /* let text_insert() (called directly or at a later time
11020 * after the gtk_editable_paste_clipboard) know that
11021 * text is to be inserted as a quotation. implemented
11022 * by using a simple refcount... */
11023 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data(
11024 G_OBJECT(compose->focused_editable),
11025 "paste_as_quotation"));
11026 g_object_set_data(G_OBJECT(compose->focused_editable),
11027 "paste_as_quotation",
11028 GINT_TO_POINTER(paste_as_quotation + 1));
11029 prefs_common.linewrap_quote = prefs_common.linewrap_pastes;
11030 entry_paste_clipboard(compose, compose->focused_editable,
11031 prefs_common.linewrap_pastes,
11032 GDK_SELECTION_CLIPBOARD, NULL);
11033 prefs_common.linewrap_quote = wrap_quote;
11037 static void compose_paste_no_wrap_cb(GtkAction *action, gpointer data)
11039 Compose *compose = (Compose *)data;
11040 gint prev_autowrap;
11041 GtkTextBuffer *buffer;
11043 if (compose->focused_editable
11044 #ifndef GENERIC_UMPC
11045 && gtk_widget_has_focus(compose->focused_editable)
11048 entry_paste_clipboard(compose, compose->focused_editable, FALSE,
11049 GDK_SELECTION_CLIPBOARD, NULL);
11054 #ifndef GENERIC_UMPC
11055 gtk_widget_has_focus(compose->text) &&
11057 compose->gtkaspell &&
11058 compose->gtkaspell->check_while_typing)
11059 gtkaspell_highlight_all(compose->gtkaspell);
11063 static void compose_paste_wrap_cb(GtkAction *action, gpointer data)
11065 Compose *compose = (Compose *)data;
11066 gint prev_autowrap;
11067 GtkTextBuffer *buffer;
11069 if (compose->focused_editable
11070 #ifndef GENERIC_UMPC
11071 && gtk_widget_has_focus(compose->focused_editable)
11074 entry_paste_clipboard(compose, compose->focused_editable, TRUE,
11075 GDK_SELECTION_CLIPBOARD, NULL);
11080 #ifndef GENERIC_UMPC
11081 gtk_widget_has_focus(compose->text) &&
11083 compose->gtkaspell &&
11084 compose->gtkaspell->check_while_typing)
11085 gtkaspell_highlight_all(compose->gtkaspell);
11089 static void compose_allsel_cb(GtkAction *action, gpointer data)
11091 Compose *compose = (Compose *)data;
11092 if (compose->focused_editable
11093 #ifndef GENERIC_UMPC
11094 && gtk_widget_has_focus(compose->focused_editable)
11097 entry_allsel(compose->focused_editable);
11100 static void textview_move_beginning_of_line (GtkTextView *text)
11102 GtkTextBuffer *buffer;
11106 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11108 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11109 mark = gtk_text_buffer_get_insert(buffer);
11110 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11111 gtk_text_iter_set_line_offset(&ins, 0);
11112 gtk_text_buffer_place_cursor(buffer, &ins);
11115 static void textview_move_forward_character (GtkTextView *text)
11117 GtkTextBuffer *buffer;
11121 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11123 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11124 mark = gtk_text_buffer_get_insert(buffer);
11125 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11126 if (gtk_text_iter_forward_cursor_position(&ins))
11127 gtk_text_buffer_place_cursor(buffer, &ins);
11130 static void textview_move_backward_character (GtkTextView *text)
11132 GtkTextBuffer *buffer;
11136 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11138 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11139 mark = gtk_text_buffer_get_insert(buffer);
11140 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11141 if (gtk_text_iter_backward_cursor_position(&ins))
11142 gtk_text_buffer_place_cursor(buffer, &ins);
11145 static void textview_move_forward_word (GtkTextView *text)
11147 GtkTextBuffer *buffer;
11152 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11154 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11155 mark = gtk_text_buffer_get_insert(buffer);
11156 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11157 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
11158 if (gtk_text_iter_forward_word_ends(&ins, count)) {
11159 gtk_text_iter_backward_word_start(&ins);
11160 gtk_text_buffer_place_cursor(buffer, &ins);
11164 static void textview_move_backward_word (GtkTextView *text)
11166 GtkTextBuffer *buffer;
11170 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11172 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11173 mark = gtk_text_buffer_get_insert(buffer);
11174 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11175 if (gtk_text_iter_backward_word_starts(&ins, 1))
11176 gtk_text_buffer_place_cursor(buffer, &ins);
11179 static void textview_move_end_of_line (GtkTextView *text)
11181 GtkTextBuffer *buffer;
11185 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11187 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11188 mark = gtk_text_buffer_get_insert(buffer);
11189 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11190 if (gtk_text_iter_forward_to_line_end(&ins))
11191 gtk_text_buffer_place_cursor(buffer, &ins);
11194 static void textview_move_next_line (GtkTextView *text)
11196 GtkTextBuffer *buffer;
11201 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11203 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11204 mark = gtk_text_buffer_get_insert(buffer);
11205 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11206 offset = gtk_text_iter_get_line_offset(&ins);
11207 if (gtk_text_iter_forward_line(&ins)) {
11208 gtk_text_iter_set_line_offset(&ins, offset);
11209 gtk_text_buffer_place_cursor(buffer, &ins);
11213 static void textview_move_previous_line (GtkTextView *text)
11215 GtkTextBuffer *buffer;
11220 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11222 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11223 mark = gtk_text_buffer_get_insert(buffer);
11224 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11225 offset = gtk_text_iter_get_line_offset(&ins);
11226 if (gtk_text_iter_backward_line(&ins)) {
11227 gtk_text_iter_set_line_offset(&ins, offset);
11228 gtk_text_buffer_place_cursor(buffer, &ins);
11232 static void textview_delete_forward_character (GtkTextView *text)
11234 GtkTextBuffer *buffer;
11236 GtkTextIter ins, end_iter;
11238 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11240 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11241 mark = gtk_text_buffer_get_insert(buffer);
11242 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11244 if (gtk_text_iter_forward_char(&end_iter)) {
11245 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11249 static void textview_delete_backward_character (GtkTextView *text)
11251 GtkTextBuffer *buffer;
11253 GtkTextIter ins, end_iter;
11255 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11257 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11258 mark = gtk_text_buffer_get_insert(buffer);
11259 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11261 if (gtk_text_iter_backward_char(&end_iter)) {
11262 gtk_text_buffer_delete(buffer, &end_iter, &ins);
11266 static void textview_delete_forward_word (GtkTextView *text)
11268 GtkTextBuffer *buffer;
11270 GtkTextIter ins, end_iter;
11272 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11274 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11275 mark = gtk_text_buffer_get_insert(buffer);
11276 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11278 if (gtk_text_iter_forward_word_end(&end_iter)) {
11279 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11283 static void textview_delete_backward_word (GtkTextView *text)
11285 GtkTextBuffer *buffer;
11287 GtkTextIter ins, end_iter;
11289 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11291 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11292 mark = gtk_text_buffer_get_insert(buffer);
11293 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11295 if (gtk_text_iter_backward_word_start(&end_iter)) {
11296 gtk_text_buffer_delete(buffer, &end_iter, &ins);
11300 static void textview_delete_line (GtkTextView *text)
11302 GtkTextBuffer *buffer;
11304 GtkTextIter ins, start_iter, end_iter;
11306 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11308 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11309 mark = gtk_text_buffer_get_insert(buffer);
11310 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11313 gtk_text_iter_set_line_offset(&start_iter, 0);
11316 if (gtk_text_iter_ends_line(&end_iter)){
11317 if (!gtk_text_iter_forward_char(&end_iter))
11318 gtk_text_iter_backward_char(&start_iter);
11321 gtk_text_iter_forward_to_line_end(&end_iter);
11322 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
11325 static void textview_delete_to_line_end (GtkTextView *text)
11327 GtkTextBuffer *buffer;
11329 GtkTextIter ins, end_iter;
11331 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11333 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11334 mark = gtk_text_buffer_get_insert(buffer);
11335 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11337 if (gtk_text_iter_ends_line(&end_iter))
11338 gtk_text_iter_forward_char(&end_iter);
11340 gtk_text_iter_forward_to_line_end(&end_iter);
11341 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11344 #define DO_ACTION(name, act) { \
11345 if(!strcmp(name, a_name)) { \
11349 static ComposeCallAdvancedAction compose_call_advanced_action_from_path(GtkAction *action)
11351 const gchar *a_name = gtk_action_get_name(action);
11352 DO_ACTION("Edit/Advanced/BackChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER);
11353 DO_ACTION("Edit/Advanced/ForwChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER);
11354 DO_ACTION("Edit/Advanced/BackWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD);
11355 DO_ACTION("Edit/Advanced/ForwWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD);
11356 DO_ACTION("Edit/Advanced/BegLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE);
11357 DO_ACTION("Edit/Advanced/EndLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE);
11358 DO_ACTION("Edit/Advanced/PrevLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE);
11359 DO_ACTION("Edit/Advanced/NextLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE);
11360 DO_ACTION("Edit/Advanced/DelBackChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER);
11361 DO_ACTION("Edit/Advanced/DelForwChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER);
11362 DO_ACTION("Edit/Advanced/DelBackWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD);
11363 DO_ACTION("Edit/Advanced/DelForwWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD);
11364 DO_ACTION("Edit/Advanced/DelLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE);
11365 DO_ACTION("Edit/Advanced/DelEndLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END);
11366 return COMPOSE_CALL_ADVANCED_ACTION_UNDEFINED;
11369 static void compose_advanced_action_cb(GtkAction *gaction, gpointer data)
11371 Compose *compose = (Compose *)data;
11372 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11373 ComposeCallAdvancedAction action = COMPOSE_CALL_ADVANCED_ACTION_UNDEFINED;
11375 action = compose_call_advanced_action_from_path(gaction);
11378 void (*do_action) (GtkTextView *text);
11379 } action_table[] = {
11380 {textview_move_beginning_of_line},
11381 {textview_move_forward_character},
11382 {textview_move_backward_character},
11383 {textview_move_forward_word},
11384 {textview_move_backward_word},
11385 {textview_move_end_of_line},
11386 {textview_move_next_line},
11387 {textview_move_previous_line},
11388 {textview_delete_forward_character},
11389 {textview_delete_backward_character},
11390 {textview_delete_forward_word},
11391 {textview_delete_backward_word},
11392 {textview_delete_line},
11393 {textview_delete_to_line_end}
11396 if (!gtk_widget_has_focus(GTK_WIDGET(text))) return;
11398 if (action >= COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE &&
11399 action <= COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END) {
11400 if (action_table[action].do_action)
11401 action_table[action].do_action(text);
11403 g_warning("Not implemented yet.");
11407 static void compose_grab_focus_cb(GtkWidget *widget, Compose *compose)
11409 GtkAllocation allocation;
11413 if (GTK_IS_EDITABLE(widget)) {
11414 str = gtk_editable_get_chars(GTK_EDITABLE(widget), 0, -1);
11415 gtk_editable_set_position(GTK_EDITABLE(widget),
11418 if ((parent = gtk_widget_get_parent(widget))
11419 && (parent = gtk_widget_get_parent(parent))
11420 && (parent = gtk_widget_get_parent(parent))) {
11421 if (GTK_IS_SCROLLED_WINDOW(parent)) {
11422 gtk_widget_get_allocation(widget, &allocation);
11423 gint y = allocation.y;
11424 gint height = allocation.height;
11425 GtkAdjustment *shown = gtk_scrolled_window_get_vadjustment
11426 (GTK_SCROLLED_WINDOW(parent));
11428 gfloat value = gtk_adjustment_get_value(shown);
11429 gfloat upper = gtk_adjustment_get_upper(shown);
11430 gfloat page_size = gtk_adjustment_get_page_size(shown);
11431 if (y < (int)value) {
11432 gtk_adjustment_set_value(shown, y - 1);
11434 if ((y + height) > ((int)value + (int)page_size)) {
11435 if ((y - height - 1) < ((int)upper - (int)page_size)) {
11436 gtk_adjustment_set_value(shown,
11437 y + height - (int)page_size - 1);
11439 gtk_adjustment_set_value(shown,
11440 (int)upper - (int)page_size - 1);
11447 if (GTK_IS_EDITABLE(widget) || GTK_IS_TEXT_VIEW(widget))
11448 compose->focused_editable = widget;
11450 #ifdef GENERIC_UMPC
11451 if (GTK_IS_TEXT_VIEW(widget)
11452 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->edit_vbox) {
11453 g_object_ref(compose->notebook);
11454 g_object_ref(compose->edit_vbox);
11455 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
11456 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
11457 gtk_paned_add1(GTK_PANED(compose->paned), compose->edit_vbox);
11458 gtk_paned_add2(GTK_PANED(compose->paned), compose->notebook);
11459 g_object_unref(compose->notebook);
11460 g_object_unref(compose->edit_vbox);
11461 g_signal_handlers_block_by_func(G_OBJECT(widget),
11462 G_CALLBACK(compose_grab_focus_cb),
11464 gtk_widget_grab_focus(widget);
11465 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
11466 G_CALLBACK(compose_grab_focus_cb),
11468 } else if (!GTK_IS_TEXT_VIEW(widget)
11469 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->notebook) {
11470 g_object_ref(compose->notebook);
11471 g_object_ref(compose->edit_vbox);
11472 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
11473 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
11474 gtk_paned_add1(GTK_PANED(compose->paned), compose->notebook);
11475 gtk_paned_add2(GTK_PANED(compose->paned), compose->edit_vbox);
11476 g_object_unref(compose->notebook);
11477 g_object_unref(compose->edit_vbox);
11478 g_signal_handlers_block_by_func(G_OBJECT(widget),
11479 G_CALLBACK(compose_grab_focus_cb),
11481 gtk_widget_grab_focus(widget);
11482 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
11483 G_CALLBACK(compose_grab_focus_cb),
11489 static void compose_changed_cb(GtkTextBuffer *textbuf, Compose *compose)
11491 compose->modified = TRUE;
11492 /* compose_beautify_paragraph(compose, NULL, TRUE); */
11493 #ifndef GENERIC_UMPC
11494 compose_set_title(compose);
11498 static void compose_wrap_cb(GtkAction *action, gpointer data)
11500 Compose *compose = (Compose *)data;
11501 compose_beautify_paragraph(compose, NULL, TRUE);
11504 static void compose_wrap_all_cb(GtkAction *action, gpointer data)
11506 Compose *compose = (Compose *)data;
11507 compose_wrap_all_full(compose, TRUE);
11510 static void compose_find_cb(GtkAction *action, gpointer data)
11512 Compose *compose = (Compose *)data;
11514 message_search_compose(compose);
11517 static void compose_toggle_autowrap_cb(GtkToggleAction *action,
11520 Compose *compose = (Compose *)data;
11521 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11522 if (compose->autowrap)
11523 compose_wrap_all_full(compose, TRUE);
11524 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11527 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
11530 Compose *compose = (Compose *)data;
11531 compose->autoindent = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11534 static void compose_toggle_sign_cb(GtkToggleAction *action, gpointer data)
11536 Compose *compose = (Compose *)data;
11538 compose->use_signing = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11539 gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(compose->toolbar->privacy_sign_btn), compose->use_signing);
11542 static void compose_toggle_encrypt_cb(GtkToggleAction *action, gpointer data)
11544 Compose *compose = (Compose *)data;
11546 compose->use_encryption = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11547 gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(compose->toolbar->privacy_encrypt_btn), compose->use_encryption);
11550 static void compose_activate_privacy_system(Compose *compose, PrefsAccount *account, gboolean warn)
11552 g_free(compose->privacy_system);
11553 g_free(compose->encdata);
11555 compose->privacy_system = g_strdup(account->default_privacy_system);
11556 compose_update_privacy_system_menu_item(compose, warn);
11559 static void compose_apply_folder_privacy_settings(Compose *compose, FolderItem *folder_item)
11561 if (folder_item != NULL) {
11562 if (folder_item->prefs->always_sign != SIGN_OR_ENCRYPT_DEFAULT &&
11563 privacy_system_can_sign(compose->privacy_system)) {
11564 compose_use_signing(compose,
11565 (folder_item->prefs->always_sign == SIGN_OR_ENCRYPT_ALWAYS) ? TRUE : FALSE);
11567 if (folder_item->prefs->always_encrypt != SIGN_OR_ENCRYPT_DEFAULT &&
11568 privacy_system_can_encrypt(compose->privacy_system)) {
11569 compose_use_encryption(compose,
11570 (folder_item->prefs->always_encrypt == SIGN_OR_ENCRYPT_ALWAYS) ? TRUE : FALSE);
11575 static void compose_toggle_ruler_cb(GtkToggleAction *action, gpointer data)
11577 Compose *compose = (Compose *)data;
11579 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action))) {
11580 gtk_widget_show(compose->ruler_hbox);
11581 prefs_common.show_ruler = TRUE;
11583 gtk_widget_hide(compose->ruler_hbox);
11584 gtk_widget_queue_resize(compose->edit_vbox);
11585 prefs_common.show_ruler = FALSE;
11589 static void compose_attach_drag_received_cb (GtkWidget *widget,
11590 GdkDragContext *context,
11593 GtkSelectionData *data,
11596 gpointer user_data)
11598 Compose *compose = (Compose *)user_data;
11602 type = gtk_selection_data_get_data_type(data);
11603 if ((gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list"))
11604 && gtk_drag_get_source_widget(context) !=
11605 summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
11606 list = uri_list_extract_filenames(
11607 (const gchar *)gtk_selection_data_get_data(data));
11608 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11609 gchar *utf8_filename = conv_filename_to_utf8((const gchar *)tmp->data);
11610 compose_attach_append
11611 (compose, (const gchar *)tmp->data,
11612 utf8_filename, NULL, NULL);
11613 g_free(utf8_filename);
11616 compose_changed_cb(NULL, compose);
11617 list_free_strings_full(list);
11618 } else if (gtk_drag_get_source_widget(context)
11619 == summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
11620 /* comes from our summaryview */
11621 SummaryView * summaryview = NULL;
11622 GSList * list = NULL, *cur = NULL;
11624 if (mainwindow_get_mainwindow())
11625 summaryview = mainwindow_get_mainwindow()->summaryview;
11628 list = summary_get_selected_msg_list(summaryview);
11630 for (cur = list; cur; cur = cur->next) {
11631 MsgInfo *msginfo = (MsgInfo *)cur->data;
11632 gchar *file = NULL;
11634 file = procmsg_get_message_file_full(msginfo,
11637 compose_attach_append(compose, (const gchar *)file,
11638 (const gchar *)file, "message/rfc822", NULL);
11642 g_slist_free(list);
11646 static gboolean compose_drag_drop(GtkWidget *widget,
11647 GdkDragContext *drag_context,
11649 guint time, gpointer user_data)
11651 /* not handling this signal makes compose_insert_drag_received_cb
11656 static gboolean completion_set_focus_to_subject
11657 (GtkWidget *widget,
11658 GdkEventKey *event,
11661 cm_return_val_if_fail(compose != NULL, FALSE);
11663 /* make backtab move to subject field */
11664 if(event->keyval == GDK_KEY_ISO_Left_Tab) {
11665 gtk_widget_grab_focus(compose->subject_entry);
11671 static void compose_insert_drag_received_cb (GtkWidget *widget,
11672 GdkDragContext *drag_context,
11675 GtkSelectionData *data,
11678 gpointer user_data)
11680 Compose *compose = (Compose *)user_data;
11686 /* strangely, testing data->type == gdk_atom_intern("text/uri-list", TRUE)
11688 type = gtk_selection_data_get_data_type(data);
11689 if (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list")) {
11690 AlertValue val = G_ALERTDEFAULT;
11691 const gchar* ddata = (const gchar *)gtk_selection_data_get_data(data);
11693 list = uri_list_extract_filenames(ddata);
11694 num_files = g_list_length(list);
11695 if (list == NULL && strstr(ddata, "://")) {
11696 /* Assume a list of no files, and data has ://, is a remote link */
11697 gchar *tmpdata = g_strstrip(g_strdup(ddata));
11698 gchar *tmpfile = get_tmp_file();
11699 str_write_to_file(tmpdata, tmpfile, TRUE);
11701 compose_insert_file(compose, tmpfile);
11702 claws_unlink(tmpfile);
11704 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11705 compose_beautify_paragraph(compose, NULL, TRUE);
11708 switch (prefs_common.compose_dnd_mode) {
11709 case COMPOSE_DND_ASK:
11710 msg = g_strdup_printf(
11712 "Do you want to insert the contents of the file "
11713 "into the message body, or attach it to the email?",
11714 "Do you want to insert the contents of the %d files "
11715 "into the message body, or attach them to the email?",
11718 val = alertpanel_full(_("Insert or attach?"), msg,
11719 GTK_STOCK_CANCEL, _("_Insert"), _("_Attach"),
11721 TRUE, NULL, ALERT_QUESTION);
11724 case COMPOSE_DND_INSERT:
11725 val = G_ALERTALTERNATE;
11727 case COMPOSE_DND_ATTACH:
11728 val = G_ALERTOTHER;
11731 /* unexpected case */
11732 g_warning("error: unexpected compose_dnd_mode option value in compose_insert_drag_received_cb()");
11735 if (val & G_ALERTDISABLE) {
11736 val &= ~G_ALERTDISABLE;
11737 /* remember what action to perform by default, only if we don't click Cancel */
11738 if (val == G_ALERTALTERNATE)
11739 prefs_common.compose_dnd_mode = COMPOSE_DND_INSERT;
11740 else if (val == G_ALERTOTHER)
11741 prefs_common.compose_dnd_mode = COMPOSE_DND_ATTACH;
11744 if (val == G_ALERTDEFAULT || val == G_ALERTCANCEL) {
11745 gtk_drag_finish(drag_context, FALSE, FALSE, time);
11746 list_free_strings_full(list);
11748 } else if (val == G_ALERTOTHER) {
11749 compose_attach_drag_received_cb(widget, drag_context, x, y, data, info, time, user_data);
11750 list_free_strings_full(list);
11754 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11755 compose_insert_file(compose, (const gchar *)tmp->data);
11757 list_free_strings_full(list);
11758 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11763 static void compose_header_drag_received_cb (GtkWidget *widget,
11764 GdkDragContext *drag_context,
11767 GtkSelectionData *data,
11770 gpointer user_data)
11772 GtkEditable *entry = (GtkEditable *)user_data;
11773 const gchar *email = (const gchar *)gtk_selection_data_get_data(data);
11775 /* strangely, testing data->type == gdk_atom_intern("text/plain", TRUE)
11778 if (!strncmp(email, "mailto:", strlen("mailto:"))) {
11779 gchar *decoded=g_new(gchar, strlen(email));
11782 decode_uri(decoded, email + strlen("mailto:")); /* will fit */
11783 gtk_editable_delete_text(entry, 0, -1);
11784 gtk_editable_insert_text(entry, decoded, strlen(decoded), &start);
11785 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11789 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11792 static void compose_toggle_return_receipt_cb(GtkToggleAction *action, gpointer data)
11794 Compose *compose = (Compose *)data;
11796 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11797 compose->return_receipt = TRUE;
11799 compose->return_receipt = FALSE;
11802 static void compose_toggle_remove_refs_cb(GtkToggleAction *action, gpointer data)
11804 Compose *compose = (Compose *)data;
11806 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11807 compose->remove_references = TRUE;
11809 compose->remove_references = FALSE;
11812 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
11813 ComposeHeaderEntry *headerentry)
11815 gtk_entry_set_text(GTK_ENTRY(headerentry->entry), "");
11816 gtk_widget_modify_base(GTK_WIDGET(headerentry->entry), GTK_STATE_NORMAL, NULL);
11817 gtk_widget_modify_text(GTK_WIDGET(headerentry->entry), GTK_STATE_NORMAL, NULL);
11821 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
11822 GdkEventKey *event,
11823 ComposeHeaderEntry *headerentry)
11825 if ((g_slist_length(headerentry->compose->header_list) > 0) &&
11826 ((headerentry->headernum + 1) != headerentry->compose->header_nextrow) &&
11827 !(event->state & GDK_MODIFIER_MASK) &&
11828 (event->keyval == GDK_KEY_BackSpace) &&
11829 (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) == 0)) {
11830 gtk_container_remove
11831 (GTK_CONTAINER(headerentry->compose->header_table),
11832 headerentry->combo);
11833 gtk_container_remove
11834 (GTK_CONTAINER(headerentry->compose->header_table),
11835 headerentry->entry);
11836 headerentry->compose->header_list =
11837 g_slist_remove(headerentry->compose->header_list,
11839 g_free(headerentry);
11840 } else if (event->keyval == GDK_KEY_Tab) {
11841 if (headerentry->compose->header_last == headerentry) {
11842 /* Override default next focus, and give it to subject_entry
11843 * instead of notebook tabs
11845 g_signal_stop_emission_by_name(G_OBJECT(entry), "key-press-event");
11846 gtk_widget_grab_focus(headerentry->compose->subject_entry);
11853 static gboolean scroll_postpone(gpointer data)
11855 Compose *compose = (Compose *)data;
11857 if (compose->batch)
11860 GTK_EVENTS_FLUSH();
11861 compose_show_first_last_header(compose, FALSE);
11865 static void compose_headerentry_changed_cb(GtkWidget *entry,
11866 ComposeHeaderEntry *headerentry)
11868 if (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) != 0) {
11869 compose_create_header_entry(headerentry->compose);
11870 g_signal_handlers_disconnect_matched
11871 (G_OBJECT(entry), G_SIGNAL_MATCH_DATA,
11872 0, 0, NULL, NULL, headerentry);
11874 if (!headerentry->compose->batch)
11875 g_timeout_add(0, scroll_postpone, headerentry->compose);
11879 static gboolean compose_defer_auto_save_draft(Compose *compose)
11881 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
11882 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
11886 static void compose_show_first_last_header(Compose *compose, gboolean show_first)
11888 GtkAdjustment *vadj;
11890 cm_return_if_fail(compose);
11895 cm_return_if_fail(GTK_IS_WIDGET(compose->header_table));
11896 cm_return_if_fail(GTK_IS_VIEWPORT(gtk_widget_get_parent(compose->header_table)));
11897 vadj = gtk_viewport_get_vadjustment(GTK_VIEWPORT(
11898 gtk_widget_get_parent(compose->header_table)));
11899 gtk_adjustment_set_value(vadj, (show_first ?
11900 gtk_adjustment_get_lower(vadj) :
11901 (gtk_adjustment_get_upper(vadj) -
11902 gtk_adjustment_get_page_size(vadj))));
11903 gtk_adjustment_changed(vadj);
11906 static void text_inserted(GtkTextBuffer *buffer, GtkTextIter *iter,
11907 const gchar *text, gint len, Compose *compose)
11909 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data
11910 (G_OBJECT(compose->text), "paste_as_quotation"));
11913 cm_return_if_fail(text != NULL);
11915 g_signal_handlers_block_by_func(G_OBJECT(buffer),
11916 G_CALLBACK(text_inserted),
11918 if (paste_as_quotation) {
11920 const gchar *qmark;
11922 GtkTextIter start_iter;
11925 len = strlen(text);
11927 new_text = g_strndup(text, len);
11929 qmark = compose_quote_char_from_context(compose);
11931 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11932 gtk_text_buffer_place_cursor(buffer, iter);
11934 pos = gtk_text_iter_get_offset(iter);
11936 compose_quote_fmt(compose, NULL, "%Q", qmark, new_text, TRUE, FALSE,
11937 _("Quote format error at line %d."));
11938 quote_fmt_reset_vartable();
11940 g_object_set_data(G_OBJECT(compose->text), "paste_as_quotation",
11941 GINT_TO_POINTER(paste_as_quotation - 1));
11943 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11944 gtk_text_buffer_place_cursor(buffer, iter);
11945 gtk_text_buffer_delete_mark(buffer, mark);
11947 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, pos);
11948 mark = gtk_text_buffer_create_mark(buffer, NULL, &start_iter, FALSE);
11949 compose_beautify_paragraph(compose, &start_iter, FALSE);
11950 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark);
11951 gtk_text_buffer_delete_mark(buffer, mark);
11953 if (strcmp(text, "\n") || compose->automatic_break
11954 || gtk_text_iter_starts_line(iter)) {
11955 GtkTextIter before_ins;
11956 gtk_text_buffer_insert(buffer, iter, text, len);
11957 if (!strstr(text, "\n") && gtk_text_iter_has_tag(iter, compose->no_join_tag)) {
11958 before_ins = *iter;
11959 gtk_text_iter_backward_chars(&before_ins, len);
11960 gtk_text_buffer_remove_tag_by_name(buffer, "no_join", &before_ins, iter);
11963 /* check if the preceding is just whitespace or quote */
11964 GtkTextIter start_line;
11965 gchar *tmp = NULL, *quote = NULL;
11966 gint quote_len = 0, is_normal = 0;
11967 start_line = *iter;
11968 gtk_text_iter_set_line_offset(&start_line, 0);
11969 tmp = gtk_text_buffer_get_text(buffer, &start_line, iter, FALSE);
11972 if (*tmp == '\0') {
11975 quote = compose_get_quote_str(buffer, &start_line, "e_len);
11983 gtk_text_buffer_insert(buffer, iter, text, len);
11985 gtk_text_buffer_insert_with_tags_by_name(buffer,
11986 iter, text, len, "no_join", NULL);
11991 if (!paste_as_quotation) {
11992 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11993 compose_beautify_paragraph(compose, iter, FALSE);
11994 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11995 gtk_text_buffer_delete_mark(buffer, mark);
11998 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
11999 G_CALLBACK(text_inserted),
12001 g_signal_stop_emission_by_name(G_OBJECT(buffer), "insert-text");
12003 if (compose_can_autosave(compose) &&
12004 gtk_text_buffer_get_char_count(buffer) % prefs_common.autosave_length == 0 &&
12005 compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN /* disabled while loading */)
12006 compose->draft_timeout_tag = g_timeout_add
12007 (500, (GSourceFunc) compose_defer_auto_save_draft, compose);
12011 static void compose_check_all(GtkAction *action, gpointer data)
12013 Compose *compose = (Compose *)data;
12014 if (!compose->gtkaspell)
12017 if (gtk_widget_has_focus(compose->subject_entry))
12018 claws_spell_entry_check_all(
12019 CLAWS_SPELL_ENTRY(compose->subject_entry));
12021 gtkaspell_check_all(compose->gtkaspell);
12024 static void compose_highlight_all(GtkAction *action, gpointer data)
12026 Compose *compose = (Compose *)data;
12027 if (compose->gtkaspell) {
12028 claws_spell_entry_recheck_all(
12029 CLAWS_SPELL_ENTRY(compose->subject_entry));
12030 gtkaspell_highlight_all(compose->gtkaspell);
12034 static void compose_check_backwards(GtkAction *action, gpointer data)
12036 Compose *compose = (Compose *)data;
12037 if (!compose->gtkaspell) {
12038 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
12042 if (gtk_widget_has_focus(compose->subject_entry))
12043 claws_spell_entry_check_backwards(
12044 CLAWS_SPELL_ENTRY(compose->subject_entry));
12046 gtkaspell_check_backwards(compose->gtkaspell);
12049 static void compose_check_forwards_go(GtkAction *action, gpointer data)
12051 Compose *compose = (Compose *)data;
12052 if (!compose->gtkaspell) {
12053 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
12057 if (gtk_widget_has_focus(compose->subject_entry))
12058 claws_spell_entry_check_forwards_go(
12059 CLAWS_SPELL_ENTRY(compose->subject_entry));
12061 gtkaspell_check_forwards_go(compose->gtkaspell);
12066 *\brief Guess originating forward account from MsgInfo and several
12067 * "common preference" settings. Return NULL if no guess.
12069 static PrefsAccount *compose_find_account(MsgInfo *msginfo)
12071 PrefsAccount *account = NULL;
12073 cm_return_val_if_fail(msginfo, NULL);
12074 cm_return_val_if_fail(msginfo->folder, NULL);
12075 cm_return_val_if_fail(msginfo->folder->prefs, NULL);
12077 if (msginfo->folder->prefs->enable_default_account)
12078 account = account_find_from_id(msginfo->folder->prefs->default_account);
12080 if (!account && msginfo->to && prefs_common.forward_account_autosel) {
12082 Xstrdup_a(to, msginfo->to, return NULL);
12083 extract_address(to);
12084 account = account_find_from_address(to, FALSE);
12087 if (!account && prefs_common.forward_account_autosel) {
12089 if (!procheader_get_header_from_msginfo
12090 (msginfo, &cc, "Cc:")) {
12091 gchar *buf = cc + strlen("Cc:");
12092 extract_address(buf);
12093 account = account_find_from_address(buf, FALSE);
12098 if (!account && prefs_common.forward_account_autosel) {
12099 gchar *deliveredto = NULL;
12100 if (!procheader_get_header_from_msginfo
12101 (msginfo, &deliveredto, "Delivered-To:")) {
12102 gchar *buf = deliveredto + strlen("Delivered-To:");
12103 extract_address(buf);
12104 account = account_find_from_address(buf, FALSE);
12105 g_free(deliveredto);
12110 account = msginfo->folder->folder->account;
12115 gboolean compose_close(Compose *compose)
12119 cm_return_val_if_fail(compose, FALSE);
12121 if (!g_mutex_trylock(compose->mutex)) {
12122 /* we have to wait for the (possibly deferred by auto-save)
12123 * drafting to be done, before destroying the compose under
12125 debug_print("waiting for drafting to finish...\n");
12126 compose_allow_user_actions(compose, FALSE);
12127 if (compose->close_timeout_tag == 0) {
12128 compose->close_timeout_tag =
12129 g_timeout_add (500, (GSourceFunc) compose_close,
12135 if (compose->draft_timeout_tag >= 0) {
12136 g_source_remove(compose->draft_timeout_tag);
12137 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN;
12140 gtkut_widget_get_uposition(compose->window, &x, &y);
12141 if (!compose->batch) {
12142 prefs_common.compose_x = x;
12143 prefs_common.compose_y = y;
12145 g_mutex_unlock(compose->mutex);
12146 compose_destroy(compose);
12151 * Add entry field for each address in list.
12152 * \param compose E-Mail composition object.
12153 * \param listAddress List of (formatted) E-Mail addresses.
12155 static void compose_add_field_list( Compose *compose, GList *listAddress ) {
12158 node = listAddress;
12160 addr = ( gchar * ) node->data;
12161 compose_entry_append( compose, addr, COMPOSE_TO, PREF_NONE );
12162 node = g_list_next( node );
12166 static void compose_reply_from_messageview_real(MessageView *msgview, GSList *msginfo_list,
12167 guint action, gboolean opening_multiple)
12169 gchar *body = NULL;
12170 GSList *new_msglist = NULL;
12171 MsgInfo *tmp_msginfo = NULL;
12172 gboolean originally_enc = FALSE;
12173 gboolean originally_sig = FALSE;
12174 Compose *compose = NULL;
12175 gchar *s_system = NULL;
12177 cm_return_if_fail(msginfo_list != NULL);
12179 if (g_slist_length(msginfo_list) == 1 && !opening_multiple && msgview != NULL) {
12180 MimeInfo *mimeinfo = messageview_get_selected_mime_part(msgview);
12181 MsgInfo *orig_msginfo = (MsgInfo *)msginfo_list->data;
12183 if (mimeinfo != NULL && mimeinfo->type == MIMETYPE_MESSAGE &&
12184 !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
12185 tmp_msginfo = procmsg_msginfo_new_from_mimeinfo(
12186 orig_msginfo, mimeinfo);
12187 if (tmp_msginfo != NULL) {
12188 new_msglist = g_slist_append(NULL, tmp_msginfo);
12190 originally_enc = MSG_IS_ENCRYPTED(orig_msginfo->flags);
12191 privacy_msginfo_get_signed_state(orig_msginfo, &s_system);
12192 originally_sig = MSG_IS_SIGNED(orig_msginfo->flags);
12194 tmp_msginfo->folder = orig_msginfo->folder;
12195 tmp_msginfo->msgnum = orig_msginfo->msgnum;
12196 if (orig_msginfo->tags) {
12197 tmp_msginfo->tags = g_slist_copy(orig_msginfo->tags);
12198 tmp_msginfo->folder->tags_dirty = TRUE;
12204 if (!opening_multiple && msgview != NULL)
12205 body = messageview_get_selection(msgview);
12208 compose = compose_reply_mode((ComposeMode)action, new_msglist, body);
12209 procmsg_msginfo_free(&tmp_msginfo);
12210 g_slist_free(new_msglist);
12212 compose = compose_reply_mode((ComposeMode)action, msginfo_list, body);
12214 if (compose && originally_enc) {
12215 compose_force_encryption(compose, compose->account, FALSE, s_system);
12218 if (compose && originally_sig && compose->account->default_sign_reply) {
12219 compose_force_signing(compose, compose->account, s_system);
12223 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
12226 void compose_reply_from_messageview(MessageView *msgview, GSList *msginfo_list,
12229 if ((!prefs_common.forward_as_attachment || action != COMPOSE_FORWARD)
12230 && msginfo_list != NULL
12231 && action != COMPOSE_FORWARD_AS_ATTACH && g_slist_length(msginfo_list) > 1) {
12232 GSList *cur = msginfo_list;
12233 gchar *msg = g_strdup_printf(_("You are about to reply to %d "
12234 "messages. Opening the windows "
12235 "could take some time. Do you "
12236 "want to continue?"),
12237 g_slist_length(msginfo_list));
12238 if (g_slist_length(msginfo_list) > 9
12239 && alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, GTK_STOCK_YES, NULL,
12240 ALERTFOCUS_SECOND) != G_ALERTALTERNATE) {
12245 /* We'll open multiple compose windows */
12246 /* let the WM place the next windows */
12247 compose_force_window_origin = FALSE;
12248 for (; cur; cur = cur->next) {
12250 tmplist.data = cur->data;
12251 tmplist.next = NULL;
12252 compose_reply_from_messageview_real(msgview, &tmplist, action, TRUE);
12254 compose_force_window_origin = TRUE;
12256 /* forwarding multiple mails as attachments is done via a
12257 * single compose window */
12258 if (msginfo_list != NULL) {
12259 compose_reply_from_messageview_real(msgview, msginfo_list, action, FALSE);
12260 } else if (msgview != NULL) {
12262 tmplist.data = msgview->msginfo;
12263 tmplist.next = NULL;
12264 compose_reply_from_messageview_real(msgview, &tmplist, action, FALSE);
12266 debug_print("Nothing to reply to\n");
12271 void compose_check_for_email_account(Compose *compose)
12273 PrefsAccount *ac = NULL, *curr = NULL;
12279 if (compose->account && compose->account->protocol == A_NNTP) {
12280 ac = account_get_cur_account();
12281 if (ac->protocol == A_NNTP) {
12282 list = account_get_list();
12284 for( ; list != NULL ; list = g_list_next(list)) {
12285 curr = (PrefsAccount *) list->data;
12286 if (curr->protocol != A_NNTP) {
12292 combobox_select_by_data(GTK_COMBO_BOX(compose->account_combo),
12297 void compose_reply_to_address(MessageView *msgview, MsgInfo *msginfo,
12298 const gchar *address)
12300 GSList *msginfo_list = NULL;
12301 gchar *body = messageview_get_selection(msgview);
12304 msginfo_list = g_slist_prepend(msginfo_list, msginfo);
12306 compose = compose_reply_mode(COMPOSE_REPLY_TO_ADDRESS, msginfo_list, body);
12307 compose_check_for_email_account(compose);
12308 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
12309 compose_entry_append(compose, address, COMPOSE_TO, PREF_NONE);
12310 compose_reply_set_subject(compose, msginfo);
12313 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
12316 void compose_set_position(Compose *compose, gint pos)
12318 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
12320 gtkut_text_view_set_position(text, pos);
12323 gboolean compose_search_string(Compose *compose,
12324 const gchar *str, gboolean case_sens)
12326 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
12328 return gtkut_text_view_search_string(text, str, case_sens);
12331 gboolean compose_search_string_backward(Compose *compose,
12332 const gchar *str, gboolean case_sens)
12334 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
12336 return gtkut_text_view_search_string_backward(text, str, case_sens);
12339 /* allocate a msginfo structure and populate its data from a compose data structure */
12340 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose)
12342 MsgInfo *newmsginfo;
12344 gchar date[RFC822_DATE_BUFFSIZE];
12346 cm_return_val_if_fail( compose != NULL, NULL );
12348 newmsginfo = procmsg_msginfo_new();
12351 get_rfc822_date(date, sizeof(date));
12352 newmsginfo->date = g_strdup(date);
12355 if (compose->from_name) {
12356 newmsginfo->from = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
12357 newmsginfo->fromname = procheader_get_fromname(newmsginfo->from);
12361 if (compose->subject_entry)
12362 newmsginfo->subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
12364 /* to, cc, reply-to, newsgroups */
12365 for (list = compose->header_list; list; list = list->next) {
12366 gchar *header = gtk_editable_get_chars(
12368 gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
12369 gchar *entry = gtk_editable_get_chars(
12370 GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
12372 if ( strcasecmp(header, prefs_common_translated_header_name("To:")) == 0 ) {
12373 if ( newmsginfo->to == NULL ) {
12374 newmsginfo->to = g_strdup(entry);
12375 } else if (entry && *entry) {
12376 gchar *tmp = g_strconcat(newmsginfo->to, ", ", entry, NULL);
12377 g_free(newmsginfo->to);
12378 newmsginfo->to = tmp;
12381 if ( strcasecmp(header, prefs_common_translated_header_name("Cc:")) == 0 ) {
12382 if ( newmsginfo->cc == NULL ) {
12383 newmsginfo->cc = g_strdup(entry);
12384 } else if (entry && *entry) {
12385 gchar *tmp = g_strconcat(newmsginfo->cc, ", ", entry, NULL);
12386 g_free(newmsginfo->cc);
12387 newmsginfo->cc = tmp;
12390 if ( strcasecmp(header,
12391 prefs_common_translated_header_name("Newsgroups:")) == 0 ) {
12392 if ( newmsginfo->newsgroups == NULL ) {
12393 newmsginfo->newsgroups = g_strdup(entry);
12394 } else if (entry && *entry) {
12395 gchar *tmp = g_strconcat(newmsginfo->newsgroups, ", ", entry, NULL);
12396 g_free(newmsginfo->newsgroups);
12397 newmsginfo->newsgroups = tmp;
12405 /* other data is unset */
12411 /* update compose's dictionaries from folder dict settings */
12412 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
12413 FolderItem *folder_item)
12415 cm_return_if_fail(compose != NULL);
12417 if (compose->gtkaspell && folder_item && folder_item->prefs) {
12418 FolderItemPrefs *prefs = folder_item->prefs;
12420 if (prefs->enable_default_dictionary)
12421 gtkaspell_change_dict(compose->gtkaspell,
12422 prefs->default_dictionary, FALSE);
12423 if (folder_item->prefs->enable_default_alt_dictionary)
12424 gtkaspell_change_alt_dict(compose->gtkaspell,
12425 prefs->default_alt_dictionary);
12426 if (prefs->enable_default_dictionary
12427 || prefs->enable_default_alt_dictionary)
12428 compose_spell_menu_changed(compose);
12433 static void compose_subject_entry_activated(GtkWidget *widget, gpointer data)
12435 Compose *compose = (Compose *)data;
12437 cm_return_if_fail(compose != NULL);
12439 gtk_widget_grab_focus(compose->text);
12442 static void from_name_activate_cb(GtkWidget *widget, gpointer data)
12444 gtk_combo_box_popup(GTK_COMBO_BOX(data));