2 * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2016 Hiroyuki Yamamoto and the Claws Mail team
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 3 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
21 #include "claws-features.h"
26 #ifndef PANGO_ENABLE_ENGINE
27 # define PANGO_ENABLE_ENGINE
31 #include <glib/gi18n.h>
32 #include <gdk/gdkkeysyms.h>
35 #include <pango/pango-break.h>
40 #include <sys/types.h>
46 # include <sys/wait.h>
50 #ifndef G_OS_WIN32 /* fixme we should have a configure test. */
54 #if (HAVE_WCTYPE_H && HAVE_WCHAR_H)
61 #include "mainwindow.h"
63 #ifndef USE_ALT_ADDRBOOK
64 #include "addressbook.h"
66 #include "addressbook-dbus.h"
67 #include "addressadd.h"
69 #include "folderview.h"
72 #include "stock_pixmap.h"
73 #include "send_message.h"
76 #include "customheader.h"
77 #include "prefs_common.h"
78 #include "prefs_account.h"
82 #include "procheader.h"
84 #include "statusbar.h"
86 #include "quoted-printable.h"
90 #include "gtkshruler.h"
92 #include "alertpanel.h"
93 #include "manage_window.h"
95 #include "folder_item_prefs.h"
96 #include "addr_compl.h"
97 #include "quote_fmt.h"
99 #include "foldersel.h"
102 #include "message_search.h"
103 #include "combobox.h"
107 #include "autofaces.h"
108 #include "spell_entry.h"
111 #include "password.h"
112 #include "ldapserver.h"
126 #define N_ATTACH_COLS (N_COL_COLUMNS)
130 COMPOSE_CALL_ADVANCED_ACTION_UNDEFINED = -1,
131 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE = 0,
132 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER,
133 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER,
134 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD,
135 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD,
136 COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE,
137 COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE,
138 COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE,
139 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER,
140 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER,
141 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD,
142 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD,
143 COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE,
144 COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END
145 } ComposeCallAdvancedAction;
149 PRIORITY_HIGHEST = 1,
158 COMPOSE_INSERT_SUCCESS,
159 COMPOSE_INSERT_READ_ERROR,
160 COMPOSE_INSERT_INVALID_CHARACTER,
161 COMPOSE_INSERT_NO_FILE
162 } ComposeInsertResult;
166 COMPOSE_WRITE_FOR_SEND,
167 COMPOSE_WRITE_FOR_STORE
172 COMPOSE_QUOTE_FORCED,
179 SUBJECT_FIELD_PRESENT,
184 #define B64_LINE_SIZE 57
185 #define B64_BUFFSIZE 77
187 #define MAX_REFERENCES_LEN 999
189 #define COMPOSE_DRAFT_TIMEOUT_UNSET -1
190 #define COMPOSE_DRAFT_TIMEOUT_FORBIDDEN -2
192 static GdkColor default_header_bgcolor = {
199 static GdkColor default_header_color = {
206 static GList *compose_list = NULL;
207 static GSList *extra_headers = NULL;
209 static Compose *compose_generic_new (PrefsAccount *account,
213 GList *listAddress );
215 static Compose *compose_create (PrefsAccount *account,
220 static void compose_entry_indicate (Compose *compose,
221 const gchar *address);
222 static Compose *compose_followup_and_reply_to (MsgInfo *msginfo,
223 ComposeQuoteMode quote_mode,
227 static Compose *compose_forward_multiple (PrefsAccount *account,
228 GSList *msginfo_list);
229 static Compose *compose_reply (MsgInfo *msginfo,
230 ComposeQuoteMode quote_mode,
235 static Compose *compose_reply_mode (ComposeMode mode,
236 GSList *msginfo_list,
238 static void compose_template_apply_fields(Compose *compose, Template *tmpl);
239 static void compose_update_privacy_systems_menu(Compose *compose);
241 static GtkWidget *compose_account_option_menu_create
243 static void compose_set_out_encoding (Compose *compose);
244 static void compose_set_template_menu (Compose *compose);
245 static void compose_destroy (Compose *compose);
247 static MailField compose_entries_set (Compose *compose,
249 ComposeEntryType to_type);
250 static gint compose_parse_header (Compose *compose,
252 static gint compose_parse_manual_headers (Compose *compose,
254 HeaderEntry *entries);
255 static gchar *compose_parse_references (const gchar *ref,
258 static gchar *compose_quote_fmt (Compose *compose,
264 gboolean need_unescape,
265 const gchar *err_msg);
267 static void compose_reply_set_entry (Compose *compose,
273 followup_and_reply_to);
274 static void compose_reedit_set_entry (Compose *compose,
277 static void compose_insert_sig (Compose *compose,
279 static ComposeInsertResult compose_insert_file (Compose *compose,
282 static gboolean compose_attach_append (Compose *compose,
285 const gchar *content_type,
286 const gchar *charset);
287 static void compose_attach_parts (Compose *compose,
290 static gboolean compose_beautify_paragraph (Compose *compose,
291 GtkTextIter *par_iter,
293 static void compose_wrap_all (Compose *compose);
294 static void compose_wrap_all_full (Compose *compose,
297 static void compose_set_title (Compose *compose);
298 static void compose_select_account (Compose *compose,
299 PrefsAccount *account,
302 static PrefsAccount *compose_current_mail_account(void);
303 /* static gint compose_send (Compose *compose); */
304 static gboolean compose_check_for_valid_recipient
306 static gboolean compose_check_entries (Compose *compose,
307 gboolean check_everything);
308 static gint compose_write_to_file (Compose *compose,
311 gboolean attach_parts);
312 static gint compose_write_body_to_file (Compose *compose,
314 static gint compose_remove_reedit_target (Compose *compose,
316 static void compose_remove_draft (Compose *compose);
317 static gint compose_queue_sub (Compose *compose,
321 gboolean perform_checks,
322 gboolean remove_reedit_target);
323 static int compose_add_attachments (Compose *compose,
325 static gchar *compose_get_header (Compose *compose);
326 static gchar *compose_get_manual_headers_info (Compose *compose);
328 static void compose_convert_header (Compose *compose,
333 gboolean addr_field);
335 static void compose_attach_info_free (AttachInfo *ainfo);
336 static void compose_attach_remove_selected (GtkAction *action,
339 static void compose_template_apply (Compose *compose,
342 static void compose_attach_property (GtkAction *action,
344 static void compose_attach_property_create (gboolean *cancelled);
345 static void attach_property_ok (GtkWidget *widget,
346 gboolean *cancelled);
347 static void attach_property_cancel (GtkWidget *widget,
348 gboolean *cancelled);
349 static gint attach_property_delete_event (GtkWidget *widget,
351 gboolean *cancelled);
352 static gboolean attach_property_key_pressed (GtkWidget *widget,
354 gboolean *cancelled);
356 static void compose_exec_ext_editor (Compose *compose);
358 static gint compose_exec_ext_editor_real (const gchar *file,
359 GdkNativeWindow socket_wid);
360 static gboolean compose_ext_editor_kill (Compose *compose);
361 static gboolean compose_input_cb (GIOChannel *source,
362 GIOCondition condition,
364 static void compose_set_ext_editor_sensitive (Compose *compose,
366 static gboolean compose_get_ext_editor_cmd_valid();
367 static gboolean compose_get_ext_editor_uses_socket();
368 static gboolean compose_ext_editor_plug_removed_cb
371 #endif /* G_OS_UNIX */
373 static void compose_undo_state_changed (UndoMain *undostruct,
378 static void compose_create_header_entry (Compose *compose);
379 static void compose_add_header_entry (Compose *compose, const gchar *header,
380 gchar *text, ComposePrefType pref_type);
381 static void compose_remove_header_entries(Compose *compose);
383 static void compose_update_priority_menu_item(Compose * compose);
385 static void compose_spell_menu_changed (void *data);
386 static void compose_dict_changed (void *data);
388 static void compose_add_field_list ( Compose *compose,
389 GList *listAddress );
391 /* callback functions */
393 static void compose_notebook_size_alloc (GtkNotebook *notebook,
394 GtkAllocation *allocation,
396 static gboolean compose_edit_size_alloc (GtkEditable *widget,
397 GtkAllocation *allocation,
398 GtkSHRuler *shruler);
399 static void account_activated (GtkComboBox *optmenu,
401 static void attach_selected (GtkTreeView *tree_view,
402 GtkTreePath *tree_path,
403 GtkTreeViewColumn *column,
405 static gboolean attach_button_pressed (GtkWidget *widget,
406 GdkEventButton *event,
408 static gboolean attach_key_pressed (GtkWidget *widget,
411 static void compose_send_cb (GtkAction *action, gpointer data);
412 static void compose_send_later_cb (GtkAction *action, gpointer data);
414 static void compose_save_cb (GtkAction *action,
417 static void compose_attach_cb (GtkAction *action,
419 static void compose_insert_file_cb (GtkAction *action,
421 static void compose_insert_sig_cb (GtkAction *action,
423 static void compose_replace_sig_cb (GtkAction *action,
426 static void compose_close_cb (GtkAction *action,
428 static void compose_print_cb (GtkAction *action,
431 static void compose_set_encoding_cb (GtkAction *action, GtkRadioAction *current, gpointer data);
433 static void compose_address_cb (GtkAction *action,
435 static void about_show_cb (GtkAction *action,
437 static void compose_template_activate_cb(GtkWidget *widget,
440 static void compose_ext_editor_cb (GtkAction *action,
443 static gint compose_delete_cb (GtkWidget *widget,
447 static void compose_undo_cb (GtkAction *action,
449 static void compose_redo_cb (GtkAction *action,
451 static void compose_cut_cb (GtkAction *action,
453 static void compose_copy_cb (GtkAction *action,
455 static void compose_paste_cb (GtkAction *action,
457 static void compose_paste_as_quote_cb (GtkAction *action,
459 static void compose_paste_no_wrap_cb (GtkAction *action,
461 static void compose_paste_wrap_cb (GtkAction *action,
463 static void compose_allsel_cb (GtkAction *action,
466 static void compose_advanced_action_cb (GtkAction *action,
469 static void compose_grab_focus_cb (GtkWidget *widget,
472 static void compose_changed_cb (GtkTextBuffer *textbuf,
475 static void compose_wrap_cb (GtkAction *action,
477 static void compose_wrap_all_cb (GtkAction *action,
479 static void compose_find_cb (GtkAction *action,
481 static void compose_toggle_autowrap_cb (GtkToggleAction *action,
483 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
486 static void compose_toggle_ruler_cb (GtkToggleAction *action,
488 static void compose_toggle_sign_cb (GtkToggleAction *action,
490 static void compose_toggle_encrypt_cb (GtkToggleAction *action,
492 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data);
493 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn);
494 static void activate_privacy_system (Compose *compose,
495 PrefsAccount *account,
497 static void compose_toggle_return_receipt_cb(GtkToggleAction *action,
499 static void compose_toggle_remove_refs_cb(GtkToggleAction *action,
501 static void compose_set_priority_cb (GtkAction *action, GtkRadioAction *current, gpointer data);
502 static void compose_reply_change_mode (Compose *compose, ComposeMode action);
503 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data);
505 static void compose_attach_drag_received_cb (GtkWidget *widget,
506 GdkDragContext *drag_context,
509 GtkSelectionData *data,
513 static void compose_insert_drag_received_cb (GtkWidget *widget,
514 GdkDragContext *drag_context,
517 GtkSelectionData *data,
521 static void compose_header_drag_received_cb (GtkWidget *widget,
522 GdkDragContext *drag_context,
525 GtkSelectionData *data,
530 static gboolean compose_drag_drop (GtkWidget *widget,
531 GdkDragContext *drag_context,
533 guint time, gpointer user_data);
534 static gboolean completion_set_focus_to_subject
539 static void text_inserted (GtkTextBuffer *buffer,
544 static Compose *compose_generic_reply(MsgInfo *msginfo,
545 ComposeQuoteMode quote_mode,
549 gboolean followup_and_reply_to,
552 static void compose_headerentry_changed_cb (GtkWidget *entry,
553 ComposeHeaderEntry *headerentry);
554 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
556 ComposeHeaderEntry *headerentry);
557 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
558 ComposeHeaderEntry *headerentry);
560 static void compose_show_first_last_header (Compose *compose, gboolean show_first);
562 static void compose_allow_user_actions (Compose *compose, gboolean allow);
564 static void compose_nothing_cb (GtkAction *action, gpointer data)
570 static void compose_check_all (GtkAction *action, gpointer data);
571 static void compose_highlight_all (GtkAction *action, gpointer data);
572 static void compose_check_backwards (GtkAction *action, gpointer data);
573 static void compose_check_forwards_go (GtkAction *action, gpointer data);
576 static PrefsAccount *compose_find_account (MsgInfo *msginfo);
578 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose);
581 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
582 FolderItem *folder_item);
584 static void compose_attach_update_label(Compose *compose);
585 static void compose_set_folder_prefs(Compose *compose, FolderItem *folder,
586 gboolean respect_default_to);
587 static void compose_subject_entry_activated(GtkWidget *widget, gpointer data);
588 static void from_name_activate_cb(GtkWidget *widget, gpointer data);
590 static GtkActionEntry compose_popup_entries[] =
592 {"Compose", NULL, "Compose", NULL, NULL, NULL },
593 {"Compose/Add", NULL, N_("_Add..."), NULL, NULL, G_CALLBACK(compose_attach_cb) },
594 {"Compose/Remove", NULL, N_("_Remove"), NULL, NULL, G_CALLBACK(compose_attach_remove_selected) },
595 {"Compose/---", NULL, "---", NULL, NULL, NULL },
596 {"Compose/Properties", NULL, N_("_Properties..."), NULL, NULL, G_CALLBACK(compose_attach_property) },
599 static GtkActionEntry compose_entries[] =
601 {"Menu", NULL, "Menu", NULL, NULL, NULL },
603 {"Message", NULL, N_("_Message"), NULL, NULL, NULL },
604 {"Edit", NULL, N_("_Edit"), NULL, NULL, NULL },
606 {"Spelling", NULL, N_("_Spelling"), NULL, NULL, NULL },
608 {"Options", NULL, N_("_Options"), NULL, NULL, NULL },
609 {"Tools", NULL, N_("_Tools"), NULL, NULL, NULL },
610 {"Help", NULL, N_("_Help"), NULL, NULL, NULL },
612 {"Message/Send", NULL, N_("S_end"), "<control>Return", NULL, G_CALLBACK(compose_send_cb) },
613 {"Message/SendLater", NULL, N_("Send _later"), "<shift><control>S", NULL, G_CALLBACK(compose_send_later_cb) },
614 {"Message/---", NULL, "---", NULL, NULL, NULL },
616 {"Message/AttachFile", NULL, N_("_Attach file"), "<control>M", NULL, G_CALLBACK(compose_attach_cb) },
617 {"Message/InsertFile", NULL, N_("_Insert file"), "<control>I", NULL, G_CALLBACK(compose_insert_file_cb) },
618 {"Message/InsertSig", NULL, N_("Insert si_gnature"), "<control>G", NULL, G_CALLBACK(compose_insert_sig_cb) },
619 {"Message/ReplaceSig", NULL, N_("_Replace signature"), NULL, NULL, G_CALLBACK(compose_replace_sig_cb) },
620 /* {"Message/---", NULL, "---", NULL, NULL, NULL }, */
621 {"Message/Save", NULL, N_("_Save"), "<control>S", NULL, G_CALLBACK(compose_save_cb) }, /*COMPOSE_KEEP_EDITING*/
622 /* {"Message/---", NULL, "---", NULL, NULL, NULL }, */
623 {"Message/Print", NULL, N_("_Print"), NULL, NULL, G_CALLBACK(compose_print_cb) },
624 /* {"Message/---", NULL, "---", NULL, NULL, NULL }, */
625 {"Message/Close", NULL, N_("_Close"), "<control>W", NULL, G_CALLBACK(compose_close_cb) },
628 {"Edit/Undo", NULL, N_("_Undo"), "<control>Z", NULL, G_CALLBACK(compose_undo_cb) },
629 {"Edit/Redo", NULL, N_("_Redo"), "<control>Y", NULL, G_CALLBACK(compose_redo_cb) },
630 {"Edit/---", NULL, "---", NULL, NULL, NULL },
632 {"Edit/Cut", NULL, N_("Cu_t"), "<control>X", NULL, G_CALLBACK(compose_cut_cb) },
633 {"Edit/Copy", NULL, N_("_Copy"), "<control>C", NULL, G_CALLBACK(compose_copy_cb) },
634 {"Edit/Paste", NULL, N_("_Paste"), "<control>V", NULL, G_CALLBACK(compose_paste_cb) },
636 {"Edit/SpecialPaste", NULL, N_("_Special paste"), NULL, NULL, NULL },
637 {"Edit/SpecialPaste/AsQuotation", NULL, N_("As _quotation"), NULL, NULL, G_CALLBACK(compose_paste_as_quote_cb) },
638 {"Edit/SpecialPaste/Wrapped", NULL, N_("_Wrapped"), NULL, NULL, G_CALLBACK(compose_paste_wrap_cb) },
639 {"Edit/SpecialPaste/Unwrapped", NULL, N_("_Unwrapped"), NULL, NULL, G_CALLBACK(compose_paste_no_wrap_cb) },
641 {"Edit/SelectAll", NULL, N_("Select _all"), "<control>A", NULL, G_CALLBACK(compose_allsel_cb) },
643 {"Edit/Advanced", NULL, N_("A_dvanced"), NULL, NULL, NULL },
644 {"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*/
645 {"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*/
646 {"Edit/Advanced/BackWord", NULL, N_("Move a word backward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD*/
647 {"Edit/Advanced/ForwWord", NULL, N_("Move a word forward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD*/
648 {"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*/
649 {"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*/
650 {"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*/
651 {"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*/
652 {"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*/
653 {"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*/
654 {"Edit/Advanced/DelBackWord", NULL, N_("Delete a word backward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD*/
655 {"Edit/Advanced/DelForwWord", NULL, N_("Delete a word forward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD*/
656 {"Edit/Advanced/DelLine", NULL, N_("Delete line"), "<control>U", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE*/
657 {"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*/
659 /* {"Edit/---", NULL, "---", NULL, NULL, NULL }, */
660 {"Edit/Find", NULL, N_("_Find"), "<control>F", NULL, G_CALLBACK(compose_find_cb) },
662 /* {"Edit/---", NULL, "---", NULL, NULL, NULL }, */
663 {"Edit/WrapPara", NULL, N_("_Wrap current paragraph"), "<control>L", NULL, G_CALLBACK(compose_wrap_cb) }, /* 0 */
664 {"Edit/WrapAllLines", NULL, N_("Wrap all long _lines"), "<control><alt>L", NULL, G_CALLBACK(compose_wrap_all_cb) }, /* 1 */
665 /* {"Edit/---", NULL, "---", NULL, NULL, NULL }, */
666 {"Edit/ExtEditor", NULL, N_("Edit with e_xternal editor"), "<shift><control>X", NULL, G_CALLBACK(compose_ext_editor_cb) },
669 {"Spelling/CheckAllSel", NULL, N_("_Check all or check selection"), NULL, NULL, G_CALLBACK(compose_check_all) },
670 {"Spelling/HighlightAll", NULL, N_("_Highlight all misspelled words"), NULL, NULL, G_CALLBACK(compose_highlight_all) },
671 {"Spelling/CheckBackwards", NULL, N_("Check _backwards misspelled word"), NULL, NULL, G_CALLBACK(compose_check_backwards) },
672 {"Spelling/ForwardNext", NULL, N_("_Forward to next misspelled word"), NULL, NULL, G_CALLBACK(compose_check_forwards_go) },
674 {"Spelling/---", NULL, "---", NULL, NULL, NULL },
675 {"Spelling/Options", NULL, N_("_Options"), NULL, NULL, NULL },
679 {"Options/ReplyMode", NULL, N_("Reply _mode"), NULL, NULL, NULL },
680 {"Options/---", NULL, "---", NULL, NULL, NULL },
681 {"Options/PrivacySystem", NULL, N_("Privacy _System"), NULL, NULL, NULL },
682 {"Options/PrivacySystem/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
684 /* {"Options/---", NULL, "---", NULL, NULL, NULL }, */
685 {"Options/Priority", NULL, N_("_Priority"), NULL, NULL, NULL },
687 {"Options/Encoding", NULL, N_("Character _encoding"), NULL, NULL, NULL },
688 {"Options/Encoding/---", NULL, "---", NULL, NULL, NULL },
689 #define ENC_ACTION(cs_char,c_char,string) \
690 {"Options/Encoding/" cs_char, NULL, N_(string), NULL, NULL, c_char }
692 {"Options/Encoding/Western", NULL, N_("Western European"), NULL, NULL, NULL },
693 {"Options/Encoding/Baltic", NULL, N_("Baltic"), NULL, NULL, NULL },
694 {"Options/Encoding/Hebrew", NULL, N_("Hebrew"), NULL, NULL, NULL },
695 {"Options/Encoding/Arabic", NULL, N_("Arabic"), NULL, NULL, NULL },
696 {"Options/Encoding/Cyrillic", NULL, N_("Cyrillic"), NULL, NULL, NULL },
697 {"Options/Encoding/Japanese", NULL, N_("Japanese"), NULL, NULL, NULL },
698 {"Options/Encoding/Chinese", NULL, N_("Chinese"), NULL, NULL, NULL },
699 {"Options/Encoding/Korean", NULL, N_("Korean"), NULL, NULL, NULL },
700 {"Options/Encoding/Thai", NULL, N_("Thai"), NULL, NULL, NULL },
703 {"Tools/AddressBook", NULL, N_("_Address book"), NULL, NULL, G_CALLBACK(compose_address_cb) },
705 {"Tools/Template", NULL, N_("_Template"), NULL, NULL, NULL },
706 {"Tools/Template/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
707 {"Tools/Actions", NULL, N_("Actio_ns"), NULL, NULL, NULL },
708 {"Tools/Actions/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
711 {"Help/About", NULL, N_("_About"), NULL, NULL, G_CALLBACK(about_show_cb) },
714 static GtkToggleActionEntry compose_toggle_entries[] =
716 {"Edit/AutoWrap", NULL, N_("Aut_o wrapping"), "<shift><control>L", NULL, G_CALLBACK(compose_toggle_autowrap_cb), FALSE }, /* Toggle */
717 {"Edit/AutoIndent", NULL, N_("Auto _indent"), NULL, NULL, G_CALLBACK(compose_toggle_autoindent_cb), FALSE }, /* Toggle */
718 {"Options/Sign", NULL, N_("Si_gn"), NULL, NULL, G_CALLBACK(compose_toggle_sign_cb), FALSE }, /* Toggle */
719 {"Options/Encrypt", NULL, N_("_Encrypt"), NULL, NULL, G_CALLBACK(compose_toggle_encrypt_cb), FALSE }, /* Toggle */
720 {"Options/RequestRetRcpt", NULL, N_("_Request Return Receipt"), NULL, NULL, G_CALLBACK(compose_toggle_return_receipt_cb), FALSE }, /* Toggle */
721 {"Options/RemoveReferences", NULL, N_("Remo_ve references"), NULL, NULL, G_CALLBACK(compose_toggle_remove_refs_cb), FALSE }, /* Toggle */
722 {"Tools/ShowRuler", NULL, N_("Show _ruler"), NULL, NULL, G_CALLBACK(compose_toggle_ruler_cb), FALSE }, /* Toggle */
725 static GtkRadioActionEntry compose_radio_rm_entries[] =
727 {"Options/ReplyMode/Normal", NULL, N_("_Normal"), NULL, NULL, COMPOSE_REPLY }, /* RADIO compose_reply_change_mode_cb */
728 {"Options/ReplyMode/All", NULL, N_("_All"), NULL, NULL, COMPOSE_REPLY_TO_ALL }, /* RADIO compose_reply_change_mode_cb */
729 {"Options/ReplyMode/Sender", NULL, N_("_Sender"), NULL, NULL, COMPOSE_REPLY_TO_SENDER }, /* RADIO compose_reply_change_mode_cb */
730 {"Options/ReplyMode/List", NULL, N_("_Mailing-list"), NULL, NULL, COMPOSE_REPLY_TO_LIST }, /* RADIO compose_reply_change_mode_cb */
733 static GtkRadioActionEntry compose_radio_prio_entries[] =
735 {"Options/Priority/Highest", NULL, N_("_Highest"), NULL, NULL, PRIORITY_HIGHEST }, /* RADIO compose_set_priority_cb */
736 {"Options/Priority/High", NULL, N_("Hi_gh"), NULL, NULL, PRIORITY_HIGH }, /* RADIO compose_set_priority_cb */
737 {"Options/Priority/Normal", NULL, N_("_Normal"), NULL, NULL, PRIORITY_NORMAL }, /* RADIO compose_set_priority_cb */
738 {"Options/Priority/Low", NULL, N_("Lo_w"), NULL, NULL, PRIORITY_LOW }, /* RADIO compose_set_priority_cb */
739 {"Options/Priority/Lowest", NULL, N_("_Lowest"), NULL, NULL, PRIORITY_LOWEST }, /* RADIO compose_set_priority_cb */
742 static GtkRadioActionEntry compose_radio_enc_entries[] =
744 ENC_ACTION(CS_AUTO, C_AUTO, N_("_Automatic")), /* RADIO compose_set_encoding_cb */
745 ENC_ACTION(CS_US_ASCII, C_US_ASCII, N_("7bit ASCII (US-ASC_II)")), /* RADIO compose_set_encoding_cb */
746 ENC_ACTION(CS_UTF_8, C_UTF_8, N_("Unicode (_UTF-8)")), /* RADIO compose_set_encoding_cb */
747 ENC_ACTION("Western/"CS_ISO_8859_1, C_ISO_8859_1, "ISO-8859-_1"), /* RADIO compose_set_encoding_cb */
748 ENC_ACTION("Western/"CS_ISO_8859_15, C_ISO_8859_15, "ISO-8859-15"), /* RADIO compose_set_encoding_cb */
749 ENC_ACTION("Western/"CS_WINDOWS_1252, C_WINDOWS_1252, "Windows-1252"), /* RADIO compose_set_encoding_cb */
750 ENC_ACTION(CS_ISO_8859_2, C_ISO_8859_2, N_("Central European (ISO-8859-_2)")), /* RADIO compose_set_encoding_cb */
751 ENC_ACTION("Baltic/"CS_ISO_8859_13, C_ISO_8859_13, "ISO-8859-13"), /* RADIO compose_set_encoding_cb */
752 ENC_ACTION("Baltic/"CS_ISO_8859_4, C_ISO_8859_14, "ISO-8859-_4"), /* RADIO compose_set_encoding_cb */
753 ENC_ACTION(CS_ISO_8859_7, C_ISO_8859_7, N_("Greek (ISO-8859-_7)")), /* RADIO compose_set_encoding_cb */
754 ENC_ACTION("Hebrew/"CS_ISO_8859_8, C_ISO_8859_8, "ISO-8859-_8"), /* RADIO compose_set_encoding_cb */
755 ENC_ACTION("Hebrew/"CS_WINDOWS_1255, C_WINDOWS_1255, "Windows-1255"), /* RADIO compose_set_encoding_cb */
756 ENC_ACTION("Arabic/"CS_ISO_8859_6, C_ISO_8859_6, "ISO-8859-_6"), /* RADIO compose_set_encoding_cb */
757 ENC_ACTION("Arabic/"CS_WINDOWS_1256, C_WINDOWS_1256, "Windows-1256"), /* RADIO compose_set_encoding_cb */
758 ENC_ACTION(CS_ISO_8859_9, C_ISO_8859_9, N_("Turkish (ISO-8859-_9)")), /* RADIO compose_set_encoding_cb */
759 ENC_ACTION("Cyrillic/"CS_ISO_8859_5, C_ISO_8859_5, "ISO-8859-_5"), /* RADIO compose_set_encoding_cb */
760 ENC_ACTION("Cyrillic/"CS_KOI8_R, C_KOI8_R, "KOI8-_R"), /* RADIO compose_set_encoding_cb */
761 ENC_ACTION("Cyrillic/"CS_MACCYR, C_MACCYR, "_Mac-Cyrillic"), /* RADIO compose_set_encoding_cb */
762 ENC_ACTION("Cyrillic/"CS_KOI8_U, C_KOI8_U, "KOI8-_U"), /* RADIO compose_set_encoding_cb */
763 ENC_ACTION("Cyrillic/"CS_WINDOWS_1251, C_WINDOWS_1251, "Windows-1251"), /* RADIO compose_set_encoding_cb */
764 ENC_ACTION("Japanese/"CS_ISO_2022_JP, C_ISO_2022_JP, "ISO-2022-_JP"), /* RADIO compose_set_encoding_cb */
765 ENC_ACTION("Japanese/"CS_ISO_2022_JP_2, C_ISO_2022_JP_2, "ISO-2022-JP-_2"), /* RADIO compose_set_encoding_cb */
766 ENC_ACTION("Japanese/"CS_EUC_JP, C_EUC_JP, "_EUC-JP"), /* RADIO compose_set_encoding_cb */
767 ENC_ACTION("Japanese/"CS_SHIFT_JIS, C_SHIFT_JIS, "_Shift-JIS"), /* RADIO compose_set_encoding_cb */
768 ENC_ACTION("Chinese/"CS_GB18030, C_GB18030, "_GB18030"), /* RADIO compose_set_encoding_cb */
769 ENC_ACTION("Chinese/"CS_GB2312, C_GB2312, "_GB2312"), /* RADIO compose_set_encoding_cb */
770 ENC_ACTION("Chinese/"CS_GBK, C_GBK, "GB_K"), /* RADIO compose_set_encoding_cb */
771 ENC_ACTION("Chinese/"CS_BIG5, C_BIG5, "_Big5-JP"), /* RADIO compose_set_encoding_cb */
772 ENC_ACTION("Chinese/"CS_EUC_TW, C_EUC_TW, "EUC-_TW"), /* RADIO compose_set_encoding_cb */
773 ENC_ACTION("Korean/"CS_EUC_KR, C_EUC_KR, "_EUC-KR"), /* RADIO compose_set_encoding_cb */
774 ENC_ACTION("Korean/"CS_ISO_2022_KR, C_ISO_2022_KR, "_ISO-2022-KR"), /* RADIO compose_set_encoding_cb */
775 ENC_ACTION("Thai/"CS_TIS_620, C_TIS_620, "_TIS-620-KR"), /* RADIO compose_set_encoding_cb */
776 ENC_ACTION("Thai/"CS_WINDOWS_874, C_WINDOWS_874, "_Windows-874"), /* RADIO compose_set_encoding_cb */
779 static GtkTargetEntry compose_mime_types[] =
781 {"text/uri-list", 0, 0},
782 {"UTF8_STRING", 0, 0},
786 static gboolean compose_put_existing_to_front(MsgInfo *info)
788 const GList *compose_list = compose_get_compose_list();
789 const GList *elem = NULL;
792 for (elem = compose_list; elem != NULL && elem->data != NULL;
794 Compose *c = (Compose*)elem->data;
796 if (!c->targetinfo || !c->targetinfo->msgid ||
800 if (!strcmp(c->targetinfo->msgid, info->msgid)) {
801 gtkut_window_popup(c->window);
809 static GdkColor quote_color1 =
810 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
811 static GdkColor quote_color2 =
812 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
813 static GdkColor quote_color3 =
814 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
816 static GdkColor quote_bgcolor1 =
817 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
818 static GdkColor quote_bgcolor2 =
819 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
820 static GdkColor quote_bgcolor3 =
821 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
823 static GdkColor signature_color = {
830 static GdkColor uri_color = {
837 static void compose_create_tags(GtkTextView *text, Compose *compose)
839 GtkTextBuffer *buffer;
840 GdkColor black = {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
841 #if !GTK_CHECK_VERSION(2, 24, 0)
848 buffer = gtk_text_view_get_buffer(text);
850 if (prefs_common.enable_color) {
851 /* grab the quote colors, converting from an int to a GdkColor */
852 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_col,
854 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_col,
856 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_col,
858 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_bgcol,
860 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_bgcol,
862 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_bgcol,
864 gtkut_convert_int_to_gdk_color(prefs_common.signature_col,
866 gtkut_convert_int_to_gdk_color(prefs_common.uri_col,
869 signature_color = quote_color1 = quote_color2 = quote_color3 =
870 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 = uri_color = black;
873 if (prefs_common.enable_color && prefs_common.enable_bgcolor) {
874 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
875 "foreground-gdk", "e_color1,
876 "paragraph-background-gdk", "e_bgcolor1,
878 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
879 "foreground-gdk", "e_color2,
880 "paragraph-background-gdk", "e_bgcolor2,
882 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
883 "foreground-gdk", "e_color3,
884 "paragraph-background-gdk", "e_bgcolor3,
887 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
888 "foreground-gdk", "e_color1,
890 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
891 "foreground-gdk", "e_color2,
893 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
894 "foreground-gdk", "e_color3,
898 compose->signature_tag = gtk_text_buffer_create_tag(buffer, "signature",
899 "foreground-gdk", &signature_color,
902 compose->uri_tag = gtk_text_buffer_create_tag(buffer, "link",
903 "foreground-gdk", &uri_color,
905 compose->no_wrap_tag = gtk_text_buffer_create_tag(buffer, "no_wrap", NULL);
906 compose->no_join_tag = gtk_text_buffer_create_tag(buffer, "no_join", NULL);
908 #if !GTK_CHECK_VERSION(2, 24, 0)
909 color[0] = quote_color1;
910 color[1] = quote_color2;
911 color[2] = quote_color3;
912 color[3] = quote_bgcolor1;
913 color[4] = quote_bgcolor2;
914 color[5] = quote_bgcolor3;
915 color[6] = signature_color;
916 color[7] = uri_color;
918 cmap = gdk_drawable_get_colormap(gtk_widget_get_window(compose->window));
919 gdk_colormap_alloc_colors(cmap, color, 8, FALSE, TRUE, success);
921 for (i = 0; i < 8; i++) {
922 if (success[i] == FALSE) {
923 g_warning("Compose: color allocation failed.");
924 quote_color1 = quote_color2 = quote_color3 =
925 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 =
926 signature_color = uri_color = black;
932 Compose *compose_new(PrefsAccount *account, const gchar *mailto,
935 return compose_generic_new(account, mailto, NULL, attach_files, NULL);
938 Compose *compose_new_with_folderitem(PrefsAccount *account, FolderItem *item, const gchar *mailto)
940 return compose_generic_new(account, mailto, item, NULL, NULL);
943 Compose *compose_new_with_list( PrefsAccount *account, GList *listAddress )
945 return compose_generic_new( account, NULL, NULL, NULL, listAddress );
948 #define SCROLL_TO_CURSOR(compose) { \
949 GtkTextMark *cmark = gtk_text_buffer_get_insert( \
950 gtk_text_view_get_buffer( \
951 GTK_TEXT_VIEW(compose->text))); \
952 gtk_text_view_scroll_mark_onscreen( \
953 GTK_TEXT_VIEW(compose->text), \
957 static void compose_set_save_to(Compose *compose, const gchar *folderidentifier)
960 if (folderidentifier) {
961 #if !GTK_CHECK_VERSION(2, 24, 0)
962 combobox_unset_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo));
964 combobox_unset_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo));
966 prefs_common.compose_save_to_history = add_history(
967 prefs_common.compose_save_to_history, folderidentifier);
968 #if !GTK_CHECK_VERSION(2, 24, 0)
969 combobox_set_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo),
970 prefs_common.compose_save_to_history);
972 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo),
973 prefs_common.compose_save_to_history);
977 entry = GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(compose->savemsg_combo)));
978 if (folderidentifier)
979 gtk_entry_set_text(GTK_ENTRY(entry), folderidentifier);
981 gtk_entry_set_text(GTK_ENTRY(entry), "");
984 static gchar *compose_get_save_to(Compose *compose)
987 gchar *result = NULL;
988 entry = GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(compose->savemsg_combo)));
989 result = gtk_editable_get_chars(entry, 0, -1);
992 #if !GTK_CHECK_VERSION(2, 24, 0)
993 combobox_unset_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo));
995 combobox_unset_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo));
997 prefs_common.compose_save_to_history = add_history(
998 prefs_common.compose_save_to_history, result);
999 #if !GTK_CHECK_VERSION(2, 24, 0)
1000 combobox_set_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo),
1001 prefs_common.compose_save_to_history);
1003 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo),
1004 prefs_common.compose_save_to_history);
1010 Compose *compose_generic_new(PrefsAccount *account, const gchar *mailto, FolderItem *item,
1011 GList *attach_files, GList *listAddress )
1014 GtkTextView *textview;
1015 GtkTextBuffer *textbuf;
1017 const gchar *subject_format = NULL;
1018 const gchar *body_format = NULL;
1019 gchar *mailto_from = NULL;
1020 PrefsAccount *mailto_account = NULL;
1021 MsgInfo* dummyinfo = NULL;
1022 gint cursor_pos = -1;
1023 MailField mfield = NO_FIELD_PRESENT;
1027 /* check if mailto defines a from */
1028 if (mailto && *mailto != '\0') {
1029 scan_mailto_url(mailto, &mailto_from, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
1030 /* mailto defines a from, check if we can get account prefs from it,
1031 if not, the account prefs will be guessed using other ways, but we'll keep
1034 mailto_account = account_find_from_address(mailto_from, TRUE);
1035 if (mailto_account == NULL) {
1037 Xstrdup_a(tmp_from, mailto_from, return NULL);
1038 extract_address(tmp_from);
1039 mailto_account = account_find_from_address(tmp_from, TRUE);
1043 account = mailto_account;
1046 /* if no account prefs set from mailto, set if from folder prefs (if any) */
1047 if (!mailto_account && item && item->prefs && item->prefs->enable_default_account)
1048 account = account_find_from_id(item->prefs->default_account);
1050 /* if no account prefs set, fallback to the current one */
1051 if (!account) account = cur_account;
1052 cm_return_val_if_fail(account != NULL, NULL);
1054 compose = compose_create(account, item, COMPOSE_NEW, FALSE);
1056 /* override from name if mailto asked for it */
1058 gtk_entry_set_text(GTK_ENTRY(compose->from_name), mailto_from);
1059 g_free(mailto_from);
1061 /* override from name according to folder properties */
1062 if (item && item->prefs &&
1063 item->prefs->compose_with_format &&
1064 item->prefs->compose_override_from_format &&
1065 *item->prefs->compose_override_from_format != '\0') {
1070 dummyinfo = compose_msginfo_new_from_compose(compose);
1072 /* decode \-escape sequences in the internal representation of the quote format */
1073 tmp = g_malloc(strlen(item->prefs->compose_override_from_format)+1);
1074 pref_get_unescaped_pref(tmp, item->prefs->compose_override_from_format);
1077 quote_fmt_init(dummyinfo, NULL, NULL, FALSE, compose->account, FALSE,
1078 compose->gtkaspell);
1080 quote_fmt_init(dummyinfo, NULL, NULL, FALSE, compose->account, FALSE);
1082 quote_fmt_scan_string(tmp);
1085 buf = quote_fmt_get_buffer();
1087 alertpanel_error(_("New message From format error."));
1089 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1090 quote_fmt_reset_vartable();
1091 quote_fmtlex_destroy();
1096 compose->replyinfo = NULL;
1097 compose->fwdinfo = NULL;
1099 textview = GTK_TEXT_VIEW(compose->text);
1100 textbuf = gtk_text_view_get_buffer(textview);
1101 compose_create_tags(textview, compose);
1103 undo_block(compose->undostruct);
1105 compose_set_dictionaries_from_folder_prefs(compose, item);
1108 if (account->auto_sig)
1109 compose_insert_sig(compose, FALSE);
1110 gtk_text_buffer_get_start_iter(textbuf, &iter);
1111 gtk_text_buffer_place_cursor(textbuf, &iter);
1113 if (account->protocol != A_NNTP) {
1114 if (mailto && *mailto != '\0') {
1115 mfield = compose_entries_set(compose, mailto, COMPOSE_TO);
1118 compose_set_folder_prefs(compose, item, TRUE);
1120 if (item && item->ret_rcpt) {
1121 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
1124 if (mailto && *mailto != '\0') {
1125 if (!strchr(mailto, '@'))
1126 mfield = compose_entries_set(compose, mailto, COMPOSE_NEWSGROUPS);
1128 mfield = compose_entries_set(compose, mailto, COMPOSE_TO);
1129 } else if (item && FOLDER_CLASS(item->folder) == news_get_class()) {
1130 compose_entry_append(compose, item->path, COMPOSE_NEWSGROUPS, PREF_FOLDER);
1131 mfield = TO_FIELD_PRESENT;
1134 * CLAWS: just don't allow return receipt request, even if the user
1135 * may want to send an email. simple but foolproof.
1137 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", FALSE);
1139 compose_add_field_list( compose, listAddress );
1141 if (item && item->prefs && item->prefs->compose_with_format) {
1142 subject_format = item->prefs->compose_subject_format;
1143 body_format = item->prefs->compose_body_format;
1144 } else if (account->compose_with_format) {
1145 subject_format = account->compose_subject_format;
1146 body_format = account->compose_body_format;
1147 } else if (prefs_common.compose_with_format) {
1148 subject_format = prefs_common.compose_subject_format;
1149 body_format = prefs_common.compose_body_format;
1152 if (subject_format || body_format) {
1155 && *subject_format != '\0' )
1157 gchar *subject = NULL;
1162 dummyinfo = compose_msginfo_new_from_compose(compose);
1164 /* decode \-escape sequences in the internal representation of the quote format */
1165 tmp = g_malloc(strlen(subject_format)+1);
1166 pref_get_unescaped_pref(tmp, subject_format);
1168 subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1170 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account, FALSE,
1171 compose->gtkaspell);
1173 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account, FALSE);
1175 quote_fmt_scan_string(tmp);
1178 buf = quote_fmt_get_buffer();
1180 alertpanel_error(_("New message subject format error."));
1182 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1183 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1184 quote_fmt_reset_vartable();
1185 quote_fmtlex_destroy();
1189 mfield = SUBJECT_FIELD_PRESENT;
1193 && *body_format != '\0' )
1196 GtkTextBuffer *buffer;
1197 GtkTextIter start, end;
1201 dummyinfo = compose_msginfo_new_from_compose(compose);
1203 text = GTK_TEXT_VIEW(compose->text);
1204 buffer = gtk_text_view_get_buffer(text);
1205 gtk_text_buffer_get_start_iter(buffer, &start);
1206 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
1207 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
1209 compose_quote_fmt(compose, dummyinfo,
1211 NULL, tmp, FALSE, TRUE,
1212 _("The body of the \"New message\" template has an error at line %d."));
1213 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1214 quote_fmt_reset_vartable();
1218 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1219 gtkaspell_highlight_all(compose->gtkaspell);
1221 mfield = BODY_FIELD_PRESENT;
1225 procmsg_msginfo_free( &dummyinfo );
1231 for (curr = attach_files ; curr != NULL ; curr = curr->next) {
1232 ainfo = (AttachInfo *) curr->data;
1234 compose_insert_file(compose, ainfo->file);
1236 compose_attach_append(compose, ainfo->file, ainfo->file,
1237 ainfo->content_type, ainfo->charset);
1241 compose_show_first_last_header(compose, TRUE);
1243 /* Set save folder */
1244 if (item && item->prefs && item->prefs->save_copy_to_folder) {
1245 gchar *folderidentifier;
1247 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
1248 folderidentifier = folder_item_get_identifier(item);
1249 compose_set_save_to(compose, folderidentifier);
1250 g_free(folderidentifier);
1253 /* Place cursor according to provided input (mfield) */
1255 case NO_FIELD_PRESENT:
1256 if (compose->header_last)
1257 gtk_widget_grab_focus(compose->header_last->entry);
1259 case TO_FIELD_PRESENT:
1260 buf = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1262 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1265 gtk_widget_grab_focus(compose->subject_entry);
1267 case SUBJECT_FIELD_PRESENT:
1268 textview = GTK_TEXT_VIEW(compose->text);
1271 textbuf = gtk_text_view_get_buffer(textview);
1274 mark = gtk_text_buffer_get_insert(textbuf);
1275 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
1276 gtk_text_buffer_insert(textbuf, &iter, "", -1);
1278 * SUBJECT_FIELD_PRESENT and BODY_FIELD_PRESENT
1279 * only defers where it comes to the variable body
1280 * is not null. If no body is present compose->text
1281 * will be null in which case you cannot place the
1282 * cursor inside the component so. An empty component
1283 * is therefore created before placing the cursor
1285 case BODY_FIELD_PRESENT:
1286 cursor_pos = quote_fmt_get_cursor_pos();
1287 if (cursor_pos == -1)
1288 gtk_widget_grab_focus(compose->header_last->entry);
1290 gtk_widget_grab_focus(compose->text);
1294 undo_unblock(compose->undostruct);
1296 if (prefs_common.auto_exteditor)
1297 compose_exec_ext_editor(compose);
1299 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
1301 SCROLL_TO_CURSOR(compose);
1303 compose->modified = FALSE;
1304 compose_set_title(compose);
1306 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
1311 static void compose_force_encryption(Compose *compose, PrefsAccount *account,
1312 gboolean override_pref, const gchar *system)
1314 const gchar *privacy = NULL;
1316 cm_return_if_fail(compose != NULL);
1317 cm_return_if_fail(account != NULL);
1319 if (override_pref == FALSE && account->default_encrypt_reply == FALSE)
1322 if (account->default_privacy_system && strlen(account->default_privacy_system))
1323 privacy = account->default_privacy_system;
1327 GSList *privacy_avail = privacy_get_system_ids();
1328 if (privacy_avail && g_slist_length(privacy_avail)) {
1329 privacy = (gchar *)(privacy_avail->data);
1331 g_slist_free_full(privacy_avail, g_free);
1333 if (privacy != NULL) {
1335 g_free(compose->privacy_system);
1336 compose->privacy_system = NULL;
1337 g_free(compose->encdata);
1338 compose->encdata = NULL;
1340 if (compose->privacy_system == NULL)
1341 compose->privacy_system = g_strdup(privacy);
1342 else if (*(compose->privacy_system) == '\0') {
1343 g_free(compose->privacy_system);
1344 g_free(compose->encdata);
1345 compose->encdata = NULL;
1346 compose->privacy_system = g_strdup(privacy);
1348 compose_update_privacy_system_menu_item(compose, FALSE);
1349 compose_use_encryption(compose, TRUE);
1353 static void compose_force_signing(Compose *compose, PrefsAccount *account, const gchar *system)
1355 const gchar *privacy = NULL;
1357 if (account->default_privacy_system && strlen(account->default_privacy_system))
1358 privacy = account->default_privacy_system;
1362 GSList *privacy_avail = privacy_get_system_ids();
1363 if (privacy_avail && g_slist_length(privacy_avail)) {
1364 privacy = (gchar *)(privacy_avail->data);
1368 if (privacy != NULL) {
1370 g_free(compose->privacy_system);
1371 compose->privacy_system = NULL;
1372 g_free(compose->encdata);
1373 compose->encdata = NULL;
1375 if (compose->privacy_system == NULL)
1376 compose->privacy_system = g_strdup(privacy);
1377 compose_update_privacy_system_menu_item(compose, FALSE);
1378 compose_use_signing(compose, TRUE);
1382 static Compose *compose_reply_mode(ComposeMode mode, GSList *msginfo_list, gchar *body)
1386 Compose *compose = NULL;
1388 cm_return_val_if_fail(msginfo_list != NULL, NULL);
1390 msginfo = (MsgInfo*)g_slist_nth_data(msginfo_list, 0);
1391 cm_return_val_if_fail(msginfo != NULL, NULL);
1393 list_len = g_slist_length(msginfo_list);
1397 case COMPOSE_REPLY_TO_ADDRESS:
1398 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1399 FALSE, prefs_common.default_reply_list, FALSE, body);
1401 case COMPOSE_REPLY_WITH_QUOTE:
1402 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1403 FALSE, prefs_common.default_reply_list, FALSE, body);
1405 case COMPOSE_REPLY_WITHOUT_QUOTE:
1406 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1407 FALSE, prefs_common.default_reply_list, FALSE, NULL);
1409 case COMPOSE_REPLY_TO_SENDER:
1410 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1411 FALSE, FALSE, TRUE, body);
1413 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1414 compose = compose_followup_and_reply_to(msginfo,
1415 COMPOSE_QUOTE_CHECK,
1416 FALSE, FALSE, body);
1418 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1419 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1420 FALSE, FALSE, TRUE, body);
1422 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1423 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1424 FALSE, FALSE, TRUE, NULL);
1426 case COMPOSE_REPLY_TO_ALL:
1427 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1428 TRUE, FALSE, FALSE, body);
1430 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1431 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1432 TRUE, FALSE, FALSE, body);
1434 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1435 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1436 TRUE, FALSE, FALSE, NULL);
1438 case COMPOSE_REPLY_TO_LIST:
1439 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1440 FALSE, TRUE, FALSE, body);
1442 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1443 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1444 FALSE, TRUE, FALSE, body);
1446 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1447 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1448 FALSE, TRUE, FALSE, NULL);
1450 case COMPOSE_FORWARD:
1451 if (prefs_common.forward_as_attachment) {
1452 compose = compose_reply_mode(COMPOSE_FORWARD_AS_ATTACH, msginfo_list, body);
1455 compose = compose_reply_mode(COMPOSE_FORWARD_INLINE, msginfo_list, body);
1459 case COMPOSE_FORWARD_INLINE:
1460 /* check if we reply to more than one Message */
1461 if (list_len == 1) {
1462 compose = compose_forward(NULL, msginfo, FALSE, body, FALSE, FALSE);
1465 /* more messages FALL THROUGH */
1466 case COMPOSE_FORWARD_AS_ATTACH:
1467 compose = compose_forward_multiple(NULL, msginfo_list);
1469 case COMPOSE_REDIRECT:
1470 compose = compose_redirect(NULL, msginfo, FALSE);
1473 g_warning("compose_reply_mode(): invalid Compose Mode: %d", mode);
1476 if (compose == NULL) {
1477 alertpanel_error(_("Unable to reply. The original email probably doesn't exist."));
1481 compose->rmode = mode;
1482 switch (compose->rmode) {
1484 case COMPOSE_REPLY_WITH_QUOTE:
1485 case COMPOSE_REPLY_WITHOUT_QUOTE:
1486 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1487 debug_print("reply mode Normal\n");
1488 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/Normal", TRUE);
1489 compose_reply_change_mode(compose, COMPOSE_REPLY); /* force update */
1491 case COMPOSE_REPLY_TO_SENDER:
1492 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1493 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1494 debug_print("reply mode Sender\n");
1495 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/Sender", TRUE);
1497 case COMPOSE_REPLY_TO_ALL:
1498 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1499 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1500 debug_print("reply mode All\n");
1501 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/All", TRUE);
1503 case COMPOSE_REPLY_TO_LIST:
1504 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1505 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1506 debug_print("reply mode List\n");
1507 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/List", TRUE);
1509 case COMPOSE_REPLY_TO_ADDRESS:
1510 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", FALSE);
1518 static Compose *compose_reply(MsgInfo *msginfo,
1519 ComposeQuoteMode quote_mode,
1525 return compose_generic_reply(msginfo, quote_mode, to_all, to_ml,
1526 to_sender, FALSE, body);
1529 static Compose *compose_followup_and_reply_to(MsgInfo *msginfo,
1530 ComposeQuoteMode quote_mode,
1535 return compose_generic_reply(msginfo, quote_mode, to_all, FALSE,
1536 to_sender, TRUE, body);
1539 static void compose_extract_original_charset(Compose *compose)
1541 MsgInfo *info = NULL;
1542 if (compose->replyinfo) {
1543 info = compose->replyinfo;
1544 } else if (compose->fwdinfo) {
1545 info = compose->fwdinfo;
1546 } else if (compose->targetinfo) {
1547 info = compose->targetinfo;
1550 MimeInfo *mimeinfo = procmime_scan_message_short(info);
1551 MimeInfo *partinfo = mimeinfo;
1552 while (partinfo && partinfo->type != MIMETYPE_TEXT)
1553 partinfo = procmime_mimeinfo_next(partinfo);
1555 compose->orig_charset =
1556 g_strdup(procmime_mimeinfo_get_parameter(
1557 partinfo, "charset"));
1559 procmime_mimeinfo_free_all(&mimeinfo);
1563 #define SIGNAL_BLOCK(buffer) { \
1564 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1565 G_CALLBACK(compose_changed_cb), \
1567 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1568 G_CALLBACK(text_inserted), \
1572 #define SIGNAL_UNBLOCK(buffer) { \
1573 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1574 G_CALLBACK(compose_changed_cb), \
1576 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1577 G_CALLBACK(text_inserted), \
1581 static Compose *compose_generic_reply(MsgInfo *msginfo,
1582 ComposeQuoteMode quote_mode,
1583 gboolean to_all, gboolean to_ml,
1585 gboolean followup_and_reply_to,
1589 PrefsAccount *account = NULL;
1590 GtkTextView *textview;
1591 GtkTextBuffer *textbuf;
1592 gboolean quote = FALSE;
1593 const gchar *qmark = NULL;
1594 const gchar *body_fmt = NULL;
1595 gchar *s_system = NULL;
1597 cm_return_val_if_fail(msginfo != NULL, NULL);
1598 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
1600 account = account_get_reply_account(msginfo, prefs_common.reply_account_autosel);
1602 cm_return_val_if_fail(account != NULL, NULL);
1604 compose = compose_create(account, msginfo->folder, COMPOSE_REPLY, FALSE);
1606 compose->updating = TRUE;
1608 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
1609 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", TRUE);
1611 compose->replyinfo = procmsg_msginfo_get_full_info(msginfo);
1612 if (!compose->replyinfo)
1613 compose->replyinfo = procmsg_msginfo_copy(msginfo);
1615 compose_extract_original_charset(compose);
1617 if (msginfo->folder && msginfo->folder->ret_rcpt)
1618 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
1620 /* Set save folder */
1621 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1622 gchar *folderidentifier;
1624 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1625 folderidentifier = folder_item_get_identifier(msginfo->folder);
1626 compose_set_save_to(compose, folderidentifier);
1627 g_free(folderidentifier);
1630 if (compose_parse_header(compose, msginfo) < 0) {
1631 compose->updating = FALSE;
1632 compose_destroy(compose);
1636 /* override from name according to folder properties */
1637 if (msginfo->folder && msginfo->folder->prefs &&
1638 msginfo->folder->prefs->reply_with_format &&
1639 msginfo->folder->prefs->reply_override_from_format &&
1640 *msginfo->folder->prefs->reply_override_from_format != '\0') {
1645 /* decode \-escape sequences in the internal representation of the quote format */
1646 tmp = g_malloc(strlen(msginfo->folder->prefs->reply_override_from_format)+1);
1647 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->reply_override_from_format);
1650 quote_fmt_init(compose->replyinfo, NULL, NULL, FALSE, compose->account, FALSE,
1651 compose->gtkaspell);
1653 quote_fmt_init(compose->replyinfo, NULL, NULL, FALSE, compose->account, FALSE);
1655 quote_fmt_scan_string(tmp);
1658 buf = quote_fmt_get_buffer();
1660 alertpanel_error(_("The \"From\" field of the \"Reply\" template contains an invalid email address."));
1662 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1663 quote_fmt_reset_vartable();
1664 quote_fmtlex_destroy();
1669 textview = (GTK_TEXT_VIEW(compose->text));
1670 textbuf = gtk_text_view_get_buffer(textview);
1671 compose_create_tags(textview, compose);
1673 undo_block(compose->undostruct);
1675 compose_set_dictionaries_from_folder_prefs(compose, msginfo->folder);
1676 gtkaspell_block_check(compose->gtkaspell);
1679 if (quote_mode == COMPOSE_QUOTE_FORCED ||
1680 (quote_mode == COMPOSE_QUOTE_CHECK && prefs_common.reply_with_quote)) {
1681 /* use the reply format of folder (if enabled), or the account's one
1682 (if enabled) or fallback to the global reply format, which is always
1683 enabled (even if empty), and use the relevant quotemark */
1685 if (msginfo->folder && msginfo->folder->prefs &&
1686 msginfo->folder->prefs->reply_with_format) {
1687 qmark = msginfo->folder->prefs->reply_quotemark;
1688 body_fmt = msginfo->folder->prefs->reply_body_format;
1690 } else if (account->reply_with_format) {
1691 qmark = account->reply_quotemark;
1692 body_fmt = account->reply_body_format;
1695 qmark = prefs_common.quotemark;
1696 if (prefs_common.quotefmt && *prefs_common.quotefmt)
1697 body_fmt = gettext(prefs_common.quotefmt);
1704 /* empty quotemark is not allowed */
1705 if (qmark == NULL || *qmark == '\0')
1707 compose_quote_fmt(compose, compose->replyinfo,
1708 body_fmt, qmark, body, FALSE, TRUE,
1709 _("The body of the \"Reply\" template has an error at line %d."));
1710 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1711 quote_fmt_reset_vartable();
1714 if (MSG_IS_ENCRYPTED(compose->replyinfo->flags)) {
1715 compose_force_encryption(compose, account, FALSE, s_system);
1718 privacy_msginfo_get_signed_state(compose->replyinfo, &s_system);
1719 if (MSG_IS_SIGNED(compose->replyinfo->flags) && account->default_sign_reply) {
1720 compose_force_signing(compose, account, s_system);
1724 SIGNAL_BLOCK(textbuf);
1726 if (account->auto_sig)
1727 compose_insert_sig(compose, FALSE);
1729 compose_wrap_all(compose);
1732 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1733 gtkaspell_highlight_all(compose->gtkaspell);
1734 gtkaspell_unblock_check(compose->gtkaspell);
1736 SIGNAL_UNBLOCK(textbuf);
1738 gtk_widget_grab_focus(compose->text);
1740 undo_unblock(compose->undostruct);
1742 if (prefs_common.auto_exteditor)
1743 compose_exec_ext_editor(compose);
1745 compose->modified = FALSE;
1746 compose_set_title(compose);
1748 compose->updating = FALSE;
1749 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
1750 SCROLL_TO_CURSOR(compose);
1752 if (compose->deferred_destroy) {
1753 compose_destroy(compose);
1761 #define INSERT_FW_HEADER(var, hdr) \
1762 if (msginfo->var && *msginfo->var) { \
1763 gtk_stext_insert(text, NULL, NULL, NULL, hdr, -1); \
1764 gtk_stext_insert(text, NULL, NULL, NULL, msginfo->var, -1); \
1765 gtk_stext_insert(text, NULL, NULL, NULL, "\n", 1); \
1768 Compose *compose_forward(PrefsAccount *account, MsgInfo *msginfo,
1769 gboolean as_attach, const gchar *body,
1770 gboolean no_extedit,
1774 GtkTextView *textview;
1775 GtkTextBuffer *textbuf;
1776 gint cursor_pos = -1;
1779 cm_return_val_if_fail(msginfo != NULL, NULL);
1780 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
1782 if (!account && !(account = compose_find_account(msginfo)))
1783 account = cur_account;
1785 if (!prefs_common.forward_as_attachment)
1786 mode = COMPOSE_FORWARD_INLINE;
1788 mode = COMPOSE_FORWARD;
1789 compose = compose_create(account, msginfo->folder, mode, batch);
1791 compose->updating = TRUE;
1792 compose->fwdinfo = procmsg_msginfo_get_full_info(msginfo);
1793 if (!compose->fwdinfo)
1794 compose->fwdinfo = procmsg_msginfo_copy(msginfo);
1796 compose_extract_original_charset(compose);
1798 if (msginfo->subject && *msginfo->subject) {
1799 gchar *buf, *buf2, *p;
1801 buf = p = g_strdup(msginfo->subject);
1802 p += subject_get_prefix_length(p);
1803 memmove(buf, p, strlen(p) + 1);
1805 buf2 = g_strdup_printf("Fw: %s", buf);
1806 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
1812 /* override from name according to folder properties */
1813 if (msginfo->folder && msginfo->folder->prefs &&
1814 msginfo->folder->prefs->forward_with_format &&
1815 msginfo->folder->prefs->forward_override_from_format &&
1816 *msginfo->folder->prefs->forward_override_from_format != '\0') {
1820 MsgInfo *full_msginfo = NULL;
1823 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1825 full_msginfo = procmsg_msginfo_copy(msginfo);
1827 /* decode \-escape sequences in the internal representation of the quote format */
1828 tmp = g_malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
1829 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
1832 gtkaspell_block_check(compose->gtkaspell);
1833 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE,
1834 compose->gtkaspell);
1836 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE);
1838 quote_fmt_scan_string(tmp);
1841 buf = quote_fmt_get_buffer();
1843 alertpanel_error(_("The \"From\" field of the \"Forward\" template contains an invalid email address."));
1845 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1846 quote_fmt_reset_vartable();
1847 quote_fmtlex_destroy();
1850 procmsg_msginfo_free(&full_msginfo);
1853 textview = GTK_TEXT_VIEW(compose->text);
1854 textbuf = gtk_text_view_get_buffer(textview);
1855 compose_create_tags(textview, compose);
1857 undo_block(compose->undostruct);
1861 msgfile = procmsg_get_message_file(msginfo);
1862 if (!is_file_exist(msgfile))
1863 g_warning("%s: file does not exist", msgfile);
1865 compose_attach_append(compose, msgfile, msgfile,
1866 "message/rfc822", NULL);
1870 const gchar *qmark = NULL;
1871 const gchar *body_fmt = NULL;
1872 MsgInfo *full_msginfo;
1874 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1876 full_msginfo = procmsg_msginfo_copy(msginfo);
1878 /* use the forward format of folder (if enabled), or the account's one
1879 (if enabled) or fallback to the global forward format, which is always
1880 enabled (even if empty), and use the relevant quotemark */
1881 if (msginfo->folder && msginfo->folder->prefs &&
1882 msginfo->folder->prefs->forward_with_format) {
1883 qmark = msginfo->folder->prefs->forward_quotemark;
1884 body_fmt = msginfo->folder->prefs->forward_body_format;
1886 } else if (account->forward_with_format) {
1887 qmark = account->forward_quotemark;
1888 body_fmt = account->forward_body_format;
1891 qmark = prefs_common.fw_quotemark;
1892 if (prefs_common.fw_quotefmt && *prefs_common.fw_quotefmt)
1893 body_fmt = gettext(prefs_common.fw_quotefmt);
1898 /* empty quotemark is not allowed */
1899 if (qmark == NULL || *qmark == '\0')
1902 compose_quote_fmt(compose, full_msginfo,
1903 body_fmt, qmark, body, FALSE, TRUE,
1904 _("The body of the \"Forward\" template has an error at line %d."));
1905 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1906 quote_fmt_reset_vartable();
1907 compose_attach_parts(compose, msginfo);
1909 procmsg_msginfo_free(&full_msginfo);
1912 SIGNAL_BLOCK(textbuf);
1914 if (account->auto_sig)
1915 compose_insert_sig(compose, FALSE);
1917 compose_wrap_all(compose);
1920 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1921 gtkaspell_highlight_all(compose->gtkaspell);
1922 gtkaspell_unblock_check(compose->gtkaspell);
1924 SIGNAL_UNBLOCK(textbuf);
1926 cursor_pos = quote_fmt_get_cursor_pos();
1927 if (cursor_pos == -1)
1928 gtk_widget_grab_focus(compose->header_last->entry);
1930 gtk_widget_grab_focus(compose->text);
1932 if (!no_extedit && prefs_common.auto_exteditor)
1933 compose_exec_ext_editor(compose);
1936 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1937 gchar *folderidentifier;
1939 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1940 folderidentifier = folder_item_get_identifier(msginfo->folder);
1941 compose_set_save_to(compose, folderidentifier);
1942 g_free(folderidentifier);
1945 undo_unblock(compose->undostruct);
1947 compose->modified = FALSE;
1948 compose_set_title(compose);
1950 compose->updating = FALSE;
1951 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
1952 SCROLL_TO_CURSOR(compose);
1954 if (compose->deferred_destroy) {
1955 compose_destroy(compose);
1959 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
1964 #undef INSERT_FW_HEADER
1966 static Compose *compose_forward_multiple(PrefsAccount *account, GSList *msginfo_list)
1969 GtkTextView *textview;
1970 GtkTextBuffer *textbuf;
1974 gboolean single_mail = TRUE;
1976 cm_return_val_if_fail(msginfo_list != NULL, NULL);
1978 if (g_slist_length(msginfo_list) > 1)
1979 single_mail = FALSE;
1981 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next)
1982 if (((MsgInfo *)msginfo->data)->folder == NULL)
1985 /* guess account from first selected message */
1987 !(account = compose_find_account(msginfo_list->data)))
1988 account = cur_account;
1990 cm_return_val_if_fail(account != NULL, NULL);
1992 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
1993 if (msginfo->data) {
1994 MSG_UNSET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_REPLIED);
1995 MSG_SET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_FORWARDED);
1999 if (msginfo_list == NULL || msginfo_list->data == NULL) {
2000 g_warning("no msginfo_list");
2004 compose = compose_create(account, ((MsgInfo *)msginfo_list->data)->folder, COMPOSE_FORWARD, FALSE);
2006 compose->updating = TRUE;
2008 /* override from name according to folder properties */
2009 if (msginfo_list->data) {
2010 MsgInfo *msginfo = msginfo_list->data;
2012 if (msginfo->folder && msginfo->folder->prefs &&
2013 msginfo->folder->prefs->forward_with_format &&
2014 msginfo->folder->prefs->forward_override_from_format &&
2015 *msginfo->folder->prefs->forward_override_from_format != '\0') {
2020 /* decode \-escape sequences in the internal representation of the quote format */
2021 tmp = g_malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
2022 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
2025 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
2026 compose->gtkaspell);
2028 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
2030 quote_fmt_scan_string(tmp);
2033 buf = quote_fmt_get_buffer();
2035 alertpanel_error(_("The \"From\" field of the \"Forward\" template contains an invalid email address."));
2037 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
2038 quote_fmt_reset_vartable();
2039 quote_fmtlex_destroy();
2045 textview = GTK_TEXT_VIEW(compose->text);
2046 textbuf = gtk_text_view_get_buffer(textview);
2047 compose_create_tags(textview, compose);
2049 undo_block(compose->undostruct);
2050 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
2051 msgfile = procmsg_get_message_file((MsgInfo *)msginfo->data);
2053 if (!is_file_exist(msgfile))
2054 g_warning("%s: file does not exist", msgfile);
2056 compose_attach_append(compose, msgfile, msgfile,
2057 "message/rfc822", NULL);
2062 MsgInfo *info = (MsgInfo *)msginfo_list->data;
2063 if (info->subject && *info->subject) {
2064 gchar *buf, *buf2, *p;
2066 buf = p = g_strdup(info->subject);
2067 p += subject_get_prefix_length(p);
2068 memmove(buf, p, strlen(p) + 1);
2070 buf2 = g_strdup_printf("Fw: %s", buf);
2071 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
2077 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2078 _("Fw: multiple emails"));
2081 SIGNAL_BLOCK(textbuf);
2083 if (account->auto_sig)
2084 compose_insert_sig(compose, FALSE);
2086 compose_wrap_all(compose);
2088 SIGNAL_UNBLOCK(textbuf);
2090 gtk_text_buffer_get_start_iter(textbuf, &iter);
2091 gtk_text_buffer_place_cursor(textbuf, &iter);
2093 if (prefs_common.auto_exteditor)
2094 compose_exec_ext_editor(compose);
2096 gtk_widget_grab_focus(compose->header_last->entry);
2097 undo_unblock(compose->undostruct);
2098 compose->modified = FALSE;
2099 compose_set_title(compose);
2101 compose->updating = FALSE;
2102 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2103 SCROLL_TO_CURSOR(compose);
2105 if (compose->deferred_destroy) {
2106 compose_destroy(compose);
2110 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2115 static gboolean compose_is_sig_separator(Compose *compose, GtkTextBuffer *textbuf, GtkTextIter *iter)
2117 GtkTextIter start = *iter;
2118 GtkTextIter end_iter;
2119 int start_pos = gtk_text_iter_get_offset(&start);
2121 if (!compose->account->sig_sep)
2124 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
2125 start_pos+strlen(compose->account->sig_sep));
2127 /* check sig separator */
2128 str = gtk_text_iter_get_text(&start, &end_iter);
2129 if (!strcmp(str, compose->account->sig_sep)) {
2131 /* check end of line (\n) */
2132 gtk_text_buffer_get_iter_at_offset(textbuf, &start,
2133 start_pos+strlen(compose->account->sig_sep));
2134 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
2135 start_pos+strlen(compose->account->sig_sep)+1);
2136 tmp = gtk_text_iter_get_text(&start, &end_iter);
2137 if (!strcmp(tmp,"\n")) {
2149 static gboolean compose_update_folder_hook(gpointer source, gpointer data)
2151 FolderUpdateData *hookdata = (FolderUpdateData *)source;
2152 Compose *compose = (Compose *)data;
2153 FolderItem *old_item = NULL;
2154 FolderItem *new_item = NULL;
2155 gchar *old_id, *new_id;
2157 if (!(hookdata->update_flags & FOLDER_REMOVE_FOLDERITEM)
2158 && !(hookdata->update_flags & FOLDER_MOVE_FOLDERITEM))
2161 old_item = hookdata->item;
2162 new_item = hookdata->item2;
2164 old_id = folder_item_get_identifier(old_item);
2165 new_id = new_item ? folder_item_get_identifier(new_item) : g_strdup("NULL");
2167 if (compose->targetinfo && compose->targetinfo->folder == old_item) {
2168 debug_print("updating targetinfo folder: %s -> %s\n", old_id, new_id);
2169 compose->targetinfo->folder = new_item;
2172 if (compose->replyinfo && compose->replyinfo->folder == old_item) {
2173 debug_print("updating replyinfo folder: %s -> %s\n", old_id, new_id);
2174 compose->replyinfo->folder = new_item;
2177 if (compose->fwdinfo && compose->fwdinfo->folder == old_item) {
2178 debug_print("updating fwdinfo folder: %s -> %s\n", old_id, new_id);
2179 compose->fwdinfo->folder = new_item;
2187 static void compose_colorize_signature(Compose *compose)
2189 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
2191 GtkTextIter end_iter;
2192 gtk_text_buffer_get_start_iter(buffer, &iter);
2193 while (gtk_text_iter_forward_line(&iter))
2194 if (compose_is_sig_separator(compose, buffer, &iter)) {
2195 gtk_text_buffer_get_end_iter(buffer, &end_iter);
2196 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &end_iter);
2200 #define BLOCK_WRAP() { \
2201 prev_autowrap = compose->autowrap; \
2202 buffer = gtk_text_view_get_buffer( \
2203 GTK_TEXT_VIEW(compose->text)); \
2204 compose->autowrap = FALSE; \
2206 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2207 G_CALLBACK(compose_changed_cb), \
2209 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2210 G_CALLBACK(text_inserted), \
2213 #define UNBLOCK_WRAP() { \
2214 compose->autowrap = prev_autowrap; \
2215 if (compose->autowrap) { \
2216 gint old = compose->draft_timeout_tag; \
2217 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN; \
2218 compose_wrap_all(compose); \
2219 compose->draft_timeout_tag = old; \
2222 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2223 G_CALLBACK(compose_changed_cb), \
2225 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2226 G_CALLBACK(text_inserted), \
2230 Compose *compose_reedit(MsgInfo *msginfo, gboolean batch)
2232 Compose *compose = NULL;
2233 PrefsAccount *account = NULL;
2234 GtkTextView *textview;
2235 GtkTextBuffer *textbuf;
2239 gboolean use_signing = FALSE;
2240 gboolean use_encryption = FALSE;
2241 gchar *privacy_system = NULL;
2242 int priority = PRIORITY_NORMAL;
2243 MsgInfo *replyinfo = NULL, *fwdinfo = NULL;
2244 gboolean autowrap = prefs_common.autowrap;
2245 gboolean autoindent = prefs_common.auto_indent;
2246 HeaderEntry *manual_headers = NULL;
2248 cm_return_val_if_fail(msginfo != NULL, NULL);
2249 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
2251 if (compose_put_existing_to_front(msginfo)) {
2255 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2256 folder_has_parent_of_type(msginfo->folder, F_DRAFT) ||
2257 folder_has_parent_of_type(msginfo->folder, F_OUTBOX)) {
2258 gchar *queueheader_buf = NULL;
2261 /* Select Account from queue headers */
2262 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2263 "X-Claws-Account-Id:")) {
2264 id = atoi(&queueheader_buf[strlen("X-Claws-Account-Id:")]);
2265 account = account_find_from_id(id);
2266 g_free(queueheader_buf);
2268 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2269 "X-Sylpheed-Account-Id:")) {
2270 id = atoi(&queueheader_buf[strlen("X-Sylpheed-Account-Id:")]);
2271 account = account_find_from_id(id);
2272 g_free(queueheader_buf);
2274 if (!account && !procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2276 id = atoi(&queueheader_buf[strlen("NAID:")]);
2277 account = account_find_from_id(id);
2278 g_free(queueheader_buf);
2280 if (!account && !procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2282 id = atoi(&queueheader_buf[strlen("MAID:")]);
2283 account = account_find_from_id(id);
2284 g_free(queueheader_buf);
2286 if (!account && !procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2288 account = account_find_from_address(queueheader_buf, FALSE);
2289 g_free(queueheader_buf);
2291 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2293 param = atoi(&queueheader_buf[strlen("X-Claws-Sign:")]);
2294 use_signing = param;
2295 g_free(queueheader_buf);
2297 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2298 "X-Sylpheed-Sign:")) {
2299 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Sign:")]);
2300 use_signing = param;
2301 g_free(queueheader_buf);
2303 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2304 "X-Claws-Encrypt:")) {
2305 param = atoi(&queueheader_buf[strlen("X-Claws-Encrypt:")]);
2306 use_encryption = param;
2307 g_free(queueheader_buf);
2309 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2310 "X-Sylpheed-Encrypt:")) {
2311 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Encrypt:")]);
2312 use_encryption = param;
2313 g_free(queueheader_buf);
2315 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2316 "X-Claws-Auto-Wrapping:")) {
2317 param = atoi(&queueheader_buf[strlen("X-Claws-Auto-Wrapping:")]);
2319 g_free(queueheader_buf);
2321 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2322 "X-Claws-Auto-Indent:")) {
2323 param = atoi(&queueheader_buf[strlen("X-Claws-Auto-Indent:")]);
2325 g_free(queueheader_buf);
2327 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2328 "X-Claws-Privacy-System:")) {
2329 privacy_system = g_strdup(&queueheader_buf[strlen("X-Claws-Privacy-System:")]);
2330 g_free(queueheader_buf);
2332 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2333 "X-Sylpheed-Privacy-System:")) {
2334 privacy_system = g_strdup(&queueheader_buf[strlen("X-Sylpheed-Privacy-System:")]);
2335 g_free(queueheader_buf);
2337 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2339 param = atoi(&queueheader_buf[strlen("X-Priority: ")]); /* mind the space */
2341 g_free(queueheader_buf);
2343 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2345 gchar **tokens = g_strsplit(&queueheader_buf[strlen("RMID:")], "\t", 0);
2346 if (tokens && tokens[0] && tokens[1] && tokens[2]) {
2347 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2348 if (orig_item != NULL) {
2349 replyinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2353 g_free(queueheader_buf);
2355 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2357 gchar **tokens = g_strsplit(&queueheader_buf[strlen("FMID:")], "\t", 0);
2358 if (tokens && tokens[0] && tokens[1] && tokens[2]) {
2359 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2360 if (orig_item != NULL) {
2361 fwdinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2365 g_free(queueheader_buf);
2367 /* Get manual headers */
2368 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2369 "X-Claws-Manual-Headers:")) {
2370 gchar *listmh = g_strdup(&queueheader_buf[strlen("X-Claws-Manual-Headers:")]);
2371 if (listmh && *listmh != '\0') {
2372 debug_print("Got manual headers: %s\n", listmh);
2373 manual_headers = procheader_entries_from_str(listmh);
2376 g_free(queueheader_buf);
2379 account = msginfo->folder->folder->account;
2382 if (!account && prefs_common.reedit_account_autosel) {
2384 if (!procheader_get_header_from_msginfo(msginfo, &from, "FROM:")) {
2385 extract_address(from);
2386 account = account_find_from_address(from, FALSE);
2391 account = cur_account;
2393 cm_return_val_if_fail(account != NULL, NULL);
2395 compose = compose_create(account, msginfo->folder, COMPOSE_REEDIT, batch);
2397 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", autowrap);
2398 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", autoindent);
2399 compose->autowrap = autowrap;
2400 compose->replyinfo = replyinfo;
2401 compose->fwdinfo = fwdinfo;
2403 compose->updating = TRUE;
2404 compose->priority = priority;
2406 if (privacy_system != NULL) {
2407 compose->privacy_system = privacy_system;
2408 compose_use_signing(compose, use_signing);
2409 compose_use_encryption(compose, use_encryption);
2410 compose_update_privacy_system_menu_item(compose, FALSE);
2412 activate_privacy_system(compose, account, FALSE);
2415 compose->targetinfo = procmsg_msginfo_copy(msginfo);
2417 compose_extract_original_charset(compose);
2419 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2420 folder_has_parent_of_type(msginfo->folder, F_DRAFT) ||
2421 folder_has_parent_of_type(msginfo->folder, F_OUTBOX)) {
2422 gchar *queueheader_buf = NULL;
2424 /* Set message save folder */
2425 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf, "SCF:")) {
2426 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
2427 compose_set_save_to(compose, &queueheader_buf[4]);
2428 g_free(queueheader_buf);
2430 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf, "RRCPT:")) {
2431 gint active = atoi(&queueheader_buf[strlen("RRCPT:")]);
2433 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
2435 g_free(queueheader_buf);
2439 if (compose_parse_header(compose, msginfo) < 0) {
2440 compose->updating = FALSE;
2441 compose_destroy(compose);
2444 compose_reedit_set_entry(compose, msginfo);
2446 textview = GTK_TEXT_VIEW(compose->text);
2447 textbuf = gtk_text_view_get_buffer(textview);
2448 compose_create_tags(textview, compose);
2450 mark = gtk_text_buffer_get_insert(textbuf);
2451 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
2453 g_signal_handlers_block_by_func(G_OBJECT(textbuf),
2454 G_CALLBACK(compose_changed_cb),
2457 if (MSG_IS_ENCRYPTED(msginfo->flags)) {
2458 fp = procmime_get_first_encrypted_text_content(msginfo);
2460 compose_force_encryption(compose, account, TRUE, NULL);
2463 fp = procmime_get_first_text_content(msginfo);
2466 g_warning("Can't get text part");
2470 gchar buf[BUFFSIZE];
2471 gboolean prev_autowrap;
2472 GtkTextBuffer *buffer;
2474 while (fgets(buf, sizeof(buf), fp) != NULL) {
2476 gtk_text_buffer_insert(textbuf, &iter, buf, -1);
2482 compose_attach_parts(compose, msginfo);
2484 compose_colorize_signature(compose);
2486 g_signal_handlers_unblock_by_func(G_OBJECT(textbuf),
2487 G_CALLBACK(compose_changed_cb),
2490 if (manual_headers != NULL) {
2491 if (compose_parse_manual_headers(compose, msginfo, manual_headers) < 0) {
2492 procheader_entries_free(manual_headers);
2493 compose->updating = FALSE;
2494 compose_destroy(compose);
2497 procheader_entries_free(manual_headers);
2500 gtk_widget_grab_focus(compose->text);
2502 if (prefs_common.auto_exteditor) {
2503 compose_exec_ext_editor(compose);
2505 compose->modified = FALSE;
2506 compose_set_title(compose);
2508 compose->updating = FALSE;
2509 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2510 SCROLL_TO_CURSOR(compose);
2512 if (compose->deferred_destroy) {
2513 compose_destroy(compose);
2517 compose->sig_str = account_get_signature_str(compose->account);
2519 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2524 Compose *compose_redirect(PrefsAccount *account, MsgInfo *msginfo,
2531 cm_return_val_if_fail(msginfo != NULL, NULL);
2534 account = account_get_reply_account(msginfo,
2535 prefs_common.reply_account_autosel);
2536 cm_return_val_if_fail(account != NULL, NULL);
2538 compose = compose_create(account, msginfo->folder, COMPOSE_REDIRECT, batch);
2540 compose->updating = TRUE;
2542 compose_create_tags(GTK_TEXT_VIEW(compose->text), compose);
2543 compose->replyinfo = NULL;
2544 compose->fwdinfo = NULL;
2546 compose_show_first_last_header(compose, TRUE);
2548 gtk_widget_grab_focus(compose->header_last->entry);
2550 filename = procmsg_get_message_file(msginfo);
2552 if (filename == NULL) {
2553 compose->updating = FALSE;
2554 compose_destroy(compose);
2559 compose->redirect_filename = filename;
2561 /* Set save folder */
2562 item = msginfo->folder;
2563 if (item && item->prefs && item->prefs->save_copy_to_folder) {
2564 gchar *folderidentifier;
2566 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
2567 folderidentifier = folder_item_get_identifier(item);
2568 compose_set_save_to(compose, folderidentifier);
2569 g_free(folderidentifier);
2572 compose_attach_parts(compose, msginfo);
2574 if (msginfo->subject)
2575 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2577 gtk_editable_set_editable(GTK_EDITABLE(compose->subject_entry), FALSE);
2579 compose_quote_fmt(compose, msginfo, "%M", NULL, NULL, FALSE, FALSE,
2580 _("The body of the \"Redirect\" template has an error at line %d."));
2581 quote_fmt_reset_vartable();
2582 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), FALSE);
2584 compose_colorize_signature(compose);
2587 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Add", FALSE);
2588 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", FALSE);
2589 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", FALSE);
2591 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/Save", FALSE);
2592 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertFile", FALSE);
2593 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/AttachFile", FALSE);
2594 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertSig", FALSE);
2595 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/ReplaceSig", FALSE);
2596 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", FALSE);
2597 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", FALSE);
2598 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/ShowRuler", FALSE);
2599 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Actions", FALSE);
2601 if (compose->toolbar->draft_btn)
2602 gtk_widget_set_sensitive(compose->toolbar->draft_btn, FALSE);
2603 if (compose->toolbar->insert_btn)
2604 gtk_widget_set_sensitive(compose->toolbar->insert_btn, FALSE);
2605 if (compose->toolbar->attach_btn)
2606 gtk_widget_set_sensitive(compose->toolbar->attach_btn, FALSE);
2607 if (compose->toolbar->sig_btn)
2608 gtk_widget_set_sensitive(compose->toolbar->sig_btn, FALSE);
2609 if (compose->toolbar->exteditor_btn)
2610 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, FALSE);
2611 if (compose->toolbar->linewrap_current_btn)
2612 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, FALSE);
2613 if (compose->toolbar->linewrap_all_btn)
2614 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, FALSE);
2616 compose->modified = FALSE;
2617 compose_set_title(compose);
2618 compose->updating = FALSE;
2619 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2620 SCROLL_TO_CURSOR(compose);
2622 if (compose->deferred_destroy) {
2623 compose_destroy(compose);
2627 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2632 const GList *compose_get_compose_list(void)
2634 return compose_list;
2637 void compose_entry_append(Compose *compose, const gchar *address,
2638 ComposeEntryType type, ComposePrefType pref_type)
2640 const gchar *header;
2642 gboolean in_quote = FALSE;
2643 if (!address || *address == '\0') return;
2650 header = N_("Bcc:");
2652 case COMPOSE_REPLYTO:
2653 header = N_("Reply-To:");
2655 case COMPOSE_NEWSGROUPS:
2656 header = N_("Newsgroups:");
2658 case COMPOSE_FOLLOWUPTO:
2659 header = N_( "Followup-To:");
2661 case COMPOSE_INREPLYTO:
2662 header = N_( "In-Reply-To:");
2669 header = prefs_common_translated_header_name(header);
2671 cur = begin = (gchar *)address;
2673 /* we separate the line by commas, but not if we're inside a quoted
2675 while (*cur != '\0') {
2677 in_quote = !in_quote;
2678 if (*cur == ',' && !in_quote) {
2679 gchar *tmp = g_strdup(begin);
2681 tmp[cur-begin]='\0';
2684 while (*tmp == ' ' || *tmp == '\t')
2686 compose_add_header_entry(compose, header, tmp, pref_type);
2687 compose_entry_indicate(compose, tmp);
2694 gchar *tmp = g_strdup(begin);
2696 tmp[cur-begin]='\0';
2697 while (*tmp == ' ' || *tmp == '\t')
2699 compose_add_header_entry(compose, header, tmp, pref_type);
2700 compose_entry_indicate(compose, tmp);
2705 static void compose_entry_indicate(Compose *compose, const gchar *mailto)
2710 for (h_list = compose->header_list; h_list != NULL; h_list = h_list->next) {
2711 entry = GTK_ENTRY(((ComposeHeaderEntry *)h_list->data)->entry);
2712 if (gtk_entry_get_text(entry) &&
2713 !g_utf8_collate(gtk_entry_get_text(entry), mailto)) {
2714 gtk_widget_modify_base(
2715 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2716 GTK_STATE_NORMAL, &default_header_bgcolor);
2717 gtk_widget_modify_text(
2718 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2719 GTK_STATE_NORMAL, &default_header_color);
2724 void compose_toolbar_cb(gint action, gpointer data)
2726 ToolbarItem *toolbar_item = (ToolbarItem*)data;
2727 Compose *compose = (Compose*)toolbar_item->parent;
2729 cm_return_if_fail(compose != NULL);
2733 compose_send_cb(NULL, compose);
2736 compose_send_later_cb(NULL, compose);
2739 compose_draft(compose, COMPOSE_QUIT_EDITING);
2742 compose_insert_file_cb(NULL, compose);
2745 compose_attach_cb(NULL, compose);
2748 compose_insert_sig(compose, FALSE);
2751 compose_insert_sig(compose, TRUE);
2754 compose_ext_editor_cb(NULL, compose);
2756 case A_LINEWRAP_CURRENT:
2757 compose_beautify_paragraph(compose, NULL, TRUE);
2759 case A_LINEWRAP_ALL:
2760 compose_wrap_all_full(compose, TRUE);
2763 compose_address_cb(NULL, compose);
2766 case A_CHECK_SPELLING:
2767 compose_check_all(NULL, compose);
2770 case A_PRIVACY_SIGN:
2772 case A_PRIVACY_ENCRYPT:
2779 static MailField compose_entries_set(Compose *compose, const gchar *mailto, ComposeEntryType to_type)
2784 gchar *subject = NULL;
2788 gchar **attach = NULL;
2789 gchar *inreplyto = NULL;
2790 MailField mfield = NO_FIELD_PRESENT;
2792 /* get mailto parts but skip from */
2793 scan_mailto_url(mailto, NULL, &to, &cc, &bcc, &subject, &body, &attach, &inreplyto);
2796 compose_entry_append(compose, to, to_type, PREF_MAILTO);
2797 mfield = TO_FIELD_PRESENT;
2800 compose_entry_append(compose, cc, COMPOSE_CC, PREF_MAILTO);
2802 compose_entry_append(compose, bcc, COMPOSE_BCC, PREF_MAILTO);
2804 if (!g_utf8_validate (subject, -1, NULL)) {
2805 temp = g_locale_to_utf8 (subject, -1, NULL, &len, NULL);
2806 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), temp);
2809 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), subject);
2811 mfield = SUBJECT_FIELD_PRESENT;
2814 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2815 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2818 gboolean prev_autowrap = compose->autowrap;
2820 compose->autowrap = FALSE;
2822 mark = gtk_text_buffer_get_insert(buffer);
2823 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2825 if (!g_utf8_validate (body, -1, NULL)) {
2826 temp = g_locale_to_utf8 (body, -1, NULL, &len, NULL);
2827 gtk_text_buffer_insert(buffer, &iter, temp, -1);
2830 gtk_text_buffer_insert(buffer, &iter, body, -1);
2832 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
2834 compose->autowrap = prev_autowrap;
2835 if (compose->autowrap)
2836 compose_wrap_all(compose);
2837 mfield = BODY_FIELD_PRESENT;
2841 gint i = 0, att = 0;
2842 gchar *warn_files = NULL;
2843 while (attach[i] != NULL) {
2844 gchar *utf8_filename = conv_filename_to_utf8(attach[i]);
2845 if (utf8_filename) {
2846 if (compose_attach_append(compose, attach[i], utf8_filename, NULL, NULL)) {
2847 gchar *tmp = g_strdup_printf("%s%s\n",
2848 warn_files?warn_files:"",
2854 g_free(utf8_filename);
2856 alertpanel_error(_("Couldn't attach a file (charset conversion failed)."));
2861 alertpanel_notice(ngettext(
2862 "The following file has been attached: \n%s",
2863 "The following files have been attached: \n%s", att), warn_files);
2868 compose_entry_append(compose, inreplyto, COMPOSE_INREPLYTO, PREF_MAILTO);
2881 static gint compose_parse_header(Compose *compose, MsgInfo *msginfo)
2883 static HeaderEntry hentry[] = {
2884 {"Reply-To:", NULL, TRUE },
2885 {"Cc:", NULL, TRUE },
2886 {"References:", NULL, FALSE },
2887 {"Bcc:", NULL, TRUE },
2888 {"Newsgroups:", NULL, TRUE },
2889 {"Followup-To:", NULL, TRUE },
2890 {"List-Post:", NULL, FALSE },
2891 {"X-Priority:", NULL, FALSE },
2892 {NULL, NULL, FALSE }
2909 cm_return_val_if_fail(msginfo != NULL, -1);
2911 if ((fp = procmsg_open_message(msginfo)) == NULL) return -1;
2912 procheader_get_header_fields(fp, hentry);
2915 if (hentry[H_REPLY_TO].body != NULL) {
2916 if (hentry[H_REPLY_TO].body[0] != '\0') {
2918 conv_unmime_header(hentry[H_REPLY_TO].body,
2921 g_free(hentry[H_REPLY_TO].body);
2922 hentry[H_REPLY_TO].body = NULL;
2924 if (hentry[H_CC].body != NULL) {
2925 compose->cc = conv_unmime_header(hentry[H_CC].body, NULL, TRUE);
2926 g_free(hentry[H_CC].body);
2927 hentry[H_CC].body = NULL;
2929 if (hentry[H_REFERENCES].body != NULL) {
2930 if (compose->mode == COMPOSE_REEDIT)
2931 compose->references = hentry[H_REFERENCES].body;
2933 compose->references = compose_parse_references
2934 (hentry[H_REFERENCES].body, msginfo->msgid);
2935 g_free(hentry[H_REFERENCES].body);
2937 hentry[H_REFERENCES].body = NULL;
2939 if (hentry[H_BCC].body != NULL) {
2940 if (compose->mode == COMPOSE_REEDIT)
2942 conv_unmime_header(hentry[H_BCC].body, NULL, TRUE);
2943 g_free(hentry[H_BCC].body);
2944 hentry[H_BCC].body = NULL;
2946 if (hentry[H_NEWSGROUPS].body != NULL) {
2947 compose->newsgroups = hentry[H_NEWSGROUPS].body;
2948 hentry[H_NEWSGROUPS].body = NULL;
2950 if (hentry[H_FOLLOWUP_TO].body != NULL) {
2951 if (hentry[H_FOLLOWUP_TO].body[0] != '\0') {
2952 compose->followup_to =
2953 conv_unmime_header(hentry[H_FOLLOWUP_TO].body,
2956 g_free(hentry[H_FOLLOWUP_TO].body);
2957 hentry[H_FOLLOWUP_TO].body = NULL;
2959 if (hentry[H_LIST_POST].body != NULL) {
2960 gchar *to = NULL, *start = NULL;
2962 extract_address(hentry[H_LIST_POST].body);
2963 if (hentry[H_LIST_POST].body[0] != '\0') {
2964 start = strstr(hentry[H_LIST_POST].body, "mailto:");
2966 scan_mailto_url(start ? start : hentry[H_LIST_POST].body,
2967 NULL, &to, NULL, NULL, NULL, NULL, NULL, NULL);
2970 g_free(compose->ml_post);
2971 compose->ml_post = to;
2974 g_free(hentry[H_LIST_POST].body);
2975 hentry[H_LIST_POST].body = NULL;
2978 /* CLAWS - X-Priority */
2979 if (compose->mode == COMPOSE_REEDIT)
2980 if (hentry[H_X_PRIORITY].body != NULL) {
2983 priority = atoi(hentry[H_X_PRIORITY].body);
2984 g_free(hentry[H_X_PRIORITY].body);
2986 hentry[H_X_PRIORITY].body = NULL;
2988 if (priority < PRIORITY_HIGHEST ||
2989 priority > PRIORITY_LOWEST)
2990 priority = PRIORITY_NORMAL;
2992 compose->priority = priority;
2995 if (compose->mode == COMPOSE_REEDIT) {
2996 if (msginfo->inreplyto && *msginfo->inreplyto)
2997 compose->inreplyto = g_strdup(msginfo->inreplyto);
2999 if (msginfo->msgid && *msginfo->msgid &&
3000 compose->folder != NULL &&
3001 compose->folder->stype == F_DRAFT)
3002 compose->msgid = g_strdup(msginfo->msgid);
3004 if (msginfo->msgid && *msginfo->msgid)
3005 compose->inreplyto = g_strdup(msginfo->msgid);
3007 if (!compose->references) {
3008 if (msginfo->msgid && *msginfo->msgid) {
3009 if (msginfo->inreplyto && *msginfo->inreplyto)
3010 compose->references =
3011 g_strdup_printf("<%s>\n\t<%s>",
3015 compose->references =
3016 g_strconcat("<", msginfo->msgid, ">",
3018 } else if (msginfo->inreplyto && *msginfo->inreplyto) {
3019 compose->references =
3020 g_strconcat("<", msginfo->inreplyto, ">",
3029 static gint compose_parse_manual_headers(Compose *compose, MsgInfo *msginfo, HeaderEntry *entries)
3034 cm_return_val_if_fail(msginfo != NULL, -1);
3036 if ((fp = procmsg_open_message(msginfo)) == NULL) return -1;
3037 procheader_get_header_fields(fp, entries);
3041 while (he != NULL && he->name != NULL) {
3043 GtkListStore *model = NULL;
3045 debug_print("Adding manual header: %s with value %s\n", he->name, he->body);
3046 model = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(compose->header_last->combo)));
3047 COMBOBOX_ADD(model, he->name, COMPOSE_TO);
3048 gtk_combo_box_set_active_iter(GTK_COMBO_BOX(compose->header_last->combo), &iter);
3049 gtk_entry_set_text(GTK_ENTRY(compose->header_last->entry), he->body);
3056 static gchar *compose_parse_references(const gchar *ref, const gchar *msgid)
3058 GSList *ref_id_list, *cur;
3062 ref_id_list = references_list_append(NULL, ref);
3063 if (!ref_id_list) return NULL;
3064 if (msgid && *msgid)
3065 ref_id_list = g_slist_append(ref_id_list, g_strdup(msgid));
3070 for (cur = ref_id_list; cur != NULL; cur = cur->next)
3071 /* "<" + Message-ID + ">" + CR+LF+TAB */
3072 len += strlen((gchar *)cur->data) + 5;
3074 if (len > MAX_REFERENCES_LEN) {
3075 /* remove second message-ID */
3076 if (ref_id_list && ref_id_list->next &&
3077 ref_id_list->next->next) {
3078 g_free(ref_id_list->next->data);
3079 ref_id_list = g_slist_remove
3080 (ref_id_list, ref_id_list->next->data);
3082 slist_free_strings_full(ref_id_list);
3089 new_ref = g_string_new("");
3090 for (cur = ref_id_list; cur != NULL; cur = cur->next) {
3091 if (new_ref->len > 0)
3092 g_string_append(new_ref, "\n\t");
3093 g_string_append_printf(new_ref, "<%s>", (gchar *)cur->data);
3096 slist_free_strings_full(ref_id_list);
3098 new_ref_str = new_ref->str;
3099 g_string_free(new_ref, FALSE);
3104 static gchar *compose_quote_fmt(Compose *compose, MsgInfo *msginfo,
3105 const gchar *fmt, const gchar *qmark,
3106 const gchar *body, gboolean rewrap,
3107 gboolean need_unescape,
3108 const gchar *err_msg)
3110 MsgInfo* dummyinfo = NULL;
3111 gchar *quote_str = NULL;
3113 gboolean prev_autowrap;
3114 const gchar *trimmed_body = body;
3115 gint cursor_pos = -1;
3116 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3117 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3122 SIGNAL_BLOCK(buffer);
3125 dummyinfo = compose_msginfo_new_from_compose(compose);
3126 msginfo = dummyinfo;
3129 if (qmark != NULL) {
3131 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
3132 compose->gtkaspell);
3134 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
3136 quote_fmt_scan_string(qmark);
3139 buf = quote_fmt_get_buffer();
3142 alertpanel_error(_("The \"Quotation mark\" of the template is invalid."));
3144 Xstrdup_a(quote_str, buf, goto error)
3147 if (fmt && *fmt != '\0') {
3150 while (*trimmed_body == '\n')
3154 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE,
3155 compose->gtkaspell);
3157 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE);
3159 if (need_unescape) {
3162 /* decode \-escape sequences in the internal representation of the quote format */
3163 tmp = g_malloc(strlen(fmt)+1);
3164 pref_get_unescaped_pref(tmp, fmt);
3165 quote_fmt_scan_string(tmp);
3169 quote_fmt_scan_string(fmt);
3173 buf = quote_fmt_get_buffer();
3176 gint line = quote_fmt_get_line();
3177 alertpanel_error(err_msg, line);
3185 prev_autowrap = compose->autowrap;
3186 compose->autowrap = FALSE;
3188 mark = gtk_text_buffer_get_insert(buffer);
3189 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3190 if (g_utf8_validate(buf, -1, NULL)) {
3191 gtk_text_buffer_insert(buffer, &iter, buf, -1);
3193 gchar *tmpout = NULL;
3194 tmpout = conv_codeset_strdup
3195 (buf, conv_get_locale_charset_str_no_utf8(),
3197 if (!tmpout || !g_utf8_validate(tmpout, -1, NULL)) {
3199 tmpout = g_malloc(strlen(buf)*2+1);
3200 conv_localetodisp(tmpout, strlen(buf)*2+1, buf);
3202 gtk_text_buffer_insert(buffer, &iter, tmpout, -1);
3206 cursor_pos = quote_fmt_get_cursor_pos();
3207 if (cursor_pos == -1)
3208 cursor_pos = gtk_text_iter_get_offset(&iter);
3209 compose->set_cursor_pos = cursor_pos;
3211 gtk_text_buffer_get_start_iter(buffer, &iter);
3212 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
3213 gtk_text_buffer_place_cursor(buffer, &iter);
3215 compose->autowrap = prev_autowrap;
3216 if (compose->autowrap && rewrap)
3217 compose_wrap_all(compose);
3224 SIGNAL_UNBLOCK(buffer);
3226 procmsg_msginfo_free( &dummyinfo );
3231 /* if ml_post is of type addr@host and from is of type
3232 * addr-anything@host, return TRUE
3234 static gboolean is_subscription(const gchar *ml_post, const gchar *from)
3236 gchar *left_ml = NULL;
3237 gchar *right_ml = NULL;
3238 gchar *left_from = NULL;
3239 gchar *right_from = NULL;
3240 gboolean result = FALSE;
3242 if (!ml_post || !from)
3245 left_ml = g_strdup(ml_post);
3246 if (strstr(left_ml, "@")) {
3247 right_ml = strstr(left_ml, "@")+1;
3248 *(strstr(left_ml, "@")) = '\0';
3251 left_from = g_strdup(from);
3252 if (strstr(left_from, "@")) {
3253 right_from = strstr(left_from, "@")+1;
3254 *(strstr(left_from, "@")) = '\0';
3257 if (right_ml && right_from
3258 && !strncmp(left_from, left_ml, strlen(left_ml))
3259 && !strcmp(right_from, right_ml)) {
3268 static void compose_set_folder_prefs(Compose *compose, FolderItem *folder,
3269 gboolean respect_default_to)
3273 if (!folder || !folder->prefs)
3276 if (respect_default_to && folder->prefs->enable_default_to) {
3277 compose_entry_append(compose, folder->prefs->default_to,
3278 COMPOSE_TO, PREF_FOLDER);
3279 compose_entry_indicate(compose, folder->prefs->default_to);
3281 if (folder->prefs->enable_default_cc) {
3282 compose_entry_append(compose, folder->prefs->default_cc,
3283 COMPOSE_CC, PREF_FOLDER);
3284 compose_entry_indicate(compose, folder->prefs->default_cc);
3286 if (folder->prefs->enable_default_bcc) {
3287 compose_entry_append(compose, folder->prefs->default_bcc,
3288 COMPOSE_BCC, PREF_FOLDER);
3289 compose_entry_indicate(compose, folder->prefs->default_bcc);
3291 if (folder->prefs->enable_default_replyto) {
3292 compose_entry_append(compose, folder->prefs->default_replyto,
3293 COMPOSE_REPLYTO, PREF_FOLDER);
3294 compose_entry_indicate(compose, folder->prefs->default_replyto);
3298 static void compose_reply_set_subject(Compose *compose, MsgInfo *msginfo)
3303 if (!compose || !msginfo)
3306 if (msginfo->subject && *msginfo->subject) {
3307 buf = p = g_strdup(msginfo->subject);
3308 p += subject_get_prefix_length(p);
3309 memmove(buf, p, strlen(p) + 1);
3311 buf2 = g_strdup_printf("Re: %s", buf);
3312 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
3317 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), "Re: ");
3320 static void compose_reply_set_entry(Compose *compose, MsgInfo *msginfo,
3321 gboolean to_all, gboolean to_ml,
3323 gboolean followup_and_reply_to)
3325 GSList *cc_list = NULL;
3328 gchar *replyto = NULL;
3329 gchar *ac_email = NULL;
3331 gboolean reply_to_ml = FALSE;
3332 gboolean default_reply_to = FALSE;
3334 cm_return_if_fail(compose->account != NULL);
3335 cm_return_if_fail(msginfo != NULL);
3337 reply_to_ml = to_ml && compose->ml_post;
3339 default_reply_to = msginfo->folder &&
3340 msginfo->folder->prefs->enable_default_reply_to;
3342 if (compose->account->protocol != A_NNTP) {
3343 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
3345 if (reply_to_ml && !default_reply_to) {
3347 gboolean is_subscr = is_subscription(compose->ml_post,
3350 /* normal answer to ml post with a reply-to */
3351 compose_entry_append(compose,
3353 COMPOSE_TO, PREF_ML);
3354 if (compose->replyto)
3355 compose_entry_append(compose,
3357 COMPOSE_CC, PREF_ML);
3359 /* answer to subscription confirmation */
3360 if (compose->replyto)
3361 compose_entry_append(compose,
3363 COMPOSE_TO, PREF_ML);
3364 else if (msginfo->from)
3365 compose_entry_append(compose,
3367 COMPOSE_TO, PREF_ML);
3370 else if (!(to_all || to_sender) && default_reply_to) {
3371 compose_entry_append(compose,
3372 msginfo->folder->prefs->default_reply_to,
3373 COMPOSE_TO, PREF_FOLDER);
3374 compose_entry_indicate(compose,
3375 msginfo->folder->prefs->default_reply_to);
3381 compose_entry_append(compose, msginfo->from,
3382 COMPOSE_TO, PREF_NONE);
3384 Xstrdup_a(tmp1, msginfo->from, return);
3385 extract_address(tmp1);
3386 compose_entry_append(compose,
3387 (!account_find_from_address(tmp1, FALSE))
3390 COMPOSE_TO, PREF_NONE);
3391 if (compose->replyto)
3392 compose_entry_append(compose,
3394 COMPOSE_CC, PREF_NONE);
3396 if (!folder_has_parent_of_type(msginfo->folder, F_QUEUE) &&
3397 !folder_has_parent_of_type(msginfo->folder, F_OUTBOX) &&
3398 !folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
3399 if (compose->replyto) {
3400 compose_entry_append(compose,
3402 COMPOSE_TO, PREF_NONE);
3404 compose_entry_append(compose,
3405 msginfo->from ? msginfo->from : "",
3406 COMPOSE_TO, PREF_NONE);
3409 /* replying to own mail, use original recp */
3410 compose_entry_append(compose,
3411 msginfo->to ? msginfo->to : "",
3412 COMPOSE_TO, PREF_NONE);
3413 compose_entry_append(compose,
3414 msginfo->cc ? msginfo->cc : "",
3415 COMPOSE_CC, PREF_NONE);
3420 if (to_sender || (compose->followup_to &&
3421 !strncmp(compose->followup_to, "poster", 6)))
3422 compose_entry_append
3424 (compose->replyto ? compose->replyto :
3425 msginfo->from ? msginfo->from : ""),
3426 COMPOSE_TO, PREF_NONE);
3428 else if (followup_and_reply_to || to_all) {
3429 compose_entry_append
3431 (compose->replyto ? compose->replyto :
3432 msginfo->from ? msginfo->from : ""),
3433 COMPOSE_TO, PREF_NONE);
3435 compose_entry_append
3437 compose->followup_to ? compose->followup_to :
3438 compose->newsgroups ? compose->newsgroups : "",
3439 COMPOSE_NEWSGROUPS, PREF_NONE);
3441 compose_entry_append
3443 msginfo->cc ? msginfo->cc : "",
3444 COMPOSE_CC, PREF_NONE);
3447 compose_entry_append
3449 compose->followup_to ? compose->followup_to :
3450 compose->newsgroups ? compose->newsgroups : "",
3451 COMPOSE_NEWSGROUPS, PREF_NONE);
3453 compose_reply_set_subject(compose, msginfo);
3455 if (to_ml && compose->ml_post) return;
3456 if (!to_all || compose->account->protocol == A_NNTP) return;
3458 if (compose->replyto) {
3459 Xstrdup_a(replyto, compose->replyto, return);
3460 extract_address(replyto);
3462 if (msginfo->from) {
3463 Xstrdup_a(from, msginfo->from, return);
3464 extract_address(from);
3467 if (replyto && from)
3468 cc_list = address_list_append_with_comments(cc_list, from);
3469 if (to_all && msginfo->folder &&
3470 msginfo->folder->prefs->enable_default_reply_to)
3471 cc_list = address_list_append_with_comments(cc_list,
3472 msginfo->folder->prefs->default_reply_to);
3473 cc_list = address_list_append_with_comments(cc_list, msginfo->to);
3474 cc_list = address_list_append_with_comments(cc_list, compose->cc);
3476 ac_email = g_utf8_strdown(compose->account->address, -1);
3479 for (cur = cc_list; cur != NULL; cur = cur->next) {
3480 gchar *addr = g_utf8_strdown(cur->data, -1);
3481 extract_address(addr);
3483 if (strcmp(ac_email, addr))
3484 compose_entry_append(compose, (gchar *)cur->data,
3485 COMPOSE_CC, PREF_NONE);
3487 debug_print("Cc address same as compose account's, ignoring\n");
3492 slist_free_strings_full(cc_list);
3498 #define SET_ENTRY(entry, str) \
3501 gtk_entry_set_text(GTK_ENTRY(compose->entry), str); \
3504 #define SET_ADDRESS(type, str) \
3507 compose_entry_append(compose, str, type, PREF_NONE); \
3510 static void compose_reedit_set_entry(Compose *compose, MsgInfo *msginfo)
3512 cm_return_if_fail(msginfo != NULL);
3514 SET_ENTRY(subject_entry, msginfo->subject);
3515 SET_ENTRY(from_name, msginfo->from);
3516 SET_ADDRESS(COMPOSE_TO, msginfo->to);
3517 SET_ADDRESS(COMPOSE_CC, compose->cc);
3518 SET_ADDRESS(COMPOSE_BCC, compose->bcc);
3519 SET_ADDRESS(COMPOSE_REPLYTO, compose->replyto);
3520 SET_ADDRESS(COMPOSE_NEWSGROUPS, compose->newsgroups);
3521 SET_ADDRESS(COMPOSE_FOLLOWUPTO, compose->followup_to);
3523 compose_update_priority_menu_item(compose);
3524 compose_update_privacy_system_menu_item(compose, FALSE);
3525 compose_show_first_last_header(compose, TRUE);
3531 static void compose_insert_sig(Compose *compose, gboolean replace)
3533 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3534 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3536 GtkTextIter iter, iter_end;
3537 gint cur_pos, ins_pos;
3538 gboolean prev_autowrap;
3539 gboolean found = FALSE;
3540 gboolean exists = FALSE;
3542 cm_return_if_fail(compose->account != NULL);
3546 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3547 G_CALLBACK(compose_changed_cb),
3550 mark = gtk_text_buffer_get_insert(buffer);
3551 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3552 cur_pos = gtk_text_iter_get_offset (&iter);
3555 gtk_text_buffer_get_end_iter(buffer, &iter);
3557 exists = (compose->sig_str != NULL);
3560 GtkTextIter first_iter, start_iter, end_iter;
3562 gtk_text_buffer_get_start_iter(buffer, &first_iter);
3564 if (!exists || compose->sig_str[0] == '\0')
3567 found = gtk_text_iter_forward_to_tag_toggle(&first_iter,
3568 compose->signature_tag);
3571 /* include previous \n\n */
3572 gtk_text_iter_backward_chars(&first_iter, 1);
3573 start_iter = first_iter;
3574 end_iter = first_iter;
3576 found = gtk_text_iter_forward_to_tag_toggle(&end_iter,
3577 compose->signature_tag);
3578 found &= gtk_text_iter_forward_to_tag_toggle(&end_iter,
3579 compose->signature_tag);
3581 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
3587 g_free(compose->sig_str);
3588 compose->sig_str = account_get_signature_str(compose->account);
3590 cur_pos = gtk_text_iter_get_offset(&iter);
3592 if (!compose->sig_str || (replace && !compose->account->auto_sig)) {
3593 g_free(compose->sig_str);
3594 compose->sig_str = NULL;
3596 if (compose->sig_inserted == FALSE)
3597 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
3598 compose->sig_inserted = TRUE;
3600 cur_pos = gtk_text_iter_get_offset(&iter);
3601 gtk_text_buffer_insert(buffer, &iter, compose->sig_str, -1);
3603 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cur_pos);
3604 gtk_text_iter_forward_chars(&iter, 1);
3605 gtk_text_buffer_get_end_iter(buffer, &iter_end);
3606 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &iter_end);
3608 if (cur_pos > gtk_text_buffer_get_char_count (buffer))
3609 cur_pos = gtk_text_buffer_get_char_count (buffer);
3612 /* put the cursor where it should be
3613 * either where the quote_fmt says, either where it was */
3614 if (compose->set_cursor_pos < 0)
3615 gtk_text_buffer_get_iter_at_offset(buffer, &iter, ins_pos);
3617 gtk_text_buffer_get_iter_at_offset(buffer, &iter,
3618 compose->set_cursor_pos);
3620 compose->set_cursor_pos = -1;
3621 gtk_text_buffer_place_cursor(buffer, &iter);
3622 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3623 G_CALLBACK(compose_changed_cb),
3629 static ComposeInsertResult compose_insert_file(Compose *compose, const gchar *file)
3632 GtkTextBuffer *buffer;
3635 const gchar *cur_encoding;
3636 gchar buf[BUFFSIZE];
3639 gboolean prev_autowrap;
3643 GError *error = NULL;
3649 GString *file_contents = NULL;
3650 ComposeInsertResult result = COMPOSE_INSERT_SUCCESS;
3652 cm_return_val_if_fail(file != NULL, COMPOSE_INSERT_NO_FILE);
3654 /* get the size of the file we are about to insert */
3656 f = g_file_new_for_path(file);
3657 fi = g_file_query_info(f, "standard::size",
3658 G_FILE_QUERY_INFO_NONE, NULL, &error);
3660 if (error != NULL) {
3661 g_warning(error->message);
3663 g_error_free(error);
3667 ret = g_stat(file, &file_stat);
3670 gchar *shortfile = g_path_get_basename(file);
3671 alertpanel_error(_("Could not get size of file '%s'."), shortfile);
3673 return COMPOSE_INSERT_NO_FILE;
3674 } else if (prefs_common.warn_large_insert == TRUE) {
3676 size = g_file_info_get_size(fi);
3680 size = file_stat.st_size;
3683 /* ask user for confirmation if the file is large */
3684 if (prefs_common.warn_large_insert_size < 0 ||
3685 size > ((goffset) prefs_common.warn_large_insert_size * 1024)) {
3689 msg = g_strdup_printf(_("You are about to insert a file of %s "
3690 "in the message body. Are you sure you want to do that?"),
3691 to_human_readable(size));
3692 aval = alertpanel_full(_("Are you sure?"), msg, GTK_STOCK_CANCEL,
3693 g_strconcat("+", _("_Insert"), NULL), NULL, TRUE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
3696 /* do we ask for confirmation next time? */
3697 if (aval & G_ALERTDISABLE) {
3698 /* no confirmation next time, disable feature in preferences */
3699 aval &= ~G_ALERTDISABLE;
3700 prefs_common.warn_large_insert = FALSE;
3703 /* abort file insertion if user canceled action */
3704 if (aval != G_ALERTALTERNATE) {
3705 return COMPOSE_INSERT_NO_FILE;
3711 if ((fp = g_fopen(file, "rb")) == NULL) {
3712 FILE_OP_ERROR(file, "fopen");
3713 return COMPOSE_INSERT_READ_ERROR;
3716 prev_autowrap = compose->autowrap;
3717 compose->autowrap = FALSE;
3719 text = GTK_TEXT_VIEW(compose->text);
3720 buffer = gtk_text_view_get_buffer(text);
3721 mark = gtk_text_buffer_get_insert(buffer);
3722 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3724 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3725 G_CALLBACK(text_inserted),
3728 cur_encoding = conv_get_locale_charset_str_no_utf8();
3730 file_contents = g_string_new("");
3731 while (fgets(buf, sizeof(buf), fp) != NULL) {
3734 if (g_utf8_validate(buf, -1, NULL) == TRUE)
3735 str = g_strdup(buf);
3737 codeconv_set_strict(TRUE);
3738 str = conv_codeset_strdup
3739 (buf, cur_encoding, CS_INTERNAL);
3740 codeconv_set_strict(FALSE);
3743 result = COMPOSE_INSERT_INVALID_CHARACTER;
3749 /* strip <CR> if DOS/Windows file,
3750 replace <CR> with <LF> if Macintosh file. */
3753 if (len > 0 && str[len - 1] != '\n') {
3755 if (str[len] == '\r') str[len] = '\n';
3758 file_contents = g_string_append(file_contents, str);
3762 if (result == COMPOSE_INSERT_SUCCESS) {
3763 gtk_text_buffer_insert(buffer, &iter, file_contents->str, -1);
3765 compose_changed_cb(NULL, compose);
3766 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3767 G_CALLBACK(text_inserted),
3769 compose->autowrap = prev_autowrap;
3770 if (compose->autowrap)
3771 compose_wrap_all(compose);
3774 g_string_free(file_contents, TRUE);
3780 static gboolean compose_attach_append(Compose *compose, const gchar *file,
3781 const gchar *filename,
3782 const gchar *content_type,
3783 const gchar *charset)
3791 GtkListStore *store;
3793 gboolean has_binary = FALSE;
3795 if (!is_file_exist(file)) {
3796 gchar *file_from_uri = g_filename_from_uri(file, NULL, NULL);
3797 gboolean result = FALSE;
3798 if (file_from_uri && is_file_exist(file_from_uri)) {
3799 result = compose_attach_append(
3800 compose, file_from_uri,
3801 filename, content_type,
3804 g_free(file_from_uri);
3807 alertpanel_error("File %s doesn't exist or permission denied\n", filename);
3810 if ((size = get_file_size(file)) < 0) {
3811 alertpanel_error("Can't get file size of %s\n", filename);
3815 /* In batch mode, we allow 0-length files to be attached no questions asked */
3816 if (size == 0 && !compose->batch) {
3817 gchar * msg = g_strdup_printf(_("File %s is empty."), filename);
3818 AlertValue aval = alertpanel_full(_("Empty file"), msg,
3819 GTK_STOCK_CANCEL, g_strconcat("+", _("_Attach anyway"), NULL), NULL, FALSE,
3820 NULL, ALERT_WARNING, G_ALERTDEFAULT);
3823 if (aval != G_ALERTALTERNATE) {
3827 if ((fp = g_fopen(file, "rb")) == NULL) {
3828 alertpanel_error(_("Can't read %s."), filename);
3833 ainfo = g_new0(AttachInfo, 1);
3834 auto_ainfo = g_auto_pointer_new_with_free
3835 (ainfo, (GFreeFunc) compose_attach_info_free);
3836 ainfo->file = g_strdup(file);
3839 ainfo->content_type = g_strdup(content_type);
3840 if (!g_ascii_strcasecmp(content_type, "message/rfc822")) {
3842 MsgFlags flags = {0, 0};
3844 if (procmime_get_encoding_for_text_file(file, &has_binary) == ENC_7BIT)
3845 ainfo->encoding = ENC_7BIT;
3847 ainfo->encoding = ENC_8BIT;
3849 msginfo = procheader_parse_file(file, flags, FALSE, FALSE);
3850 if (msginfo && msginfo->subject)
3851 name = g_strdup(msginfo->subject);
3853 name = g_path_get_basename(filename ? filename : file);
3855 ainfo->name = g_strdup_printf(_("Message: %s"), name);
3857 procmsg_msginfo_free(&msginfo);
3859 if (!g_ascii_strncasecmp(content_type, "text/", 5)) {
3860 ainfo->charset = g_strdup(charset);
3861 ainfo->encoding = procmime_get_encoding_for_text_file(file, &has_binary);
3863 ainfo->encoding = ENC_BASE64;
3865 name = g_path_get_basename(filename ? filename : file);
3866 ainfo->name = g_strdup(name);
3870 ainfo->content_type = procmime_get_mime_type(file);
3871 if (!ainfo->content_type) {
3872 ainfo->content_type =
3873 g_strdup("application/octet-stream");
3874 ainfo->encoding = ENC_BASE64;
3875 } else if (!g_ascii_strncasecmp(ainfo->content_type, "text/", 5))
3877 procmime_get_encoding_for_text_file(file, &has_binary);
3879 ainfo->encoding = ENC_BASE64;
3880 name = g_path_get_basename(filename ? filename : file);
3881 ainfo->name = g_strdup(name);
3885 if (ainfo->name != NULL
3886 && !strcmp(ainfo->name, ".")) {
3887 g_free(ainfo->name);
3891 if (!strcmp(ainfo->content_type, "unknown") || has_binary) {
3892 g_free(ainfo->content_type);
3893 ainfo->content_type = g_strdup("application/octet-stream");
3894 g_free(ainfo->charset);
3895 ainfo->charset = NULL;
3898 ainfo->size = (goffset)size;
3899 size_text = to_human_readable((goffset)size);
3901 store = GTK_LIST_STORE(gtk_tree_view_get_model
3902 (GTK_TREE_VIEW(compose->attach_clist)));
3904 gtk_list_store_append(store, &iter);
3905 gtk_list_store_set(store, &iter,
3906 COL_MIMETYPE, ainfo->content_type,
3907 COL_SIZE, size_text,
3908 COL_NAME, ainfo->name,
3909 COL_CHARSET, ainfo->charset,
3911 COL_AUTODATA, auto_ainfo,
3914 g_auto_pointer_free(auto_ainfo);
3915 compose_attach_update_label(compose);
3919 void compose_use_signing(Compose *compose, gboolean use_signing)
3921 compose->use_signing = use_signing;
3922 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", use_signing);
3925 void compose_use_encryption(Compose *compose, gboolean use_encryption)
3927 compose->use_encryption = use_encryption;
3928 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", use_encryption);
3931 #define NEXT_PART_NOT_CHILD(info) \
3933 node = info->node; \
3934 while (node->children) \
3935 node = g_node_last_child(node); \
3936 info = procmime_mimeinfo_next((MimeInfo *)node->data); \
3939 static void compose_attach_parts(Compose *compose, MsgInfo *msginfo)
3943 MimeInfo *firsttext = NULL;
3944 MimeInfo *encrypted = NULL;
3947 const gchar *partname = NULL;
3949 mimeinfo = procmime_scan_message(msginfo);
3950 if (!mimeinfo) return;
3952 if (mimeinfo->node->children == NULL) {
3953 procmime_mimeinfo_free_all(&mimeinfo);
3957 /* find first content part */
3958 child = (MimeInfo *) mimeinfo->node->children->data;
3959 while (child && child->node->children && (child->type == MIMETYPE_MULTIPART))
3960 child = (MimeInfo *)child->node->children->data;
3963 if (child->type == MIMETYPE_TEXT) {
3965 debug_print("First text part found\n");
3966 } else if (compose->mode == COMPOSE_REEDIT &&
3967 child->type == MIMETYPE_APPLICATION &&
3968 !g_ascii_strcasecmp(child->subtype, "pgp-encrypted")) {
3969 encrypted = (MimeInfo *)child->node->parent->data;
3972 child = (MimeInfo *) mimeinfo->node->children->data;
3973 while (child != NULL) {
3976 if (child == encrypted) {
3977 /* skip this part of tree */
3978 NEXT_PART_NOT_CHILD(child);
3982 if (child->type == MIMETYPE_MULTIPART) {
3983 /* get the actual content */
3984 child = procmime_mimeinfo_next(child);
3988 if (child == firsttext) {
3989 child = procmime_mimeinfo_next(child);
3993 outfile = procmime_get_tmp_file_name(child);
3994 if ((err = procmime_get_part(outfile, child)) < 0)
3995 g_warning("Can't get the part of multipart message. (%s)", g_strerror(-err));
3997 gchar *content_type;
3999 content_type = procmime_get_content_type_str(child->type, child->subtype);
4001 /* if we meet a pgp signature, we don't attach it, but
4002 * we force signing. */
4003 if ((strcmp(content_type, "application/pgp-signature") &&
4004 strcmp(content_type, "application/pkcs7-signature") &&
4005 strcmp(content_type, "application/x-pkcs7-signature"))
4006 || compose->mode == COMPOSE_REDIRECT) {
4007 partname = procmime_mimeinfo_get_parameter(child, "filename");
4008 if (partname == NULL)
4009 partname = procmime_mimeinfo_get_parameter(child, "name");
4010 if (partname == NULL)
4012 compose_attach_append(compose, outfile,
4013 partname, content_type,
4014 procmime_mimeinfo_get_parameter(child, "charset"));
4016 compose_force_signing(compose, compose->account, NULL);
4018 g_free(content_type);
4021 NEXT_PART_NOT_CHILD(child);
4023 procmime_mimeinfo_free_all(&mimeinfo);
4026 #undef NEXT_PART_NOT_CHILD
4031 WAIT_FOR_INDENT_CHAR,
4032 WAIT_FOR_INDENT_CHAR_OR_SPACE,
4035 /* return indent length, we allow:
4036 indent characters followed by indent characters or spaces/tabs,
4037 alphabets and numbers immediately followed by indent characters,
4038 and the repeating sequences of the above
4039 If quote ends with multiple spaces, only the first one is included. */
4040 static gchar *compose_get_quote_str(GtkTextBuffer *buffer,
4041 const GtkTextIter *start, gint *len)
4043 GtkTextIter iter = *start;
4047 IndentState state = WAIT_FOR_INDENT_CHAR;
4050 gint alnum_count = 0;
4051 gint space_count = 0;
4054 if (prefs_common.quote_chars == NULL) {
4058 while (!gtk_text_iter_ends_line(&iter)) {
4059 wc = gtk_text_iter_get_char(&iter);
4060 if (g_unichar_iswide(wc))
4062 clen = g_unichar_to_utf8(wc, ch);
4066 is_indent = strchr(prefs_common.quote_chars, ch[0]) ? TRUE : FALSE;
4067 is_space = g_unichar_isspace(wc);
4069 if (state == WAIT_FOR_INDENT_CHAR) {
4070 if (!is_indent && !g_unichar_isalnum(wc))
4073 quote_len += alnum_count + space_count + 1;
4074 alnum_count = space_count = 0;
4075 state = WAIT_FOR_INDENT_CHAR_OR_SPACE;
4078 } else if (state == WAIT_FOR_INDENT_CHAR_OR_SPACE) {
4079 if (!is_indent && !is_space && !g_unichar_isalnum(wc))
4083 else if (is_indent) {
4084 quote_len += alnum_count + space_count + 1;
4085 alnum_count = space_count = 0;
4088 state = WAIT_FOR_INDENT_CHAR;
4092 gtk_text_iter_forward_char(&iter);
4095 if (quote_len > 0 && space_count > 0)
4101 if (quote_len > 0) {
4103 gtk_text_iter_forward_chars(&iter, quote_len);
4104 return gtk_text_buffer_get_text(buffer, start, &iter, FALSE);
4110 /* return >0 if the line is itemized */
4111 static int compose_itemized_length(GtkTextBuffer *buffer,
4112 const GtkTextIter *start)
4114 GtkTextIter iter = *start;
4119 if (gtk_text_iter_ends_line(&iter))
4124 wc = gtk_text_iter_get_char(&iter);
4125 if (!g_unichar_isspace(wc))
4127 gtk_text_iter_forward_char(&iter);
4128 if (gtk_text_iter_ends_line(&iter))
4132 clen = g_unichar_to_utf8(wc, ch);
4133 if (!((clen == 1 && strchr("*-+", ch[0])) ||
4135 wc == 0x2022 || /* BULLET */
4136 wc == 0x2023 || /* TRIANGULAR BULLET */
4137 wc == 0x2043 || /* HYPHEN BULLET */
4138 wc == 0x204c || /* BLACK LEFTWARDS BULLET */
4139 wc == 0x204d || /* BLACK RIGHTWARDS BULLET */
4140 wc == 0x2219 || /* BULLET OPERATOR */
4141 wc == 0x25d8 || /* INVERSE BULLET */
4142 wc == 0x25e6 || /* WHITE BULLET */
4143 wc == 0x2619 || /* REVERSED ROTATED FLORAL HEART BULLET */
4144 wc == 0x2765 || /* ROTATED HEAVY BLACK HEART BULLET */
4145 wc == 0x2767 || /* ROTATED FLORAL HEART BULLET */
4146 wc == 0x29be || /* CIRCLED WHITE BULLET */
4147 wc == 0x29bf /* CIRCLED BULLET */
4151 gtk_text_iter_forward_char(&iter);
4152 if (gtk_text_iter_ends_line(&iter))
4154 wc = gtk_text_iter_get_char(&iter);
4155 if (g_unichar_isspace(wc)) {
4161 /* return the string at the start of the itemization */
4162 static gchar * compose_get_itemized_chars(GtkTextBuffer *buffer,
4163 const GtkTextIter *start)
4165 GtkTextIter iter = *start;
4168 GString *item_chars = g_string_new("");
4171 if (gtk_text_iter_ends_line(&iter))
4176 wc = gtk_text_iter_get_char(&iter);
4177 if (!g_unichar_isspace(wc))
4179 gtk_text_iter_forward_char(&iter);
4180 if (gtk_text_iter_ends_line(&iter))
4182 g_string_append_unichar(item_chars, wc);
4185 str = item_chars->str;
4186 g_string_free(item_chars, FALSE);
4190 /* return the number of spaces at a line's start */
4191 static int compose_left_offset_length(GtkTextBuffer *buffer,
4192 const GtkTextIter *start)
4194 GtkTextIter iter = *start;
4197 if (gtk_text_iter_ends_line(&iter))
4201 wc = gtk_text_iter_get_char(&iter);
4202 if (!g_unichar_isspace(wc))
4205 gtk_text_iter_forward_char(&iter);
4206 if (gtk_text_iter_ends_line(&iter))
4210 gtk_text_iter_forward_char(&iter);
4211 if (gtk_text_iter_ends_line(&iter))
4216 static gboolean compose_get_line_break_pos(GtkTextBuffer *buffer,
4217 const GtkTextIter *start,
4218 GtkTextIter *break_pos,
4222 GtkTextIter iter = *start, line_end = *start;
4223 PangoLogAttr *attrs;
4230 gboolean can_break = FALSE;
4231 gboolean do_break = FALSE;
4232 gboolean was_white = FALSE;
4233 gboolean prev_dont_break = FALSE;
4235 gtk_text_iter_forward_to_line_end(&line_end);
4236 str = gtk_text_buffer_get_text(buffer, &iter, &line_end, FALSE);
4237 len = g_utf8_strlen(str, -1);
4241 g_warning("compose_get_line_break_pos: len = 0!");
4245 /* g_print("breaking line: %d: %s (len = %d)\n",
4246 gtk_text_iter_get_line(&iter), str, len); */
4248 attrs = g_new(PangoLogAttr, len + 1);
4250 pango_default_break(str, -1, NULL, attrs, len + 1);
4254 /* skip quote and leading spaces */
4255 for (i = 0; *p != '\0' && i < len; i++) {
4258 wc = g_utf8_get_char(p);
4259 if (i >= quote_len && !g_unichar_isspace(wc))
4261 if (g_unichar_iswide(wc))
4263 else if (*p == '\t')
4267 p = g_utf8_next_char(p);
4270 for (; *p != '\0' && i < len; i++) {
4271 PangoLogAttr *attr = attrs + i;
4275 if (attr->is_line_break && can_break && was_white && !prev_dont_break)
4278 was_white = attr->is_white;
4280 /* don't wrap URI */
4281 if ((uri_len = get_uri_len(p)) > 0) {
4283 if (pos > 0 && col > max_col) {
4293 wc = g_utf8_get_char(p);
4294 if (g_unichar_iswide(wc)) {
4296 if (prev_dont_break && can_break && attr->is_line_break)
4298 } else if (*p == '\t')
4302 if (pos > 0 && col > max_col) {
4307 if (*p == '-' || *p == '/')
4308 prev_dont_break = TRUE;
4310 prev_dont_break = FALSE;
4312 p = g_utf8_next_char(p);
4316 /* debug_print("compose_get_line_break_pos(): do_break = %d, pos = %d, col = %d\n", do_break, pos, col); */
4321 *break_pos = *start;
4322 gtk_text_iter_set_line_offset(break_pos, pos);
4327 static gboolean compose_join_next_line(Compose *compose,
4328 GtkTextBuffer *buffer,
4330 const gchar *quote_str)
4332 GtkTextIter iter_ = *iter, cur, prev, next, end;
4333 PangoLogAttr attrs[3];
4335 gchar *next_quote_str;
4338 gboolean keep_cursor = FALSE;
4340 if (!gtk_text_iter_forward_line(&iter_) ||
4341 gtk_text_iter_ends_line(&iter_)) {
4344 next_quote_str = compose_get_quote_str(buffer, &iter_, "e_len);
4346 if ((quote_str || next_quote_str) &&
4347 strcmp2(quote_str, next_quote_str) != 0) {
4348 g_free(next_quote_str);
4351 g_free(next_quote_str);
4354 if (quote_len > 0) {
4355 gtk_text_iter_forward_chars(&end, quote_len);
4356 if (gtk_text_iter_ends_line(&end)) {
4361 /* don't join itemized lines */
4362 if (compose_itemized_length(buffer, &end) > 0) {
4366 /* don't join signature separator */
4367 if (compose_is_sig_separator(compose, buffer, &iter_)) {
4370 /* delete quote str */
4372 gtk_text_buffer_delete(buffer, &iter_, &end);
4374 /* don't join line breaks put by the user */
4376 gtk_text_iter_backward_char(&cur);
4377 if (gtk_text_iter_has_tag(&cur, compose->no_join_tag)) {
4378 gtk_text_iter_forward_char(&cur);
4382 gtk_text_iter_forward_char(&cur);
4383 /* delete linebreak and extra spaces */
4384 while (gtk_text_iter_backward_char(&cur)) {
4385 wc1 = gtk_text_iter_get_char(&cur);
4386 if (!g_unichar_isspace(wc1))
4391 while (!gtk_text_iter_ends_line(&cur)) {
4392 wc1 = gtk_text_iter_get_char(&cur);
4393 if (!g_unichar_isspace(wc1))
4395 gtk_text_iter_forward_char(&cur);
4398 if (!gtk_text_iter_equal(&prev, &next)) {
4401 mark = gtk_text_buffer_get_insert(buffer);
4402 gtk_text_buffer_get_iter_at_mark(buffer, &cur, mark);
4403 if (gtk_text_iter_equal(&prev, &cur))
4405 gtk_text_buffer_delete(buffer, &prev, &next);
4409 /* insert space if required */
4410 gtk_text_iter_backward_char(&prev);
4411 wc1 = gtk_text_iter_get_char(&prev);
4412 wc2 = gtk_text_iter_get_char(&next);
4413 gtk_text_iter_forward_char(&next);
4414 str = gtk_text_buffer_get_text(buffer, &prev, &next, FALSE);
4415 pango_default_break(str, -1, NULL, attrs, 3);
4416 if (!attrs[1].is_line_break ||
4417 (!g_unichar_iswide(wc1) || !g_unichar_iswide(wc2))) {
4418 gtk_text_buffer_insert(buffer, &iter_, " ", 1);
4420 gtk_text_iter_backward_char(&iter_);
4421 gtk_text_buffer_place_cursor(buffer, &iter_);
4430 #define ADD_TXT_POS(bp_, ep_, pti_) \
4431 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
4432 last = last->next; \
4433 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
4434 last->next = NULL; \
4436 g_warning("alloc error scanning URIs"); \
4439 static gboolean compose_beautify_paragraph(Compose *compose, GtkTextIter *par_iter, gboolean force)
4441 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4442 GtkTextBuffer *buffer;
4443 GtkTextIter iter, break_pos, end_of_line;
4444 gchar *quote_str = NULL;
4446 gboolean wrap_quote = force || prefs_common.linewrap_quote;
4447 gboolean prev_autowrap = compose->autowrap;
4448 gint startq_offset = -1, noq_offset = -1;
4449 gint uri_start = -1, uri_stop = -1;
4450 gint nouri_start = -1, nouri_stop = -1;
4451 gint num_blocks = 0;
4452 gint quotelevel = -1;
4453 gboolean modified = force;
4454 gboolean removed = FALSE;
4455 gboolean modified_before_remove = FALSE;
4457 gboolean start = TRUE;
4458 gint itemized_len = 0, rem_item_len = 0;
4459 gchar *itemized_chars = NULL;
4460 gboolean item_continuation = FALSE;
4465 if (compose->draft_timeout_tag == COMPOSE_DRAFT_TIMEOUT_FORBIDDEN) {
4469 compose->autowrap = FALSE;
4471 buffer = gtk_text_view_get_buffer(text);
4472 undo_wrapping(compose->undostruct, TRUE);
4477 mark = gtk_text_buffer_get_insert(buffer);
4478 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
4482 if (compose->draft_timeout_tag == COMPOSE_DRAFT_TIMEOUT_FORBIDDEN) {
4483 if (gtk_text_iter_ends_line(&iter)) {
4484 while (gtk_text_iter_ends_line(&iter) &&
4485 gtk_text_iter_forward_line(&iter))
4488 while (gtk_text_iter_backward_line(&iter)) {
4489 if (gtk_text_iter_ends_line(&iter)) {
4490 gtk_text_iter_forward_line(&iter);
4496 /* move to line start */
4497 gtk_text_iter_set_line_offset(&iter, 0);
4500 itemized_len = compose_itemized_length(buffer, &iter);
4502 if (!itemized_len) {
4503 itemized_len = compose_left_offset_length(buffer, &iter);
4504 item_continuation = TRUE;
4508 itemized_chars = compose_get_itemized_chars(buffer, &iter);
4510 /* go until paragraph end (empty line) */
4511 while (start || !gtk_text_iter_ends_line(&iter)) {
4512 gchar *scanpos = NULL;
4513 /* parse table - in order of priority */
4515 const gchar *needle; /* token */
4517 /* token search function */
4518 gchar *(*search) (const gchar *haystack,
4519 const gchar *needle);
4520 /* part parsing function */
4521 gboolean (*parse) (const gchar *start,
4522 const gchar *scanpos,
4526 /* part to URI function */
4527 gchar *(*build_uri) (const gchar *bp,
4531 static struct table parser[] = {
4532 {"http://", strcasestr, get_uri_part, make_uri_string},
4533 {"https://", strcasestr, get_uri_part, make_uri_string},
4534 {"ftp://", strcasestr, get_uri_part, make_uri_string},
4535 {"sftp://", strcasestr, get_uri_part, make_uri_string},
4536 {"gopher://",strcasestr, get_uri_part, make_uri_string},
4537 {"www.", strcasestr, get_uri_part, make_http_string},
4538 {"mailto:", strcasestr, get_uri_part, make_uri_string},
4539 {"@", strcasestr, get_email_part, make_email_string}
4541 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
4542 gint last_index = PARSE_ELEMS;
4544 gchar *o_walk = NULL, *walk = NULL, *bp = NULL, *ep = NULL;
4548 if (!prev_autowrap && num_blocks == 0) {
4550 g_signal_handlers_block_by_func(G_OBJECT(buffer),
4551 G_CALLBACK(text_inserted),
4554 if (gtk_text_iter_has_tag(&iter, compose->no_wrap_tag) && !force)
4557 uri_start = uri_stop = -1;
4559 quote_str = compose_get_quote_str(buffer, &iter, "e_len);
4562 /* debug_print("compose_beautify_paragraph(): quote_str = '%s'\n", quote_str); */
4563 if (startq_offset == -1)
4564 startq_offset = gtk_text_iter_get_offset(&iter);
4565 quotelevel = get_quote_level(quote_str, prefs_common.quote_chars);
4566 if (quotelevel > 2) {
4567 /* recycle colors */
4568 if (prefs_common.recycle_quote_colors)
4577 if (startq_offset == -1)
4578 noq_offset = gtk_text_iter_get_offset(&iter);
4582 if (prev_autowrap == FALSE && !force && !wrap_quote) {
4585 if (gtk_text_iter_ends_line(&iter)) {
4587 } else if (compose_get_line_break_pos(buffer, &iter, &break_pos,
4588 prefs_common.linewrap_len,
4590 GtkTextIter prev, next, cur;
4591 if (prev_autowrap != FALSE || force) {
4592 compose->automatic_break = TRUE;
4594 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4595 compose->automatic_break = FALSE;
4596 if (itemized_len && compose->autoindent) {
4597 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4598 if (!item_continuation)
4599 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4601 } else if (quote_str && wrap_quote) {
4602 compose->automatic_break = TRUE;
4604 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4605 compose->automatic_break = FALSE;
4606 if (itemized_len && compose->autoindent) {
4607 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4608 if (!item_continuation)
4609 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4613 /* remove trailing spaces */
4615 rem_item_len = itemized_len;
4616 while (compose->autoindent && rem_item_len-- > 0)
4617 gtk_text_iter_backward_char(&cur);
4618 gtk_text_iter_backward_char(&cur);
4621 while (!gtk_text_iter_starts_line(&cur)) {
4624 gtk_text_iter_backward_char(&cur);
4625 wc = gtk_text_iter_get_char(&cur);
4626 if (!g_unichar_isspace(wc))
4630 if (!gtk_text_iter_equal(&prev, &next)) {
4631 gtk_text_buffer_delete(buffer, &prev, &next);
4633 gtk_text_iter_forward_char(&break_pos);
4637 gtk_text_buffer_insert(buffer, &break_pos,
4641 modified |= compose_join_next_line(compose, buffer, &iter, quote_str);
4643 /* move iter to current line start */
4644 gtk_text_iter_set_line_offset(&iter, 0);
4651 /* move iter to next line start */
4657 if (!prev_autowrap && num_blocks > 0) {
4659 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
4660 G_CALLBACK(text_inserted),
4664 while (!gtk_text_iter_ends_line(&end_of_line)) {
4665 gtk_text_iter_forward_char(&end_of_line);
4667 o_walk = walk = gtk_text_buffer_get_text(buffer, &iter, &end_of_line, FALSE);
4669 nouri_start = gtk_text_iter_get_offset(&iter);
4670 nouri_stop = gtk_text_iter_get_offset(&end_of_line);
4672 walk_pos = gtk_text_iter_get_offset(&iter);
4673 /* FIXME: this looks phony. scanning for anything in the parse table */
4674 for (n = 0; n < PARSE_ELEMS; n++) {
4677 tmp = parser[n].search(walk, parser[n].needle);
4679 if (scanpos == NULL || tmp < scanpos) {
4688 /* check if URI can be parsed */
4689 if (parser[last_index].parse(walk, scanpos, (const gchar **)&bp,
4690 (const gchar **)&ep, FALSE)
4691 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
4695 strlen(parser[last_index].needle);
4698 uri_start = walk_pos + (bp - o_walk);
4699 uri_stop = walk_pos + (ep - o_walk);
4703 gtk_text_iter_forward_line(&iter);
4706 if (startq_offset != -1) {
4707 GtkTextIter startquote, endquote;
4708 gtk_text_buffer_get_iter_at_offset(
4709 buffer, &startquote, startq_offset);
4712 switch (quotelevel) {
4714 if (!gtk_text_iter_has_tag(&startquote, compose->quote0_tag) ||
4715 !gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) {
4716 gtk_text_buffer_apply_tag_by_name(
4717 buffer, "quote0", &startquote, &endquote);
4718 gtk_text_buffer_remove_tag_by_name(
4719 buffer, "quote1", &startquote, &endquote);
4720 gtk_text_buffer_remove_tag_by_name(
4721 buffer, "quote2", &startquote, &endquote);
4726 if (!gtk_text_iter_has_tag(&startquote, compose->quote1_tag) ||
4727 !gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) {
4728 gtk_text_buffer_apply_tag_by_name(
4729 buffer, "quote1", &startquote, &endquote);
4730 gtk_text_buffer_remove_tag_by_name(
4731 buffer, "quote0", &startquote, &endquote);
4732 gtk_text_buffer_remove_tag_by_name(
4733 buffer, "quote2", &startquote, &endquote);
4738 if (!gtk_text_iter_has_tag(&startquote, compose->quote2_tag) ||
4739 !gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag)) {
4740 gtk_text_buffer_apply_tag_by_name(
4741 buffer, "quote2", &startquote, &endquote);
4742 gtk_text_buffer_remove_tag_by_name(
4743 buffer, "quote0", &startquote, &endquote);
4744 gtk_text_buffer_remove_tag_by_name(
4745 buffer, "quote1", &startquote, &endquote);
4751 } else if (noq_offset != -1) {
4752 GtkTextIter startnoquote, endnoquote;
4753 gtk_text_buffer_get_iter_at_offset(
4754 buffer, &startnoquote, noq_offset);
4757 if ((gtk_text_iter_has_tag(&startnoquote, compose->quote0_tag)
4758 && gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) ||
4759 (gtk_text_iter_has_tag(&startnoquote, compose->quote1_tag)
4760 && gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) ||
4761 (gtk_text_iter_has_tag(&startnoquote, compose->quote2_tag)
4762 && gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag))) {
4763 gtk_text_buffer_remove_tag_by_name(
4764 buffer, "quote0", &startnoquote, &endnoquote);
4765 gtk_text_buffer_remove_tag_by_name(
4766 buffer, "quote1", &startnoquote, &endnoquote);
4767 gtk_text_buffer_remove_tag_by_name(
4768 buffer, "quote2", &startnoquote, &endnoquote);
4774 if (uri_start != nouri_start && uri_stop != nouri_stop) {
4775 GtkTextIter nouri_start_iter, nouri_end_iter;
4776 gtk_text_buffer_get_iter_at_offset(
4777 buffer, &nouri_start_iter, nouri_start);
4778 gtk_text_buffer_get_iter_at_offset(
4779 buffer, &nouri_end_iter, nouri_stop);
4780 if (gtk_text_iter_has_tag(&nouri_start_iter, compose->uri_tag) &&
4781 gtk_text_iter_has_tag(&nouri_end_iter, compose->uri_tag)) {
4782 gtk_text_buffer_remove_tag_by_name(
4783 buffer, "link", &nouri_start_iter, &nouri_end_iter);
4784 modified_before_remove = modified;
4789 if (uri_start >= 0 && uri_stop > 0) {
4790 GtkTextIter uri_start_iter, uri_end_iter, back;
4791 gtk_text_buffer_get_iter_at_offset(
4792 buffer, &uri_start_iter, uri_start);
4793 gtk_text_buffer_get_iter_at_offset(
4794 buffer, &uri_end_iter, uri_stop);
4795 back = uri_end_iter;
4796 gtk_text_iter_backward_char(&back);
4797 if (!gtk_text_iter_has_tag(&uri_start_iter, compose->uri_tag) ||
4798 !gtk_text_iter_has_tag(&back, compose->uri_tag)) {
4799 gtk_text_buffer_apply_tag_by_name(
4800 buffer, "link", &uri_start_iter, &uri_end_iter);
4802 if (removed && !modified_before_remove) {
4808 /* debug_print("not modified, out after %d lines\n", lines); */
4812 /* debug_print("modified, out after %d lines\n", lines); */
4814 g_free(itemized_chars);
4817 undo_wrapping(compose->undostruct, FALSE);
4818 compose->autowrap = prev_autowrap;
4823 void compose_action_cb(void *data)
4825 Compose *compose = (Compose *)data;
4826 compose_wrap_all(compose);
4829 static void compose_wrap_all(Compose *compose)
4831 compose_wrap_all_full(compose, FALSE);
4834 static void compose_wrap_all_full(Compose *compose, gboolean force)
4836 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4837 GtkTextBuffer *buffer;
4839 gboolean modified = TRUE;
4841 buffer = gtk_text_view_get_buffer(text);
4843 gtk_text_buffer_get_start_iter(buffer, &iter);
4845 undo_wrapping(compose->undostruct, TRUE);
4847 while (!gtk_text_iter_is_end(&iter) && modified)
4848 modified = compose_beautify_paragraph(compose, &iter, force);
4850 undo_wrapping(compose->undostruct, FALSE);
4854 static void compose_set_title(Compose *compose)
4860 edited = compose->modified ? _(" [Edited]") : "";
4862 subject = gtk_editable_get_chars(
4863 GTK_EDITABLE(compose->subject_entry), 0, -1);
4865 #ifndef GENERIC_UMPC
4866 if (subject && strlen(subject))
4867 str = g_strdup_printf(_("%s - Compose message%s"),
4870 str = g_strdup_printf(_("[no subject] - Compose message%s"), edited);
4872 str = g_strdup(_("Compose message"));
4875 gtk_window_set_title(GTK_WINDOW(compose->window), str);
4881 * compose_current_mail_account:
4883 * Find a current mail account (the currently selected account, or the
4884 * default account, if a news account is currently selected). If a
4885 * mail account cannot be found, display an error message.
4887 * Return value: Mail account, or NULL if not found.
4889 static PrefsAccount *
4890 compose_current_mail_account(void)
4894 if (cur_account && cur_account->protocol != A_NNTP)
4897 ac = account_get_default();
4898 if (!ac || ac->protocol == A_NNTP) {
4899 alertpanel_error(_("Account for sending mail is not specified.\n"
4900 "Please select a mail account before sending."));
4907 #define QUOTE_IF_REQUIRED(out, str) \
4909 if (*str != '"' && strpbrk(str, ",.:;[]<>()@\\\"")) { \
4913 len = strlen(str) + 3; \
4914 if ((__tmp = alloca(len)) == NULL) { \
4915 g_warning("can't allocate memory"); \
4916 g_string_free(header, TRUE); \
4919 g_snprintf(__tmp, len, "\"%s\"", str); \
4924 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4925 g_warning("can't allocate memory"); \
4926 g_string_free(header, TRUE); \
4929 strcpy(__tmp, str); \
4935 #define QUOTE_IF_REQUIRED_NORMAL(out, str, errret) \
4937 if (*str != '"' && strpbrk(str, ",.:;[]<>()@\\\"")) { \
4941 len = strlen(str) + 3; \
4942 if ((__tmp = alloca(len)) == NULL) { \
4943 g_warning("can't allocate memory"); \
4946 g_snprintf(__tmp, len, "\"%s\"", str); \
4951 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4952 g_warning("can't allocate memory"); \
4955 strcpy(__tmp, str); \
4961 static void compose_select_account(Compose *compose, PrefsAccount *account,
4964 gchar *from = NULL, *header = NULL;
4965 ComposeHeaderEntry *header_entry;
4966 #if GTK_CHECK_VERSION(2, 24, 0)
4970 cm_return_if_fail(account != NULL);
4972 compose->account = account;
4973 if (account->name && *account->name) {
4975 QUOTE_IF_REQUIRED_NORMAL(buf, account->name, return);
4976 qbuf = escape_internal_quotes(buf, '"');
4977 from = g_strdup_printf("%s <%s>",
4978 qbuf, account->address);
4981 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4983 from = g_strdup_printf("<%s>",
4985 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4990 compose_set_title(compose);
4992 if (account->default_sign && compose->mode != COMPOSE_REDIRECT)
4993 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", TRUE);
4995 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", FALSE);
4996 if (account->default_encrypt && compose->mode != COMPOSE_REDIRECT)
4997 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", TRUE);
4999 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", FALSE);
5001 activate_privacy_system(compose, account, FALSE);
5003 if (!init && compose->mode != COMPOSE_REDIRECT) {
5004 undo_block(compose->undostruct);
5005 compose_insert_sig(compose, TRUE);
5006 undo_unblock(compose->undostruct);
5009 header_entry = (ComposeHeaderEntry *) compose->header_list->data;
5010 #if !GTK_CHECK_VERSION(2, 24, 0)
5011 header = gtk_combo_box_get_active_text(GTK_COMBO_BOX(header_entry->combo));
5013 if (gtk_combo_box_get_active_iter(GTK_COMBO_BOX(header_entry->combo), &iter))
5014 gtk_tree_model_get(gtk_combo_box_get_model(GTK_COMBO_BOX(
5015 header_entry->combo)), &iter, COMBOBOX_TEXT, &header, -1);
5018 if (header && !strlen(gtk_entry_get_text(GTK_ENTRY(header_entry->entry)))) {
5019 if (account->protocol == A_NNTP) {
5020 if (!strcmp(header, _("To:")))
5021 combobox_select_by_text(
5022 GTK_COMBO_BOX(header_entry->combo),
5025 if (!strcmp(header, _("Newsgroups:")))
5026 combobox_select_by_text(
5027 GTK_COMBO_BOX(header_entry->combo),
5035 /* use account's dict info if set */
5036 if (compose->gtkaspell) {
5037 if (account->enable_default_dictionary)
5038 gtkaspell_change_dict(compose->gtkaspell,
5039 account->default_dictionary, FALSE);
5040 if (account->enable_default_alt_dictionary)
5041 gtkaspell_change_alt_dict(compose->gtkaspell,
5042 account->default_alt_dictionary);
5043 if (account->enable_default_dictionary
5044 || account->enable_default_alt_dictionary)
5045 compose_spell_menu_changed(compose);
5050 gboolean compose_check_for_valid_recipient(Compose *compose) {
5051 gchar *recipient_headers_mail[] = {"To:", "Cc:", "Bcc:", NULL};
5052 gchar *recipient_headers_news[] = {"Newsgroups:", NULL};
5053 gboolean recipient_found = FALSE;
5057 /* free to and newsgroup list */
5058 slist_free_strings_full(compose->to_list);
5059 compose->to_list = NULL;
5061 slist_free_strings_full(compose->newsgroup_list);
5062 compose->newsgroup_list = NULL;
5064 /* search header entries for to and newsgroup entries */
5065 for (list = compose->header_list; list; list = list->next) {
5068 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5069 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5072 if (entry[0] != '\0') {
5073 for (strptr = recipient_headers_mail; *strptr != NULL; strptr++) {
5074 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
5075 compose->to_list = address_list_append(compose->to_list, entry);
5076 recipient_found = TRUE;
5079 for (strptr = recipient_headers_news; *strptr != NULL; strptr++) {
5080 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
5081 compose->newsgroup_list = newsgroup_list_append(compose->newsgroup_list, entry);
5082 recipient_found = TRUE;
5089 return recipient_found;
5092 static gboolean compose_check_for_set_recipients(Compose *compose)
5094 if (compose->account->set_autocc && compose->account->auto_cc) {
5095 gboolean found_other = FALSE;
5097 /* search header entries for to and newsgroup entries */
5098 for (list = compose->header_list; list; list = list->next) {
5101 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5102 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5105 if (strcmp(entry, compose->account->auto_cc)
5106 || strcmp(header, prefs_common_translated_header_name("Cc:"))) {
5117 if (compose->batch) {
5118 gtk_widget_show_all(compose->window);
5120 text = g_strdup_printf(_("The only recipient is the default '%s' address. Send anyway?"),
5121 prefs_common_translated_header_name("Cc"));
5122 aval = alertpanel(_("Send"),
5124 GTK_STOCK_CANCEL, g_strconcat("+", _("_Send"), NULL), NULL);
5126 if (aval != G_ALERTALTERNATE)
5130 if (compose->account->set_autobcc && compose->account->auto_bcc) {
5131 gboolean found_other = FALSE;
5133 /* search header entries for to and newsgroup entries */
5134 for (list = compose->header_list; list; list = list->next) {
5137 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5138 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5141 if (strcmp(entry, compose->account->auto_bcc)
5142 || strcmp(header, prefs_common_translated_header_name("Bcc:"))) {
5154 if (compose->batch) {
5155 gtk_widget_show_all(compose->window);
5157 text = g_strdup_printf(_("The only recipient is the default '%s' address. Send anyway?"),
5158 prefs_common_translated_header_name("Bcc"));
5159 aval = alertpanel(_("Send"),
5161 GTK_STOCK_CANCEL, g_strconcat("+", _("_Send"), NULL), NULL);
5163 if (aval != G_ALERTALTERNATE)
5170 static gboolean compose_check_entries(Compose *compose, gboolean check_everything)
5174 if (compose_check_for_valid_recipient(compose) == FALSE) {
5175 if (compose->batch) {
5176 gtk_widget_show_all(compose->window);
5178 alertpanel_error(_("Recipient is not specified."));
5182 if (compose_check_for_set_recipients(compose) == FALSE) {
5186 if (!compose->batch && prefs_common.warn_empty_subj == TRUE) {
5187 str = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5188 if (*str == '\0' && check_everything == TRUE &&
5189 compose->mode != COMPOSE_REDIRECT) {
5191 gchar *button_label;
5194 if (compose->sending)
5195 button_label = g_strconcat("+", _("_Send"), NULL);
5197 button_label = g_strconcat("+", _("_Queue"), NULL);
5198 message = g_strdup_printf(_("Subject is empty. %s"),
5199 compose->sending?_("Send it anyway?"):
5200 _("Queue it anyway?"));
5202 aval = alertpanel_full(compose->sending?_("Send"):_("Send later"), message,
5203 GTK_STOCK_CANCEL, button_label, NULL, TRUE, NULL,
5204 ALERT_QUESTION, G_ALERTDEFAULT);
5206 g_free(button_label);
5207 if (aval & G_ALERTDISABLE) {
5208 aval &= ~G_ALERTDISABLE;
5209 prefs_common.warn_empty_subj = FALSE;
5211 if (aval != G_ALERTALTERNATE)
5216 if (!compose->batch && prefs_common.warn_sending_many_recipients_num > 0
5217 && check_everything == TRUE) {
5221 /* count To and Cc recipients */
5222 for (list = compose->header_list; list; list = list->next) {
5226 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5227 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5230 if ((entry[0] != '\0') &&
5231 (!strcmp(header, prefs_common_translated_header_name("To:")) ||
5232 !strcmp(header, prefs_common_translated_header_name("Cc:")))) {
5238 if (cnt > prefs_common.warn_sending_many_recipients_num) {
5240 gchar *button_label;
5243 if (compose->sending)
5244 button_label = g_strconcat("+", _("_Send"), NULL);
5246 button_label = g_strconcat("+", _("_Queue"), NULL);
5247 message = g_strdup_printf(_("Sending to %d recipients. %s"), cnt,
5248 compose->sending?_("Send it anyway?"):
5249 _("Queue it anyway?"));
5251 aval = alertpanel_full(compose->sending?_("Send"):_("Send later"), message,
5252 GTK_STOCK_CANCEL, button_label, NULL, TRUE, NULL,
5253 ALERT_QUESTION, G_ALERTDEFAULT);
5255 if (aval & G_ALERTDISABLE) {
5256 aval &= ~G_ALERTDISABLE;
5257 prefs_common.warn_sending_many_recipients_num = 0;
5259 if (aval != G_ALERTALTERNATE)
5264 if (check_everything && hooks_invoke(COMPOSE_CHECK_BEFORE_SEND_HOOKLIST, compose))
5270 gint compose_send(Compose *compose)
5273 FolderItem *folder = NULL;
5275 gchar *msgpath = NULL;
5276 gboolean discard_window = FALSE;
5277 gchar *errstr = NULL;
5278 gchar *tmsgid = NULL;
5279 MainWindow *mainwin = mainwindow_get_mainwindow();
5280 gboolean queued_removed = FALSE;
5282 if (prefs_common.send_dialog_invisible
5283 || compose->batch == TRUE)
5284 discard_window = TRUE;
5286 compose_allow_user_actions (compose, FALSE);
5287 compose->sending = TRUE;
5289 if (compose_check_entries(compose, TRUE) == FALSE) {
5290 if (compose->batch) {
5291 gtk_widget_show_all(compose->window);
5297 val = compose_queue(compose, &msgnum, &folder, &msgpath, TRUE);
5300 if (compose->batch) {
5301 gtk_widget_show_all(compose->window);
5304 alertpanel_error(_("Could not queue message for sending:\n\n"
5305 "Charset conversion failed."));
5306 } else if (val == -5) {
5307 alertpanel_error(_("Could not queue message for sending:\n\n"
5308 "Couldn't get recipient encryption key."));
5309 } else if (val == -6) {
5311 } else if (val == -3) {
5312 if (privacy_peek_error())
5313 alertpanel_error(_("Could not queue message for sending:\n\n"
5314 "Signature failed: %s"), privacy_get_error());
5315 } else if (val == -2 && errno != 0) {
5316 alertpanel_error(_("Could not queue message for sending:\n\n%s."), g_strerror(errno));
5318 alertpanel_error(_("Could not queue message for sending."));
5323 tmsgid = compose->msgid ? g_strdup(compose->msgid) : NULL;
5324 if (discard_window) {
5325 compose->sending = FALSE;
5326 compose_close(compose);
5327 /* No more compose access in the normal codepath
5328 * after this point! */
5333 alertpanel_error(_("The message was queued but could not be "
5334 "sent.\nUse \"Send queued messages\" from "
5335 "the main window to retry."));
5336 if (!discard_window) {
5343 if (msgpath == NULL) {
5344 msgpath = folder_item_fetch_msg(folder, msgnum);
5345 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5348 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5349 claws_unlink(msgpath);
5352 if (!discard_window) {
5354 if (!queued_removed)
5355 folder_item_remove_msg(folder, msgnum);
5356 folder_item_scan(folder);
5358 /* make sure we delete that */
5359 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5361 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5362 folder_item_remove_msg(folder, tmp->msgnum);
5363 procmsg_msginfo_free(&tmp);
5370 if (!queued_removed)
5371 folder_item_remove_msg(folder, msgnum);
5372 folder_item_scan(folder);
5374 /* make sure we delete that */
5375 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5377 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5378 folder_item_remove_msg(folder, tmp->msgnum);
5379 procmsg_msginfo_free(&tmp);
5382 if (!discard_window) {
5383 compose->sending = FALSE;
5384 compose_allow_user_actions (compose, TRUE);
5385 compose_close(compose);
5389 alertpanel_error_log(_("%s\nUse \"Send queued messages\" from "
5390 "the main window to retry."), errstr);
5393 alertpanel_error_log(_("The message was queued but could not be "
5394 "sent.\nUse \"Send queued messages\" from "
5395 "the main window to retry."));
5397 if (!discard_window) {
5406 toolbar_main_set_sensitive(mainwin);
5407 main_window_set_menu_sensitive(mainwin);
5413 compose_allow_user_actions (compose, TRUE);
5414 compose->sending = FALSE;
5415 compose->modified = TRUE;
5416 toolbar_main_set_sensitive(mainwin);
5417 main_window_set_menu_sensitive(mainwin);
5422 static gboolean compose_use_attach(Compose *compose)
5424 GtkTreeModel *model = gtk_tree_view_get_model
5425 (GTK_TREE_VIEW(compose->attach_clist));
5426 return gtk_tree_model_iter_n_children(model, NULL) > 0;
5429 static gint compose_redirect_write_headers_from_headerlist(Compose *compose,
5432 gchar buf[BUFFSIZE];
5434 gboolean first_to_address;
5435 gboolean first_cc_address;
5437 ComposeHeaderEntry *headerentry;
5438 const gchar *headerentryname;
5439 const gchar *cc_hdr;
5440 const gchar *to_hdr;
5441 gboolean err = FALSE;
5443 debug_print("Writing redirect header\n");
5445 cc_hdr = prefs_common_translated_header_name("Cc:");
5446 to_hdr = prefs_common_translated_header_name("To:");
5448 first_to_address = TRUE;
5449 for (list = compose->header_list; list; list = list->next) {
5450 headerentry = ((ComposeHeaderEntry *)list->data);
5451 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5453 if (g_utf8_collate(headerentryname, to_hdr) == 0) {
5454 const gchar *entstr = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5455 Xstrdup_a(str, entstr, return -1);
5457 if (str[0] != '\0') {
5458 compose_convert_header
5459 (compose, buf, sizeof(buf), str,
5460 strlen("Resent-To") + 2, TRUE);
5462 if (first_to_address) {
5463 err |= (fprintf(fp, "Resent-To: ") < 0);
5464 first_to_address = FALSE;
5466 err |= (fprintf(fp, ",") < 0);
5468 err |= (fprintf(fp, "%s", buf) < 0);
5472 if (!first_to_address) {
5473 err |= (fprintf(fp, "\n") < 0);
5476 first_cc_address = TRUE;
5477 for (list = compose->header_list; list; list = list->next) {
5478 headerentry = ((ComposeHeaderEntry *)list->data);
5479 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5481 if (g_utf8_collate(headerentryname, cc_hdr) == 0) {
5482 const gchar *strg = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5483 Xstrdup_a(str, strg, return -1);
5485 if (str[0] != '\0') {
5486 compose_convert_header
5487 (compose, buf, sizeof(buf), str,
5488 strlen("Resent-Cc") + 2, TRUE);
5490 if (first_cc_address) {
5491 err |= (fprintf(fp, "Resent-Cc: ") < 0);
5492 first_cc_address = FALSE;
5494 err |= (fprintf(fp, ",") < 0);
5496 err |= (fprintf(fp, "%s", buf) < 0);
5500 if (!first_cc_address) {
5501 err |= (fprintf(fp, "\n") < 0);
5504 return (err ? -1:0);
5507 static gint compose_redirect_write_headers(Compose *compose, FILE *fp)
5509 gchar date[RFC822_DATE_BUFFSIZE];
5510 gchar buf[BUFFSIZE];
5512 const gchar *entstr;
5513 /* struct utsname utsbuf; */
5514 gboolean err = FALSE;
5516 cm_return_val_if_fail(fp != NULL, -1);
5517 cm_return_val_if_fail(compose->account != NULL, -1);
5518 cm_return_val_if_fail(compose->account->address != NULL, -1);
5521 if (prefs_common.hide_timezone)
5522 get_rfc822_date_hide_tz(date, sizeof(date));
5524 get_rfc822_date(date, sizeof(date));
5525 err |= (fprintf(fp, "Resent-Date: %s\n", date) < 0);
5528 if (compose->account->name && *compose->account->name) {
5529 compose_convert_header
5530 (compose, buf, sizeof(buf), compose->account->name,
5531 strlen("From: "), TRUE);
5532 err |= (fprintf(fp, "Resent-From: %s <%s>\n",
5533 buf, compose->account->address) < 0);
5535 err |= (fprintf(fp, "Resent-From: %s\n", compose->account->address) < 0);
5538 entstr = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5539 if (*entstr != '\0') {
5540 Xstrdup_a(str, entstr, return -1);
5543 compose_convert_header(compose, buf, sizeof(buf), str,
5544 strlen("Subject: "), FALSE);
5545 err |= (fprintf(fp, "Subject: %s\n", buf) < 0);
5549 /* Resent-Message-ID */
5550 if (compose->account->gen_msgid) {
5551 gchar *addr = prefs_account_generate_msgid(compose->account);
5552 err |= (fprintf(fp, "Resent-Message-ID: <%s>\n", addr) < 0);
5554 g_free(compose->msgid);
5555 compose->msgid = addr;
5557 compose->msgid = NULL;
5560 if (compose_redirect_write_headers_from_headerlist(compose, fp))
5563 /* separator between header and body */
5564 err |= (fputs("\n", fp) == EOF);
5566 return (err ? -1:0);
5569 static gint compose_redirect_write_to_file(Compose *compose, FILE *fdest)
5574 gchar rewrite_buf[BUFFSIZE];
5576 gboolean skip = FALSE;
5577 gboolean err = FALSE;
5578 gchar *not_included[]={
5579 "Return-Path:", "Delivered-To:", "Received:",
5580 "Subject:", "X-UIDL:", "AF:",
5581 "NF:", "PS:", "SRH:",
5582 "SFN:", "DSR:", "MID:",
5583 "CFG:", "PT:", "S:",
5584 "RQ:", "SSV:", "NSV:",
5585 "SSH:", "R:", "MAID:",
5586 "NAID:", "RMID:", "FMID:",
5587 "SCF:", "RRCPT:", "NG:",
5588 "X-Claws-Privacy", "X-Claws-Sign:", "X-Claws-Encrypt",
5589 "X-Claws-End-Special-Headers:", "X-Claws-Account-Id:",
5590 "X-Sylpheed-Privacy", "X-Sylpheed-Sign:", "X-Sylpheed-Encrypt",
5591 "X-Sylpheed-End-Special-Headers:", "X-Sylpheed-Account-Id:",
5592 "X-Claws-Auto-Wrapping:", "X-Claws-Auto-Indent:",
5597 if ((fp = g_fopen(compose->redirect_filename, "rb")) == NULL) {
5598 FILE_OP_ERROR(compose->redirect_filename, "fopen");
5602 while ((ret = procheader_get_one_field_asis(&buf, fp)) != -1) {
5604 for (i = 0; not_included[i] != NULL; i++) {
5605 if (g_ascii_strncasecmp(buf, not_included[i],
5606 strlen(not_included[i])) == 0) {
5616 if (fputs(buf, fdest) == -1) {
5622 if (!prefs_common.redirect_keep_from) {
5623 if (g_ascii_strncasecmp(buf, "From:",
5624 strlen("From:")) == 0) {
5625 err |= (fputs(" (by way of ", fdest) == EOF);
5626 if (compose->account->name
5627 && *compose->account->name) {
5628 gchar buffer[BUFFSIZE];
5630 compose_convert_header
5631 (compose, buffer, sizeof(buffer),
5632 compose->account->name,
5635 err |= (fprintf(fdest, "%s <%s>",
5637 compose->account->address) < 0);
5639 err |= (fprintf(fdest, "%s",
5640 compose->account->address) < 0);
5641 err |= (fputs(")", fdest) == EOF);
5647 if (fputs("\n", fdest) == -1)
5654 if (compose_redirect_write_headers(compose, fdest))
5657 while ((len = fread(rewrite_buf, sizeof(gchar), sizeof(rewrite_buf), fp)) > 0) {
5658 if (fwrite(rewrite_buf, sizeof(gchar), len, fdest) != len)
5672 static gint compose_write_to_file(Compose *compose, FILE *fp, gint action, gboolean attach_parts)
5674 GtkTextBuffer *buffer;
5675 GtkTextIter start, end, tmp;
5676 gchar *chars, *tmp_enc_file, *content;
5678 const gchar *out_codeset;
5679 EncodingType encoding = ENC_UNKNOWN;
5680 MimeInfo *mimemsg, *mimetext;
5682 const gchar *src_codeset = CS_INTERNAL;
5683 gchar *from_addr = NULL;
5684 gchar *from_name = NULL;
5687 if (action == COMPOSE_WRITE_FOR_SEND) {
5688 attach_parts = TRUE;
5690 /* We're sending the message, generate a Message-ID
5692 if (compose->msgid == NULL &&
5693 compose->account->gen_msgid) {
5694 compose->msgid = prefs_account_generate_msgid(compose->account);
5698 /* create message MimeInfo */
5699 mimemsg = procmime_mimeinfo_new();
5700 mimemsg->type = MIMETYPE_MESSAGE;
5701 mimemsg->subtype = g_strdup("rfc822");
5702 mimemsg->content = MIMECONTENT_MEM;
5703 mimemsg->tmp = TRUE; /* must free content later */
5704 mimemsg->data.mem = compose_get_header(compose);
5706 /* Create text part MimeInfo */
5707 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5708 gtk_text_buffer_get_end_iter(buffer, &end);
5711 /* We make sure that there is a newline at the end. */
5712 if (action == COMPOSE_WRITE_FOR_SEND && gtk_text_iter_backward_char(&tmp)) {
5713 chars = gtk_text_buffer_get_text(buffer, &tmp, &end, FALSE);
5714 if (*chars != '\n') {
5715 gtk_text_buffer_insert(buffer, &end, "\n", 1);
5720 /* get all composed text */
5721 gtk_text_buffer_get_start_iter(buffer, &start);
5722 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5724 out_codeset = conv_get_charset_str(compose->out_encoding);
5726 if (!out_codeset && is_ascii_str(chars)) {
5727 out_codeset = CS_US_ASCII;
5728 } else if (prefs_common.outgoing_fallback_to_ascii &&
5729 is_ascii_str(chars)) {
5730 out_codeset = CS_US_ASCII;
5731 encoding = ENC_7BIT;
5735 gchar *test_conv_global_out = NULL;
5736 gchar *test_conv_reply = NULL;
5738 /* automatic mode. be automatic. */
5739 codeconv_set_strict(TRUE);
5741 out_codeset = conv_get_outgoing_charset_str();
5743 debug_print("trying to convert to %s\n", out_codeset);
5744 test_conv_global_out = conv_codeset_strdup(chars, src_codeset, out_codeset);
5747 if (!test_conv_global_out && compose->orig_charset
5748 && strcmp(compose->orig_charset, CS_US_ASCII)) {
5749 out_codeset = compose->orig_charset;
5750 debug_print("failure; trying to convert to %s\n", out_codeset);
5751 test_conv_reply = conv_codeset_strdup(chars, src_codeset, out_codeset);
5754 if (!test_conv_global_out && !test_conv_reply) {
5756 out_codeset = CS_INTERNAL;
5757 debug_print("failure; finally using %s\n", out_codeset);
5759 g_free(test_conv_global_out);
5760 g_free(test_conv_reply);
5761 codeconv_set_strict(FALSE);
5764 if (encoding == ENC_UNKNOWN) {
5765 if (prefs_common.encoding_method == CTE_BASE64)
5766 encoding = ENC_BASE64;
5767 else if (prefs_common.encoding_method == CTE_QUOTED_PRINTABLE)
5768 encoding = ENC_QUOTED_PRINTABLE;
5769 else if (prefs_common.encoding_method == CTE_8BIT)
5770 encoding = ENC_8BIT;
5772 encoding = procmime_get_encoding_for_charset(out_codeset);
5775 debug_print("src encoding = %s, out encoding = %s, transfer encoding = %s\n",
5776 src_codeset, out_codeset, procmime_get_encoding_str(encoding));
5778 if (action == COMPOSE_WRITE_FOR_SEND) {
5779 codeconv_set_strict(TRUE);
5780 buf = conv_codeset_strdup(chars, src_codeset, out_codeset);
5781 codeconv_set_strict(FALSE);
5786 msg = g_strdup_printf(_("Can't convert the character encoding of the message \n"
5787 "to the specified %s charset.\n"
5788 "Send it as %s?"), out_codeset, src_codeset);
5789 aval = alertpanel_full(_("Error"), msg, GTK_STOCK_CANCEL,
5790 g_strconcat("+", _("_Send"), NULL), NULL, FALSE,
5791 NULL, ALERT_ERROR, G_ALERTDEFAULT);
5794 if (aval != G_ALERTALTERNATE) {
5799 out_codeset = src_codeset;
5805 out_codeset = src_codeset;
5810 if (prefs_common.rewrite_first_from && (encoding == ENC_8BIT || encoding == ENC_7BIT)) {
5811 if (!strncmp(buf, "From ", sizeof("From ")-1) ||
5812 strstr(buf, "\nFrom ") != NULL) {
5813 encoding = ENC_QUOTED_PRINTABLE;
5817 mimetext = procmime_mimeinfo_new();
5818 mimetext->content = MIMECONTENT_MEM;
5819 mimetext->tmp = TRUE; /* must free content later */
5820 /* dup'ed because procmime_encode_content can turn it into a tmpfile
5821 * and free the data, which we need later. */
5822 mimetext->data.mem = g_strdup(buf);
5823 mimetext->type = MIMETYPE_TEXT;
5824 mimetext->subtype = g_strdup("plain");
5825 g_hash_table_insert(mimetext->typeparameters, g_strdup("charset"),
5826 g_strdup(out_codeset));
5828 /* protect trailing spaces when signing message */
5829 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5830 privacy_system_can_sign(compose->privacy_system)) {
5831 encoding = ENC_QUOTED_PRINTABLE;
5835 debug_print("main text: %Id bytes encoded as %s in %d\n",
5837 debug_print("main text: %zd bytes encoded as %s in %d\n",
5839 strlen(buf), out_codeset, encoding);
5841 /* check for line length limit */
5842 if (action == COMPOSE_WRITE_FOR_SEND &&
5843 encoding != ENC_QUOTED_PRINTABLE && encoding != ENC_BASE64 &&
5844 check_line_length(buf, 1000, &line) < 0) {
5847 msg = g_strdup_printf
5848 (_("Line %d exceeds the line length limit (998 bytes).\n"
5849 "The contents of the message might be broken on the way to the delivery.\n"
5851 "Send it anyway?"), line + 1);
5852 aval = alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, GTK_STOCK_OK, NULL);
5854 if (aval != G_ALERTALTERNATE) {
5860 if (encoding != ENC_UNKNOWN)
5861 procmime_encode_content(mimetext, encoding);
5863 /* append attachment parts */
5864 if (compose_use_attach(compose) && attach_parts) {
5865 MimeInfo *mimempart;
5866 gchar *boundary = NULL;
5867 mimempart = procmime_mimeinfo_new();
5868 mimempart->content = MIMECONTENT_EMPTY;
5869 mimempart->type = MIMETYPE_MULTIPART;
5870 mimempart->subtype = g_strdup("mixed");
5874 boundary = generate_mime_boundary(NULL);
5875 } while (strstr(buf, boundary) != NULL);
5877 g_hash_table_insert(mimempart->typeparameters, g_strdup("boundary"),
5880 mimetext->disposition = DISPOSITIONTYPE_INLINE;
5882 g_node_append(mimempart->node, mimetext->node);
5883 g_node_append(mimemsg->node, mimempart->node);
5885 if (compose_add_attachments(compose, mimempart) < 0)
5888 g_node_append(mimemsg->node, mimetext->node);
5892 if (strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) != 0) {
5893 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
5894 /* extract name and address */
5895 if (strstr(spec, " <") && strstr(spec, ">")) {
5896 from_addr = g_strdup(strrchr(spec, '<')+1);
5897 *(strrchr(from_addr, '>')) = '\0';
5898 from_name = g_strdup(spec);
5899 *(strrchr(from_name, '<')) = '\0';
5906 /* sign message if sending */
5907 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5908 privacy_system_can_sign(compose->privacy_system))
5909 if (!privacy_sign(compose->privacy_system, mimemsg,
5910 compose->account, from_addr)) {
5918 if (compose->use_encryption) {
5919 if (compose->encdata != NULL &&
5920 strcmp(compose->encdata, "_DONT_ENCRYPT_")) {
5922 /* First, write an unencrypted copy and save it to outbox, if
5923 * user wants that. */
5924 if (compose->account->save_encrypted_as_clear_text) {
5925 debug_print("saving sent message unencrypted...\n");
5926 FILE *tmpfp = get_tmpfile_in_dir(get_mime_tmp_dir(), &tmp_enc_file);
5930 /* fp now points to a file with headers written,
5931 * let's make a copy. */
5933 content = file_read_stream_to_str(fp);
5935 str_write_to_file(content, tmp_enc_file);
5938 /* Now write the unencrypted body. */
5939 if ((tmpfp = g_fopen(tmp_enc_file, "a")) != NULL) {
5940 procmime_write_mimeinfo(mimemsg, tmpfp);
5943 outbox = folder_find_item_from_identifier(compose_get_save_to(compose));
5945 outbox = folder_get_default_outbox();
5947 procmsg_save_to_outbox(outbox, tmp_enc_file, TRUE);
5948 claws_unlink(tmp_enc_file);
5950 g_warning("Can't open file '%s'", tmp_enc_file);
5953 g_warning("couldn't get tempfile");
5956 if (!privacy_encrypt(compose->privacy_system, mimemsg, compose->encdata)) {
5957 debug_print("Couldn't encrypt mime structure: %s.\n",
5958 privacy_get_error());
5959 alertpanel_error(_("Couldn't encrypt the email: %s"),
5960 privacy_get_error());
5965 procmime_write_mimeinfo(mimemsg, fp);
5967 procmime_mimeinfo_free_all(&mimemsg);
5972 static gint compose_write_body_to_file(Compose *compose, const gchar *file)
5974 GtkTextBuffer *buffer;
5975 GtkTextIter start, end;
5980 if ((fp = g_fopen(file, "wb")) == NULL) {
5981 FILE_OP_ERROR(file, "fopen");
5985 /* chmod for security */
5986 if (change_file_mode_rw(fp, file) < 0) {
5987 FILE_OP_ERROR(file, "chmod");
5988 g_warning("can't change file mode");
5991 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5992 gtk_text_buffer_get_start_iter(buffer, &start);
5993 gtk_text_buffer_get_end_iter(buffer, &end);
5994 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5996 chars = conv_codeset_strdup
5997 (tmp, CS_INTERNAL, conv_get_locale_charset_str());
6006 len = strlen(chars);
6007 if (fwrite(chars, sizeof(gchar), len, fp) != len) {
6008 FILE_OP_ERROR(file, "fwrite");
6017 if (fclose(fp) == EOF) {
6018 FILE_OP_ERROR(file, "fclose");
6025 static gint compose_remove_reedit_target(Compose *compose, gboolean force)
6028 MsgInfo *msginfo = compose->targetinfo;
6030 cm_return_val_if_fail(compose->mode == COMPOSE_REEDIT, -1);
6031 if (!msginfo) return -1;
6033 if (!force && MSG_IS_LOCKED(msginfo->flags))
6036 item = msginfo->folder;
6037 cm_return_val_if_fail(item != NULL, -1);
6039 if (procmsg_msg_exist(msginfo) &&
6040 (folder_has_parent_of_type(item, F_QUEUE) ||
6041 folder_has_parent_of_type(item, F_DRAFT)
6042 || msginfo == compose->autosaved_draft)) {
6043 if (folder_item_remove_msg(item, msginfo->msgnum) < 0) {
6044 g_warning("can't remove the old message");
6047 debug_print("removed reedit target %d\n", msginfo->msgnum);
6054 static void compose_remove_draft(Compose *compose)
6057 MsgInfo *msginfo = compose->targetinfo;
6058 drafts = account_get_special_folder(compose->account, F_DRAFT);
6060 if (procmsg_msg_exist(msginfo)) {
6061 folder_item_remove_msg(drafts, msginfo->msgnum);
6066 gint compose_queue(Compose *compose, gint *msgnum, FolderItem **item, gchar **msgpath,
6067 gboolean remove_reedit_target)
6069 return compose_queue_sub (compose, msgnum, item, msgpath, FALSE, remove_reedit_target);
6072 static gboolean compose_warn_encryption(Compose *compose)
6074 const gchar *warning = privacy_get_encrypt_warning(compose->privacy_system);
6075 AlertValue val = G_ALERTALTERNATE;
6077 if (warning == NULL)
6080 val = alertpanel_full(_("Encryption warning"), warning,
6081 GTK_STOCK_CANCEL, g_strconcat("+", _("C_ontinue"), NULL), NULL,
6082 TRUE, NULL, ALERT_WARNING, G_ALERTALTERNATE);
6083 if (val & G_ALERTDISABLE) {
6084 val &= ~G_ALERTDISABLE;
6085 if (val == G_ALERTALTERNATE)
6086 privacy_inhibit_encrypt_warning(compose->privacy_system,
6090 if (val == G_ALERTALTERNATE) {
6097 static gint compose_queue_sub(Compose *compose, gint *msgnum, FolderItem **item,
6098 gchar **msgpath, gboolean perform_checks,
6099 gboolean remove_reedit_target)
6106 PrefsAccount *mailac = NULL, *newsac = NULL;
6107 gboolean err = FALSE;
6109 debug_print("queueing message...\n");
6110 cm_return_val_if_fail(compose->account != NULL, -1);
6112 if (compose_check_entries(compose, perform_checks) == FALSE) {
6113 if (compose->batch) {
6114 gtk_widget_show_all(compose->window);
6119 if (!compose->to_list && !compose->newsgroup_list) {
6120 g_warning("can't get recipient list.");
6124 if (compose->to_list) {
6125 if (compose->account->protocol != A_NNTP)
6126 mailac = compose->account;
6127 else if (cur_account && cur_account->protocol != A_NNTP)
6128 mailac = cur_account;
6129 else if (!(mailac = compose_current_mail_account())) {
6130 alertpanel_error(_("No account for sending mails available!"));
6135 if (compose->newsgroup_list) {
6136 if (compose->account->protocol == A_NNTP)
6137 newsac = compose->account;
6139 alertpanel_error(_("Selected account isn't NNTP: Posting is impossible."));
6144 /* write queue header */
6145 tmp = g_strdup_printf("%s%cqueue.%p%08x", get_tmp_dir(),
6146 G_DIR_SEPARATOR, compose, (guint) rand());
6147 debug_print("queuing to %s\n", tmp);
6148 if ((fp = g_fopen(tmp, "w+b")) == NULL) {
6149 FILE_OP_ERROR(tmp, "fopen");
6154 if (change_file_mode_rw(fp, tmp) < 0) {
6155 FILE_OP_ERROR(tmp, "chmod");
6156 g_warning("can't change file mode");
6159 /* queueing variables */
6160 err |= (fprintf(fp, "AF:\n") < 0);
6161 err |= (fprintf(fp, "NF:0\n") < 0);
6162 err |= (fprintf(fp, "PS:10\n") < 0);
6163 err |= (fprintf(fp, "SRH:1\n") < 0);
6164 err |= (fprintf(fp, "SFN:\n") < 0);
6165 err |= (fprintf(fp, "DSR:\n") < 0);
6167 err |= (fprintf(fp, "MID:<%s>\n", compose->msgid) < 0);
6169 err |= (fprintf(fp, "MID:\n") < 0);
6170 err |= (fprintf(fp, "CFG:\n") < 0);
6171 err |= (fprintf(fp, "PT:0\n") < 0);
6172 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
6173 err |= (fprintf(fp, "RQ:\n") < 0);
6175 err |= (fprintf(fp, "SSV:%s\n", mailac->smtp_server) < 0);
6177 err |= (fprintf(fp, "SSV:\n") < 0);
6179 err |= (fprintf(fp, "NSV:%s\n", newsac->nntp_server) < 0);
6181 err |= (fprintf(fp, "NSV:\n") < 0);
6182 err |= (fprintf(fp, "SSH:\n") < 0);
6183 /* write recepient list */
6184 if (compose->to_list) {
6185 err |= (fprintf(fp, "R:<%s>", (gchar *)compose->to_list->data) < 0);
6186 for (cur = compose->to_list->next; cur != NULL;
6188 err |= (fprintf(fp, ",<%s>", (gchar *)cur->data) < 0);
6189 err |= (fprintf(fp, "\n") < 0);
6191 /* write newsgroup list */
6192 if (compose->newsgroup_list) {
6193 err |= (fprintf(fp, "NG:") < 0);
6194 err |= (fprintf(fp, "%s", (gchar *)compose->newsgroup_list->data) < 0);
6195 for (cur = compose->newsgroup_list->next; cur != NULL; cur = cur->next)
6196 err |= (fprintf(fp, ",%s", (gchar *)cur->data) < 0);
6197 err |= (fprintf(fp, "\n") < 0);
6199 /* Sylpheed account IDs */
6201 err |= (fprintf(fp, "MAID:%d\n", mailac->account_id) < 0);
6203 err |= (fprintf(fp, "NAID:%d\n", newsac->account_id) < 0);
6206 if (compose->privacy_system != NULL) {
6207 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
6208 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
6209 if (compose->use_encryption) {
6210 if (!compose_warn_encryption(compose)) {
6216 if (mailac && mailac->encrypt_to_self) {
6217 GSList *tmp_list = g_slist_copy(compose->to_list);
6218 tmp_list = g_slist_append(tmp_list, compose->account->address);
6219 compose->encdata = privacy_get_encrypt_data(compose->privacy_system, tmp_list);
6220 g_slist_free(tmp_list);
6222 compose->encdata = privacy_get_encrypt_data(compose->privacy_system, compose->to_list);
6224 if (compose->encdata != NULL) {
6225 if (strcmp(compose->encdata, "_DONT_ENCRYPT_")) {
6226 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
6227 err |= (fprintf(fp, "X-Claws-Encrypt-Data:%s\n",
6228 compose->encdata) < 0);
6229 } /* else we finally dont want to encrypt */
6231 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
6232 /* and if encdata was null, it means there's been a problem in
6235 g_warning("failed to write queue message");
6244 /* Save copy folder */
6245 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
6246 gchar *savefolderid;
6248 savefolderid = compose_get_save_to(compose);
6249 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
6250 g_free(savefolderid);
6252 /* Save copy folder */
6253 if (compose->return_receipt) {
6254 err |= (fprintf(fp, "RRCPT:1\n") < 0);
6256 /* Message-ID of message replying to */
6257 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
6258 gchar *folderid = NULL;
6260 if (compose->replyinfo->folder)
6261 folderid = folder_item_get_identifier(compose->replyinfo->folder);
6262 if (folderid == NULL)
6263 folderid = g_strdup("NULL");
6265 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
6268 /* Message-ID of message forwarding to */
6269 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
6270 gchar *folderid = NULL;
6272 if (compose->fwdinfo->folder)
6273 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
6274 if (folderid == NULL)
6275 folderid = g_strdup("NULL");
6277 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
6281 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
6282 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
6284 /* end of headers */
6285 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
6287 if (compose->redirect_filename != NULL) {
6288 if (compose_redirect_write_to_file(compose, fp) < 0) {
6296 if ((result = compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_SEND, TRUE)) < 0) {
6300 return result - 1; /* -2 for a generic error, -3 for signing error, -4 for encoding */
6304 g_warning("failed to write queue message");
6310 if (fclose(fp) == EOF) {
6311 FILE_OP_ERROR(tmp, "fclose");
6317 if (item && *item) {
6320 queue = account_get_special_folder(compose->account, F_QUEUE);
6323 g_warning("can't find queue folder");
6328 folder_item_scan(queue);
6329 if ((num = folder_item_add_msg(queue, tmp, NULL, FALSE)) < 0) {
6330 g_warning("can't queue the message");
6336 if (msgpath == NULL) {
6342 if (compose->mode == COMPOSE_REEDIT && remove_reedit_target) {
6343 compose_remove_reedit_target(compose, FALSE);
6346 if ((msgnum != NULL) && (item != NULL)) {
6354 static int compose_add_attachments(Compose *compose, MimeInfo *parent)
6357 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
6362 GError *error = NULL;
6367 gchar *type, *subtype;
6368 GtkTreeModel *model;
6371 model = gtk_tree_view_get_model(tree_view);
6373 if (!gtk_tree_model_get_iter_first(model, &iter))
6376 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
6378 if (!is_file_exist(ainfo->file)) {
6379 gchar *msg = g_strdup_printf(_("Attachment %s doesn't exist anymore. Ignore?"), ainfo->file);
6380 AlertValue val = alertpanel_full(_("Warning"), msg, _("Cancel sending"), _("Ignore attachment"),
6381 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
6383 if (val == G_ALERTDEFAULT) {
6389 f = g_file_new_for_path(ainfo->file);
6390 fi = g_file_query_info(f, "standard::size",
6391 G_FILE_QUERY_INFO_NONE, NULL, &error);
6392 if (error != NULL) {
6393 g_warning(error->message);
6394 g_error_free(error);
6398 size = g_file_info_get_size(fi);
6402 if (g_stat(ainfo->file, &statbuf) < 0)
6404 size = statbuf.st_size;
6407 mimepart = procmime_mimeinfo_new();
6408 mimepart->content = MIMECONTENT_FILE;
6409 mimepart->data.filename = g_strdup(ainfo->file);
6410 mimepart->tmp = FALSE; /* or we destroy our attachment */
6411 mimepart->offset = 0;
6412 mimepart->length = size;
6414 type = g_strdup(ainfo->content_type);
6416 if (!strchr(type, '/')) {
6418 type = g_strdup("application/octet-stream");
6421 subtype = strchr(type, '/') + 1;
6422 *(subtype - 1) = '\0';
6423 mimepart->type = procmime_get_media_type(type);
6424 mimepart->subtype = g_strdup(subtype);
6427 if (mimepart->type == MIMETYPE_MESSAGE &&
6428 !g_ascii_strcasecmp(mimepart->subtype, "rfc822")) {
6429 mimepart->disposition = DISPOSITIONTYPE_INLINE;
6430 } else if (mimepart->type == MIMETYPE_TEXT) {
6431 if (!ainfo->name && g_ascii_strcasecmp(mimepart->subtype, "plain")) {
6432 /* Text parts with no name come from multipart/alternative
6433 * forwards. Make sure the recipient won't look at the
6434 * original HTML part by mistake. */
6435 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6436 ainfo->name = g_strdup_printf(_("Original %s part"),
6440 g_hash_table_insert(mimepart->typeparameters,
6441 g_strdup("charset"), g_strdup(ainfo->charset));
6443 if (ainfo->name && mimepart->type != MIMETYPE_MESSAGE) {
6444 if (mimepart->type == MIMETYPE_APPLICATION &&
6445 !strcmp2(mimepart->subtype, "octet-stream"))
6446 g_hash_table_insert(mimepart->typeparameters,
6447 g_strdup("name"), g_strdup(ainfo->name));
6448 g_hash_table_insert(mimepart->dispositionparameters,
6449 g_strdup("filename"), g_strdup(ainfo->name));
6450 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6453 if (mimepart->type == MIMETYPE_MESSAGE
6454 || mimepart->type == MIMETYPE_MULTIPART)
6455 ainfo->encoding = ENC_BINARY;
6456 else if (compose->use_signing) {
6457 if (ainfo->encoding == ENC_7BIT)
6458 ainfo->encoding = ENC_QUOTED_PRINTABLE;
6459 else if (ainfo->encoding == ENC_8BIT)
6460 ainfo->encoding = ENC_BASE64;
6463 procmime_encode_content(mimepart, ainfo->encoding);
6465 g_node_append(parent->node, mimepart->node);
6466 } while (gtk_tree_model_iter_next(model, &iter));
6471 static gchar *compose_quote_list_of_addresses(gchar *str)
6473 GSList *list = NULL, *item = NULL;
6474 gchar *qname = NULL, *faddr = NULL, *result = NULL;
6476 list = address_list_append_with_comments(list, str);
6477 for (item = list; item != NULL; item = item->next) {
6478 gchar *spec = item->data;
6479 gchar *endofname = strstr(spec, " <");
6480 if (endofname != NULL) {
6483 QUOTE_IF_REQUIRED_NORMAL(qname, spec, return NULL);
6484 qqname = escape_internal_quotes(qname, '"');
6486 if (*qname != *spec || qqname != qname) { /* has been quoted, compute new */
6487 gchar *addr = g_strdup(endofname);
6488 gchar *name = (qqname != qname)? qqname: g_strdup(qname);
6489 faddr = g_strconcat(name, addr, NULL);
6492 debug_print("new auto-quoted address: '%s'\n", faddr);
6496 result = g_strdup((faddr != NULL)? faddr: spec);
6498 result = g_strconcat(result,
6500 (faddr != NULL)? faddr: spec,
6503 if (faddr != NULL) {
6508 slist_free_strings_full(list);
6513 #define IS_IN_CUSTOM_HEADER(header) \
6514 (compose->account->add_customhdr && \
6515 custom_header_find(compose->account->customhdr_list, header) != NULL)
6517 static const gchar *compose_untranslated_header_name(gchar *header_name)
6519 /* return the untranslated header name, if header_name is a known
6520 header name, in either its translated or untranslated form, with
6521 or without trailing colon. otherwise, returns header_name. */
6522 gchar *translated_header_name;
6523 gchar *translated_header_name_wcolon;
6524 const gchar *untranslated_header_name;
6525 const gchar *untranslated_header_name_wcolon;
6528 cm_return_val_if_fail(header_name != NULL, NULL);
6530 for (i = 0; HEADERS[i].header_name != NULL; i++) {
6531 untranslated_header_name = HEADERS[i].header_name;
6532 untranslated_header_name_wcolon = HEADERS[i].header_name_w_colon;
6534 translated_header_name = gettext(untranslated_header_name);
6535 translated_header_name_wcolon = gettext(untranslated_header_name_wcolon);
6537 if (!strcmp(header_name, untranslated_header_name) ||
6538 !strcmp(header_name, translated_header_name)) {
6539 return untranslated_header_name;
6541 if (!strcmp(header_name, untranslated_header_name_wcolon) ||
6542 !strcmp(header_name, translated_header_name_wcolon)) {
6543 return untranslated_header_name_wcolon;
6547 debug_print("compose_untranslated_header_name: unknown header '%s'\n", header_name);
6551 static void compose_add_headerfield_from_headerlist(Compose *compose,
6553 const gchar *fieldname,
6554 const gchar *seperator)
6556 gchar *str, *fieldname_w_colon;
6557 gboolean add_field = FALSE;
6559 ComposeHeaderEntry *headerentry;
6560 const gchar *headerentryname;
6561 const gchar *trans_fieldname;
6564 if (IS_IN_CUSTOM_HEADER(fieldname))
6567 debug_print("Adding %s-fields\n", fieldname);
6569 fieldstr = g_string_sized_new(64);
6571 fieldname_w_colon = g_strconcat(fieldname, ":", NULL);
6572 trans_fieldname = prefs_common_translated_header_name(fieldname_w_colon);
6574 for (list = compose->header_list; list; list = list->next) {
6575 headerentry = ((ComposeHeaderEntry *)list->data);
6576 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
6578 if (!g_utf8_collate(trans_fieldname, headerentryname)) {
6579 gchar * ustr = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
6581 str = compose_quote_list_of_addresses(ustr);
6583 if (str != NULL && str[0] != '\0') {
6585 g_string_append(fieldstr, seperator);
6586 g_string_append(fieldstr, str);
6595 buf = g_new0(gchar, fieldstr->len * 4 + 256);
6596 compose_convert_header
6597 (compose, buf, fieldstr->len * 4 + 256, fieldstr->str,
6598 strlen(fieldname) + 2, TRUE);
6599 g_string_append_printf(header, "%s: %s\n", fieldname, buf);
6603 g_free(fieldname_w_colon);
6604 g_string_free(fieldstr, TRUE);
6609 static gchar *compose_get_manual_headers_info(Compose *compose)
6611 GString *sh_header = g_string_new(" ");
6613 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6615 for (list = compose->header_list; list; list = list->next) {
6616 ComposeHeaderEntry *headerentry;
6619 gchar *headername_wcolon;
6620 const gchar *headername_trans;
6622 gboolean standard_header = FALSE;
6624 headerentry = ((ComposeHeaderEntry *)list->data);
6626 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6628 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6633 if (!strstr(tmp, ":")) {
6634 headername_wcolon = g_strconcat(tmp, ":", NULL);
6635 headername = g_strdup(tmp);
6637 headername_wcolon = g_strdup(tmp);
6638 headername = g_strdup(strtok(tmp, ":"));
6642 string = std_headers;
6643 while (*string != NULL) {
6644 headername_trans = prefs_common_translated_header_name(*string);
6645 if (!strcmp(headername_trans, headername_wcolon))
6646 standard_header = TRUE;
6649 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
6650 g_string_append_printf(sh_header, "%s ", headername);
6652 g_free(headername_wcolon);
6654 g_string_truncate(sh_header, strlen(sh_header->str) - 1); /* remove last space */
6655 return g_string_free(sh_header, FALSE);
6658 static gchar *compose_get_header(Compose *compose)
6660 gchar date[RFC822_DATE_BUFFSIZE];
6661 gchar buf[BUFFSIZE];
6662 const gchar *entry_str;
6666 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6668 gchar *from_name = NULL, *from_address = NULL;
6671 cm_return_val_if_fail(compose->account != NULL, NULL);
6672 cm_return_val_if_fail(compose->account->address != NULL, NULL);
6674 header = g_string_sized_new(64);
6677 if (prefs_common.hide_timezone)
6678 get_rfc822_date_hide_tz(date, sizeof(date));
6680 get_rfc822_date(date, sizeof(date));
6681 g_string_append_printf(header, "Date: %s\n", date);
6685 if (compose->account->name && *compose->account->name) {
6687 QUOTE_IF_REQUIRED(buf, compose->account->name);
6688 tmp = g_strdup_printf("%s <%s>",
6689 buf, compose->account->address);
6691 tmp = g_strdup_printf("%s",
6692 compose->account->address);
6694 if (!strcmp(gtk_entry_get_text(GTK_ENTRY(compose->from_name)), tmp)
6695 || strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) == 0) {
6697 from_name = compose->account->name ? g_strdup(compose->account->name):NULL;
6698 from_address = g_strdup(compose->account->address);
6700 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
6701 /* extract name and address */
6702 if (strstr(spec, " <") && strstr(spec, ">")) {
6703 from_address = g_strdup(strrchr(spec, '<')+1);
6704 *(strrchr(from_address, '>')) = '\0';
6705 from_name = g_strdup(spec);
6706 *(strrchr(from_name, '<')) = '\0';
6709 from_address = g_strdup(spec);
6716 if (from_name && *from_name) {
6718 compose_convert_header
6719 (compose, buf, sizeof(buf), from_name,
6720 strlen("From: "), TRUE);
6721 QUOTE_IF_REQUIRED(name, buf);
6722 qname = escape_internal_quotes(name, '"');
6724 g_string_append_printf(header, "From: %s <%s>\n",
6725 qname, from_address);
6726 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To") &&
6727 compose->return_receipt) {
6728 compose_convert_header(compose, buf, sizeof(buf), from_name,
6729 strlen("Disposition-Notification-To: "),
6731 g_string_append_printf(header, "Disposition-Notification-To: %s <%s>\n", buf, from_address);
6736 g_string_append_printf(header, "From: %s\n", from_address);
6737 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To") &&
6738 compose->return_receipt)
6739 g_string_append_printf(header, "Disposition-Notification-To: %s\n", from_address);
6743 g_free(from_address);
6746 compose_add_headerfield_from_headerlist(compose, header, "To", ", ");
6749 compose_add_headerfield_from_headerlist(compose, header, "Newsgroups", ",");
6752 compose_add_headerfield_from_headerlist(compose, header, "Cc", ", ");
6756 * If this account is a NNTP account remove Bcc header from
6757 * message body since it otherwise will be publicly shown
6759 if (compose->account->protocol != A_NNTP)
6760 compose_add_headerfield_from_headerlist(compose, header, "Bcc", ", ");
6763 str = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
6765 if (*str != '\0' && !IS_IN_CUSTOM_HEADER("Subject")) {
6768 compose_convert_header(compose, buf, sizeof(buf), str,
6769 strlen("Subject: "), FALSE);
6770 g_string_append_printf(header, "Subject: %s\n", buf);
6776 if (compose->msgid != NULL && strlen(compose->msgid) > 0) {
6777 g_string_append_printf(header, "Message-ID: <%s>\n",
6781 if (compose->remove_references == FALSE) {
6783 if (compose->inreplyto && compose->to_list)
6784 g_string_append_printf(header, "In-Reply-To: <%s>\n", compose->inreplyto);
6787 if (compose->references)
6788 g_string_append_printf(header, "References: %s\n", compose->references);
6792 compose_add_headerfield_from_headerlist(compose, header, "Followup-To", ",");
6795 compose_add_headerfield_from_headerlist(compose, header, "Reply-To", ", ");
6798 if (compose->account->organization &&
6799 strlen(compose->account->organization) &&
6800 !IS_IN_CUSTOM_HEADER("Organization")) {
6801 compose_convert_header(compose, buf, sizeof(buf),
6802 compose->account->organization,
6803 strlen("Organization: "), FALSE);
6804 g_string_append_printf(header, "Organization: %s\n", buf);
6807 /* Program version and system info */
6808 if (compose->account->gen_xmailer &&
6809 g_slist_length(compose->to_list) && !IS_IN_CUSTOM_HEADER("X-Mailer") &&
6810 !compose->newsgroup_list) {
6811 g_string_append_printf(header, "X-Mailer: %s (GTK+ %d.%d.%d; %s)\n",
6813 gtk_major_version, gtk_minor_version, gtk_micro_version,
6816 if (compose->account->gen_xmailer &&
6817 g_slist_length(compose->newsgroup_list) && !IS_IN_CUSTOM_HEADER("X-Newsreader")) {
6818 g_string_append_printf(header, "X-Newsreader: %s (GTK+ %d.%d.%d; %s)\n",
6820 gtk_major_version, gtk_minor_version, gtk_micro_version,
6824 /* custom headers */
6825 if (compose->account->add_customhdr) {
6828 for (cur = compose->account->customhdr_list; cur != NULL;
6830 CustomHeader *chdr = (CustomHeader *)cur->data;
6832 if (custom_header_is_allowed(chdr->name)
6833 && chdr->value != NULL
6834 && *(chdr->value) != '\0') {
6835 compose_convert_header
6836 (compose, buf, sizeof(buf),
6838 strlen(chdr->name) + 2, FALSE);
6839 g_string_append_printf(header, "%s: %s\n", chdr->name, buf);
6844 /* Automatic Faces and X-Faces */
6845 if (get_account_xface (buf, sizeof(buf), compose->account->account_name) == 0) {
6846 g_string_append_printf(header, "X-Face: %s\n", buf);
6848 else if (get_default_xface (buf, sizeof(buf)) == 0) {
6849 g_string_append_printf(header, "X-Face: %s\n", buf);
6851 if (get_account_face (buf, sizeof(buf), compose->account->account_name) == 0) {
6852 g_string_append_printf(header, "Face: %s\n", buf);
6854 else if (get_default_face (buf, sizeof(buf)) == 0) {
6855 g_string_append_printf(header, "Face: %s\n", buf);
6859 switch (compose->priority) {
6860 case PRIORITY_HIGHEST: g_string_append_printf(header, "Importance: high\n"
6861 "X-Priority: 1 (Highest)\n");
6863 case PRIORITY_HIGH: g_string_append_printf(header, "Importance: high\n"
6864 "X-Priority: 2 (High)\n");
6866 case PRIORITY_NORMAL: break;
6867 case PRIORITY_LOW: g_string_append_printf(header, "Importance: low\n"
6868 "X-Priority: 4 (Low)\n");
6870 case PRIORITY_LOWEST: g_string_append_printf(header, "Importance: low\n"
6871 "X-Priority: 5 (Lowest)\n");
6873 default: debug_print("compose: priority unknown : %d\n",
6877 /* get special headers */
6878 for (list = compose->header_list; list; list = list->next) {
6879 ComposeHeaderEntry *headerentry;
6882 gchar *headername_wcolon;
6883 const gchar *headername_trans;
6886 gboolean standard_header = FALSE;
6888 headerentry = ((ComposeHeaderEntry *)list->data);
6890 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6892 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6897 if (!strstr(tmp, ":")) {
6898 headername_wcolon = g_strconcat(tmp, ":", NULL);
6899 headername = g_strdup(tmp);
6901 headername_wcolon = g_strdup(tmp);
6902 headername = g_strdup(strtok(tmp, ":"));
6906 entry_str = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
6907 Xstrdup_a(headervalue, entry_str, return NULL);
6908 subst_char(headervalue, '\r', ' ');
6909 subst_char(headervalue, '\n', ' ');
6910 g_strstrip(headervalue);
6911 if (*headervalue != '\0') {
6912 string = std_headers;
6913 while (*string != NULL && !standard_header) {
6914 headername_trans = prefs_common_translated_header_name(*string);
6915 /* support mixed translated and untranslated headers */
6916 if (!strcmp(headername_trans, headername_wcolon) || !strcmp(*string, headername_wcolon))
6917 standard_header = TRUE;
6920 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername)) {
6921 /* store untranslated header name */
6922 g_string_append_printf(header, "%s %s\n",
6923 compose_untranslated_header_name(headername_wcolon), headervalue);
6927 g_free(headername_wcolon);
6931 g_string_free(header, FALSE);
6936 #undef IS_IN_CUSTOM_HEADER
6938 static void compose_convert_header(Compose *compose, gchar *dest, gint len, gchar *src,
6939 gint header_len, gboolean addr_field)
6941 gchar *tmpstr = NULL;
6942 const gchar *out_codeset = NULL;
6944 cm_return_if_fail(src != NULL);
6945 cm_return_if_fail(dest != NULL);
6947 if (len < 1) return;
6949 tmpstr = g_strdup(src);
6951 subst_char(tmpstr, '\n', ' ');
6952 subst_char(tmpstr, '\r', ' ');
6955 if (!g_utf8_validate(tmpstr, -1, NULL)) {
6956 gchar *mybuf = g_malloc(strlen(tmpstr)*2 +1);
6957 conv_localetodisp(mybuf, strlen(tmpstr)*2 +1, tmpstr);
6962 codeconv_set_strict(TRUE);
6963 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6964 conv_get_charset_str(compose->out_encoding));
6965 codeconv_set_strict(FALSE);
6967 if (!dest || *dest == '\0') {
6968 gchar *test_conv_global_out = NULL;
6969 gchar *test_conv_reply = NULL;
6971 /* automatic mode. be automatic. */
6972 codeconv_set_strict(TRUE);
6974 out_codeset = conv_get_outgoing_charset_str();
6976 debug_print("trying to convert to %s\n", out_codeset);
6977 test_conv_global_out = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6980 if (!test_conv_global_out && compose->orig_charset
6981 && strcmp(compose->orig_charset, CS_US_ASCII)) {
6982 out_codeset = compose->orig_charset;
6983 debug_print("failure; trying to convert to %s\n", out_codeset);
6984 test_conv_reply = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6987 if (!test_conv_global_out && !test_conv_reply) {
6989 out_codeset = CS_INTERNAL;
6990 debug_print("finally using %s\n", out_codeset);
6992 g_free(test_conv_global_out);
6993 g_free(test_conv_reply);
6994 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6996 codeconv_set_strict(FALSE);
7001 static void compose_add_to_addressbook_cb(GtkMenuItem *menuitem, gpointer user_data)
7005 cm_return_if_fail(user_data != NULL);
7007 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(user_data)));
7008 g_strstrip(address);
7009 if (*address != '\0') {
7010 gchar *name = procheader_get_fromname(address);
7011 extract_address(address);
7012 #ifndef USE_ALT_ADDRBOOK
7013 addressbook_add_contact(name, address, NULL, NULL);
7015 debug_print("%s: %s\n", name, address);
7016 if (addressadd_selection(name, address, NULL, NULL)) {
7017 debug_print( "addressbook_add_contact - added\n" );
7024 static void compose_entry_popup_extend(GtkEntry *entry, GtkMenu *menu, gpointer user_data)
7026 GtkWidget *menuitem;
7029 cm_return_if_fail(menu != NULL);
7030 cm_return_if_fail(GTK_IS_MENU_SHELL(menu));
7032 menuitem = gtk_separator_menu_item_new();
7033 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
7034 gtk_widget_show(menuitem);
7036 menuitem = gtk_menu_item_new_with_mnemonic(_("Add to address _book"));
7037 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
7039 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
7040 g_strstrip(address);
7041 if (*address == '\0') {
7042 gtk_widget_set_sensitive(GTK_WIDGET(menuitem), FALSE);
7045 g_signal_connect(G_OBJECT(menuitem), "activate",
7046 G_CALLBACK(compose_add_to_addressbook_cb), entry);
7047 gtk_widget_show(menuitem);
7050 void compose_add_extra_header(gchar *header, GtkListStore *model)
7053 if (strcmp(header, "")) {
7054 COMBOBOX_ADD(model, header, COMPOSE_TO);
7058 void compose_add_extra_header_entries(GtkListStore *model)
7062 gchar buf[BUFFSIZE];
7065 if (extra_headers == NULL) {
7066 exhrc = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, "extraheaderrc", NULL);
7067 if ((exh = g_fopen(exhrc, "rb")) == NULL) {
7068 debug_print("extra headers file not found\n");
7069 goto extra_headers_done;
7071 while (fgets(buf, BUFFSIZE, exh) != NULL) {
7072 lastc = strlen(buf) - 1; /* remove trailing control chars */
7073 while (lastc >= 0 && buf[lastc] != ':')
7074 buf[lastc--] = '\0';
7075 if (lastc > 0 && buf[0] != '#' && buf[lastc] == ':') {
7076 buf[lastc] = '\0'; /* remove trailing : for comparison */
7077 if (custom_header_is_allowed(buf)) {
7079 extra_headers = g_slist_prepend(extra_headers, g_strdup(buf));
7082 g_message("disallowed extra header line: %s\n", buf);
7086 g_message("invalid extra header line: %s\n", buf);
7092 extra_headers = g_slist_prepend(extra_headers, g_strdup("")); /* end of list */
7093 extra_headers = g_slist_reverse(extra_headers);
7095 g_slist_foreach(extra_headers, (GFunc)compose_add_extra_header, (gpointer)model);
7098 static void _ldap_srv_func(gpointer data, gpointer user_data)
7100 LdapServer *server = (LdapServer *)data;
7101 gboolean *enable = (gboolean *)user_data;
7103 debug_print("%s server '%s'\n", (*enable == TRUE ? "enabling" : "disabling"), server->control->hostName);
7104 server->searchFlag = *enable;
7107 static void compose_create_header_entry(Compose *compose)
7109 gchar *headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
7116 const gchar *header = NULL;
7117 ComposeHeaderEntry *headerentry;
7118 gboolean standard_header = FALSE;
7119 GtkListStore *model;
7122 headerentry = g_new0(ComposeHeaderEntry, 1);
7124 /* Combo box model */
7125 model = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_BOOLEAN);
7126 #if !GTK_CHECK_VERSION(2, 24, 0)
7127 combo = gtk_combo_box_entry_new_with_model(GTK_TREE_MODEL(model), 0);
7129 COMBOBOX_ADD(model, prefs_common_translated_header_name("To:"),
7131 COMBOBOX_ADD(model, prefs_common_translated_header_name("Cc:"),
7133 COMBOBOX_ADD(model, prefs_common_translated_header_name("Bcc:"),
7135 COMBOBOX_ADD(model, prefs_common_translated_header_name("Newsgroups:"),
7136 COMPOSE_NEWSGROUPS);
7137 COMBOBOX_ADD(model, prefs_common_translated_header_name("Reply-To:"),
7139 COMBOBOX_ADD(model, prefs_common_translated_header_name("Followup-To:"),
7140 COMPOSE_FOLLOWUPTO);
7141 compose_add_extra_header_entries(model);
7144 #if GTK_CHECK_VERSION(2, 24, 0)
7145 combo = gtk_combo_box_new_with_model_and_entry(GTK_TREE_MODEL(model));
7146 GtkCellRenderer *cell = gtk_cell_renderer_text_new();
7147 gtk_cell_renderer_set_alignment(cell, 0.0, 0.5);
7148 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), cell, TRUE);
7149 gtk_combo_box_set_entry_text_column(GTK_COMBO_BOX(combo), 0);
7151 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
7152 g_signal_connect(G_OBJECT(gtk_bin_get_child(GTK_BIN(combo))), "grab_focus",
7153 G_CALLBACK(compose_grab_focus_cb), compose);
7154 gtk_widget_show(combo);
7156 /* Putting only the combobox child into focus chain of its parent causes
7157 * the parent to be skipped when changing focus via Tab or Shift+Tab.
7158 * This eliminates need to pres Tab twice in order to really get from the
7159 * combobox to next widget. */
7161 l = g_list_prepend(l, gtk_bin_get_child(GTK_BIN(combo)));
7162 gtk_container_set_focus_chain(GTK_CONTAINER(combo), l);
7165 gtk_table_attach(GTK_TABLE(compose->header_table), combo, 0, 1,
7166 compose->header_nextrow, compose->header_nextrow+1,
7167 GTK_SHRINK, GTK_FILL, 0, 0);
7168 if (compose->header_last && (compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN)) {
7169 const gchar *last_header_entry = gtk_entry_get_text(
7170 GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
7172 while (*string != NULL) {
7173 if (!strcmp(prefs_common_translated_header_name(*string), last_header_entry))
7174 standard_header = TRUE;
7177 if (standard_header)
7178 header = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
7180 if (!compose->header_last || !standard_header) {
7181 switch(compose->account->protocol) {
7183 header = prefs_common_translated_header_name("Newsgroups:");
7186 header = prefs_common_translated_header_name("To:");
7191 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((combo)))), header);
7193 gtk_editable_set_editable(
7194 GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((combo)))),
7195 prefs_common.type_any_header);
7197 g_signal_connect_after(G_OBJECT(gtk_bin_get_child(GTK_BIN((combo)))), "grab_focus",
7198 G_CALLBACK(compose_grab_focus_cb), compose);
7200 /* Entry field with cleanup button */
7201 button = gtk_button_new();
7202 gtk_button_set_image(GTK_BUTTON(button),
7203 gtk_image_new_from_stock(GTK_STOCK_CLEAR, GTK_ICON_SIZE_MENU));
7204 gtk_widget_show(button);
7205 CLAWS_SET_TIP(button,
7206 _("Delete entry contents"));
7207 entry = gtk_entry_new();
7208 gtk_widget_show(entry);
7209 CLAWS_SET_TIP(entry,
7210 _("Use <tab> to autocomplete from addressbook"));
7211 hbox = gtk_hbox_new (FALSE, 0);
7212 gtk_widget_show(hbox);
7213 gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, TRUE, 0);
7214 gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
7215 gtk_table_attach(GTK_TABLE(compose->header_table), hbox, 1, 2,
7216 compose->header_nextrow, compose->header_nextrow+1,
7217 GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
7219 g_signal_connect(G_OBJECT(entry), "key-press-event",
7220 G_CALLBACK(compose_headerentry_key_press_event_cb),
7222 g_signal_connect(G_OBJECT(entry), "changed",
7223 G_CALLBACK(compose_headerentry_changed_cb),
7225 g_signal_connect_after(G_OBJECT(entry), "grab_focus",
7226 G_CALLBACK(compose_grab_focus_cb), compose);
7228 g_signal_connect(G_OBJECT(button), "clicked",
7229 G_CALLBACK(compose_headerentry_button_clicked_cb),
7233 gtk_drag_dest_set(entry, GTK_DEST_DEFAULT_ALL, compose_mime_types,
7234 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7235 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7236 g_signal_connect(G_OBJECT(entry), "drag_data_received",
7237 G_CALLBACK(compose_header_drag_received_cb),
7239 g_signal_connect(G_OBJECT(entry), "drag-drop",
7240 G_CALLBACK(compose_drag_drop),
7242 g_signal_connect(G_OBJECT(entry), "populate-popup",
7243 G_CALLBACK(compose_entry_popup_extend),
7247 GSList *pwd_servers = addrindex_get_password_protected_ldap_servers();
7248 if (pwd_servers != NULL && master_passphrase() == NULL) {
7249 gboolean enable = FALSE;
7250 debug_print("Master passphrase not available, disabling password-protected LDAP servers for this compose window.\n");
7251 /* Temporarily disable password-protected LDAP servers,
7252 * because user did not provide a master passphrase.
7253 * We can safely enable searchFlag on all servers in this list
7254 * later, since addrindex_get_password_protected_ldap_servers()
7255 * includes servers which have it enabled initially. */
7256 g_slist_foreach(pwd_servers, _ldap_srv_func, &enable);
7257 compose->passworded_ldap_servers = pwd_servers;
7261 address_completion_register_entry(GTK_ENTRY(entry), TRUE);
7263 headerentry->compose = compose;
7264 headerentry->combo = combo;
7265 headerentry->entry = entry;
7266 headerentry->button = button;
7267 headerentry->hbox = hbox;
7268 headerentry->headernum = compose->header_nextrow;
7269 headerentry->type = PREF_NONE;
7271 compose->header_nextrow++;
7272 compose->header_last = headerentry;
7273 compose->header_list =
7274 g_slist_append(compose->header_list,
7278 static void compose_add_header_entry(Compose *compose, const gchar *header,
7279 gchar *text, ComposePrefType pref_type)
7281 ComposeHeaderEntry *last_header = compose->header_last;
7282 gchar *tmp = g_strdup(text), *email;
7283 gboolean replyto_hdr;
7285 replyto_hdr = (!strcasecmp(header,
7286 prefs_common_translated_header_name("Reply-To:")) ||
7288 prefs_common_translated_header_name("Followup-To:")) ||
7290 prefs_common_translated_header_name("In-Reply-To:")));
7292 extract_address(tmp);
7293 email = g_utf8_strdown(tmp, -1);
7295 if (replyto_hdr == FALSE &&
7296 g_hash_table_lookup(compose->email_hashtable, email) != NULL)
7298 debug_print("Ignoring duplicate address - %s %s, pref_type: %d\n",
7299 header, text, (gint) pref_type);
7305 if (!strcasecmp(header, prefs_common_translated_header_name("In-Reply-To:")))
7306 gtk_entry_set_text(GTK_ENTRY(
7307 gtk_bin_get_child(GTK_BIN(last_header->combo))), header);
7309 combobox_select_by_text(GTK_COMBO_BOX(last_header->combo), header);
7310 gtk_entry_set_text(GTK_ENTRY(last_header->entry), text);
7311 last_header->type = pref_type;
7313 if (replyto_hdr == FALSE)
7314 g_hash_table_insert(compose->email_hashtable, email,
7315 GUINT_TO_POINTER(1));
7322 static void compose_destroy_headerentry(Compose *compose,
7323 ComposeHeaderEntry *headerentry)
7325 gchar *text = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
7328 extract_address(text);
7329 email = g_utf8_strdown(text, -1);
7330 g_hash_table_remove(compose->email_hashtable, email);
7334 gtk_widget_destroy(headerentry->combo);
7335 gtk_widget_destroy(headerentry->entry);
7336 gtk_widget_destroy(headerentry->button);
7337 gtk_widget_destroy(headerentry->hbox);
7338 g_free(headerentry);
7341 static void compose_remove_header_entries(Compose *compose)
7344 for (list = compose->header_list; list; list = list->next)
7345 compose_destroy_headerentry(compose, (ComposeHeaderEntry *)list->data);
7347 compose->header_last = NULL;
7348 g_slist_free(compose->header_list);
7349 compose->header_list = NULL;
7350 compose->header_nextrow = 1;
7351 compose_create_header_entry(compose);
7354 static GtkWidget *compose_create_header(Compose *compose)
7356 GtkWidget *from_optmenu_hbox;
7357 GtkWidget *header_table_main;
7358 GtkWidget *header_scrolledwin;
7359 GtkWidget *header_table;
7361 /* parent with account selection and from header */
7362 header_table_main = gtk_table_new(2, 2, FALSE);
7363 gtk_widget_show(header_table_main);
7364 gtk_container_set_border_width(GTK_CONTAINER(header_table_main), BORDER_WIDTH);
7366 from_optmenu_hbox = compose_account_option_menu_create(compose);
7367 gtk_table_attach(GTK_TABLE(header_table_main), from_optmenu_hbox,
7368 0, 2, 0, 1, GTK_EXPAND | GTK_FILL, GTK_SHRINK, 0, 0);
7370 /* child with header labels and entries */
7371 header_scrolledwin = gtk_scrolled_window_new(NULL, NULL);
7372 gtk_widget_show(header_scrolledwin);
7373 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
7375 header_table = gtk_table_new(2, 2, FALSE);
7376 gtk_widget_show(header_table);
7377 gtk_container_set_border_width(GTK_CONTAINER(header_table), 0);
7378 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin), header_table);
7379 gtk_container_set_focus_vadjustment(GTK_CONTAINER(header_table),
7380 gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(header_scrolledwin)));
7381 gtk_viewport_set_shadow_type(GTK_VIEWPORT(gtk_bin_get_child(GTK_BIN(header_scrolledwin))), GTK_SHADOW_NONE);
7383 gtk_table_attach(GTK_TABLE(header_table_main), header_scrolledwin,
7384 0, 2, 1, 2, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 2);
7386 compose->header_table = header_table;
7387 compose->header_list = NULL;
7388 compose->header_nextrow = 0;
7390 compose_create_header_entry(compose);
7392 compose->table = NULL;
7394 return header_table_main;
7397 static gboolean popup_attach_button_pressed(GtkWidget *widget, gpointer data)
7399 Compose *compose = (Compose *)data;
7400 GdkEventButton event;
7403 event.time = gtk_get_current_event_time();
7405 return attach_button_pressed(compose->attach_clist, &event, compose);
7408 static GtkWidget *compose_create_attach(Compose *compose)
7410 GtkWidget *attach_scrwin;
7411 GtkWidget *attach_clist;
7413 GtkListStore *store;
7414 GtkCellRenderer *renderer;
7415 GtkTreeViewColumn *column;
7416 GtkTreeSelection *selection;
7418 /* attachment list */
7419 attach_scrwin = gtk_scrolled_window_new(NULL, NULL);
7420 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(attach_scrwin),
7421 GTK_POLICY_AUTOMATIC,
7422 GTK_POLICY_AUTOMATIC);
7423 gtk_widget_set_size_request(attach_scrwin, -1, 80);
7425 store = gtk_list_store_new(N_ATTACH_COLS,
7431 G_TYPE_AUTO_POINTER,
7433 attach_clist = GTK_WIDGET(gtk_tree_view_new_with_model
7434 (GTK_TREE_MODEL(store)));
7435 gtk_container_add(GTK_CONTAINER(attach_scrwin), attach_clist);
7436 g_object_unref(store);
7438 renderer = gtk_cell_renderer_text_new();
7439 column = gtk_tree_view_column_new_with_attributes
7440 (_("Mime type"), renderer, "text",
7441 COL_MIMETYPE, NULL);
7442 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7444 renderer = gtk_cell_renderer_text_new();
7445 column = gtk_tree_view_column_new_with_attributes
7446 (_("Size"), renderer, "text",
7448 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7450 renderer = gtk_cell_renderer_text_new();
7451 column = gtk_tree_view_column_new_with_attributes
7452 (_("Name"), renderer, "text",
7454 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7456 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(attach_clist),
7457 prefs_common.use_stripes_everywhere);
7458 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(attach_clist));
7459 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
7461 g_signal_connect(G_OBJECT(attach_clist), "row_activated",
7462 G_CALLBACK(attach_selected), compose);
7463 g_signal_connect(G_OBJECT(attach_clist), "button_press_event",
7464 G_CALLBACK(attach_button_pressed), compose);
7465 g_signal_connect(G_OBJECT(attach_clist), "popup-menu",
7466 G_CALLBACK(popup_attach_button_pressed), compose);
7467 g_signal_connect(G_OBJECT(attach_clist), "key_press_event",
7468 G_CALLBACK(attach_key_pressed), compose);
7471 gtk_drag_dest_set(attach_clist,
7472 GTK_DEST_DEFAULT_ALL, compose_mime_types,
7473 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7474 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7475 g_signal_connect(G_OBJECT(attach_clist), "drag_data_received",
7476 G_CALLBACK(compose_attach_drag_received_cb),
7478 g_signal_connect(G_OBJECT(attach_clist), "drag-drop",
7479 G_CALLBACK(compose_drag_drop),
7482 compose->attach_scrwin = attach_scrwin;
7483 compose->attach_clist = attach_clist;
7485 return attach_scrwin;
7488 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose);
7489 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose);
7491 static GtkWidget *compose_create_others(Compose *compose)
7494 GtkWidget *savemsg_checkbtn;
7495 GtkWidget *savemsg_combo;
7496 GtkWidget *savemsg_select;
7499 gchar *folderidentifier;
7501 /* Table for settings */
7502 table = gtk_table_new(3, 1, FALSE);
7503 gtk_container_set_border_width(GTK_CONTAINER(table), BORDER_WIDTH);
7504 gtk_widget_show(table);
7505 gtk_table_set_row_spacings(GTK_TABLE(table), VSPACING_NARROW);
7508 /* Save Message to folder */
7509 savemsg_checkbtn = gtk_check_button_new_with_label(_("Save Message to "));
7510 gtk_widget_show(savemsg_checkbtn);
7511 gtk_table_attach(GTK_TABLE(table), savemsg_checkbtn, 0, 1, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7512 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7513 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), prefs_common.savemsg);
7515 g_signal_connect(G_OBJECT(savemsg_checkbtn), "toggled",
7516 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
7518 #if !GTK_CHECK_VERSION(2, 24, 0)
7519 savemsg_combo = gtk_combo_box_entry_new_text();
7521 savemsg_combo = gtk_combo_box_text_new_with_entry();
7523 compose->savemsg_checkbtn = savemsg_checkbtn;
7524 compose->savemsg_combo = savemsg_combo;
7525 gtk_widget_show(savemsg_combo);
7527 if (prefs_common.compose_save_to_history)
7528 #if !GTK_CHECK_VERSION(2, 24, 0)
7529 combobox_set_popdown_strings(GTK_COMBO_BOX(savemsg_combo),
7530 prefs_common.compose_save_to_history);
7532 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(savemsg_combo),
7533 prefs_common.compose_save_to_history);
7535 gtk_table_attach(GTK_TABLE(table), savemsg_combo, 1, 2, rowcount, rowcount + 1, GTK_FILL|GTK_EXPAND, GTK_SHRINK, 0, 0);
7536 gtk_widget_set_sensitive(GTK_WIDGET(savemsg_combo), prefs_common.savemsg);
7537 g_signal_connect_after(G_OBJECT(savemsg_combo), "grab_focus",
7538 G_CALLBACK(compose_grab_focus_cb), compose);
7539 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7540 folderidentifier = folder_item_get_identifier(account_get_special_folder
7541 (compose->account, F_OUTBOX));
7542 compose_set_save_to(compose, folderidentifier);
7543 g_free(folderidentifier);
7546 savemsg_select = gtkut_get_browse_file_btn(_("_Browse"));
7547 gtk_widget_show(savemsg_select);
7548 gtk_table_attach(GTK_TABLE(table), savemsg_select, 2, 3, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7549 g_signal_connect(G_OBJECT(savemsg_select), "clicked",
7550 G_CALLBACK(compose_savemsg_select_cb),
7556 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose)
7558 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo),
7559 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn)));
7562 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose)
7567 dest = foldersel_folder_sel(NULL, FOLDER_SEL_COPY, NULL, FALSE,
7568 _("Select folder to save message to"));
7571 path = folder_item_get_identifier(dest);
7573 compose_set_save_to(compose, path);
7577 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry, gboolean wrap,
7578 GdkAtom clip, GtkTextIter *insert_place);
7581 static gboolean text_clicked(GtkWidget *text, GdkEventButton *event,
7585 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
7587 if (event->button == 3) {
7589 GtkTextIter sel_start, sel_end;
7590 gboolean stuff_selected;
7592 /* move the cursor to allow GtkAspell to check the word
7593 * under the mouse */
7594 if (event->x && event->y) {
7595 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7596 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7598 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7601 GtkTextMark *mark = gtk_text_buffer_get_insert(buffer);
7602 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7605 stuff_selected = gtk_text_buffer_get_selection_bounds(
7607 &sel_start, &sel_end);
7609 gtk_text_buffer_place_cursor (buffer, &iter);
7610 /* reselect stuff */
7612 && gtk_text_iter_in_range(&iter, &sel_start, &sel_end)) {
7613 gtk_text_buffer_select_range(buffer,
7614 &sel_start, &sel_end);
7616 return FALSE; /* pass the event so that the right-click goes through */
7619 if (event->button == 2) {
7624 /* get the middle-click position to paste at the correct place */
7625 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7626 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7628 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7631 entry_paste_clipboard(compose, text,
7632 prefs_common.linewrap_pastes,
7633 GDK_SELECTION_PRIMARY, &iter);
7641 static void compose_spell_menu_changed(void *data)
7643 Compose *compose = (Compose *)data;
7645 GtkWidget *menuitem;
7646 GtkWidget *parent_item;
7647 GtkMenu *menu = GTK_MENU(gtk_menu_new());
7650 if (compose->gtkaspell == NULL)
7653 parent_item = gtk_ui_manager_get_widget(compose->ui_manager,
7654 "/Menu/Spelling/Options");
7656 /* setting the submenu removes /Spelling/Options from the factory
7657 * so we need to save it */
7659 if (parent_item == NULL) {
7660 parent_item = compose->aspell_options_menu;
7661 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), NULL);
7663 compose->aspell_options_menu = parent_item;
7665 spell_menu = gtkaspell_make_config_menu(compose->gtkaspell);
7667 spell_menu = g_slist_reverse(spell_menu);
7668 for (items = spell_menu;
7669 items; items = items->next) {
7670 menuitem = GTK_WIDGET(GTK_MENU_ITEM(items->data));
7671 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), GTK_WIDGET(menuitem));
7672 gtk_widget_show(GTK_WIDGET(menuitem));
7674 g_slist_free(spell_menu);
7676 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), GTK_WIDGET(menu));
7677 gtk_widget_show(parent_item);
7680 static void compose_dict_changed(void *data)
7682 Compose *compose = (Compose *) data;
7684 if(!compose->gtkaspell)
7686 if(compose->gtkaspell->recheck_when_changing_dict == FALSE)
7689 gtkaspell_highlight_all(compose->gtkaspell);
7690 claws_spell_entry_recheck_all(CLAWS_SPELL_ENTRY(compose->subject_entry));
7694 static gboolean compose_popup_menu(GtkWidget *widget, gpointer data)
7696 Compose *compose = (Compose *)data;
7697 GdkEventButton event;
7700 event.time = gtk_get_current_event_time();
7704 return text_clicked(compose->text, &event, compose);
7707 static gboolean compose_force_window_origin = TRUE;
7708 static Compose *compose_create(PrefsAccount *account,
7717 GtkWidget *handlebox;
7719 GtkWidget *notebook;
7721 GtkWidget *attach_hbox;
7722 GtkWidget *attach_lab1;
7723 GtkWidget *attach_lab2;
7728 GtkWidget *subject_hbox;
7729 GtkWidget *subject_frame;
7730 GtkWidget *subject_entry;
7734 GtkWidget *edit_vbox;
7735 GtkWidget *ruler_hbox;
7737 GtkWidget *scrolledwin;
7739 GtkTextBuffer *buffer;
7740 GtkClipboard *clipboard;
7742 UndoMain *undostruct;
7744 GtkWidget *popupmenu;
7745 GtkWidget *tmpl_menu;
7746 GtkActionGroup *action_group = NULL;
7749 GtkAspell * gtkaspell = NULL;
7752 static GdkGeometry geometry;
7754 cm_return_val_if_fail(account != NULL, NULL);
7756 gtkut_convert_int_to_gdk_color(prefs_common.default_header_bgcolor,
7757 &default_header_bgcolor);
7758 gtkut_convert_int_to_gdk_color(prefs_common.default_header_color,
7759 &default_header_color);
7761 debug_print("Creating compose window...\n");
7762 compose = g_new0(Compose, 1);
7764 compose->batch = batch;
7765 compose->account = account;
7766 compose->folder = folder;
7768 compose->mutex = cm_mutex_new();
7769 compose->set_cursor_pos = -1;
7771 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose");
7773 gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
7774 gtk_widget_set_size_request(window, prefs_common.compose_width,
7775 prefs_common.compose_height);
7777 if (!geometry.max_width) {
7778 geometry.max_width = gdk_screen_width();
7779 geometry.max_height = gdk_screen_height();
7782 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7783 &geometry, GDK_HINT_MAX_SIZE);
7784 if (!geometry.min_width) {
7785 geometry.min_width = 600;
7786 geometry.min_height = 440;
7788 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7789 &geometry, GDK_HINT_MIN_SIZE);
7791 #ifndef GENERIC_UMPC
7792 if (compose_force_window_origin)
7793 gtk_window_move(GTK_WINDOW(window), prefs_common.compose_x,
7794 prefs_common.compose_y);
7796 g_signal_connect(G_OBJECT(window), "delete_event",
7797 G_CALLBACK(compose_delete_cb), compose);
7798 MANAGE_WINDOW_SIGNALS_CONNECT(window);
7799 gtk_widget_realize(window);
7801 gtkut_widget_set_composer_icon(window);
7803 vbox = gtk_vbox_new(FALSE, 0);
7804 gtk_container_add(GTK_CONTAINER(window), vbox);
7806 compose->ui_manager = gtk_ui_manager_new();
7807 action_group = cm_menu_create_action_group_full(compose->ui_manager,"Menu", compose_entries,
7808 G_N_ELEMENTS(compose_entries), (gpointer)compose);
7809 gtk_action_group_add_toggle_actions(action_group, compose_toggle_entries,
7810 G_N_ELEMENTS(compose_toggle_entries), (gpointer)compose);
7811 gtk_action_group_add_radio_actions(action_group, compose_radio_rm_entries,
7812 G_N_ELEMENTS(compose_radio_rm_entries), COMPOSE_REPLY, G_CALLBACK(compose_reply_change_mode_cb), (gpointer)compose);
7813 gtk_action_group_add_radio_actions(action_group, compose_radio_prio_entries,
7814 G_N_ELEMENTS(compose_radio_prio_entries), PRIORITY_NORMAL, G_CALLBACK(compose_set_priority_cb), (gpointer)compose);
7815 gtk_action_group_add_radio_actions(action_group, compose_radio_enc_entries,
7816 G_N_ELEMENTS(compose_radio_enc_entries), C_AUTO, G_CALLBACK(compose_set_encoding_cb), (gpointer)compose);
7818 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Menu", NULL, GTK_UI_MANAGER_MENUBAR)
7820 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Message", "Message", GTK_UI_MANAGER_MENU)
7821 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Edit", "Edit", GTK_UI_MANAGER_MENU)
7823 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Spelling", "Spelling", GTK_UI_MANAGER_MENU)
7825 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Options", "Options", GTK_UI_MANAGER_MENU)
7826 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Tools", "Tools", GTK_UI_MANAGER_MENU)
7827 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Help", "Help", GTK_UI_MANAGER_MENU)
7830 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Send", "Message/Send", GTK_UI_MANAGER_MENUITEM)
7831 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "SendLater", "Message/SendLater", GTK_UI_MANAGER_MENUITEM)
7832 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator1", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7833 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "AttachFile", "Message/AttachFile", GTK_UI_MANAGER_MENUITEM)
7834 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertFile", "Message/InsertFile", GTK_UI_MANAGER_MENUITEM)
7835 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertSig", "Message/InsertSig", GTK_UI_MANAGER_MENUITEM)
7836 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "ReplaceSig", "Message/ReplaceSig", GTK_UI_MANAGER_MENUITEM)
7837 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator2", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7838 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Save", "Message/Save", GTK_UI_MANAGER_MENUITEM)
7839 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator3", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7840 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Print", "Message/Print", GTK_UI_MANAGER_MENUITEM)
7841 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator4", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7842 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Close", "Message/Close", GTK_UI_MANAGER_MENUITEM)
7845 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Undo", "Edit/Undo", GTK_UI_MANAGER_MENUITEM)
7846 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Redo", "Edit/Redo", GTK_UI_MANAGER_MENUITEM)
7847 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator1", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7849 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Cut", "Edit/Cut", GTK_UI_MANAGER_MENUITEM)
7850 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Copy", "Edit/Copy", GTK_UI_MANAGER_MENUITEM)
7851 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Paste", "Edit/Paste", GTK_UI_MANAGER_MENUITEM)
7853 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SpecialPaste", "Edit/SpecialPaste", GTK_UI_MANAGER_MENU)
7854 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "AsQuotation", "Edit/SpecialPaste/AsQuotation", GTK_UI_MANAGER_MENUITEM)
7855 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Wrapped", "Edit/SpecialPaste/Wrapped", GTK_UI_MANAGER_MENUITEM)
7856 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Unwrapped", "Edit/SpecialPaste/Unwrapped", GTK_UI_MANAGER_MENUITEM)
7858 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SelectAll", "Edit/SelectAll", GTK_UI_MANAGER_MENUITEM)
7860 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Advanced", "Edit/Advanced", GTK_UI_MANAGER_MENU)
7861 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackChar", "Edit/Advanced/BackChar", GTK_UI_MANAGER_MENUITEM)
7862 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwChar", "Edit/Advanced/ForwChar", GTK_UI_MANAGER_MENUITEM)
7863 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackWord", "Edit/Advanced/BackWord", GTK_UI_MANAGER_MENUITEM)
7864 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwWord", "Edit/Advanced/ForwWord", GTK_UI_MANAGER_MENUITEM)
7865 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BegLine", "Edit/Advanced/BegLine", GTK_UI_MANAGER_MENUITEM)
7866 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "EndLine", "Edit/Advanced/EndLine", GTK_UI_MANAGER_MENUITEM)
7867 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "PrevLine", "Edit/Advanced/PrevLine", GTK_UI_MANAGER_MENUITEM)
7868 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "NextLine", "Edit/Advanced/NextLine", GTK_UI_MANAGER_MENUITEM)
7869 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackChar", "Edit/Advanced/DelBackChar", GTK_UI_MANAGER_MENUITEM)
7870 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwChar", "Edit/Advanced/DelForwChar", GTK_UI_MANAGER_MENUITEM)
7871 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackWord", "Edit/Advanced/DelBackWord", GTK_UI_MANAGER_MENUITEM)
7872 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwWord", "Edit/Advanced/DelForwWord", GTK_UI_MANAGER_MENUITEM)
7873 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelLine", "Edit/Advanced/DelLine", GTK_UI_MANAGER_MENUITEM)
7874 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelEndLine", "Edit/Advanced/DelEndLine", GTK_UI_MANAGER_MENUITEM)
7876 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator2", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7878 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Find", "Edit/Find", GTK_UI_MANAGER_MENUITEM)
7879 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapPara", "Edit/WrapPara", GTK_UI_MANAGER_MENUITEM)
7880 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapAllLines", "Edit/WrapAllLines", GTK_UI_MANAGER_MENUITEM)
7881 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoWrap", "Edit/AutoWrap", GTK_UI_MANAGER_MENUITEM)
7882 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoIndent", "Edit/AutoIndent", GTK_UI_MANAGER_MENUITEM)
7884 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator3", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7886 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "ExtEditor", "Edit/ExtEditor", GTK_UI_MANAGER_MENUITEM)
7890 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckAllSel", "Spelling/CheckAllSel", GTK_UI_MANAGER_MENUITEM)
7891 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "HighlightAll", "Spelling/HighlightAll", GTK_UI_MANAGER_MENUITEM)
7892 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckBackwards", "Spelling/CheckBackwards", GTK_UI_MANAGER_MENUITEM)
7893 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "ForwardNext", "Spelling/ForwardNext", GTK_UI_MANAGER_MENUITEM)
7894 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Separator1", "Spelling/---", GTK_UI_MANAGER_SEPARATOR)
7895 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Options", "Spelling/Options", GTK_UI_MANAGER_MENU)
7899 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "ReplyMode", "Options/ReplyMode", GTK_UI_MANAGER_MENU)
7900 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Normal", "Options/ReplyMode/Normal", GTK_UI_MANAGER_MENUITEM)
7901 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "All", "Options/ReplyMode/All", GTK_UI_MANAGER_MENUITEM)
7902 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Sender", "Options/ReplyMode/Sender", GTK_UI_MANAGER_MENUITEM)
7903 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "List", "Options/ReplyMode/List", GTK_UI_MANAGER_MENUITEM)
7905 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator1", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7906 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "PrivacySystem", "Options/PrivacySystem", GTK_UI_MANAGER_MENU)
7907 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/PrivacySystem", "PlaceHolder", "Options/PrivacySystem/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7908 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Sign", "Options/Sign", GTK_UI_MANAGER_MENUITEM)
7909 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encrypt", "Options/Encrypt", GTK_UI_MANAGER_MENUITEM)
7912 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator2", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7913 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Priority", "Options/Priority", GTK_UI_MANAGER_MENU)
7914 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Highest", "Options/Priority/Highest", GTK_UI_MANAGER_MENUITEM)
7915 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "High", "Options/Priority/High", GTK_UI_MANAGER_MENUITEM)
7916 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Normal", "Options/Priority/Normal", GTK_UI_MANAGER_MENUITEM)
7917 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Low", "Options/Priority/Low", GTK_UI_MANAGER_MENUITEM)
7918 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Lowest", "Options/Priority/Lowest", GTK_UI_MANAGER_MENUITEM)
7920 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator3", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7921 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RequestRetRcpt", "Options/RequestRetRcpt", GTK_UI_MANAGER_MENUITEM)
7922 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator4", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7923 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RemoveReferences", "Options/RemoveReferences", GTK_UI_MANAGER_MENUITEM)
7924 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator5", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7926 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encoding", "Options/Encoding", GTK_UI_MANAGER_MENU)
7928 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_AUTO, "Options/Encoding/"CS_AUTO, GTK_UI_MANAGER_MENUITEM)
7929 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator1", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7930 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_US_ASCII, "Options/Encoding/"CS_US_ASCII, GTK_UI_MANAGER_MENUITEM)
7931 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_UTF_8, "Options/Encoding/"CS_UTF_8, GTK_UI_MANAGER_MENUITEM)
7932 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator2", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7934 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Western", "Options/Encoding/Western", GTK_UI_MANAGER_MENU)
7935 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)
7936 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)
7937 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Western", CS_WINDOWS_1252, "Options/Encoding/Western/"CS_WINDOWS_1252, GTK_UI_MANAGER_MENUITEM)
7939 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_2, "Options/Encoding/"CS_ISO_8859_2, GTK_UI_MANAGER_MENUITEM)
7941 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Baltic", "Options/Encoding/Baltic", GTK_UI_MANAGER_MENU)
7942 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)
7943 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)
7945 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_7, "Options/Encoding/"CS_ISO_8859_7, GTK_UI_MANAGER_MENUITEM)
7947 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Hebrew", "Options/Encoding/Hebrew", GTK_UI_MANAGER_MENU)
7948 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)
7949 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Hebrew", CS_WINDOWS_1255, "Options/Encoding/Hebrew/"CS_WINDOWS_1255, GTK_UI_MANAGER_MENUITEM)
7951 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Arabic", "Options/Encoding/Arabic", GTK_UI_MANAGER_MENU)
7952 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)
7953 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Arabic", CS_WINDOWS_1256, "Options/Encoding/Arabic/"CS_WINDOWS_1256, GTK_UI_MANAGER_MENUITEM)
7955 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_9, "Options/Encoding/"CS_ISO_8859_9, GTK_UI_MANAGER_MENUITEM)
7957 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Cyrillic", "Options/Encoding/Cyrillic", GTK_UI_MANAGER_MENU)
7958 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)
7959 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_R, "Options/Encoding/Cyrillic/"CS_KOI8_R, GTK_UI_MANAGER_MENUITEM)
7960 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_MACCYR, "Options/Encoding/Cyrillic/"CS_MACCYR, GTK_UI_MANAGER_MENUITEM)
7961 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_U, "Options/Encoding/Cyrillic/"CS_KOI8_U, GTK_UI_MANAGER_MENUITEM)
7962 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_WINDOWS_1251, "Options/Encoding/Cyrillic/"CS_WINDOWS_1251, GTK_UI_MANAGER_MENUITEM)
7964 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Japanese", "Options/Encoding/Japanese", GTK_UI_MANAGER_MENU)
7965 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)
7966 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)
7967 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_EUC_JP, "Options/Encoding/Japanese/"CS_EUC_JP, GTK_UI_MANAGER_MENUITEM)
7968 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_SHIFT_JIS, "Options/Encoding/Japanese/"CS_SHIFT_JIS, GTK_UI_MANAGER_MENUITEM)
7970 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Chinese", "Options/Encoding/Chinese", GTK_UI_MANAGER_MENU)
7971 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB18030, "Options/Encoding/Chinese/"CS_GB18030, GTK_UI_MANAGER_MENUITEM)
7972 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB2312, "Options/Encoding/Chinese/"CS_GB2312, GTK_UI_MANAGER_MENUITEM)
7973 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GBK, "Options/Encoding/Chinese/"CS_GBK, GTK_UI_MANAGER_MENUITEM)
7974 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_BIG5, "Options/Encoding/Chinese/"CS_BIG5, GTK_UI_MANAGER_MENUITEM)
7975 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_EUC_TW, "Options/Encoding/Chinese/"CS_EUC_TW, GTK_UI_MANAGER_MENUITEM)
7977 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Korean", "Options/Encoding/Korean", GTK_UI_MANAGER_MENU)
7978 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Korean", CS_EUC_KR, "Options/Encoding/Korean/"CS_EUC_KR, GTK_UI_MANAGER_MENUITEM)
7979 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)
7981 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Thai", "Options/Encoding/Thai", GTK_UI_MANAGER_MENU)
7982 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_TIS_620, "Options/Encoding/Thai/"CS_TIS_620, GTK_UI_MANAGER_MENUITEM)
7983 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_WINDOWS_874, "Options/Encoding/Thai/"CS_WINDOWS_874, GTK_UI_MANAGER_MENUITEM)
7987 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "ShowRuler", "Tools/ShowRuler", GTK_UI_MANAGER_MENUITEM)
7988 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "AddressBook", "Tools/AddressBook", GTK_UI_MANAGER_MENUITEM)
7989 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Template", "Tools/Template", GTK_UI_MANAGER_MENU)
7990 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Template", "PlaceHolder", "Tools/Template/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7991 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Actions", "Tools/Actions", GTK_UI_MANAGER_MENU)
7992 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Actions", "PlaceHolder", "Tools/Actions/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7995 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Help", "About", "Help/About", GTK_UI_MANAGER_MENUITEM)
7997 menubar = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu");
7998 gtk_widget_show_all(menubar);
8000 gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(compose->ui_manager));
8001 gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, TRUE, 0);
8003 if (prefs_common.toolbar_detachable) {
8004 handlebox = gtk_handle_box_new();
8006 handlebox = gtk_hbox_new(FALSE, 0);
8008 gtk_box_pack_start(GTK_BOX(vbox), handlebox, FALSE, FALSE, 0);
8010 gtk_widget_realize(handlebox);
8011 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, handlebox,
8014 vbox2 = gtk_vbox_new(FALSE, 2);
8015 gtk_box_pack_start(GTK_BOX(vbox), vbox2, TRUE, TRUE, 0);
8016 gtk_container_set_border_width(GTK_CONTAINER(vbox2), 0);
8019 notebook = gtk_notebook_new();
8020 gtk_widget_show(notebook);
8022 /* header labels and entries */
8023 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
8024 compose_create_header(compose),
8025 gtk_label_new_with_mnemonic(_("Hea_der")));
8026 /* attachment list */
8027 attach_hbox = gtk_hbox_new(FALSE, 0);
8028 gtk_widget_show(attach_hbox);
8030 attach_lab1 = gtk_label_new_with_mnemonic(_("_Attachments"));
8031 gtk_widget_show(attach_lab1);
8032 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab1, TRUE, TRUE, 0);
8034 attach_lab2 = gtk_label_new("");
8035 gtk_widget_show(attach_lab2);
8036 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab2, FALSE, FALSE, 0);
8038 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
8039 compose_create_attach(compose),
8042 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
8043 compose_create_others(compose),
8044 gtk_label_new_with_mnemonic(_("Othe_rs")));
8047 subject_hbox = gtk_hbox_new(FALSE, 0);
8048 gtk_widget_show(subject_hbox);
8050 subject_frame = gtk_frame_new(NULL);
8051 gtk_frame_set_shadow_type(GTK_FRAME(subject_frame), GTK_SHADOW_NONE);
8052 gtk_box_pack_start(GTK_BOX(subject_hbox), subject_frame, TRUE, TRUE, 0);
8053 gtk_widget_show(subject_frame);
8055 subject = gtk_hbox_new(FALSE, HSPACING_NARROW);
8056 gtk_container_set_border_width(GTK_CONTAINER(subject), 0);
8057 gtk_widget_show(subject);
8059 label = gtk_label_new_with_mnemonic(_("S_ubject:"));
8060 gtk_box_pack_start(GTK_BOX(subject), label, FALSE, FALSE, 0);
8061 gtk_widget_show(label);
8064 subject_entry = claws_spell_entry_new();
8066 subject_entry = gtk_entry_new();
8068 gtk_box_pack_start(GTK_BOX(subject), subject_entry, TRUE, TRUE, 0);
8069 g_signal_connect_after(G_OBJECT(subject_entry), "grab_focus",
8070 G_CALLBACK(compose_grab_focus_cb), compose);
8071 gtk_label_set_mnemonic_widget(GTK_LABEL(label), subject_entry);
8072 gtk_widget_show(subject_entry);
8073 compose->subject_entry = subject_entry;
8074 gtk_container_add(GTK_CONTAINER(subject_frame), subject);
8076 edit_vbox = gtk_vbox_new(FALSE, 0);
8078 gtk_box_pack_start(GTK_BOX(edit_vbox), subject_hbox, FALSE, FALSE, 0);
8081 ruler_hbox = gtk_hbox_new(FALSE, 0);
8082 gtk_box_pack_start(GTK_BOX(edit_vbox), ruler_hbox, FALSE, FALSE, 0);
8084 ruler = gtk_shruler_new(GTK_ORIENTATION_HORIZONTAL);
8085 gtk_shruler_set_range(GTK_SHRULER(ruler), 0.0, 100.0, 1.0);
8086 gtk_box_pack_start(GTK_BOX(ruler_hbox), ruler, TRUE, TRUE,
8090 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
8091 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
8092 GTK_POLICY_AUTOMATIC,
8093 GTK_POLICY_AUTOMATIC);
8094 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
8096 gtk_box_pack_start(GTK_BOX(edit_vbox), scrolledwin, TRUE, TRUE, 0);
8098 text = gtk_text_view_new();
8099 if (prefs_common.show_compose_margin) {
8100 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text), 6);
8101 gtk_text_view_set_right_margin(GTK_TEXT_VIEW(text), 6);
8103 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
8104 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
8105 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), TRUE);
8106 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
8107 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
8109 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
8110 g_signal_connect_after(G_OBJECT(text), "size_allocate",
8111 G_CALLBACK(compose_edit_size_alloc),
8113 g_signal_connect(G_OBJECT(buffer), "changed",
8114 G_CALLBACK(compose_changed_cb), compose);
8115 g_signal_connect(G_OBJECT(text), "grab_focus",
8116 G_CALLBACK(compose_grab_focus_cb), compose);
8117 g_signal_connect(G_OBJECT(buffer), "insert_text",
8118 G_CALLBACK(text_inserted), compose);
8119 g_signal_connect(G_OBJECT(text), "button_press_event",
8120 G_CALLBACK(text_clicked), compose);
8121 g_signal_connect(G_OBJECT(text), "popup-menu",
8122 G_CALLBACK(compose_popup_menu), compose);
8123 g_signal_connect(G_OBJECT(subject_entry), "changed",
8124 G_CALLBACK(compose_changed_cb), compose);
8125 g_signal_connect(G_OBJECT(subject_entry), "activate",
8126 G_CALLBACK(compose_subject_entry_activated), compose);
8129 gtk_drag_dest_set(text, GTK_DEST_DEFAULT_ALL, compose_mime_types,
8130 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
8131 GDK_ACTION_COPY | GDK_ACTION_MOVE);
8132 g_signal_connect(G_OBJECT(text), "drag_data_received",
8133 G_CALLBACK(compose_insert_drag_received_cb),
8135 g_signal_connect(G_OBJECT(text), "drag-drop",
8136 G_CALLBACK(compose_drag_drop),
8138 g_signal_connect(G_OBJECT(text), "key-press-event",
8139 G_CALLBACK(completion_set_focus_to_subject),
8141 gtk_widget_show_all(vbox);
8143 /* pane between attach clist and text */
8144 paned = gtk_vpaned_new();
8145 gtk_container_add(GTK_CONTAINER(vbox2), paned);
8146 gtk_paned_pack1(GTK_PANED(paned), notebook, FALSE, FALSE);
8147 gtk_paned_pack2(GTK_PANED(paned), edit_vbox, TRUE, FALSE);
8148 gtk_paned_set_position(GTK_PANED(paned), prefs_common.compose_notebook_height);
8149 g_signal_connect(G_OBJECT(notebook), "size_allocate",
8150 G_CALLBACK(compose_notebook_size_alloc), paned);
8152 gtk_widget_show_all(paned);
8155 if (prefs_common.textfont) {
8156 PangoFontDescription *font_desc;
8158 font_desc = pango_font_description_from_string
8159 (prefs_common.textfont);
8161 gtk_widget_modify_font(text, font_desc);
8162 pango_font_description_free(font_desc);
8166 gtk_action_group_add_actions(action_group, compose_popup_entries,
8167 G_N_ELEMENTS(compose_popup_entries), (gpointer)compose);
8168 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Popup", NULL, GTK_UI_MANAGER_MENUBAR)
8169 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup", "Compose", "Compose", GTK_UI_MANAGER_MENU)
8170 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Add", "Compose/Add", GTK_UI_MANAGER_MENUITEM)
8171 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Remove", "Compose/Remove", GTK_UI_MANAGER_MENUITEM)
8172 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Separator1", "Compose/---", GTK_UI_MANAGER_SEPARATOR)
8173 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Properties", "Compose/Properties", GTK_UI_MANAGER_MENUITEM)
8175 popupmenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(gtk_ui_manager_get_widget(compose->ui_manager, "/Popup/Compose")));
8177 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
8178 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
8179 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
8181 tmpl_menu = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu/Tools/Template");
8183 undostruct = undo_init(text);
8184 undo_set_change_state_func(undostruct, &compose_undo_state_changed,
8187 address_completion_start(window);
8189 compose->window = window;
8190 compose->vbox = vbox;
8191 compose->menubar = menubar;
8192 compose->handlebox = handlebox;
8194 compose->vbox2 = vbox2;
8196 compose->paned = paned;
8198 compose->attach_label = attach_lab2;
8200 compose->notebook = notebook;
8201 compose->edit_vbox = edit_vbox;
8202 compose->ruler_hbox = ruler_hbox;
8203 compose->ruler = ruler;
8204 compose->scrolledwin = scrolledwin;
8205 compose->text = text;
8207 compose->focused_editable = NULL;
8209 compose->popupmenu = popupmenu;
8211 compose->tmpl_menu = tmpl_menu;
8213 compose->mode = mode;
8214 compose->rmode = mode;
8216 compose->targetinfo = NULL;
8217 compose->replyinfo = NULL;
8218 compose->fwdinfo = NULL;
8220 compose->email_hashtable = g_hash_table_new_full(g_str_hash,
8221 g_str_equal, (GDestroyNotify) g_free, NULL);
8223 compose->replyto = NULL;
8225 compose->bcc = NULL;
8226 compose->followup_to = NULL;
8228 compose->ml_post = NULL;
8230 compose->inreplyto = NULL;
8231 compose->references = NULL;
8232 compose->msgid = NULL;
8233 compose->boundary = NULL;
8235 compose->autowrap = prefs_common.autowrap;
8236 compose->autoindent = prefs_common.auto_indent;
8237 compose->use_signing = FALSE;
8238 compose->use_encryption = FALSE;
8239 compose->privacy_system = NULL;
8240 compose->encdata = NULL;
8242 compose->modified = FALSE;
8244 compose->return_receipt = FALSE;
8246 compose->to_list = NULL;
8247 compose->newsgroup_list = NULL;
8249 compose->undostruct = undostruct;
8251 compose->sig_str = NULL;
8253 compose->exteditor_file = NULL;
8254 compose->exteditor_pid = -1;
8255 compose->exteditor_tag = -1;
8256 compose->exteditor_socket = NULL;
8257 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN; /* inhibit auto-drafting while loading */
8259 compose->folder_update_callback_id =
8260 hooks_register_hook(FOLDER_UPDATE_HOOKLIST,
8261 compose_update_folder_hook,
8262 (gpointer) compose);
8265 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
8266 if (mode != COMPOSE_REDIRECT) {
8267 if (prefs_common.enable_aspell && prefs_common.dictionary &&
8268 strcmp(prefs_common.dictionary, "")) {
8269 gtkaspell = gtkaspell_new(prefs_common.dictionary,
8270 prefs_common.alt_dictionary,
8271 conv_get_locale_charset_str(),
8272 prefs_common.misspelled_col,
8273 prefs_common.check_while_typing,
8274 prefs_common.recheck_when_changing_dict,
8275 prefs_common.use_alternate,
8276 prefs_common.use_both_dicts,
8277 GTK_TEXT_VIEW(text),
8278 GTK_WINDOW(compose->window),
8279 compose_dict_changed,
8280 compose_spell_menu_changed,
8283 alertpanel_error(_("Spell checker could not "
8285 gtkaspell_checkers_strerror());
8286 gtkaspell_checkers_reset_error();
8288 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", TRUE);
8292 compose->gtkaspell = gtkaspell;
8293 compose_spell_menu_changed(compose);
8294 claws_spell_entry_set_gtkaspell(CLAWS_SPELL_ENTRY(subject_entry), gtkaspell);
8297 compose_select_account(compose, account, TRUE);
8299 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", prefs_common.autowrap);
8300 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", prefs_common.auto_indent);
8302 if (account->set_autocc && account->auto_cc && mode != COMPOSE_REEDIT)
8303 compose_entry_append(compose, account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8305 if (account->set_autobcc && account->auto_bcc && mode != COMPOSE_REEDIT)
8306 compose_entry_append(compose, account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8308 if (account->set_autoreplyto && account->auto_replyto && mode != COMPOSE_REEDIT)
8309 compose_entry_append(compose, account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8311 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", compose->mode == COMPOSE_REPLY);
8312 if (account->protocol != A_NNTP)
8313 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
8314 prefs_common_translated_header_name("To:"));
8316 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
8317 prefs_common_translated_header_name("Newsgroups:"));
8319 #ifndef USE_ALT_ADDRBOOK
8320 addressbook_set_target_compose(compose);
8322 if (mode != COMPOSE_REDIRECT)
8323 compose_set_template_menu(compose);
8325 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Template", FALSE);
8328 compose_list = g_list_append(compose_list, compose);
8330 if (!prefs_common.show_ruler)
8331 gtk_widget_hide(ruler_hbox);
8333 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Tools/ShowRuler", prefs_common.show_ruler);
8336 compose->priority = PRIORITY_NORMAL;
8337 compose_update_priority_menu_item(compose);
8339 compose_set_out_encoding(compose);
8342 compose_update_actions_menu(compose);
8344 /* Privacy Systems menu */
8345 compose_update_privacy_systems_menu(compose);
8347 activate_privacy_system(compose, account, TRUE);
8348 toolbar_set_style(compose->toolbar->toolbar, compose->handlebox, prefs_common.toolbar_style);
8350 gtk_widget_realize(window);
8352 gtk_widget_show(window);
8358 static GtkWidget *compose_account_option_menu_create(Compose *compose)
8363 GtkWidget *optmenubox;
8364 GtkWidget *fromlabel;
8367 GtkWidget *from_name = NULL;
8369 gint num = 0, def_menu = 0;
8371 accounts = account_get_list();
8372 cm_return_val_if_fail(accounts != NULL, NULL);
8374 optmenubox = gtk_event_box_new();
8375 optmenu = gtkut_sc_combobox_create(optmenubox, FALSE);
8376 menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
8378 hbox = gtk_hbox_new(FALSE, 4);
8379 from_name = gtk_entry_new();
8381 g_signal_connect_after(G_OBJECT(from_name), "grab_focus",
8382 G_CALLBACK(compose_grab_focus_cb), compose);
8383 g_signal_connect_after(G_OBJECT(from_name), "activate",
8384 G_CALLBACK(from_name_activate_cb), optmenu);
8386 for (; accounts != NULL; accounts = accounts->next, num++) {
8387 PrefsAccount *ac = (PrefsAccount *)accounts->data;
8388 gchar *name, *from = NULL;
8390 if (ac == compose->account) def_menu = num;
8392 name = g_markup_printf_escaped("<i>%s</i>",
8395 if (ac == compose->account) {
8396 if (ac->name && *ac->name) {
8398 QUOTE_IF_REQUIRED_NORMAL(buf, ac->name, return NULL);
8399 from = g_strdup_printf("%s <%s>",
8401 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8403 from = g_strdup_printf("%s",
8405 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8407 if (cur_account != compose->account) {
8408 gtk_widget_modify_base(
8409 GTK_WIDGET(from_name),
8410 GTK_STATE_NORMAL, &default_header_bgcolor);
8411 gtk_widget_modify_text(
8412 GTK_WIDGET(from_name),
8413 GTK_STATE_NORMAL, &default_header_color);
8416 COMBOBOX_ADD(menu, name, ac->account_id);
8421 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), def_menu);
8423 g_signal_connect(G_OBJECT(optmenu), "changed",
8424 G_CALLBACK(account_activated),
8426 g_signal_connect(G_OBJECT(from_name), "populate-popup",
8427 G_CALLBACK(compose_entry_popup_extend),
8430 fromlabel = gtk_label_new_with_mnemonic(_("_From:"));
8431 gtk_label_set_mnemonic_widget(GTK_LABEL(fromlabel), from_name);
8433 gtk_box_pack_start(GTK_BOX(hbox), fromlabel, FALSE, FALSE, 4);
8434 gtk_box_pack_start(GTK_BOX(hbox), optmenubox, FALSE, FALSE, 0);
8435 gtk_box_pack_start(GTK_BOX(hbox), from_name, TRUE, TRUE, 0);
8437 /* Putting only the GtkEntry into focus chain of parent hbox causes
8438 * the account selector combobox next to it to be unreachable when
8439 * navigating widgets in GtkTable with up/down arrow keys.
8440 * Note: gtk_widget_set_can_focus() was not enough. */
8442 l = g_list_prepend(l, from_name);
8443 gtk_container_set_focus_chain(GTK_CONTAINER(hbox), l);
8446 CLAWS_SET_TIP(optmenubox,
8447 _("Account to use for this email"));
8448 CLAWS_SET_TIP(from_name,
8449 _("Sender address to be used"));
8451 compose->account_combo = optmenu;
8452 compose->from_name = from_name;
8457 static void compose_set_priority_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8459 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8460 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8461 Compose *compose = (Compose *) data;
8463 compose->priority = value;
8467 static void compose_reply_change_mode(Compose *compose,
8470 gboolean was_modified = compose->modified;
8472 gboolean all = FALSE, ml = FALSE, sender = FALSE, followup = FALSE;
8474 cm_return_if_fail(compose->replyinfo != NULL);
8476 if (action == COMPOSE_REPLY && prefs_common.default_reply_list)
8478 if (action == COMPOSE_REPLY && compose->rmode == COMPOSE_FOLLOWUP_AND_REPLY_TO)
8480 if (action == COMPOSE_REPLY_TO_ALL)
8482 if (action == COMPOSE_REPLY_TO_SENDER)
8484 if (action == COMPOSE_REPLY_TO_LIST)
8487 compose_remove_header_entries(compose);
8488 compose_reply_set_entry(compose, compose->replyinfo, all, ml, sender, followup);
8489 if (compose->account->set_autocc && compose->account->auto_cc)
8490 compose_entry_append(compose, compose->account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8492 if (compose->account->set_autobcc && compose->account->auto_bcc)
8493 compose_entry_append(compose, compose->account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8495 if (compose->account->set_autoreplyto && compose->account->auto_replyto)
8496 compose_entry_append(compose, compose->account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8497 compose_show_first_last_header(compose, TRUE);
8498 compose->modified = was_modified;
8499 compose_set_title(compose);
8502 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8504 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8505 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8506 Compose *compose = (Compose *) data;
8509 compose_reply_change_mode(compose, value);
8512 static void compose_update_priority_menu_item(Compose * compose)
8514 GtkWidget *menuitem = NULL;
8515 switch (compose->priority) {
8516 case PRIORITY_HIGHEST:
8517 menuitem = gtk_ui_manager_get_widget
8518 (compose->ui_manager, "/Menu/Options/Priority/Highest");
8521 menuitem = gtk_ui_manager_get_widget
8522 (compose->ui_manager, "/Menu/Options/Priority/High");
8524 case PRIORITY_NORMAL:
8525 menuitem = gtk_ui_manager_get_widget
8526 (compose->ui_manager, "/Menu/Options/Priority/Normal");
8529 menuitem = gtk_ui_manager_get_widget
8530 (compose->ui_manager, "/Menu/Options/Priority/Low");
8532 case PRIORITY_LOWEST:
8533 menuitem = gtk_ui_manager_get_widget
8534 (compose->ui_manager, "/Menu/Options/Priority/Lowest");
8537 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8540 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data)
8542 Compose *compose = (Compose *) data;
8544 gboolean can_sign = FALSE, can_encrypt = FALSE;
8546 cm_return_if_fail(GTK_IS_CHECK_MENU_ITEM(widget));
8548 if (!gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget)))
8551 systemid = g_object_get_data(G_OBJECT(widget), "privacy_system");
8552 g_free(compose->privacy_system);
8553 compose->privacy_system = NULL;
8554 g_free(compose->encdata);
8555 compose->encdata = NULL;
8556 if (systemid != NULL) {
8557 compose->privacy_system = g_strdup(systemid);
8559 can_sign = privacy_system_can_sign(systemid);
8560 can_encrypt = privacy_system_can_encrypt(systemid);
8563 debug_print("activated privacy system: %s\n", systemid != NULL ? systemid : "None");
8565 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8566 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8567 if (compose->toolbar->privacy_sign_btn != NULL) {
8568 gtk_widget_set_sensitive(
8569 GTK_WIDGET(compose->toolbar->privacy_sign_btn),
8571 gtk_toggle_tool_button_set_active(
8572 GTK_TOGGLE_TOOL_BUTTON(compose->toolbar->privacy_sign_btn),
8573 can_sign ? compose->use_signing : FALSE);
8575 if (compose->toolbar->privacy_encrypt_btn != NULL) {
8576 gtk_widget_set_sensitive(
8577 GTK_WIDGET(compose->toolbar->privacy_encrypt_btn),
8579 gtk_toggle_tool_button_set_active(
8580 GTK_TOGGLE_TOOL_BUTTON(compose->toolbar->privacy_encrypt_btn),
8581 can_encrypt ? compose->use_encryption : FALSE);
8585 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn)
8587 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8588 GtkWidget *menuitem = NULL;
8589 GList *children, *amenu;
8590 gboolean can_sign = FALSE, can_encrypt = FALSE;
8591 gboolean found = FALSE;
8593 if (compose->privacy_system != NULL) {
8595 menuitem = gtk_menu_item_get_submenu(GTK_MENU_ITEM(
8596 gtk_ui_manager_get_widget(compose->ui_manager, branch_path)));
8597 cm_return_if_fail(menuitem != NULL);
8599 children = gtk_container_get_children(GTK_CONTAINER(GTK_MENU_SHELL(menuitem)));
8602 while (amenu != NULL) {
8603 systemid = g_object_get_data(G_OBJECT(amenu->data), "privacy_system");
8604 if (systemid != NULL) {
8605 if (strcmp(systemid, compose->privacy_system) == 0 &&
8606 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8607 menuitem = GTK_WIDGET(amenu->data);
8609 can_sign = privacy_system_can_sign(systemid);
8610 can_encrypt = privacy_system_can_encrypt(systemid);
8614 } else if (strlen(compose->privacy_system) == 0 &&
8615 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8616 menuitem = GTK_WIDGET(amenu->data);
8619 can_encrypt = FALSE;
8624 amenu = amenu->next;
8626 g_list_free(children);
8627 if (menuitem != NULL)
8628 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8630 if (warn && !found && strlen(compose->privacy_system)) {
8631 alertpanel_warning(_("The privacy system '%s' cannot be loaded. You "
8632 "will not be able to sign or encrypt this message."),
8633 compose->privacy_system);
8637 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8638 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8639 if (compose->toolbar->privacy_sign_btn != NULL) {
8640 gtk_widget_set_sensitive(
8641 GTK_WIDGET(compose->toolbar->privacy_sign_btn),
8644 if (compose->toolbar->privacy_encrypt_btn != NULL) {
8645 gtk_widget_set_sensitive(
8646 GTK_WIDGET(compose->toolbar->privacy_encrypt_btn),
8651 static void compose_set_out_encoding(Compose *compose)
8653 CharSet out_encoding;
8654 const gchar *branch = NULL;
8655 out_encoding = conv_get_charset_from_str(prefs_common.outgoing_charset);
8657 switch(out_encoding) {
8658 case C_AUTO: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8659 case C_US_ASCII: branch = "Menu/Options/Encoding/" CS_US_ASCII; break;
8660 case C_UTF_8: branch = "Menu/Options/Encoding/" CS_UTF_8; break;
8661 case C_ISO_8859_2: branch = "Menu/Options/Encoding/" CS_ISO_8859_2; break;
8662 case C_ISO_8859_7: branch = "Menu/Options/Encoding/" CS_ISO_8859_7; break;
8663 case C_ISO_8859_9: branch = "Menu/Options/Encoding/" CS_ISO_8859_9; break;
8664 case C_ISO_8859_1: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_1; break;
8665 case C_ISO_8859_15: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_15; break;
8666 case C_WINDOWS_1252: branch = "Menu/Options/Encoding/Western/" CS_WINDOWS_1252; break;
8667 case C_ISO_8859_13: branch = "Menu/Options/Encoding/Baltic/" CS_ISO_8859_13; break;
8668 case C_ISO_8859_4: branch = "Menu/Options/Encoding/Baltic" CS_ISO_8859_4; break;
8669 case C_ISO_8859_8: branch = "Menu/Options/Encoding/Hebrew/" CS_ISO_8859_8; break;
8670 case C_WINDOWS_1255: branch = "Menu/Options/Encoding/Hebrew/" CS_WINDOWS_1255; break;
8671 case C_ISO_8859_6: branch = "Menu/Options/Encoding/Arabic/" CS_ISO_8859_6; break;
8672 case C_WINDOWS_1256: branch = "Menu/Options/Encoding/Arabic/" CS_WINDOWS_1256; break;
8673 case C_ISO_8859_5: branch = "Menu/Options/Encoding/Cyrillic/" CS_ISO_8859_5; break;
8674 case C_KOI8_R: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_R; break;
8675 case C_MACCYR: branch = "Menu/Options/Encoding/Cyrillic/" CS_MACCYR; break;
8676 case C_KOI8_U: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_U; break;
8677 case C_WINDOWS_1251: branch = "Menu/Options/Encoding/Cyrillic/" CS_WINDOWS_1251; break;
8678 case C_ISO_2022_JP: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP; break;
8679 case C_ISO_2022_JP_2: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP_2; break;
8680 case C_EUC_JP: branch = "Menu/Options/Encoding/Japanese/" CS_EUC_JP; break;
8681 case C_SHIFT_JIS: branch = "Menu/Options/Encoding/Japanese/" CS_SHIFT_JIS; break;
8682 case C_GB18030: branch = "Menu/Options/Encoding/Chinese/" CS_GB18030; break;
8683 case C_GB2312: branch = "Menu/Options/Encoding/Chinese/" CS_GB2312; break;
8684 case C_GBK: branch = "Menu/Options/Encoding/Chinese/" CS_GBK; break;
8685 case C_BIG5: branch = "Menu/Options/Encoding/Chinese/" CS_BIG5; break;
8686 case C_EUC_TW: branch = "Menu/Options/Encoding/Chinese/" CS_EUC_TW; break;
8687 case C_EUC_KR: branch = "Menu/Options/Encoding/Korean/" CS_EUC_KR; break;
8688 case C_ISO_2022_KR: branch = "Menu/Options/Encoding/Korean/" CS_ISO_2022_KR; break;
8689 case C_TIS_620: branch = "Menu/Options/Encoding/Thai/" CS_TIS_620; break;
8690 case C_WINDOWS_874: branch = "Menu/Options/Encoding/Thai/" CS_WINDOWS_874; break;
8691 default: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8693 cm_toggle_menu_set_active_full(compose->ui_manager, (gchar *)branch, TRUE);
8696 static void compose_set_template_menu(Compose *compose)
8698 GSList *tmpl_list, *cur;
8702 tmpl_list = template_get_config();
8704 menu = gtk_menu_new();
8706 gtk_menu_set_accel_group (GTK_MENU (menu),
8707 gtk_ui_manager_get_accel_group(compose->ui_manager));
8708 for (cur = tmpl_list; cur != NULL; cur = cur->next) {
8709 Template *tmpl = (Template *)cur->data;
8710 gchar *accel_path = NULL;
8711 item = gtk_menu_item_new_with_label(tmpl->name);
8712 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
8713 g_signal_connect(G_OBJECT(item), "activate",
8714 G_CALLBACK(compose_template_activate_cb),
8716 g_object_set_data(G_OBJECT(item), "template", tmpl);
8717 gtk_widget_show(item);
8718 accel_path = g_strconcat("<ComposeTemplates>" , "/", tmpl->name, NULL);
8719 gtk_menu_item_set_accel_path(GTK_MENU_ITEM(item), accel_path);
8723 gtk_widget_show(menu);
8724 gtk_menu_item_set_submenu(GTK_MENU_ITEM(compose->tmpl_menu), menu);
8727 void compose_update_actions_menu(Compose *compose)
8729 action_update_compose_menu(compose->ui_manager, "/Menu/Tools/Actions", compose);
8732 static void compose_update_privacy_systems_menu(Compose *compose)
8734 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8735 GSList *systems, *cur;
8737 GtkWidget *system_none;
8739 GtkWidget *privacy_menuitem = gtk_ui_manager_get_widget(compose->ui_manager, branch_path);
8740 GtkWidget *privacy_menu = gtk_menu_new();
8742 system_none = gtk_radio_menu_item_new_with_mnemonic(NULL, _("_None"));
8743 g_object_set_data_full(G_OBJECT(system_none), "privacy_system", NULL, NULL);
8745 g_signal_connect(G_OBJECT(system_none), "activate",
8746 G_CALLBACK(compose_set_privacy_system_cb), compose);
8748 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), system_none);
8749 gtk_widget_show(system_none);
8751 systems = privacy_get_system_ids();
8752 for (cur = systems; cur != NULL; cur = g_slist_next(cur)) {
8753 gchar *systemid = cur->data;
8755 group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(system_none));
8756 widget = gtk_radio_menu_item_new_with_label(group,
8757 privacy_system_get_name(systemid));
8758 g_object_set_data_full(G_OBJECT(widget), "privacy_system",
8759 g_strdup(systemid), g_free);
8760 g_signal_connect(G_OBJECT(widget), "activate",
8761 G_CALLBACK(compose_set_privacy_system_cb), compose);
8763 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), widget);
8764 gtk_widget_show(widget);
8767 g_slist_free(systems);
8768 gtk_menu_item_set_submenu(GTK_MENU_ITEM(privacy_menuitem), privacy_menu);
8769 gtk_widget_show_all(privacy_menu);
8770 gtk_widget_show_all(privacy_menuitem);
8773 void compose_reflect_prefs_all(void)
8778 for (cur = compose_list; cur != NULL; cur = cur->next) {
8779 compose = (Compose *)cur->data;
8780 compose_set_template_menu(compose);
8784 void compose_reflect_prefs_pixmap_theme(void)
8789 for (cur = compose_list; cur != NULL; cur = cur->next) {
8790 compose = (Compose *)cur->data;
8791 toolbar_update(TOOLBAR_COMPOSE, compose);
8795 static const gchar *compose_quote_char_from_context(Compose *compose)
8797 const gchar *qmark = NULL;
8799 cm_return_val_if_fail(compose != NULL, NULL);
8801 switch (compose->mode) {
8802 /* use forward-specific quote char */
8803 case COMPOSE_FORWARD:
8804 case COMPOSE_FORWARD_AS_ATTACH:
8805 case COMPOSE_FORWARD_INLINE:
8806 if (compose->folder && compose->folder->prefs &&
8807 compose->folder->prefs->forward_with_format)
8808 qmark = compose->folder->prefs->forward_quotemark;
8809 else if (compose->account->forward_with_format)
8810 qmark = compose->account->forward_quotemark;
8812 qmark = prefs_common.fw_quotemark;
8815 /* use reply-specific quote char in all other modes */
8817 if (compose->folder && compose->folder->prefs &&
8818 compose->folder->prefs->reply_with_format)
8819 qmark = compose->folder->prefs->reply_quotemark;
8820 else if (compose->account->reply_with_format)
8821 qmark = compose->account->reply_quotemark;
8823 qmark = prefs_common.quotemark;
8827 if (qmark == NULL || *qmark == '\0')
8833 static void compose_template_apply(Compose *compose, Template *tmpl,
8837 GtkTextBuffer *buffer;
8841 gchar *parsed_str = NULL;
8842 gint cursor_pos = 0;
8843 const gchar *err_msg = _("The body of the template has an error at line %d.");
8846 /* process the body */
8848 text = GTK_TEXT_VIEW(compose->text);
8849 buffer = gtk_text_view_get_buffer(text);
8852 qmark = compose_quote_char_from_context(compose);
8854 if (compose->replyinfo != NULL) {
8857 gtk_text_buffer_set_text(buffer, "", -1);
8858 mark = gtk_text_buffer_get_insert(buffer);
8859 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8861 parsed_str = compose_quote_fmt(compose, compose->replyinfo,
8862 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8864 } else if (compose->fwdinfo != NULL) {
8867 gtk_text_buffer_set_text(buffer, "", -1);
8868 mark = gtk_text_buffer_get_insert(buffer);
8869 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8871 parsed_str = compose_quote_fmt(compose, compose->fwdinfo,
8872 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8875 MsgInfo* dummyinfo = compose_msginfo_new_from_compose(compose);
8877 GtkTextIter start, end;
8880 gtk_text_buffer_get_start_iter(buffer, &start);
8881 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
8882 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
8884 /* clear the buffer now */
8886 gtk_text_buffer_set_text(buffer, "", -1);
8888 parsed_str = compose_quote_fmt(compose, dummyinfo,
8889 tmpl->value, qmark, tmp, FALSE, FALSE, err_msg);
8890 procmsg_msginfo_free( &dummyinfo );
8896 gtk_text_buffer_set_text(buffer, "", -1);
8897 mark = gtk_text_buffer_get_insert(buffer);
8898 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8901 if (replace && parsed_str && compose->account->auto_sig)
8902 compose_insert_sig(compose, FALSE);
8904 if (replace && parsed_str) {
8905 gtk_text_buffer_get_start_iter(buffer, &iter);
8906 gtk_text_buffer_place_cursor(buffer, &iter);
8910 cursor_pos = quote_fmt_get_cursor_pos();
8911 compose->set_cursor_pos = cursor_pos;
8912 if (cursor_pos == -1)
8914 gtk_text_buffer_get_start_iter(buffer, &iter);
8915 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
8916 gtk_text_buffer_place_cursor(buffer, &iter);
8919 /* process the other fields */
8921 compose_template_apply_fields(compose, tmpl);
8922 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
8923 quote_fmt_reset_vartable();
8924 quote_fmtlex_destroy();
8926 compose_changed_cb(NULL, compose);
8929 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
8930 gtkaspell_highlight_all(compose->gtkaspell);
8934 static void compose_template_apply_fields_error(const gchar *header)
8939 tr = g_strdup(C_("'%s' stands for a header name",
8940 "Template '%s' format error."));
8941 text = g_strdup_printf(tr, prefs_common_translated_header_name(header));
8942 alertpanel_error("%s", text);
8948 static void compose_template_apply_fields(Compose *compose, Template *tmpl)
8950 MsgInfo* dummyinfo = NULL;
8951 MsgInfo *msginfo = NULL;
8954 if (compose->replyinfo != NULL)
8955 msginfo = compose->replyinfo;
8956 else if (compose->fwdinfo != NULL)
8957 msginfo = compose->fwdinfo;
8959 dummyinfo = compose_msginfo_new_from_compose(compose);
8960 msginfo = dummyinfo;
8963 if (tmpl->from && *tmpl->from != '\0') {
8965 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8966 compose->gtkaspell);
8968 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8970 quote_fmt_scan_string(tmpl->from);
8973 buf = quote_fmt_get_buffer();
8975 compose_template_apply_fields_error("From");
8977 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
8980 quote_fmt_reset_vartable();
8981 quote_fmtlex_destroy();
8984 if (tmpl->to && *tmpl->to != '\0') {
8986 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8987 compose->gtkaspell);
8989 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8991 quote_fmt_scan_string(tmpl->to);
8994 buf = quote_fmt_get_buffer();
8996 compose_template_apply_fields_error("To");
8998 compose_entry_append(compose, buf, COMPOSE_TO, PREF_TEMPLATE);
9001 quote_fmt_reset_vartable();
9002 quote_fmtlex_destroy();
9005 if (tmpl->cc && *tmpl->cc != '\0') {
9007 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
9008 compose->gtkaspell);
9010 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
9012 quote_fmt_scan_string(tmpl->cc);
9015 buf = quote_fmt_get_buffer();
9017 compose_template_apply_fields_error("Cc");
9019 compose_entry_append(compose, buf, COMPOSE_CC, PREF_TEMPLATE);
9022 quote_fmt_reset_vartable();
9023 quote_fmtlex_destroy();
9026 if (tmpl->bcc && *tmpl->bcc != '\0') {
9028 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
9029 compose->gtkaspell);
9031 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
9033 quote_fmt_scan_string(tmpl->bcc);
9036 buf = quote_fmt_get_buffer();
9038 compose_template_apply_fields_error("Bcc");
9040 compose_entry_append(compose, buf, COMPOSE_BCC, PREF_TEMPLATE);
9043 quote_fmt_reset_vartable();
9044 quote_fmtlex_destroy();
9047 if (tmpl->replyto && *tmpl->replyto != '\0') {
9049 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
9050 compose->gtkaspell);
9052 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
9054 quote_fmt_scan_string(tmpl->replyto);
9057 buf = quote_fmt_get_buffer();
9059 compose_template_apply_fields_error("Reply-To");
9061 compose_entry_append(compose, buf, COMPOSE_REPLYTO, PREF_TEMPLATE);
9064 quote_fmt_reset_vartable();
9065 quote_fmtlex_destroy();
9068 /* process the subject */
9069 if (tmpl->subject && *tmpl->subject != '\0') {
9071 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
9072 compose->gtkaspell);
9074 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
9076 quote_fmt_scan_string(tmpl->subject);
9079 buf = quote_fmt_get_buffer();
9081 compose_template_apply_fields_error("Subject");
9083 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
9086 quote_fmt_reset_vartable();
9087 quote_fmtlex_destroy();
9090 procmsg_msginfo_free( &dummyinfo );
9093 static void compose_destroy(Compose *compose)
9095 GtkAllocation allocation;
9096 GtkTextBuffer *buffer;
9097 GtkClipboard *clipboard;
9099 compose_list = g_list_remove(compose_list, compose);
9101 gboolean enable = TRUE;
9102 g_slist_foreach(compose->passworded_ldap_servers,
9103 _ldap_srv_func, &enable);
9104 g_slist_free(compose->passworded_ldap_servers);
9106 if (compose->updating) {
9107 debug_print("danger, not destroying anything now\n");
9108 compose->deferred_destroy = TRUE;
9112 /* NOTE: address_completion_end() does nothing with the window
9113 * however this may change. */
9114 address_completion_end(compose->window);
9116 slist_free_strings_full(compose->to_list);
9117 slist_free_strings_full(compose->newsgroup_list);
9118 slist_free_strings_full(compose->header_list);
9120 slist_free_strings_full(extra_headers);
9121 extra_headers = NULL;
9123 compose->header_list = compose->newsgroup_list = compose->to_list = NULL;
9125 g_hash_table_destroy(compose->email_hashtable);
9127 hooks_unregister_hook(FOLDER_UPDATE_HOOKLIST,
9128 compose->folder_update_callback_id);
9130 procmsg_msginfo_free(&(compose->targetinfo));
9131 procmsg_msginfo_free(&(compose->replyinfo));
9132 procmsg_msginfo_free(&(compose->fwdinfo));
9134 g_free(compose->replyto);
9135 g_free(compose->cc);
9136 g_free(compose->bcc);
9137 g_free(compose->newsgroups);
9138 g_free(compose->followup_to);
9140 g_free(compose->ml_post);
9142 g_free(compose->inreplyto);
9143 g_free(compose->references);
9144 g_free(compose->msgid);
9145 g_free(compose->boundary);
9147 g_free(compose->redirect_filename);
9148 if (compose->undostruct)
9149 undo_destroy(compose->undostruct);
9151 g_free(compose->sig_str);
9153 g_free(compose->exteditor_file);
9155 g_free(compose->orig_charset);
9157 g_free(compose->privacy_system);
9158 g_free(compose->encdata);
9160 #ifndef USE_ALT_ADDRBOOK
9161 if (addressbook_get_target_compose() == compose)
9162 addressbook_set_target_compose(NULL);
9165 if (compose->gtkaspell) {
9166 gtkaspell_delete(compose->gtkaspell);
9167 compose->gtkaspell = NULL;
9171 if (!compose->batch) {
9172 gtk_widget_get_allocation(compose->window, &allocation);
9173 prefs_common.compose_width = allocation.width;
9174 prefs_common.compose_height = allocation.height;
9177 if (!gtk_widget_get_parent(compose->paned))
9178 gtk_widget_destroy(compose->paned);
9179 gtk_widget_destroy(compose->popupmenu);
9181 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
9182 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
9183 gtk_text_buffer_remove_selection_clipboard(buffer, clipboard);
9185 gtk_widget_destroy(compose->window);
9186 toolbar_destroy(compose->toolbar);
9187 g_free(compose->toolbar);
9188 cm_mutex_free(compose->mutex);
9192 static void compose_attach_info_free(AttachInfo *ainfo)
9194 g_free(ainfo->file);
9195 g_free(ainfo->content_type);
9196 g_free(ainfo->name);
9197 g_free(ainfo->charset);
9201 static void compose_attach_update_label(Compose *compose)
9206 GtkTreeModel *model;
9210 if (compose == NULL)
9213 model = gtk_tree_view_get_model(GTK_TREE_VIEW(compose->attach_clist));
9214 if (!gtk_tree_model_get_iter_first(model, &iter)) {
9215 gtk_label_set_text(GTK_LABEL(compose->attach_label), "");
9219 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
9220 total_size = ainfo->size;
9221 while(gtk_tree_model_iter_next(model, &iter)) {
9222 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
9223 total_size += ainfo->size;
9226 text = g_strdup_printf(" (%d/%s)", i, to_human_readable(total_size));
9227 gtk_label_set_text(GTK_LABEL(compose->attach_label), text);
9231 static void compose_attach_remove_selected(GtkAction *action, gpointer data)
9233 Compose *compose = (Compose *)data;
9234 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
9235 GtkTreeSelection *selection;
9237 GtkTreeModel *model;
9239 selection = gtk_tree_view_get_selection(tree_view);
9240 sel = gtk_tree_selection_get_selected_rows(selection, &model);
9245 for (cur = sel; cur != NULL; cur = cur->next) {
9246 GtkTreePath *path = cur->data;
9247 GtkTreeRowReference *ref = gtk_tree_row_reference_new
9250 gtk_tree_path_free(path);
9253 for (cur = sel; cur != NULL; cur = cur->next) {
9254 GtkTreeRowReference *ref = cur->data;
9255 GtkTreePath *path = gtk_tree_row_reference_get_path(ref);
9258 if (gtk_tree_model_get_iter(model, &iter, path))
9259 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
9261 gtk_tree_path_free(path);
9262 gtk_tree_row_reference_free(ref);
9266 compose_attach_update_label(compose);
9269 static struct _AttachProperty
9272 GtkWidget *mimetype_entry;
9273 GtkWidget *encoding_optmenu;
9274 GtkWidget *path_entry;
9275 GtkWidget *filename_entry;
9277 GtkWidget *cancel_btn;
9280 static void gtk_tree_path_free_(gpointer ptr, gpointer data)
9282 gtk_tree_path_free((GtkTreePath *)ptr);
9285 static void compose_attach_property(GtkAction *action, gpointer data)
9287 Compose *compose = (Compose *)data;
9288 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
9290 GtkComboBox *optmenu;
9291 GtkTreeSelection *selection;
9293 GtkTreeModel *model;
9296 static gboolean cancelled;
9298 /* only if one selected */
9299 selection = gtk_tree_view_get_selection(tree_view);
9300 if (gtk_tree_selection_count_selected_rows(selection) != 1)
9303 sel = gtk_tree_selection_get_selected_rows(selection, &model);
9307 path = (GtkTreePath *) sel->data;
9308 gtk_tree_model_get_iter(model, &iter, path);
9309 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
9312 g_list_foreach(sel, gtk_tree_path_free_, NULL);
9318 if (!attach_prop.window)
9319 compose_attach_property_create(&cancelled);
9320 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), TRUE);
9321 gtk_widget_grab_focus(attach_prop.ok_btn);
9322 gtk_widget_show(attach_prop.window);
9323 gtk_window_set_transient_for(GTK_WINDOW(attach_prop.window),
9324 GTK_WINDOW(compose->window));
9326 optmenu = GTK_COMBO_BOX(attach_prop.encoding_optmenu);
9327 if (ainfo->encoding == ENC_UNKNOWN)
9328 combobox_select_by_data(optmenu, ENC_BASE64);
9330 combobox_select_by_data(optmenu, ainfo->encoding);
9332 gtk_entry_set_text(GTK_ENTRY(attach_prop.mimetype_entry),
9333 ainfo->content_type ? ainfo->content_type : "");
9334 gtk_entry_set_text(GTK_ENTRY(attach_prop.path_entry),
9335 ainfo->file ? ainfo->file : "");
9336 gtk_entry_set_text(GTK_ENTRY(attach_prop.filename_entry),
9337 ainfo->name ? ainfo->name : "");
9340 const gchar *entry_text;
9342 gchar *cnttype = NULL;
9349 gtk_widget_hide(attach_prop.window);
9350 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), FALSE);
9355 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.mimetype_entry));
9356 if (*entry_text != '\0') {
9359 text = g_strstrip(g_strdup(entry_text));
9360 if ((p = strchr(text, '/')) && !strchr(p + 1, '/')) {
9361 cnttype = g_strdup(text);
9364 alertpanel_error(_("Invalid MIME type."));
9370 ainfo->encoding = combobox_get_active_data(optmenu);
9372 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.path_entry));
9373 if (*entry_text != '\0') {
9374 if (is_file_exist(entry_text) &&
9375 (size = get_file_size(entry_text)) > 0)
9376 file = g_strdup(entry_text);
9379 (_("File doesn't exist or is empty."));
9385 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.filename_entry));
9386 if (*entry_text != '\0') {
9387 g_free(ainfo->name);
9388 ainfo->name = g_strdup(entry_text);
9392 g_free(ainfo->content_type);
9393 ainfo->content_type = cnttype;
9396 g_free(ainfo->file);
9400 ainfo->size = (goffset)size;
9402 /* update tree store */
9403 text = to_human_readable(ainfo->size);
9404 gtk_tree_model_get_iter(model, &iter, path);
9405 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
9406 COL_MIMETYPE, ainfo->content_type,
9408 COL_NAME, ainfo->name,
9409 COL_CHARSET, ainfo->charset,
9415 gtk_tree_path_free(path);
9418 #define SET_LABEL_AND_ENTRY(str, entry, top) \
9420 label = gtk_label_new(str); \
9421 gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), \
9422 GTK_FILL, 0, 0, 0); \
9423 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); \
9425 entry = gtk_entry_new(); \
9426 gtk_table_attach(GTK_TABLE(table), entry, 1, 2, top, (top + 1), \
9427 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); \
9430 static void compose_attach_property_create(gboolean *cancelled)
9436 GtkWidget *mimetype_entry;
9439 GtkListStore *optmenu_menu;
9440 GtkWidget *path_entry;
9441 GtkWidget *filename_entry;
9444 GtkWidget *cancel_btn;
9445 GList *mime_type_list, *strlist;
9448 debug_print("Creating attach_property window...\n");
9450 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose_attach_property");
9451 gtk_widget_set_size_request(window, 480, -1);
9452 gtk_container_set_border_width(GTK_CONTAINER(window), 8);
9453 gtk_window_set_title(GTK_WINDOW(window), _("Properties"));
9454 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
9455 g_signal_connect(G_OBJECT(window), "delete_event",
9456 G_CALLBACK(attach_property_delete_event),
9458 g_signal_connect(G_OBJECT(window), "key_press_event",
9459 G_CALLBACK(attach_property_key_pressed),
9462 vbox = gtk_vbox_new(FALSE, 8);
9463 gtk_container_add(GTK_CONTAINER(window), vbox);
9465 table = gtk_table_new(4, 2, FALSE);
9466 gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
9467 gtk_table_set_row_spacings(GTK_TABLE(table), 8);
9468 gtk_table_set_col_spacings(GTK_TABLE(table), 8);
9470 label = gtk_label_new(_("MIME type"));
9471 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, (0 + 1),
9473 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
9474 #if !GTK_CHECK_VERSION(2, 24, 0)
9475 mimetype_entry = gtk_combo_box_entry_new_text();
9477 mimetype_entry = gtk_combo_box_text_new_with_entry();
9479 gtk_table_attach(GTK_TABLE(table), mimetype_entry, 1, 2, 0, (0 + 1),
9480 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
9482 /* stuff with list */
9483 mime_type_list = procmime_get_mime_type_list();
9485 for (; mime_type_list != NULL; mime_type_list = mime_type_list->next) {
9486 MimeType *type = (MimeType *) mime_type_list->data;
9489 tmp = g_strdup_printf("%s/%s", type->type, type->sub_type);
9491 if (g_list_find_custom(strlist, tmp, (GCompareFunc)strcmp2))
9494 strlist = g_list_insert_sorted(strlist, (gpointer)tmp,
9495 (GCompareFunc)strcmp2);
9498 for (mime_type_list = strlist; mime_type_list != NULL;
9499 mime_type_list = mime_type_list->next) {
9500 #if !GTK_CHECK_VERSION(2, 24, 0)
9501 gtk_combo_box_append_text(GTK_COMBO_BOX(mimetype_entry), mime_type_list->data);
9503 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(mimetype_entry), mime_type_list->data);
9505 g_free(mime_type_list->data);
9507 g_list_free(strlist);
9508 gtk_combo_box_set_active(GTK_COMBO_BOX(mimetype_entry), 0);
9509 mimetype_entry = gtk_bin_get_child(GTK_BIN((mimetype_entry)));
9511 label = gtk_label_new(_("Encoding"));
9512 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2,
9514 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
9516 hbox = gtk_hbox_new(FALSE, 0);
9517 gtk_table_attach(GTK_TABLE(table), hbox, 1, 2, 1, 2,
9518 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
9520 optmenu = gtkut_sc_combobox_create(NULL, TRUE);
9521 optmenu_menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
9523 COMBOBOX_ADD(optmenu_menu, "7bit", ENC_7BIT);
9524 COMBOBOX_ADD(optmenu_menu, "8bit", ENC_8BIT);
9525 COMBOBOX_ADD(optmenu_menu, "quoted-printable", ENC_QUOTED_PRINTABLE);
9526 COMBOBOX_ADD(optmenu_menu, "base64", ENC_BASE64);
9527 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), 0);
9529 gtk_box_pack_start(GTK_BOX(hbox), optmenu, TRUE, TRUE, 0);
9531 SET_LABEL_AND_ENTRY(_("Path"), path_entry, 2);
9532 SET_LABEL_AND_ENTRY(_("File name"), filename_entry, 3);
9534 gtkut_stock_button_set_create(&hbbox, &cancel_btn, GTK_STOCK_CANCEL,
9535 &ok_btn, GTK_STOCK_OK,
9537 gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
9538 gtk_widget_grab_default(ok_btn);
9540 g_signal_connect(G_OBJECT(ok_btn), "clicked",
9541 G_CALLBACK(attach_property_ok),
9543 g_signal_connect(G_OBJECT(cancel_btn), "clicked",
9544 G_CALLBACK(attach_property_cancel),
9547 gtk_widget_show_all(vbox);
9549 attach_prop.window = window;
9550 attach_prop.mimetype_entry = mimetype_entry;
9551 attach_prop.encoding_optmenu = optmenu;
9552 attach_prop.path_entry = path_entry;
9553 attach_prop.filename_entry = filename_entry;
9554 attach_prop.ok_btn = ok_btn;
9555 attach_prop.cancel_btn = cancel_btn;
9558 #undef SET_LABEL_AND_ENTRY
9560 static void attach_property_ok(GtkWidget *widget, gboolean *cancelled)
9566 static void attach_property_cancel(GtkWidget *widget, gboolean *cancelled)
9572 static gint attach_property_delete_event(GtkWidget *widget, GdkEventAny *event,
9573 gboolean *cancelled)
9581 static gboolean attach_property_key_pressed(GtkWidget *widget,
9583 gboolean *cancelled)
9585 if (event && event->keyval == GDK_KEY_Escape) {
9589 if (event && event->keyval == GDK_KEY_Return) {
9597 static void compose_exec_ext_editor(Compose *compose)
9602 GdkNativeWindow socket_wid = 0;
9606 tmp = g_strdup_printf("%s%ctmpmsg.%p", get_tmp_dir(),
9607 G_DIR_SEPARATOR, compose);
9609 if (compose_get_ext_editor_uses_socket()) {
9610 /* Only allow one socket */
9611 if (compose->exteditor_socket != NULL) {
9612 if (gtk_widget_is_focus(compose->exteditor_socket)) {
9613 /* Move the focus off of the socket */
9614 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9619 /* Create the receiving GtkSocket */
9620 socket = gtk_socket_new ();
9621 g_signal_connect (GTK_OBJECT(socket), "plug-removed",
9622 G_CALLBACK(compose_ext_editor_plug_removed_cb),
9624 gtk_box_pack_start(GTK_BOX(compose->edit_vbox), socket, TRUE, TRUE, 0);
9625 gtk_widget_set_size_request(socket, prefs_common.compose_width, -1);
9626 /* Realize the socket so that we can use its ID */
9627 gtk_widget_realize(socket);
9628 socket_wid = gtk_socket_get_id(GTK_SOCKET (socket));
9629 compose->exteditor_socket = socket;
9632 if (pipe(pipe_fds) < 0) {
9638 if ((pid = fork()) < 0) {
9645 /* close the write side of the pipe */
9648 compose->exteditor_file = g_strdup(tmp);
9649 compose->exteditor_pid = pid;
9651 compose_set_ext_editor_sensitive(compose, FALSE);
9654 compose->exteditor_ch = g_io_channel_unix_new(pipe_fds[0]);
9656 compose->exteditor_ch = g_io_channel_win32_new_fd(pipe_fds[0]);
9658 compose->exteditor_tag = g_io_add_watch(compose->exteditor_ch,
9662 } else { /* process-monitoring process */
9668 /* close the read side of the pipe */
9671 if (compose_write_body_to_file(compose, tmp) < 0) {
9672 fd_write_all(pipe_fds[1], "2\n", 2);
9676 pid_ed = compose_exec_ext_editor_real(tmp, socket_wid);
9678 fd_write_all(pipe_fds[1], "1\n", 2);
9682 /* wait until editor is terminated */
9683 waitpid(pid_ed, NULL, 0);
9685 fd_write_all(pipe_fds[1], "0\n", 2);
9692 #endif /* G_OS_UNIX */
9695 static gboolean compose_can_autosave(Compose *compose)
9697 if (compose->privacy_system && compose->use_encryption)
9698 return prefs_common.autosave && prefs_common.autosave_encrypted;
9700 return prefs_common.autosave;
9704 static gboolean compose_get_ext_editor_cmd_valid()
9706 gboolean has_s = FALSE;
9707 gboolean has_w = FALSE;
9708 const gchar *p = prefs_common_get_ext_editor_cmd();
9711 while ((p = strchr(p, '%'))) {
9717 } else if (*p == 'w') {
9728 static gint compose_exec_ext_editor_real(const gchar *file, GdkNativeWindow socket_wid)
9735 cm_return_val_if_fail(file != NULL, -1);
9737 if ((pid = fork()) < 0) {
9742 if (pid != 0) return pid;
9744 /* grandchild process */
9746 if (setpgid(0, getppid()))
9749 if (compose_get_ext_editor_cmd_valid()) {
9750 if (compose_get_ext_editor_uses_socket()) {
9751 p = g_strdup(prefs_common_get_ext_editor_cmd());
9752 s = strstr(p, "%w");
9754 if (strstr(p, "%s") < s)
9755 buf = g_strdup_printf(p, file, socket_wid);
9757 buf = g_strdup_printf(p, socket_wid, file);
9760 buf = g_strdup_printf(prefs_common_get_ext_editor_cmd(), file);
9763 if (prefs_common_get_ext_editor_cmd())
9764 g_warning("External editor command-line is invalid: '%s'",
9765 prefs_common_get_ext_editor_cmd());
9766 buf = g_strdup_printf(DEFAULT_EDITOR_CMD, file);
9769 cmdline = strsplit_with_quote(buf, " ", 0);
9771 execvp(cmdline[0], cmdline);
9774 g_strfreev(cmdline);
9779 static gboolean compose_ext_editor_kill(Compose *compose)
9781 pid_t pgid = compose->exteditor_pid * -1;
9784 ret = kill(pgid, 0);
9786 if (ret == 0 || (ret == -1 && EPERM == errno)) {
9790 msg = g_strdup_printf
9791 (_("The external editor is still working.\n"
9792 "Force terminating the process?\n"
9793 "process group id: %d"), -pgid);
9794 val = alertpanel_full(_("Notice"), msg, GTK_STOCK_NO, GTK_STOCK_YES,
9795 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
9799 if (val == G_ALERTALTERNATE) {
9800 g_source_remove(compose->exteditor_tag);
9801 g_io_channel_shutdown(compose->exteditor_ch,
9803 g_io_channel_unref(compose->exteditor_ch);
9805 if (kill(pgid, SIGTERM) < 0) perror("kill");
9806 waitpid(compose->exteditor_pid, NULL, 0);
9808 g_warning("Terminated process group id: %d. "
9809 "Temporary file: %s", -pgid, compose->exteditor_file);
9811 compose_set_ext_editor_sensitive(compose, TRUE);
9813 g_free(compose->exteditor_file);
9814 compose->exteditor_file = NULL;
9815 compose->exteditor_pid = -1;
9816 compose->exteditor_ch = NULL;
9817 compose->exteditor_tag = -1;
9825 static gboolean compose_input_cb(GIOChannel *source, GIOCondition condition,
9829 Compose *compose = (Compose *)data;
9832 debug_print("Compose: input from monitoring process\n");
9834 if (g_io_channel_read_chars(source, buf, sizeof(buf), &bytes_read, NULL) != G_IO_STATUS_NORMAL) {
9839 g_io_channel_shutdown(source, FALSE, NULL);
9840 g_io_channel_unref(source);
9842 waitpid(compose->exteditor_pid, NULL, 0);
9844 if (buf[0] == '0') { /* success */
9845 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9846 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
9847 GtkTextIter start, end;
9850 gtk_text_buffer_set_text(buffer, "", -1);
9851 compose_insert_file(compose, compose->exteditor_file);
9852 compose_changed_cb(NULL, compose);
9854 /* Check if we should save the draft or not */
9855 if (compose_can_autosave(compose))
9856 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
9858 if (claws_unlink(compose->exteditor_file) < 0)
9859 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9861 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
9862 gtk_text_buffer_get_start_iter(buffer, &start);
9863 gtk_text_buffer_get_end_iter(buffer, &end);
9864 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
9865 if (chars && strlen(chars) > 0)
9866 compose->modified = TRUE;
9868 } else if (buf[0] == '1') { /* failed */
9869 g_warning("Couldn't exec external editor");
9870 if (claws_unlink(compose->exteditor_file) < 0)
9871 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9872 } else if (buf[0] == '2') {
9873 g_warning("Couldn't write to file");
9874 } else if (buf[0] == '3') {
9875 g_warning("Pipe read failed");
9878 compose_set_ext_editor_sensitive(compose, TRUE);
9880 g_free(compose->exteditor_file);
9881 compose->exteditor_file = NULL;
9882 compose->exteditor_pid = -1;
9883 compose->exteditor_ch = NULL;
9884 compose->exteditor_tag = -1;
9885 if (compose->exteditor_socket) {
9886 gtk_widget_destroy(compose->exteditor_socket);
9887 compose->exteditor_socket = NULL;
9894 static char *ext_editor_menu_entries[] = {
9895 "Menu/Message/Send",
9896 "Menu/Message/SendLater",
9897 "Menu/Message/InsertFile",
9898 "Menu/Message/InsertSig",
9899 "Menu/Message/ReplaceSig",
9900 "Menu/Message/Save",
9901 "Menu/Message/Print",
9906 "Menu/Tools/ShowRuler",
9907 "Menu/Tools/Actions",
9912 static void compose_set_ext_editor_sensitive(Compose *compose,
9917 for (i = 0; ext_editor_menu_entries[i]; ++i) {
9918 cm_menu_set_sensitive_full(compose->ui_manager,
9919 ext_editor_menu_entries[i], sensitive);
9922 if (compose_get_ext_editor_uses_socket()) {
9924 if (compose->exteditor_socket)
9925 gtk_widget_hide(compose->exteditor_socket);
9926 gtk_widget_show(compose->scrolledwin);
9927 if (prefs_common.show_ruler)
9928 gtk_widget_show(compose->ruler_hbox);
9929 /* Fix the focus, as it doesn't go anywhere when the
9930 * socket is hidden or destroyed */
9931 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9933 g_assert (compose->exteditor_socket != NULL);
9934 /* Fix the focus, as it doesn't go anywhere when the
9935 * edit box is hidden */
9936 if (gtk_widget_is_focus(compose->text))
9937 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9938 gtk_widget_hide(compose->scrolledwin);
9939 gtk_widget_hide(compose->ruler_hbox);
9940 gtk_widget_show(compose->exteditor_socket);
9943 gtk_widget_set_sensitive(compose->text, sensitive);
9945 if (compose->toolbar->send_btn)
9946 gtk_widget_set_sensitive(compose->toolbar->send_btn, sensitive);
9947 if (compose->toolbar->sendl_btn)
9948 gtk_widget_set_sensitive(compose->toolbar->sendl_btn, sensitive);
9949 if (compose->toolbar->draft_btn)
9950 gtk_widget_set_sensitive(compose->toolbar->draft_btn, sensitive);
9951 if (compose->toolbar->insert_btn)
9952 gtk_widget_set_sensitive(compose->toolbar->insert_btn, sensitive);
9953 if (compose->toolbar->sig_btn)
9954 gtk_widget_set_sensitive(compose->toolbar->sig_btn, sensitive);
9955 if (compose->toolbar->exteditor_btn)
9956 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, sensitive);
9957 if (compose->toolbar->linewrap_current_btn)
9958 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, sensitive);
9959 if (compose->toolbar->linewrap_all_btn)
9960 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, sensitive);
9963 static gboolean compose_get_ext_editor_uses_socket()
9965 return (prefs_common_get_ext_editor_cmd() &&
9966 strstr(prefs_common_get_ext_editor_cmd(), "%w"));
9969 static gboolean compose_ext_editor_plug_removed_cb(GtkSocket *socket, Compose *compose)
9971 compose->exteditor_socket = NULL;
9972 /* returning FALSE allows destruction of the socket */
9975 #endif /* G_OS_UNIX */
9978 * compose_undo_state_changed:
9980 * Change the sensivity of the menuentries undo and redo
9982 static void compose_undo_state_changed(UndoMain *undostruct, gint undo_state,
9983 gint redo_state, gpointer data)
9985 Compose *compose = (Compose *)data;
9987 switch (undo_state) {
9988 case UNDO_STATE_TRUE:
9989 if (!undostruct->undo_state) {
9990 undostruct->undo_state = TRUE;
9991 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", TRUE);
9994 case UNDO_STATE_FALSE:
9995 if (undostruct->undo_state) {
9996 undostruct->undo_state = FALSE;
9997 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
10000 case UNDO_STATE_UNCHANGED:
10002 case UNDO_STATE_REFRESH:
10003 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", undostruct->undo_state);
10006 g_warning("Undo state not recognized");
10010 switch (redo_state) {
10011 case UNDO_STATE_TRUE:
10012 if (!undostruct->redo_state) {
10013 undostruct->redo_state = TRUE;
10014 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", TRUE);
10017 case UNDO_STATE_FALSE:
10018 if (undostruct->redo_state) {
10019 undostruct->redo_state = FALSE;
10020 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
10023 case UNDO_STATE_UNCHANGED:
10025 case UNDO_STATE_REFRESH:
10026 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", undostruct->redo_state);
10029 g_warning("Redo state not recognized");
10034 /* callback functions */
10036 static void compose_notebook_size_alloc(GtkNotebook *notebook,
10037 GtkAllocation *allocation,
10040 prefs_common.compose_notebook_height = gtk_paned_get_position(paned);
10043 /* compose_edit_size_alloc() - called when resized. don't know whether Gtk
10044 * includes "non-client" (windows-izm) in calculation, so this calculation
10045 * may not be accurate.
10047 static gboolean compose_edit_size_alloc(GtkEditable *widget,
10048 GtkAllocation *allocation,
10049 GtkSHRuler *shruler)
10051 if (prefs_common.show_ruler) {
10052 gint char_width = 0, char_height = 0;
10053 gint line_width_in_chars;
10055 gtkut_get_font_size(GTK_WIDGET(widget),
10056 &char_width, &char_height);
10057 line_width_in_chars =
10058 (allocation->width - allocation->x) / char_width;
10060 /* got the maximum */
10061 gtk_shruler_set_range(GTK_SHRULER(shruler),
10062 0.0, line_width_in_chars, 0);
10071 ComposePrefType type;
10072 gboolean entry_marked;
10073 } HeaderEntryState;
10075 static void account_activated(GtkComboBox *optmenu, gpointer data)
10077 Compose *compose = (Compose *)data;
10080 gchar *folderidentifier;
10081 gint account_id = 0;
10082 GtkTreeModel *menu;
10084 GSList *list, *saved_list = NULL;
10085 HeaderEntryState *state;
10087 /* Get ID of active account in the combo box */
10088 menu = gtk_combo_box_get_model(optmenu);
10089 cm_return_if_fail(gtk_combo_box_get_active_iter(optmenu, &iter));
10090 gtk_tree_model_get(menu, &iter, 1, &account_id, -1);
10092 ac = account_find_from_id(account_id);
10093 cm_return_if_fail(ac != NULL);
10095 if (ac != compose->account) {
10096 compose_select_account(compose, ac, FALSE);
10098 for (list = compose->header_list; list; list = list->next) {
10099 ComposeHeaderEntry *hentry=(ComposeHeaderEntry *)list->data;
10101 if (hentry->type == PREF_ACCOUNT || !list->next) {
10102 compose_destroy_headerentry(compose, hentry);
10105 state = g_malloc0(sizeof(HeaderEntryState));
10106 state->header = gtk_editable_get_chars(GTK_EDITABLE(
10107 gtk_bin_get_child(GTK_BIN(hentry->combo))), 0, -1);
10108 state->entry = gtk_editable_get_chars(
10109 GTK_EDITABLE(hentry->entry), 0, -1);
10110 state->type = hentry->type;
10112 saved_list = g_slist_append(saved_list, state);
10113 compose_destroy_headerentry(compose, hentry);
10116 compose->header_last = NULL;
10117 g_slist_free(compose->header_list);
10118 compose->header_list = NULL;
10119 compose->header_nextrow = 1;
10120 compose_create_header_entry(compose);
10122 if (ac->set_autocc && ac->auto_cc)
10123 compose_entry_append(compose, ac->auto_cc,
10124 COMPOSE_CC, PREF_ACCOUNT);
10125 if (ac->set_autobcc && ac->auto_bcc)
10126 compose_entry_append(compose, ac->auto_bcc,
10127 COMPOSE_BCC, PREF_ACCOUNT);
10128 if (ac->set_autoreplyto && ac->auto_replyto)
10129 compose_entry_append(compose, ac->auto_replyto,
10130 COMPOSE_REPLYTO, PREF_ACCOUNT);
10132 for (list = saved_list; list; list = list->next) {
10133 state = (HeaderEntryState *) list->data;
10135 compose_add_header_entry(compose, state->header,
10136 state->entry, state->type);
10138 g_free(state->header);
10139 g_free(state->entry);
10142 g_slist_free(saved_list);
10144 combobox_select_by_data(GTK_COMBO_BOX(compose->header_last->combo),
10145 (ac->protocol == A_NNTP) ?
10146 COMPOSE_NEWSGROUPS : COMPOSE_TO);
10149 /* Set message save folder */
10150 if (account_get_special_folder(compose->account, F_OUTBOX)) {
10151 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
10153 g_signal_connect(G_OBJECT(compose->savemsg_checkbtn), "toggled",
10154 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
10156 compose_set_save_to(compose, NULL);
10157 if (account_get_special_folder(compose->account, F_OUTBOX)) {
10158 folderidentifier = folder_item_get_identifier(account_get_special_folder
10159 (compose->account, F_OUTBOX));
10160 compose_set_save_to(compose, folderidentifier);
10161 g_free(folderidentifier);
10165 static void attach_selected(GtkTreeView *tree_view, GtkTreePath *tree_path,
10166 GtkTreeViewColumn *column, Compose *compose)
10168 compose_attach_property(NULL, compose);
10171 static gboolean attach_button_pressed(GtkWidget *widget, GdkEventButton *event,
10174 Compose *compose = (Compose *)data;
10175 GtkTreeSelection *attach_selection;
10176 gint attach_nr_selected;
10179 if (!event) return FALSE;
10181 if (event->button == 3) {
10182 attach_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
10183 attach_nr_selected = gtk_tree_selection_count_selected_rows(attach_selection);
10185 /* If no rows, or just one row is selected, right-click should
10186 * open menu relevant to the row being right-clicked on. We
10187 * achieve that by selecting the clicked row first. If more
10188 * than one row is selected, we shouldn't modify the selection,
10189 * as user may want to remove selected rows (attachments). */
10190 if (attach_nr_selected < 2) {
10191 gtk_tree_selection_unselect_all(attach_selection);
10192 attach_nr_selected = 0;
10193 gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget),
10194 event->x, event->y, &path, NULL, NULL, NULL);
10195 if (path != NULL) {
10196 gtk_tree_selection_select_path(attach_selection, path);
10197 gtk_tree_path_free(path);
10198 attach_nr_selected++;
10202 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", (attach_nr_selected > 0));
10203 /* Properties menu item makes no sense with more than one row
10204 * selected, the properties dialog can only edit one attachment. */
10205 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", (attach_nr_selected == 1));
10207 gtk_menu_popup(GTK_MENU(compose->popupmenu), NULL, NULL,
10208 NULL, NULL, event->button, event->time);
10215 static gboolean attach_key_pressed(GtkWidget *widget, GdkEventKey *event,
10218 Compose *compose = (Compose *)data;
10220 if (!event) return FALSE;
10222 switch (event->keyval) {
10223 case GDK_KEY_Delete:
10224 compose_attach_remove_selected(NULL, compose);
10230 static void compose_allow_user_actions (Compose *compose, gboolean allow)
10232 toolbar_comp_set_sensitive(compose, allow);
10233 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message", allow);
10234 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", allow);
10236 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", allow);
10238 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", allow);
10239 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools", allow);
10240 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Help", allow);
10242 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), allow);
10246 static void compose_send_cb(GtkAction *action, gpointer data)
10248 Compose *compose = (Compose *)data;
10251 if (compose->exteditor_tag != -1) {
10252 debug_print("ignoring send: external editor still open\n");
10256 if (prefs_common.work_offline &&
10257 !inc_offline_should_override(TRUE,
10258 _("Claws Mail needs network access in order "
10259 "to send this email.")))
10262 if (compose->draft_timeout_tag >= 0) { /* CLAWS: disable draft timeout */
10263 g_source_remove(compose->draft_timeout_tag);
10264 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
10267 compose_send(compose);
10270 static void compose_send_later_cb(GtkAction *action, gpointer data)
10272 Compose *compose = (Compose *)data;
10276 compose_allow_user_actions(compose, FALSE);
10277 val = compose_queue_sub(compose, NULL, NULL, NULL, TRUE, TRUE);
10278 compose_allow_user_actions(compose, TRUE);
10282 compose_close(compose);
10283 } else if (val == -1) {
10284 alertpanel_error(_("Could not queue message."));
10285 } else if (val == -2) {
10286 alertpanel_error(_("Could not queue message:\n\n%s."), g_strerror(errno));
10287 } else if (val == -3) {
10288 if (privacy_peek_error())
10289 alertpanel_error(_("Could not queue message for sending:\n\n"
10290 "Signature failed: %s"), privacy_get_error());
10291 } else if (val == -4) {
10292 alertpanel_error(_("Could not queue message for sending:\n\n"
10293 "Charset conversion failed."));
10294 } else if (val == -5) {
10295 alertpanel_error(_("Could not queue message for sending:\n\n"
10296 "Couldn't get recipient encryption key."));
10297 } else if (val == -6) {
10300 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
10303 #define DRAFTED_AT_EXIT "drafted_at_exit"
10304 static void compose_register_draft(MsgInfo *info)
10306 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10307 DRAFTED_AT_EXIT, NULL);
10308 FILE *fp = g_fopen(filepath, "ab");
10311 fprintf(fp, "%s\t%d\n", folder_item_get_identifier(info->folder),
10319 gboolean compose_draft (gpointer data, guint action)
10321 Compose *compose = (Compose *)data;
10326 MsgFlags flag = {0, 0};
10327 static gboolean lock = FALSE;
10328 MsgInfo *newmsginfo;
10330 gboolean target_locked = FALSE;
10331 gboolean err = FALSE;
10333 if (lock) return FALSE;
10335 if (compose->sending)
10338 draft = account_get_special_folder(compose->account, F_DRAFT);
10339 cm_return_val_if_fail(draft != NULL, FALSE);
10341 if (!g_mutex_trylock(compose->mutex)) {
10342 /* we don't want to lock the mutex once it's available,
10343 * because as the only other part of compose.c locking
10344 * it is compose_close - which means once unlocked,
10345 * the compose struct will be freed */
10346 debug_print("couldn't lock mutex, probably sending\n");
10352 tmp = g_strdup_printf("%s%cdraft.%p", get_tmp_dir(),
10353 G_DIR_SEPARATOR, compose);
10354 if ((fp = g_fopen(tmp, "wb")) == NULL) {
10355 FILE_OP_ERROR(tmp, "fopen");
10359 /* chmod for security */
10360 if (change_file_mode_rw(fp, tmp) < 0) {
10361 FILE_OP_ERROR(tmp, "chmod");
10362 g_warning("can't change file mode");
10365 /* Save draft infos */
10366 err |= (fprintf(fp, "X-Claws-Account-Id:%d\n", compose->account->account_id) < 0);
10367 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
10369 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
10370 gchar *savefolderid;
10372 savefolderid = compose_get_save_to(compose);
10373 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
10374 g_free(savefolderid);
10376 if (compose->return_receipt) {
10377 err |= (fprintf(fp, "RRCPT:1\n") < 0);
10379 if (compose->privacy_system) {
10380 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
10381 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
10382 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
10385 /* Message-ID of message replying to */
10386 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
10387 gchar *folderid = NULL;
10389 if (compose->replyinfo->folder)
10390 folderid = folder_item_get_identifier(compose->replyinfo->folder);
10391 if (folderid == NULL)
10392 folderid = g_strdup("NULL");
10394 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
10397 /* Message-ID of message forwarding to */
10398 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
10399 gchar *folderid = NULL;
10401 if (compose->fwdinfo->folder)
10402 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
10403 if (folderid == NULL)
10404 folderid = g_strdup("NULL");
10406 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
10410 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
10411 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
10413 sheaders = compose_get_manual_headers_info(compose);
10414 err |= (fprintf(fp, "X-Claws-Manual-Headers:%s\n", sheaders) < 0);
10417 /* end of headers */
10418 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
10425 if (compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_STORE, action != COMPOSE_AUTO_SAVE) < 0) {
10429 if (fclose(fp) == EOF) {
10433 flag.perm_flags = MSG_NEW|MSG_UNREAD;
10434 if (compose->targetinfo) {
10435 target_locked = MSG_IS_LOCKED(compose->targetinfo->flags);
10437 flag.perm_flags |= MSG_LOCKED;
10439 flag.tmp_flags = MSG_DRAFT;
10441 folder_item_scan(draft);
10442 if ((msgnum = folder_item_add_msg(draft, tmp, &flag, TRUE)) < 0) {
10443 MsgInfo *tmpinfo = NULL;
10444 debug_print("didn't get msgnum after adding draft [%s]\n", compose->msgid?compose->msgid:"no msgid");
10445 if (compose->msgid) {
10446 tmpinfo = folder_item_get_msginfo_by_msgid(draft, compose->msgid);
10449 msgnum = tmpinfo->msgnum;
10450 procmsg_msginfo_free(&tmpinfo);
10451 debug_print("got draft msgnum %d from scanning\n", msgnum);
10453 debug_print("didn't get draft msgnum after scanning\n");
10456 debug_print("got draft msgnum %d from adding\n", msgnum);
10462 if (action != COMPOSE_AUTO_SAVE) {
10463 if (action != COMPOSE_DRAFT_FOR_EXIT)
10464 alertpanel_error(_("Could not save draft."));
10467 gtkut_window_popup(compose->window);
10468 val = alertpanel_full(_("Could not save draft"),
10469 _("Could not save draft.\n"
10470 "Do you want to cancel exit or discard this email?"),
10471 _("_Cancel exit"), _("_Discard email"), NULL,
10472 FALSE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
10473 if (val == G_ALERTALTERNATE) {
10475 g_mutex_unlock(compose->mutex); /* must be done before closing */
10476 compose_close(compose);
10480 g_mutex_unlock(compose->mutex); /* must be done before closing */
10489 if (compose->mode == COMPOSE_REEDIT) {
10490 compose_remove_reedit_target(compose, TRUE);
10493 newmsginfo = folder_item_get_msginfo(draft, msgnum);
10496 procmsg_msginfo_unset_flags(newmsginfo, ~0, ~0);
10498 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD|MSG_LOCKED, MSG_DRAFT);
10500 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD, MSG_DRAFT);
10501 if (compose_use_attach(compose) && action != COMPOSE_AUTO_SAVE)
10502 procmsg_msginfo_set_flags(newmsginfo, 0,
10503 MSG_HAS_ATTACHMENT);
10505 if (action == COMPOSE_DRAFT_FOR_EXIT) {
10506 compose_register_draft(newmsginfo);
10508 procmsg_msginfo_free(&newmsginfo);
10511 folder_item_scan(draft);
10513 if (action == COMPOSE_QUIT_EDITING || action == COMPOSE_DRAFT_FOR_EXIT) {
10515 g_mutex_unlock(compose->mutex); /* must be done before closing */
10516 compose_close(compose);
10528 goffset size, mtime;
10530 path = folder_item_fetch_msg(draft, msgnum);
10531 if (path == NULL) {
10532 debug_print("can't fetch %s:%d\n", draft->path, msgnum);
10536 f = g_file_new_for_path(path);
10537 fi = g_file_query_info(f, "standard::size,time::modified",
10538 G_FILE_QUERY_INFO_NONE, NULL, &error);
10539 if (error != NULL) {
10540 debug_print("couldn't query file info for '%s': %s\n",
10541 path, error->message);
10542 g_error_free(error);
10547 size = g_file_info_get_size(fi);
10548 g_file_info_get_modification_time(fi, &tv);
10550 g_object_unref(fi);
10554 if (g_stat(path, &s) < 0) {
10555 FILE_OP_ERROR(path, "stat");
10560 mtime = s.st_mtime;
10564 procmsg_msginfo_free(&(compose->targetinfo));
10565 compose->targetinfo = procmsg_msginfo_new();
10566 compose->targetinfo->msgnum = msgnum;
10567 compose->targetinfo->size = size;
10568 compose->targetinfo->mtime = mtime;
10569 compose->targetinfo->folder = draft;
10571 procmsg_msginfo_set_flags(compose->targetinfo, MSG_LOCKED, 0);
10572 compose->mode = COMPOSE_REEDIT;
10574 if (action == COMPOSE_AUTO_SAVE) {
10575 compose->autosaved_draft = compose->targetinfo;
10577 compose->modified = FALSE;
10578 compose_set_title(compose);
10582 g_mutex_unlock(compose->mutex);
10586 void compose_clear_exit_drafts(void)
10588 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10589 DRAFTED_AT_EXIT, NULL);
10590 if (is_file_exist(filepath))
10591 claws_unlink(filepath);
10596 void compose_reopen_exit_drafts(void)
10598 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10599 DRAFTED_AT_EXIT, NULL);
10600 FILE *fp = g_fopen(filepath, "rb");
10604 while (fgets(buf, sizeof(buf), fp)) {
10605 gchar **parts = g_strsplit(buf, "\t", 2);
10606 const gchar *folder = parts[0];
10607 int msgnum = parts[1] ? atoi(parts[1]):-1;
10609 if (folder && *folder && msgnum > -1) {
10610 FolderItem *item = folder_find_item_from_identifier(folder);
10611 MsgInfo *info = folder_item_get_msginfo(item, msgnum);
10613 compose_reedit(info, FALSE);
10620 compose_clear_exit_drafts();
10623 static void compose_save_cb(GtkAction *action, gpointer data)
10625 Compose *compose = (Compose *)data;
10626 compose_draft(compose, COMPOSE_KEEP_EDITING);
10627 compose->rmode = COMPOSE_REEDIT;
10630 void compose_attach_from_list(Compose *compose, GList *file_list, gboolean free_data)
10632 if (compose && file_list) {
10635 for ( tmp = file_list; tmp; tmp = tmp->next) {
10636 gchar *file = (gchar *) tmp->data;
10637 gchar *utf8_filename = conv_filename_to_utf8(file);
10638 compose_attach_append(compose, file, utf8_filename, NULL, NULL);
10639 compose_changed_cb(NULL, compose);
10644 g_free(utf8_filename);
10649 static void compose_attach_cb(GtkAction *action, gpointer data)
10651 Compose *compose = (Compose *)data;
10654 if (compose->redirect_filename != NULL)
10657 /* Set focus_window properly, in case we were called via popup menu,
10658 * which unsets it (via focus_out_event callback on compose window). */
10659 manage_window_focus_in(compose->window, NULL, NULL);
10661 file_list = filesel_select_multiple_files_open(_("Select file"), NULL);
10664 compose_attach_from_list(compose, file_list, TRUE);
10665 g_list_free(file_list);
10669 static void compose_insert_file_cb(GtkAction *action, gpointer data)
10671 Compose *compose = (Compose *)data;
10673 gint files_inserted = 0;
10675 file_list = filesel_select_multiple_files_open(_("Select file"), NULL);
10680 for ( tmp = file_list; tmp; tmp = tmp->next) {
10681 gchar *file = (gchar *) tmp->data;
10682 gchar *filedup = g_strdup(file);
10683 gchar *shortfile = g_path_get_basename(filedup);
10684 ComposeInsertResult res;
10685 /* insert the file if the file is short or if the user confirmed that
10686 he/she wants to insert the large file */
10687 res = compose_insert_file(compose, file);
10688 if (res == COMPOSE_INSERT_READ_ERROR) {
10689 alertpanel_error(_("File '%s' could not be read."), shortfile);
10690 } else if (res == COMPOSE_INSERT_INVALID_CHARACTER) {
10691 alertpanel_error(_("File '%s' contained invalid characters\n"
10692 "for the current encoding, insertion may be incorrect."),
10694 } else if (res == COMPOSE_INSERT_SUCCESS)
10701 g_list_free(file_list);
10705 if (files_inserted > 0 && compose->gtkaspell &&
10706 compose->gtkaspell->check_while_typing)
10707 gtkaspell_highlight_all(compose->gtkaspell);
10711 static void compose_insert_sig_cb(GtkAction *action, gpointer data)
10713 Compose *compose = (Compose *)data;
10715 compose_insert_sig(compose, FALSE);
10718 static void compose_replace_sig_cb(GtkAction *action, gpointer data)
10720 Compose *compose = (Compose *)data;
10722 compose_insert_sig(compose, TRUE);
10725 static gint compose_delete_cb(GtkWidget *widget, GdkEventAny *event,
10729 Compose *compose = (Compose *)data;
10731 gtkut_widget_get_uposition(widget, &x, &y);
10732 if (!compose->batch) {
10733 prefs_common.compose_x = x;
10734 prefs_common.compose_y = y;
10736 if (compose->sending || compose->updating)
10738 compose_close_cb(NULL, compose);
10742 void compose_close_toolbar(Compose *compose)
10744 compose_close_cb(NULL, compose);
10747 static void compose_close_cb(GtkAction *action, gpointer data)
10749 Compose *compose = (Compose *)data;
10753 if (compose->exteditor_tag != -1) {
10754 if (!compose_ext_editor_kill(compose))
10759 if (compose->modified) {
10760 gboolean reedit = (compose->rmode == COMPOSE_REEDIT);
10761 if (!g_mutex_trylock(compose->mutex)) {
10762 /* we don't want to lock the mutex once it's available,
10763 * because as the only other part of compose.c locking
10764 * it is compose_close - which means once unlocked,
10765 * the compose struct will be freed */
10766 debug_print("couldn't lock mutex, probably sending\n");
10770 val = alertpanel(_("Discard message"),
10771 _("This message has been modified. Discard it?"),
10772 _("_Discard"), _("_Save to Drafts"), GTK_STOCK_CANCEL);
10774 val = alertpanel(_("Save changes"),
10775 _("This message has been modified. Save the latest changes?"),
10776 _("_Don't save"), g_strconcat("+", _("_Save to Drafts"), NULL),
10779 g_mutex_unlock(compose->mutex);
10781 case G_ALERTDEFAULT:
10782 if (compose_can_autosave(compose) && !reedit)
10783 compose_remove_draft(compose);
10785 case G_ALERTALTERNATE:
10786 compose_draft(data, COMPOSE_QUIT_EDITING);
10793 compose_close(compose);
10796 static void compose_print_cb(GtkAction *action, gpointer data)
10798 Compose *compose = (Compose *) data;
10800 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
10801 if (compose->targetinfo)
10802 messageview_print(compose->targetinfo, FALSE, -1, -1, 0);
10805 static void compose_set_encoding_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
10807 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
10808 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
10809 Compose *compose = (Compose *) data;
10812 compose->out_encoding = (CharSet)value;
10815 static void compose_address_cb(GtkAction *action, gpointer data)
10817 Compose *compose = (Compose *)data;
10819 #ifndef USE_ALT_ADDRBOOK
10820 addressbook_open(compose);
10822 GError* error = NULL;
10823 addressbook_connect_signals(compose);
10824 addressbook_dbus_open(TRUE, &error);
10826 g_warning("%s", error->message);
10827 g_error_free(error);
10832 static void about_show_cb(GtkAction *action, gpointer data)
10837 static void compose_template_activate_cb(GtkWidget *widget, gpointer data)
10839 Compose *compose = (Compose *)data;
10844 tmpl = g_object_get_data(G_OBJECT(widget), "template");
10845 cm_return_if_fail(tmpl != NULL);
10847 msg = g_strdup_printf(_("Do you want to apply the template '%s'?"),
10849 val = alertpanel(_("Apply template"), msg,
10850 _("_Replace"), _("_Insert"), GTK_STOCK_CANCEL);
10853 if (val == G_ALERTDEFAULT)
10854 compose_template_apply(compose, tmpl, TRUE);
10855 else if (val == G_ALERTALTERNATE)
10856 compose_template_apply(compose, tmpl, FALSE);
10859 static void compose_ext_editor_cb(GtkAction *action, gpointer data)
10861 Compose *compose = (Compose *)data;
10864 if (compose->exteditor_tag != -1) {
10865 debug_print("ignoring open external editor: external editor still open\n");
10869 compose_exec_ext_editor(compose);
10872 static void compose_undo_cb(GtkAction *action, gpointer data)
10874 Compose *compose = (Compose *)data;
10875 gboolean prev_autowrap = compose->autowrap;
10877 compose->autowrap = FALSE;
10878 undo_undo(compose->undostruct);
10879 compose->autowrap = prev_autowrap;
10882 static void compose_redo_cb(GtkAction *action, gpointer data)
10884 Compose *compose = (Compose *)data;
10885 gboolean prev_autowrap = compose->autowrap;
10887 compose->autowrap = FALSE;
10888 undo_redo(compose->undostruct);
10889 compose->autowrap = prev_autowrap;
10892 static void entry_cut_clipboard(GtkWidget *entry)
10894 if (GTK_IS_EDITABLE(entry))
10895 gtk_editable_cut_clipboard (GTK_EDITABLE(entry));
10896 else if (GTK_IS_TEXT_VIEW(entry))
10897 gtk_text_buffer_cut_clipboard(
10898 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10899 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
10903 static void entry_copy_clipboard(GtkWidget *entry)
10905 if (GTK_IS_EDITABLE(entry))
10906 gtk_editable_copy_clipboard (GTK_EDITABLE(entry));
10907 else if (GTK_IS_TEXT_VIEW(entry))
10908 gtk_text_buffer_copy_clipboard(
10909 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10910 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
10913 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry,
10914 gboolean wrap, GdkAtom clip, GtkTextIter *insert_place)
10916 if (GTK_IS_TEXT_VIEW(entry)) {
10917 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10918 GtkTextMark *mark_start = gtk_text_buffer_get_insert(buffer);
10919 GtkTextIter start_iter, end_iter;
10921 gchar *contents = gtk_clipboard_wait_for_text(gtk_clipboard_get(clip));
10923 if (contents == NULL)
10926 /* we shouldn't delete the selection when middle-click-pasting, or we
10927 * can't mid-click-paste our own selection */
10928 if (clip != GDK_SELECTION_PRIMARY) {
10929 undo_paste_clipboard(GTK_TEXT_VIEW(compose->text), compose->undostruct);
10930 gtk_text_buffer_delete_selection(buffer, FALSE, TRUE);
10933 if (insert_place == NULL) {
10934 /* if insert_place isn't specified, insert at the cursor.
10935 * used for Ctrl-V pasting */
10936 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10937 start = gtk_text_iter_get_offset(&start_iter);
10938 gtk_text_buffer_insert(buffer, &start_iter, contents, strlen(contents));
10940 /* if insert_place is specified, paste here.
10941 * used for mid-click-pasting */
10942 start = gtk_text_iter_get_offset(insert_place);
10943 gtk_text_buffer_insert(buffer, insert_place, contents, strlen(contents));
10944 if (prefs_common.primary_paste_unselects)
10945 gtk_text_buffer_select_range(buffer, insert_place, insert_place);
10949 /* paste unwrapped: mark the paste so it's not wrapped later */
10950 end = start + strlen(contents);
10951 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, start);
10952 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, end);
10953 gtk_text_buffer_apply_tag_by_name(buffer, "no_wrap", &start_iter, &end_iter);
10954 } else if (wrap && clip == GDK_SELECTION_PRIMARY) {
10955 /* rewrap paragraph now (after a mid-click-paste) */
10956 mark_start = gtk_text_buffer_get_insert(buffer);
10957 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10958 gtk_text_iter_backward_char(&start_iter);
10959 compose_beautify_paragraph(compose, &start_iter, TRUE);
10961 } else if (GTK_IS_EDITABLE(entry))
10962 gtk_editable_paste_clipboard (GTK_EDITABLE(entry));
10964 compose->modified = TRUE;
10967 static void entry_allsel(GtkWidget *entry)
10969 if (GTK_IS_EDITABLE(entry))
10970 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
10971 else if (GTK_IS_TEXT_VIEW(entry)) {
10972 GtkTextIter startiter, enditer;
10973 GtkTextBuffer *textbuf;
10975 textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10976 gtk_text_buffer_get_start_iter(textbuf, &startiter);
10977 gtk_text_buffer_get_end_iter(textbuf, &enditer);
10979 gtk_text_buffer_move_mark_by_name(textbuf,
10980 "selection_bound", &startiter);
10981 gtk_text_buffer_move_mark_by_name(textbuf,
10982 "insert", &enditer);
10986 static void compose_cut_cb(GtkAction *action, gpointer data)
10988 Compose *compose = (Compose *)data;
10989 if (compose->focused_editable
10990 #ifndef GENERIC_UMPC
10991 && gtk_widget_has_focus(compose->focused_editable)
10994 entry_cut_clipboard(compose->focused_editable);
10997 static void compose_copy_cb(GtkAction *action, gpointer data)
10999 Compose *compose = (Compose *)data;
11000 if (compose->focused_editable
11001 #ifndef GENERIC_UMPC
11002 && gtk_widget_has_focus(compose->focused_editable)
11005 entry_copy_clipboard(compose->focused_editable);
11008 static void compose_paste_cb(GtkAction *action, gpointer data)
11010 Compose *compose = (Compose *)data;
11011 gint prev_autowrap;
11012 GtkTextBuffer *buffer;
11014 if (compose->focused_editable &&
11015 #ifndef GENERIC_UMPC
11016 gtk_widget_has_focus(compose->focused_editable)
11019 entry_paste_clipboard(compose, compose->focused_editable,
11020 prefs_common.linewrap_pastes,
11021 GDK_SELECTION_CLIPBOARD, NULL);
11026 #ifndef GENERIC_UMPC
11027 gtk_widget_has_focus(compose->text) &&
11029 compose->gtkaspell &&
11030 compose->gtkaspell->check_while_typing)
11031 gtkaspell_highlight_all(compose->gtkaspell);
11035 static void compose_paste_as_quote_cb(GtkAction *action, gpointer data)
11037 Compose *compose = (Compose *)data;
11038 gint wrap_quote = prefs_common.linewrap_quote;
11039 if (compose->focused_editable
11040 #ifndef GENERIC_UMPC
11041 && gtk_widget_has_focus(compose->focused_editable)
11044 /* let text_insert() (called directly or at a later time
11045 * after the gtk_editable_paste_clipboard) know that
11046 * text is to be inserted as a quotation. implemented
11047 * by using a simple refcount... */
11048 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data(
11049 G_OBJECT(compose->focused_editable),
11050 "paste_as_quotation"));
11051 g_object_set_data(G_OBJECT(compose->focused_editable),
11052 "paste_as_quotation",
11053 GINT_TO_POINTER(paste_as_quotation + 1));
11054 prefs_common.linewrap_quote = prefs_common.linewrap_pastes;
11055 entry_paste_clipboard(compose, compose->focused_editable,
11056 prefs_common.linewrap_pastes,
11057 GDK_SELECTION_CLIPBOARD, NULL);
11058 prefs_common.linewrap_quote = wrap_quote;
11062 static void compose_paste_no_wrap_cb(GtkAction *action, gpointer data)
11064 Compose *compose = (Compose *)data;
11065 gint prev_autowrap;
11066 GtkTextBuffer *buffer;
11068 if (compose->focused_editable
11069 #ifndef GENERIC_UMPC
11070 && gtk_widget_has_focus(compose->focused_editable)
11073 entry_paste_clipboard(compose, compose->focused_editable, FALSE,
11074 GDK_SELECTION_CLIPBOARD, NULL);
11079 #ifndef GENERIC_UMPC
11080 gtk_widget_has_focus(compose->text) &&
11082 compose->gtkaspell &&
11083 compose->gtkaspell->check_while_typing)
11084 gtkaspell_highlight_all(compose->gtkaspell);
11088 static void compose_paste_wrap_cb(GtkAction *action, gpointer data)
11090 Compose *compose = (Compose *)data;
11091 gint prev_autowrap;
11092 GtkTextBuffer *buffer;
11094 if (compose->focused_editable
11095 #ifndef GENERIC_UMPC
11096 && gtk_widget_has_focus(compose->focused_editable)
11099 entry_paste_clipboard(compose, compose->focused_editable, TRUE,
11100 GDK_SELECTION_CLIPBOARD, NULL);
11105 #ifndef GENERIC_UMPC
11106 gtk_widget_has_focus(compose->text) &&
11108 compose->gtkaspell &&
11109 compose->gtkaspell->check_while_typing)
11110 gtkaspell_highlight_all(compose->gtkaspell);
11114 static void compose_allsel_cb(GtkAction *action, gpointer data)
11116 Compose *compose = (Compose *)data;
11117 if (compose->focused_editable
11118 #ifndef GENERIC_UMPC
11119 && gtk_widget_has_focus(compose->focused_editable)
11122 entry_allsel(compose->focused_editable);
11125 static void textview_move_beginning_of_line (GtkTextView *text)
11127 GtkTextBuffer *buffer;
11131 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11133 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11134 mark = gtk_text_buffer_get_insert(buffer);
11135 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11136 gtk_text_iter_set_line_offset(&ins, 0);
11137 gtk_text_buffer_place_cursor(buffer, &ins);
11140 static void textview_move_forward_character (GtkTextView *text)
11142 GtkTextBuffer *buffer;
11146 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11148 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11149 mark = gtk_text_buffer_get_insert(buffer);
11150 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11151 if (gtk_text_iter_forward_cursor_position(&ins))
11152 gtk_text_buffer_place_cursor(buffer, &ins);
11155 static void textview_move_backward_character (GtkTextView *text)
11157 GtkTextBuffer *buffer;
11161 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11163 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11164 mark = gtk_text_buffer_get_insert(buffer);
11165 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11166 if (gtk_text_iter_backward_cursor_position(&ins))
11167 gtk_text_buffer_place_cursor(buffer, &ins);
11170 static void textview_move_forward_word (GtkTextView *text)
11172 GtkTextBuffer *buffer;
11177 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11179 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11180 mark = gtk_text_buffer_get_insert(buffer);
11181 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11182 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
11183 if (gtk_text_iter_forward_word_ends(&ins, count)) {
11184 gtk_text_iter_backward_word_start(&ins);
11185 gtk_text_buffer_place_cursor(buffer, &ins);
11189 static void textview_move_backward_word (GtkTextView *text)
11191 GtkTextBuffer *buffer;
11195 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11197 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11198 mark = gtk_text_buffer_get_insert(buffer);
11199 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11200 if (gtk_text_iter_backward_word_starts(&ins, 1))
11201 gtk_text_buffer_place_cursor(buffer, &ins);
11204 static void textview_move_end_of_line (GtkTextView *text)
11206 GtkTextBuffer *buffer;
11210 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11212 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11213 mark = gtk_text_buffer_get_insert(buffer);
11214 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11215 if (gtk_text_iter_forward_to_line_end(&ins))
11216 gtk_text_buffer_place_cursor(buffer, &ins);
11219 static void textview_move_next_line (GtkTextView *text)
11221 GtkTextBuffer *buffer;
11226 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11228 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11229 mark = gtk_text_buffer_get_insert(buffer);
11230 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11231 offset = gtk_text_iter_get_line_offset(&ins);
11232 if (gtk_text_iter_forward_line(&ins)) {
11233 gtk_text_iter_set_line_offset(&ins, offset);
11234 gtk_text_buffer_place_cursor(buffer, &ins);
11238 static void textview_move_previous_line (GtkTextView *text)
11240 GtkTextBuffer *buffer;
11245 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11247 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11248 mark = gtk_text_buffer_get_insert(buffer);
11249 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11250 offset = gtk_text_iter_get_line_offset(&ins);
11251 if (gtk_text_iter_backward_line(&ins)) {
11252 gtk_text_iter_set_line_offset(&ins, offset);
11253 gtk_text_buffer_place_cursor(buffer, &ins);
11257 static void textview_delete_forward_character (GtkTextView *text)
11259 GtkTextBuffer *buffer;
11261 GtkTextIter ins, end_iter;
11263 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11265 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11266 mark = gtk_text_buffer_get_insert(buffer);
11267 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11269 if (gtk_text_iter_forward_char(&end_iter)) {
11270 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11274 static void textview_delete_backward_character (GtkTextView *text)
11276 GtkTextBuffer *buffer;
11278 GtkTextIter ins, end_iter;
11280 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11282 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11283 mark = gtk_text_buffer_get_insert(buffer);
11284 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11286 if (gtk_text_iter_backward_char(&end_iter)) {
11287 gtk_text_buffer_delete(buffer, &end_iter, &ins);
11291 static void textview_delete_forward_word (GtkTextView *text)
11293 GtkTextBuffer *buffer;
11295 GtkTextIter ins, end_iter;
11297 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11299 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11300 mark = gtk_text_buffer_get_insert(buffer);
11301 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11303 if (gtk_text_iter_forward_word_end(&end_iter)) {
11304 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11308 static void textview_delete_backward_word (GtkTextView *text)
11310 GtkTextBuffer *buffer;
11312 GtkTextIter ins, end_iter;
11314 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11316 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11317 mark = gtk_text_buffer_get_insert(buffer);
11318 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11320 if (gtk_text_iter_backward_word_start(&end_iter)) {
11321 gtk_text_buffer_delete(buffer, &end_iter, &ins);
11325 static void textview_delete_line (GtkTextView *text)
11327 GtkTextBuffer *buffer;
11329 GtkTextIter ins, start_iter, 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);
11338 gtk_text_iter_set_line_offset(&start_iter, 0);
11341 if (gtk_text_iter_ends_line(&end_iter)){
11342 if (!gtk_text_iter_forward_char(&end_iter))
11343 gtk_text_iter_backward_char(&start_iter);
11346 gtk_text_iter_forward_to_line_end(&end_iter);
11347 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
11350 static void textview_delete_to_line_end (GtkTextView *text)
11352 GtkTextBuffer *buffer;
11354 GtkTextIter ins, end_iter;
11356 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11358 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11359 mark = gtk_text_buffer_get_insert(buffer);
11360 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11362 if (gtk_text_iter_ends_line(&end_iter))
11363 gtk_text_iter_forward_char(&end_iter);
11365 gtk_text_iter_forward_to_line_end(&end_iter);
11366 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11369 #define DO_ACTION(name, act) { \
11370 if(!strcmp(name, a_name)) { \
11374 static ComposeCallAdvancedAction compose_call_advanced_action_from_path(GtkAction *action)
11376 const gchar *a_name = gtk_action_get_name(action);
11377 DO_ACTION("Edit/Advanced/BackChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER);
11378 DO_ACTION("Edit/Advanced/ForwChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER);
11379 DO_ACTION("Edit/Advanced/BackWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD);
11380 DO_ACTION("Edit/Advanced/ForwWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD);
11381 DO_ACTION("Edit/Advanced/BegLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE);
11382 DO_ACTION("Edit/Advanced/EndLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE);
11383 DO_ACTION("Edit/Advanced/PrevLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE);
11384 DO_ACTION("Edit/Advanced/NextLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE);
11385 DO_ACTION("Edit/Advanced/DelBackChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER);
11386 DO_ACTION("Edit/Advanced/DelForwChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER);
11387 DO_ACTION("Edit/Advanced/DelBackWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD);
11388 DO_ACTION("Edit/Advanced/DelForwWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD);
11389 DO_ACTION("Edit/Advanced/DelLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE);
11390 DO_ACTION("Edit/Advanced/DelEndLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END);
11391 return COMPOSE_CALL_ADVANCED_ACTION_UNDEFINED;
11394 static void compose_advanced_action_cb(GtkAction *gaction, gpointer data)
11396 Compose *compose = (Compose *)data;
11397 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11398 ComposeCallAdvancedAction action = COMPOSE_CALL_ADVANCED_ACTION_UNDEFINED;
11400 action = compose_call_advanced_action_from_path(gaction);
11403 void (*do_action) (GtkTextView *text);
11404 } action_table[] = {
11405 {textview_move_beginning_of_line},
11406 {textview_move_forward_character},
11407 {textview_move_backward_character},
11408 {textview_move_forward_word},
11409 {textview_move_backward_word},
11410 {textview_move_end_of_line},
11411 {textview_move_next_line},
11412 {textview_move_previous_line},
11413 {textview_delete_forward_character},
11414 {textview_delete_backward_character},
11415 {textview_delete_forward_word},
11416 {textview_delete_backward_word},
11417 {textview_delete_line},
11418 {textview_delete_to_line_end}
11421 if (!gtk_widget_has_focus(GTK_WIDGET(text))) return;
11423 if (action >= COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE &&
11424 action <= COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END) {
11425 if (action_table[action].do_action)
11426 action_table[action].do_action(text);
11428 g_warning("Not implemented yet.");
11432 static void compose_grab_focus_cb(GtkWidget *widget, Compose *compose)
11434 GtkAllocation allocation;
11438 if (GTK_IS_EDITABLE(widget)) {
11439 str = gtk_editable_get_chars(GTK_EDITABLE(widget), 0, -1);
11440 gtk_editable_set_position(GTK_EDITABLE(widget),
11443 if ((parent = gtk_widget_get_parent(widget))
11444 && (parent = gtk_widget_get_parent(parent))
11445 && (parent = gtk_widget_get_parent(parent))) {
11446 if (GTK_IS_SCROLLED_WINDOW(parent)) {
11447 gtk_widget_get_allocation(widget, &allocation);
11448 gint y = allocation.y;
11449 gint height = allocation.height;
11450 GtkAdjustment *shown = gtk_scrolled_window_get_vadjustment
11451 (GTK_SCROLLED_WINDOW(parent));
11453 gfloat value = gtk_adjustment_get_value(shown);
11454 gfloat upper = gtk_adjustment_get_upper(shown);
11455 gfloat page_size = gtk_adjustment_get_page_size(shown);
11456 if (y < (int)value) {
11457 gtk_adjustment_set_value(shown, y - 1);
11459 if ((y + height) > ((int)value + (int)page_size)) {
11460 if ((y - height - 1) < ((int)upper - (int)page_size)) {
11461 gtk_adjustment_set_value(shown,
11462 y + height - (int)page_size - 1);
11464 gtk_adjustment_set_value(shown,
11465 (int)upper - (int)page_size - 1);
11472 if (GTK_IS_EDITABLE(widget) || GTK_IS_TEXT_VIEW(widget))
11473 compose->focused_editable = widget;
11475 #ifdef GENERIC_UMPC
11476 if (GTK_IS_TEXT_VIEW(widget)
11477 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->edit_vbox) {
11478 g_object_ref(compose->notebook);
11479 g_object_ref(compose->edit_vbox);
11480 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
11481 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
11482 gtk_paned_add1(GTK_PANED(compose->paned), compose->edit_vbox);
11483 gtk_paned_add2(GTK_PANED(compose->paned), compose->notebook);
11484 g_object_unref(compose->notebook);
11485 g_object_unref(compose->edit_vbox);
11486 g_signal_handlers_block_by_func(G_OBJECT(widget),
11487 G_CALLBACK(compose_grab_focus_cb),
11489 gtk_widget_grab_focus(widget);
11490 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
11491 G_CALLBACK(compose_grab_focus_cb),
11493 } else if (!GTK_IS_TEXT_VIEW(widget)
11494 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->notebook) {
11495 g_object_ref(compose->notebook);
11496 g_object_ref(compose->edit_vbox);
11497 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
11498 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
11499 gtk_paned_add1(GTK_PANED(compose->paned), compose->notebook);
11500 gtk_paned_add2(GTK_PANED(compose->paned), compose->edit_vbox);
11501 g_object_unref(compose->notebook);
11502 g_object_unref(compose->edit_vbox);
11503 g_signal_handlers_block_by_func(G_OBJECT(widget),
11504 G_CALLBACK(compose_grab_focus_cb),
11506 gtk_widget_grab_focus(widget);
11507 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
11508 G_CALLBACK(compose_grab_focus_cb),
11514 static void compose_changed_cb(GtkTextBuffer *textbuf, Compose *compose)
11516 compose->modified = TRUE;
11517 /* compose_beautify_paragraph(compose, NULL, TRUE); */
11518 #ifndef GENERIC_UMPC
11519 compose_set_title(compose);
11523 static void compose_wrap_cb(GtkAction *action, gpointer data)
11525 Compose *compose = (Compose *)data;
11526 compose_beautify_paragraph(compose, NULL, TRUE);
11529 static void compose_wrap_all_cb(GtkAction *action, gpointer data)
11531 Compose *compose = (Compose *)data;
11532 compose_wrap_all_full(compose, TRUE);
11535 static void compose_find_cb(GtkAction *action, gpointer data)
11537 Compose *compose = (Compose *)data;
11539 message_search_compose(compose);
11542 static void compose_toggle_autowrap_cb(GtkToggleAction *action,
11545 Compose *compose = (Compose *)data;
11546 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11547 if (compose->autowrap)
11548 compose_wrap_all_full(compose, TRUE);
11549 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11552 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
11555 Compose *compose = (Compose *)data;
11556 compose->autoindent = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11559 static void compose_toggle_sign_cb(GtkToggleAction *action, gpointer data)
11561 Compose *compose = (Compose *)data;
11563 compose->use_signing = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11564 gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(compose->toolbar->privacy_sign_btn), compose->use_signing);
11567 static void compose_toggle_encrypt_cb(GtkToggleAction *action, gpointer data)
11569 Compose *compose = (Compose *)data;
11571 compose->use_encryption = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11572 gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(compose->toolbar->privacy_encrypt_btn), compose->use_encryption);
11575 static void activate_privacy_system(Compose *compose, PrefsAccount *account, gboolean warn)
11577 g_free(compose->privacy_system);
11578 g_free(compose->encdata);
11580 compose->privacy_system = g_strdup(account->default_privacy_system);
11581 compose_update_privacy_system_menu_item(compose, warn);
11584 static void compose_toggle_ruler_cb(GtkToggleAction *action, gpointer data)
11586 Compose *compose = (Compose *)data;
11588 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action))) {
11589 gtk_widget_show(compose->ruler_hbox);
11590 prefs_common.show_ruler = TRUE;
11592 gtk_widget_hide(compose->ruler_hbox);
11593 gtk_widget_queue_resize(compose->edit_vbox);
11594 prefs_common.show_ruler = FALSE;
11598 static void compose_attach_drag_received_cb (GtkWidget *widget,
11599 GdkDragContext *context,
11602 GtkSelectionData *data,
11605 gpointer user_data)
11607 Compose *compose = (Compose *)user_data;
11611 type = gtk_selection_data_get_data_type(data);
11612 if ((gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list"))
11613 && gtk_drag_get_source_widget(context) !=
11614 summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
11615 list = uri_list_extract_filenames(
11616 (const gchar *)gtk_selection_data_get_data(data));
11617 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11618 gchar *utf8_filename = conv_filename_to_utf8((const gchar *)tmp->data);
11619 compose_attach_append
11620 (compose, (const gchar *)tmp->data,
11621 utf8_filename, NULL, NULL);
11622 g_free(utf8_filename);
11624 if (list) compose_changed_cb(NULL, compose);
11625 list_free_strings(list);
11627 } else if (gtk_drag_get_source_widget(context)
11628 == summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
11629 /* comes from our summaryview */
11630 SummaryView * summaryview = NULL;
11631 GSList * list = NULL, *cur = NULL;
11633 if (mainwindow_get_mainwindow())
11634 summaryview = mainwindow_get_mainwindow()->summaryview;
11637 list = summary_get_selected_msg_list(summaryview);
11639 for (cur = list; cur; cur = cur->next) {
11640 MsgInfo *msginfo = (MsgInfo *)cur->data;
11641 gchar *file = NULL;
11643 file = procmsg_get_message_file_full(msginfo,
11646 compose_attach_append(compose, (const gchar *)file,
11647 (const gchar *)file, "message/rfc822", NULL);
11651 g_slist_free(list);
11655 static gboolean compose_drag_drop(GtkWidget *widget,
11656 GdkDragContext *drag_context,
11658 guint time, gpointer user_data)
11660 /* not handling this signal makes compose_insert_drag_received_cb
11665 static gboolean completion_set_focus_to_subject
11666 (GtkWidget *widget,
11667 GdkEventKey *event,
11670 cm_return_val_if_fail(compose != NULL, FALSE);
11672 /* make backtab move to subject field */
11673 if(event->keyval == GDK_KEY_ISO_Left_Tab) {
11674 gtk_widget_grab_focus(compose->subject_entry);
11680 static void compose_insert_drag_received_cb (GtkWidget *widget,
11681 GdkDragContext *drag_context,
11684 GtkSelectionData *data,
11687 gpointer user_data)
11689 Compose *compose = (Compose *)user_data;
11695 /* strangely, testing data->type == gdk_atom_intern("text/uri-list", TRUE)
11697 type = gtk_selection_data_get_data_type(data);
11698 if (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list")) {
11699 AlertValue val = G_ALERTDEFAULT;
11700 const gchar* ddata = (const gchar *)gtk_selection_data_get_data(data);
11702 list = uri_list_extract_filenames(ddata);
11703 num_files = g_list_length(list);
11704 if (list == NULL && strstr(ddata, "://")) {
11705 /* Assume a list of no files, and data has ://, is a remote link */
11706 gchar *tmpdata = g_strstrip(g_strdup(ddata));
11707 gchar *tmpfile = get_tmp_file();
11708 str_write_to_file(tmpdata, tmpfile);
11710 compose_insert_file(compose, tmpfile);
11711 claws_unlink(tmpfile);
11713 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11714 compose_beautify_paragraph(compose, NULL, TRUE);
11717 switch (prefs_common.compose_dnd_mode) {
11718 case COMPOSE_DND_ASK:
11719 msg = g_strdup_printf(
11721 "Do you want to insert the contents of the file "
11722 "into the message body, or attach it to the email?",
11723 "Do you want to insert the contents of the %d files "
11724 "into the message body, or attach them to the email?",
11727 val = alertpanel_full(_("Insert or attach?"), msg,
11728 GTK_STOCK_CANCEL, g_strconcat("+", _("_Insert"), NULL), _("_Attach"),
11729 TRUE, NULL, ALERT_QUESTION, G_ALERTALTERNATE);
11732 case COMPOSE_DND_INSERT:
11733 val = G_ALERTALTERNATE;
11735 case COMPOSE_DND_ATTACH:
11736 val = G_ALERTOTHER;
11739 /* unexpected case */
11740 g_warning("error: unexpected compose_dnd_mode option value in compose_insert_drag_received_cb()");
11743 if (val & G_ALERTDISABLE) {
11744 val &= ~G_ALERTDISABLE;
11745 /* remember what action to perform by default, only if we don't click Cancel */
11746 if (val == G_ALERTALTERNATE)
11747 prefs_common.compose_dnd_mode = COMPOSE_DND_INSERT;
11748 else if (val == G_ALERTOTHER)
11749 prefs_common.compose_dnd_mode = COMPOSE_DND_ATTACH;
11752 if (val == G_ALERTDEFAULT || val == G_ALERTCANCEL) {
11753 gtk_drag_finish(drag_context, FALSE, FALSE, time);
11754 list_free_strings(list);
11757 } else if (val == G_ALERTOTHER) {
11758 compose_attach_drag_received_cb(widget, drag_context, x, y, data, info, time, user_data);
11759 list_free_strings(list);
11764 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11765 compose_insert_file(compose, (const gchar *)tmp->data);
11767 list_free_strings(list);
11769 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11774 static void compose_header_drag_received_cb (GtkWidget *widget,
11775 GdkDragContext *drag_context,
11778 GtkSelectionData *data,
11781 gpointer user_data)
11783 GtkEditable *entry = (GtkEditable *)user_data;
11784 const gchar *email = (const gchar *)gtk_selection_data_get_data(data);
11786 /* strangely, testing data->type == gdk_atom_intern("text/plain", TRUE)
11789 if (!strncmp(email, "mailto:", strlen("mailto:"))) {
11790 gchar *decoded=g_new(gchar, strlen(email));
11793 decode_uri(decoded, email + strlen("mailto:")); /* will fit */
11794 gtk_editable_delete_text(entry, 0, -1);
11795 gtk_editable_insert_text(entry, decoded, strlen(decoded), &start);
11796 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11800 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11803 static void compose_toggle_return_receipt_cb(GtkToggleAction *action, gpointer data)
11805 Compose *compose = (Compose *)data;
11807 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11808 compose->return_receipt = TRUE;
11810 compose->return_receipt = FALSE;
11813 static void compose_toggle_remove_refs_cb(GtkToggleAction *action, gpointer data)
11815 Compose *compose = (Compose *)data;
11817 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11818 compose->remove_references = TRUE;
11820 compose->remove_references = FALSE;
11823 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
11824 ComposeHeaderEntry *headerentry)
11826 gtk_entry_set_text(GTK_ENTRY(headerentry->entry), "");
11827 gtk_widget_modify_base(GTK_WIDGET(headerentry->entry), GTK_STATE_NORMAL, NULL);
11828 gtk_widget_modify_text(GTK_WIDGET(headerentry->entry), GTK_STATE_NORMAL, NULL);
11832 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
11833 GdkEventKey *event,
11834 ComposeHeaderEntry *headerentry)
11836 if ((g_slist_length(headerentry->compose->header_list) > 0) &&
11837 ((headerentry->headernum + 1) != headerentry->compose->header_nextrow) &&
11838 !(event->state & GDK_MODIFIER_MASK) &&
11839 (event->keyval == GDK_KEY_BackSpace) &&
11840 (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) == 0)) {
11841 gtk_container_remove
11842 (GTK_CONTAINER(headerentry->compose->header_table),
11843 headerentry->combo);
11844 gtk_container_remove
11845 (GTK_CONTAINER(headerentry->compose->header_table),
11846 headerentry->entry);
11847 headerentry->compose->header_list =
11848 g_slist_remove(headerentry->compose->header_list,
11850 g_free(headerentry);
11851 } else if (event->keyval == GDK_KEY_Tab) {
11852 if (headerentry->compose->header_last == headerentry) {
11853 /* Override default next focus, and give it to subject_entry
11854 * instead of notebook tabs
11856 g_signal_stop_emission_by_name(G_OBJECT(entry), "key-press-event");
11857 gtk_widget_grab_focus(headerentry->compose->subject_entry);
11864 static gboolean scroll_postpone(gpointer data)
11866 Compose *compose = (Compose *)data;
11868 if (compose->batch)
11871 GTK_EVENTS_FLUSH();
11872 compose_show_first_last_header(compose, FALSE);
11876 static void compose_headerentry_changed_cb(GtkWidget *entry,
11877 ComposeHeaderEntry *headerentry)
11879 if (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) != 0) {
11880 compose_create_header_entry(headerentry->compose);
11881 g_signal_handlers_disconnect_matched
11882 (G_OBJECT(entry), G_SIGNAL_MATCH_DATA,
11883 0, 0, NULL, NULL, headerentry);
11885 if (!headerentry->compose->batch)
11886 g_timeout_add(0, scroll_postpone, headerentry->compose);
11890 static gboolean compose_defer_auto_save_draft(Compose *compose)
11892 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
11893 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
11897 static void compose_show_first_last_header(Compose *compose, gboolean show_first)
11899 GtkAdjustment *vadj;
11901 cm_return_if_fail(compose);
11906 cm_return_if_fail(GTK_IS_WIDGET(compose->header_table));
11907 cm_return_if_fail(GTK_IS_VIEWPORT(gtk_widget_get_parent(compose->header_table)));
11908 vadj = gtk_viewport_get_vadjustment(GTK_VIEWPORT(
11909 gtk_widget_get_parent(compose->header_table)));
11910 gtk_adjustment_set_value(vadj, (show_first ?
11911 gtk_adjustment_get_lower(vadj) :
11912 (gtk_adjustment_get_upper(vadj) -
11913 gtk_adjustment_get_page_size(vadj))));
11914 gtk_adjustment_changed(vadj);
11917 static void text_inserted(GtkTextBuffer *buffer, GtkTextIter *iter,
11918 const gchar *text, gint len, Compose *compose)
11920 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data
11921 (G_OBJECT(compose->text), "paste_as_quotation"));
11924 cm_return_if_fail(text != NULL);
11926 g_signal_handlers_block_by_func(G_OBJECT(buffer),
11927 G_CALLBACK(text_inserted),
11929 if (paste_as_quotation) {
11931 const gchar *qmark;
11933 GtkTextIter start_iter;
11936 len = strlen(text);
11938 new_text = g_strndup(text, len);
11940 qmark = compose_quote_char_from_context(compose);
11942 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11943 gtk_text_buffer_place_cursor(buffer, iter);
11945 pos = gtk_text_iter_get_offset(iter);
11947 compose_quote_fmt(compose, NULL, "%Q", qmark, new_text, TRUE, FALSE,
11948 _("Quote format error at line %d."));
11949 quote_fmt_reset_vartable();
11951 g_object_set_data(G_OBJECT(compose->text), "paste_as_quotation",
11952 GINT_TO_POINTER(paste_as_quotation - 1));
11954 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11955 gtk_text_buffer_place_cursor(buffer, iter);
11956 gtk_text_buffer_delete_mark(buffer, mark);
11958 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, pos);
11959 mark = gtk_text_buffer_create_mark(buffer, NULL, &start_iter, FALSE);
11960 compose_beautify_paragraph(compose, &start_iter, FALSE);
11961 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark);
11962 gtk_text_buffer_delete_mark(buffer, mark);
11964 if (strcmp(text, "\n") || compose->automatic_break
11965 || gtk_text_iter_starts_line(iter)) {
11966 GtkTextIter before_ins;
11967 gtk_text_buffer_insert(buffer, iter, text, len);
11968 if (!strstr(text, "\n") && gtk_text_iter_has_tag(iter, compose->no_join_tag)) {
11969 before_ins = *iter;
11970 gtk_text_iter_backward_chars(&before_ins, len);
11971 gtk_text_buffer_remove_tag_by_name(buffer, "no_join", &before_ins, iter);
11974 /* check if the preceding is just whitespace or quote */
11975 GtkTextIter start_line;
11976 gchar *tmp = NULL, *quote = NULL;
11977 gint quote_len = 0, is_normal = 0;
11978 start_line = *iter;
11979 gtk_text_iter_set_line_offset(&start_line, 0);
11980 tmp = gtk_text_buffer_get_text(buffer, &start_line, iter, FALSE);
11983 if (*tmp == '\0') {
11986 quote = compose_get_quote_str(buffer, &start_line, "e_len);
11994 gtk_text_buffer_insert(buffer, iter, text, len);
11996 gtk_text_buffer_insert_with_tags_by_name(buffer,
11997 iter, text, len, "no_join", NULL);
12002 if (!paste_as_quotation) {
12003 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
12004 compose_beautify_paragraph(compose, iter, FALSE);
12005 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
12006 gtk_text_buffer_delete_mark(buffer, mark);
12009 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
12010 G_CALLBACK(text_inserted),
12012 g_signal_stop_emission_by_name(G_OBJECT(buffer), "insert-text");
12014 if (compose_can_autosave(compose) &&
12015 gtk_text_buffer_get_char_count(buffer) % prefs_common.autosave_length == 0 &&
12016 compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN /* disabled while loading */)
12017 compose->draft_timeout_tag = g_timeout_add
12018 (500, (GSourceFunc) compose_defer_auto_save_draft, compose);
12022 static void compose_check_all(GtkAction *action, gpointer data)
12024 Compose *compose = (Compose *)data;
12025 if (!compose->gtkaspell)
12028 if (gtk_widget_has_focus(compose->subject_entry))
12029 claws_spell_entry_check_all(
12030 CLAWS_SPELL_ENTRY(compose->subject_entry));
12032 gtkaspell_check_all(compose->gtkaspell);
12035 static void compose_highlight_all(GtkAction *action, gpointer data)
12037 Compose *compose = (Compose *)data;
12038 if (compose->gtkaspell) {
12039 claws_spell_entry_recheck_all(
12040 CLAWS_SPELL_ENTRY(compose->subject_entry));
12041 gtkaspell_highlight_all(compose->gtkaspell);
12045 static void compose_check_backwards(GtkAction *action, gpointer data)
12047 Compose *compose = (Compose *)data;
12048 if (!compose->gtkaspell) {
12049 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
12053 if (gtk_widget_has_focus(compose->subject_entry))
12054 claws_spell_entry_check_backwards(
12055 CLAWS_SPELL_ENTRY(compose->subject_entry));
12057 gtkaspell_check_backwards(compose->gtkaspell);
12060 static void compose_check_forwards_go(GtkAction *action, gpointer data)
12062 Compose *compose = (Compose *)data;
12063 if (!compose->gtkaspell) {
12064 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
12068 if (gtk_widget_has_focus(compose->subject_entry))
12069 claws_spell_entry_check_forwards_go(
12070 CLAWS_SPELL_ENTRY(compose->subject_entry));
12072 gtkaspell_check_forwards_go(compose->gtkaspell);
12077 *\brief Guess originating forward account from MsgInfo and several
12078 * "common preference" settings. Return NULL if no guess.
12080 static PrefsAccount *compose_find_account(MsgInfo *msginfo)
12082 PrefsAccount *account = NULL;
12084 cm_return_val_if_fail(msginfo, NULL);
12085 cm_return_val_if_fail(msginfo->folder, NULL);
12086 cm_return_val_if_fail(msginfo->folder->prefs, NULL);
12088 if (msginfo->folder->prefs->enable_default_account)
12089 account = account_find_from_id(msginfo->folder->prefs->default_account);
12091 if (!account && msginfo->to && prefs_common.forward_account_autosel) {
12093 Xstrdup_a(to, msginfo->to, return NULL);
12094 extract_address(to);
12095 account = account_find_from_address(to, FALSE);
12098 if (!account && prefs_common.forward_account_autosel) {
12100 if (!procheader_get_header_from_msginfo
12101 (msginfo, &cc, "Cc:")) {
12102 gchar *buf = cc + strlen("Cc:");
12103 extract_address(buf);
12104 account = account_find_from_address(buf, FALSE);
12109 if (!account && prefs_common.forward_account_autosel) {
12110 gchar *deliveredto = NULL;
12111 if (!procheader_get_header_from_msginfo
12112 (msginfo, &deliveredto, "Delivered-To:")) {
12113 gchar *buf = deliveredto + strlen("Delivered-To:");
12114 extract_address(buf);
12115 account = account_find_from_address(buf, FALSE);
12116 g_free(deliveredto);
12121 account = msginfo->folder->folder->account;
12126 gboolean compose_close(Compose *compose)
12130 cm_return_val_if_fail(compose, FALSE);
12132 if (!g_mutex_trylock(compose->mutex)) {
12133 /* we have to wait for the (possibly deferred by auto-save)
12134 * drafting to be done, before destroying the compose under
12136 debug_print("waiting for drafting to finish...\n");
12137 compose_allow_user_actions(compose, FALSE);
12138 if (compose->close_timeout_tag == 0) {
12139 compose->close_timeout_tag =
12140 g_timeout_add (500, (GSourceFunc) compose_close,
12146 if (compose->draft_timeout_tag >= 0) {
12147 g_source_remove(compose->draft_timeout_tag);
12148 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN;
12151 gtkut_widget_get_uposition(compose->window, &x, &y);
12152 if (!compose->batch) {
12153 prefs_common.compose_x = x;
12154 prefs_common.compose_y = y;
12156 g_mutex_unlock(compose->mutex);
12157 compose_destroy(compose);
12162 * Add entry field for each address in list.
12163 * \param compose E-Mail composition object.
12164 * \param listAddress List of (formatted) E-Mail addresses.
12166 static void compose_add_field_list( Compose *compose, GList *listAddress ) {
12169 node = listAddress;
12171 addr = ( gchar * ) node->data;
12172 compose_entry_append( compose, addr, COMPOSE_TO, PREF_NONE );
12173 node = g_list_next( node );
12177 static void compose_reply_from_messageview_real(MessageView *msgview, GSList *msginfo_list,
12178 guint action, gboolean opening_multiple)
12180 gchar *body = NULL;
12181 GSList *new_msglist = NULL;
12182 MsgInfo *tmp_msginfo = NULL;
12183 gboolean originally_enc = FALSE;
12184 gboolean originally_sig = FALSE;
12185 Compose *compose = NULL;
12186 gchar *s_system = NULL;
12188 cm_return_if_fail(msgview != NULL);
12190 cm_return_if_fail(msginfo_list != NULL);
12192 if (g_slist_length(msginfo_list) == 1 && !opening_multiple) {
12193 MimeInfo *mimeinfo = messageview_get_selected_mime_part(msgview);
12194 MsgInfo *orig_msginfo = (MsgInfo *)msginfo_list->data;
12196 if (mimeinfo != NULL && mimeinfo->type == MIMETYPE_MESSAGE &&
12197 !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
12198 tmp_msginfo = procmsg_msginfo_new_from_mimeinfo(
12199 orig_msginfo, mimeinfo);
12200 if (tmp_msginfo != NULL) {
12201 new_msglist = g_slist_append(NULL, tmp_msginfo);
12203 originally_enc = MSG_IS_ENCRYPTED(orig_msginfo->flags);
12204 privacy_msginfo_get_signed_state(orig_msginfo, &s_system);
12205 originally_sig = MSG_IS_SIGNED(orig_msginfo->flags);
12207 tmp_msginfo->folder = orig_msginfo->folder;
12208 tmp_msginfo->msgnum = orig_msginfo->msgnum;
12209 if (orig_msginfo->tags) {
12210 tmp_msginfo->tags = g_slist_copy(orig_msginfo->tags);
12211 tmp_msginfo->folder->tags_dirty = TRUE;
12217 if (!opening_multiple)
12218 body = messageview_get_selection(msgview);
12221 compose = compose_reply_mode((ComposeMode)action, new_msglist, body);
12222 procmsg_msginfo_free(&tmp_msginfo);
12223 g_slist_free(new_msglist);
12225 compose = compose_reply_mode((ComposeMode)action, msginfo_list, body);
12227 if (compose && originally_enc) {
12228 compose_force_encryption(compose, compose->account, FALSE, s_system);
12231 if (compose && originally_sig && compose->account->default_sign_reply) {
12232 compose_force_signing(compose, compose->account, s_system);
12236 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
12239 void compose_reply_from_messageview(MessageView *msgview, GSList *msginfo_list,
12242 if ((!prefs_common.forward_as_attachment || action != COMPOSE_FORWARD)
12243 && action != COMPOSE_FORWARD_AS_ATTACH && g_slist_length(msginfo_list) > 1) {
12244 GSList *cur = msginfo_list;
12245 gchar *msg = g_strdup_printf(_("You are about to reply to %d "
12246 "messages. Opening the windows "
12247 "could take some time. Do you "
12248 "want to continue?"),
12249 g_slist_length(msginfo_list));
12250 if (g_slist_length(msginfo_list) > 9
12251 && alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, "+" GTK_STOCK_YES, NULL)
12252 != G_ALERTALTERNATE) {
12257 /* We'll open multiple compose windows */
12258 /* let the WM place the next windows */
12259 compose_force_window_origin = FALSE;
12260 for (; cur; cur = cur->next) {
12262 tmplist.data = cur->data;
12263 tmplist.next = NULL;
12264 compose_reply_from_messageview_real(msgview, &tmplist, action, TRUE);
12266 compose_force_window_origin = TRUE;
12268 /* forwarding multiple mails as attachments is done via a
12269 * single compose window */
12270 compose_reply_from_messageview_real(msgview, msginfo_list, action, FALSE);
12274 void compose_check_for_email_account(Compose *compose)
12276 PrefsAccount *ac = NULL, *curr = NULL;
12282 if (compose->account && compose->account->protocol == A_NNTP) {
12283 ac = account_get_cur_account();
12284 if (ac->protocol == A_NNTP) {
12285 list = account_get_list();
12287 for( ; list != NULL ; list = g_list_next(list)) {
12288 curr = (PrefsAccount *) list->data;
12289 if (curr->protocol != A_NNTP) {
12295 combobox_select_by_data(GTK_COMBO_BOX(compose->account_combo),
12300 void compose_reply_to_address(MessageView *msgview, MsgInfo *msginfo,
12301 const gchar *address)
12303 GSList *msginfo_list = NULL;
12304 gchar *body = messageview_get_selection(msgview);
12307 msginfo_list = g_slist_prepend(msginfo_list, msginfo);
12309 compose = compose_reply_mode(COMPOSE_REPLY_TO_ADDRESS, msginfo_list, body);
12310 compose_check_for_email_account(compose);
12311 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
12312 compose_entry_append(compose, address, COMPOSE_TO, PREF_NONE);
12313 compose_reply_set_subject(compose, msginfo);
12316 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
12319 void compose_set_position(Compose *compose, gint pos)
12321 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
12323 gtkut_text_view_set_position(text, pos);
12326 gboolean compose_search_string(Compose *compose,
12327 const gchar *str, gboolean case_sens)
12329 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
12331 return gtkut_text_view_search_string(text, str, case_sens);
12334 gboolean compose_search_string_backward(Compose *compose,
12335 const gchar *str, gboolean case_sens)
12337 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
12339 return gtkut_text_view_search_string_backward(text, str, case_sens);
12342 /* allocate a msginfo structure and populate its data from a compose data structure */
12343 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose)
12345 MsgInfo *newmsginfo;
12347 gchar date[RFC822_DATE_BUFFSIZE];
12349 cm_return_val_if_fail( compose != NULL, NULL );
12351 newmsginfo = procmsg_msginfo_new();
12354 get_rfc822_date(date, sizeof(date));
12355 newmsginfo->date = g_strdup(date);
12358 if (compose->from_name) {
12359 newmsginfo->from = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
12360 newmsginfo->fromname = procheader_get_fromname(newmsginfo->from);
12364 if (compose->subject_entry)
12365 newmsginfo->subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
12367 /* to, cc, reply-to, newsgroups */
12368 for (list = compose->header_list; list; list = list->next) {
12369 gchar *header = gtk_editable_get_chars(
12371 gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
12372 gchar *entry = gtk_editable_get_chars(
12373 GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
12375 if ( strcasecmp(header, prefs_common_translated_header_name("To:")) == 0 ) {
12376 if ( newmsginfo->to == NULL ) {
12377 newmsginfo->to = g_strdup(entry);
12378 } else if (entry && *entry) {
12379 gchar *tmp = g_strconcat(newmsginfo->to, ", ", entry, NULL);
12380 g_free(newmsginfo->to);
12381 newmsginfo->to = tmp;
12384 if ( strcasecmp(header, prefs_common_translated_header_name("Cc:")) == 0 ) {
12385 if ( newmsginfo->cc == NULL ) {
12386 newmsginfo->cc = g_strdup(entry);
12387 } else if (entry && *entry) {
12388 gchar *tmp = g_strconcat(newmsginfo->cc, ", ", entry, NULL);
12389 g_free(newmsginfo->cc);
12390 newmsginfo->cc = tmp;
12393 if ( strcasecmp(header,
12394 prefs_common_translated_header_name("Newsgroups:")) == 0 ) {
12395 if ( newmsginfo->newsgroups == NULL ) {
12396 newmsginfo->newsgroups = g_strdup(entry);
12397 } else if (entry && *entry) {
12398 gchar *tmp = g_strconcat(newmsginfo->newsgroups, ", ", entry, NULL);
12399 g_free(newmsginfo->newsgroups);
12400 newmsginfo->newsgroups = tmp;
12408 /* other data is unset */
12414 /* update compose's dictionaries from folder dict settings */
12415 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
12416 FolderItem *folder_item)
12418 cm_return_if_fail(compose != NULL);
12420 if (compose->gtkaspell && folder_item && folder_item->prefs) {
12421 FolderItemPrefs *prefs = folder_item->prefs;
12423 if (prefs->enable_default_dictionary)
12424 gtkaspell_change_dict(compose->gtkaspell,
12425 prefs->default_dictionary, FALSE);
12426 if (folder_item->prefs->enable_default_alt_dictionary)
12427 gtkaspell_change_alt_dict(compose->gtkaspell,
12428 prefs->default_alt_dictionary);
12429 if (prefs->enable_default_dictionary
12430 || prefs->enable_default_alt_dictionary)
12431 compose_spell_menu_changed(compose);
12436 static void compose_subject_entry_activated(GtkWidget *widget, gpointer data)
12438 Compose *compose = (Compose *)data;
12440 cm_return_if_fail(compose != NULL);
12442 gtk_widget_grab_focus(compose->text);
12445 static void from_name_activate_cb(GtkWidget *widget, gpointer data)
12447 gtk_combo_box_popup(GTK_COMBO_BOX(data));