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 ComposeQueueResult 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};
842 buffer = gtk_text_view_get_buffer(text);
844 if (prefs_common.enable_color) {
845 /* grab the quote colors, converting from an int to a GdkColor */
846 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_QUOTE_LEVEL1],
848 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_QUOTE_LEVEL2],
850 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_QUOTE_LEVEL3],
852 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_QUOTE_LEVEL1_BG],
854 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_QUOTE_LEVEL2_BG],
856 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_QUOTE_LEVEL3_BG],
858 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_SIGNATURE],
860 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_URI],
863 signature_color = quote_color1 = quote_color2 = quote_color3 =
864 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 = uri_color = black;
867 if (prefs_common.enable_color && prefs_common.enable_bgcolor) {
868 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
869 "foreground-gdk", "e_color1,
870 "paragraph-background-gdk", "e_bgcolor1,
872 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
873 "foreground-gdk", "e_color2,
874 "paragraph-background-gdk", "e_bgcolor2,
876 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
877 "foreground-gdk", "e_color3,
878 "paragraph-background-gdk", "e_bgcolor3,
881 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
882 "foreground-gdk", "e_color1,
884 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
885 "foreground-gdk", "e_color2,
887 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
888 "foreground-gdk", "e_color3,
892 compose->signature_tag = gtk_text_buffer_create_tag(buffer, "signature",
893 "foreground-gdk", &signature_color,
896 compose->uri_tag = gtk_text_buffer_create_tag(buffer, "link",
897 "foreground-gdk", &uri_color,
899 compose->no_wrap_tag = gtk_text_buffer_create_tag(buffer, "no_wrap", NULL);
900 compose->no_join_tag = gtk_text_buffer_create_tag(buffer, "no_join", NULL);
903 Compose *compose_new(PrefsAccount *account, const gchar *mailto,
906 return compose_generic_new(account, mailto, NULL, attach_files, NULL);
909 Compose *compose_new_with_folderitem(PrefsAccount *account, FolderItem *item, const gchar *mailto)
911 return compose_generic_new(account, mailto, item, NULL, NULL);
914 Compose *compose_new_with_list( PrefsAccount *account, GList *listAddress )
916 return compose_generic_new( account, NULL, NULL, NULL, listAddress );
919 #define SCROLL_TO_CURSOR(compose) { \
920 GtkTextMark *cmark = gtk_text_buffer_get_insert( \
921 gtk_text_view_get_buffer( \
922 GTK_TEXT_VIEW(compose->text))); \
923 gtk_text_view_scroll_mark_onscreen( \
924 GTK_TEXT_VIEW(compose->text), \
928 static void compose_set_save_to(Compose *compose, const gchar *folderidentifier)
931 if (folderidentifier) {
932 combobox_unset_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo));
933 prefs_common.compose_save_to_history = add_history(
934 prefs_common.compose_save_to_history, folderidentifier);
935 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo),
936 prefs_common.compose_save_to_history);
939 entry = GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(compose->savemsg_combo)));
940 if (folderidentifier)
941 gtk_entry_set_text(GTK_ENTRY(entry), folderidentifier);
943 gtk_entry_set_text(GTK_ENTRY(entry), "");
946 static gchar *compose_get_save_to(Compose *compose)
949 gchar *result = NULL;
950 entry = GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(compose->savemsg_combo)));
951 result = gtk_editable_get_chars(entry, 0, -1);
954 combobox_unset_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo));
955 prefs_common.compose_save_to_history = add_history(
956 prefs_common.compose_save_to_history, result);
957 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo),
958 prefs_common.compose_save_to_history);
963 Compose *compose_generic_new(PrefsAccount *account, const gchar *mailto, FolderItem *item,
964 GList *attach_files, GList *listAddress )
967 GtkTextView *textview;
968 GtkTextBuffer *textbuf;
970 const gchar *subject_format = NULL;
971 const gchar *body_format = NULL;
972 gchar *mailto_from = NULL;
973 PrefsAccount *mailto_account = NULL;
974 MsgInfo* dummyinfo = NULL;
975 gint cursor_pos = -1;
976 MailField mfield = NO_FIELD_PRESENT;
980 /* check if mailto defines a from */
981 if (mailto && *mailto != '\0') {
982 scan_mailto_url(mailto, &mailto_from, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
983 /* mailto defines a from, check if we can get account prefs from it,
984 if not, the account prefs will be guessed using other ways, but we'll keep
987 mailto_account = account_find_from_address(mailto_from, TRUE);
988 if (mailto_account == NULL) {
990 Xstrdup_a(tmp_from, mailto_from, return NULL);
991 extract_address(tmp_from);
992 mailto_account = account_find_from_address(tmp_from, TRUE);
996 account = mailto_account;
999 /* if no account prefs set from mailto, set if from folder prefs (if any) */
1000 if (!mailto_account && item && item->prefs && item->prefs->enable_default_account)
1001 account = account_find_from_id(item->prefs->default_account);
1003 /* if no account prefs set, fallback to the current one */
1004 if (!account) account = cur_account;
1005 cm_return_val_if_fail(account != NULL, NULL);
1007 compose = compose_create(account, item, COMPOSE_NEW, FALSE);
1009 /* override from name if mailto asked for it */
1011 gtk_entry_set_text(GTK_ENTRY(compose->from_name), mailto_from);
1012 g_free(mailto_from);
1014 /* override from name according to folder properties */
1015 if (item && item->prefs &&
1016 item->prefs->compose_with_format &&
1017 item->prefs->compose_override_from_format &&
1018 *item->prefs->compose_override_from_format != '\0') {
1023 dummyinfo = compose_msginfo_new_from_compose(compose);
1025 /* decode \-escape sequences in the internal representation of the quote format */
1026 tmp = g_malloc(strlen(item->prefs->compose_override_from_format)+1);
1027 pref_get_unescaped_pref(tmp, item->prefs->compose_override_from_format);
1030 quote_fmt_init(dummyinfo, NULL, NULL, FALSE, compose->account, FALSE,
1031 compose->gtkaspell);
1033 quote_fmt_init(dummyinfo, NULL, NULL, FALSE, compose->account, FALSE);
1035 quote_fmt_scan_string(tmp);
1038 buf = quote_fmt_get_buffer();
1040 alertpanel_error(_("New message From format error."));
1042 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1043 quote_fmt_reset_vartable();
1044 quote_fmtlex_destroy();
1049 compose->replyinfo = NULL;
1050 compose->fwdinfo = NULL;
1052 textview = GTK_TEXT_VIEW(compose->text);
1053 textbuf = gtk_text_view_get_buffer(textview);
1054 compose_create_tags(textview, compose);
1056 undo_block(compose->undostruct);
1058 compose_set_dictionaries_from_folder_prefs(compose, item);
1061 if (account->auto_sig)
1062 compose_insert_sig(compose, FALSE);
1063 gtk_text_buffer_get_start_iter(textbuf, &iter);
1064 gtk_text_buffer_place_cursor(textbuf, &iter);
1066 if (account->protocol != A_NNTP) {
1067 if (mailto && *mailto != '\0') {
1068 mfield = compose_entries_set(compose, mailto, COMPOSE_TO);
1071 compose_set_folder_prefs(compose, item, TRUE);
1073 if (item && item->ret_rcpt) {
1074 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
1077 if (mailto && *mailto != '\0') {
1078 if (!strchr(mailto, '@'))
1079 mfield = compose_entries_set(compose, mailto, COMPOSE_NEWSGROUPS);
1081 mfield = compose_entries_set(compose, mailto, COMPOSE_TO);
1082 } else if (item && FOLDER_CLASS(item->folder) == news_get_class()) {
1083 compose_entry_append(compose, item->path, COMPOSE_NEWSGROUPS, PREF_FOLDER);
1084 mfield = TO_FIELD_PRESENT;
1087 * CLAWS: just don't allow return receipt request, even if the user
1088 * may want to send an email. simple but foolproof.
1090 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", FALSE);
1092 compose_add_field_list( compose, listAddress );
1094 if (item && item->prefs && item->prefs->compose_with_format) {
1095 subject_format = item->prefs->compose_subject_format;
1096 body_format = item->prefs->compose_body_format;
1097 } else if (account->compose_with_format) {
1098 subject_format = account->compose_subject_format;
1099 body_format = account->compose_body_format;
1100 } else if (prefs_common.compose_with_format) {
1101 subject_format = prefs_common.compose_subject_format;
1102 body_format = prefs_common.compose_body_format;
1105 if (subject_format || body_format) {
1108 && *subject_format != '\0' )
1110 gchar *subject = NULL;
1115 dummyinfo = compose_msginfo_new_from_compose(compose);
1117 /* decode \-escape sequences in the internal representation of the quote format */
1118 tmp = g_malloc(strlen(subject_format)+1);
1119 pref_get_unescaped_pref(tmp, subject_format);
1121 subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1123 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account, FALSE,
1124 compose->gtkaspell);
1126 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account, FALSE);
1128 quote_fmt_scan_string(tmp);
1131 buf = quote_fmt_get_buffer();
1133 alertpanel_error(_("New message subject format error."));
1135 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1136 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1137 quote_fmt_reset_vartable();
1138 quote_fmtlex_destroy();
1142 mfield = SUBJECT_FIELD_PRESENT;
1146 && *body_format != '\0' )
1149 GtkTextBuffer *buffer;
1150 GtkTextIter start, end;
1154 dummyinfo = compose_msginfo_new_from_compose(compose);
1156 text = GTK_TEXT_VIEW(compose->text);
1157 buffer = gtk_text_view_get_buffer(text);
1158 gtk_text_buffer_get_start_iter(buffer, &start);
1159 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
1160 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
1162 compose_quote_fmt(compose, dummyinfo,
1164 NULL, tmp, FALSE, TRUE,
1165 _("The body of the \"New message\" template has an error at line %d."));
1166 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1167 quote_fmt_reset_vartable();
1171 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1172 gtkaspell_highlight_all(compose->gtkaspell);
1174 mfield = BODY_FIELD_PRESENT;
1178 procmsg_msginfo_free( &dummyinfo );
1184 for (curr = attach_files ; curr != NULL ; curr = curr->next) {
1185 ainfo = (AttachInfo *) curr->data;
1187 compose_insert_file(compose, ainfo->file);
1189 compose_attach_append(compose, ainfo->file, ainfo->file,
1190 ainfo->content_type, ainfo->charset);
1194 compose_show_first_last_header(compose, TRUE);
1196 /* Set save folder */
1197 if (item && item->prefs && item->prefs->save_copy_to_folder) {
1198 gchar *folderidentifier;
1200 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
1201 folderidentifier = folder_item_get_identifier(item);
1202 compose_set_save_to(compose, folderidentifier);
1203 g_free(folderidentifier);
1206 /* Place cursor according to provided input (mfield) */
1208 case NO_FIELD_PRESENT:
1209 if (compose->header_last)
1210 gtk_widget_grab_focus(compose->header_last->entry);
1212 case TO_FIELD_PRESENT:
1213 buf = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1215 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1218 gtk_widget_grab_focus(compose->subject_entry);
1220 case SUBJECT_FIELD_PRESENT:
1221 textview = GTK_TEXT_VIEW(compose->text);
1224 textbuf = gtk_text_view_get_buffer(textview);
1227 mark = gtk_text_buffer_get_insert(textbuf);
1228 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
1229 gtk_text_buffer_insert(textbuf, &iter, "", -1);
1231 * SUBJECT_FIELD_PRESENT and BODY_FIELD_PRESENT
1232 * only defers where it comes to the variable body
1233 * is not null. If no body is present compose->text
1234 * will be null in which case you cannot place the
1235 * cursor inside the component so. An empty component
1236 * is therefore created before placing the cursor
1238 case BODY_FIELD_PRESENT:
1239 cursor_pos = quote_fmt_get_cursor_pos();
1240 if (cursor_pos == -1)
1241 gtk_widget_grab_focus(compose->header_last->entry);
1243 gtk_widget_grab_focus(compose->text);
1247 undo_unblock(compose->undostruct);
1249 if (prefs_common.auto_exteditor)
1250 compose_exec_ext_editor(compose);
1252 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
1254 SCROLL_TO_CURSOR(compose);
1256 compose->modified = FALSE;
1257 compose_set_title(compose);
1259 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
1264 static void compose_force_encryption(Compose *compose, PrefsAccount *account,
1265 gboolean override_pref, const gchar *system)
1267 const gchar *privacy = NULL;
1269 cm_return_if_fail(compose != NULL);
1270 cm_return_if_fail(account != NULL);
1272 if (override_pref == FALSE && account->default_encrypt_reply == FALSE)
1275 if (account->default_privacy_system && strlen(account->default_privacy_system))
1276 privacy = account->default_privacy_system;
1280 GSList *privacy_avail = privacy_get_system_ids();
1281 if (privacy_avail && g_slist_length(privacy_avail)) {
1282 privacy = (gchar *)(privacy_avail->data);
1284 g_slist_free_full(privacy_avail, g_free);
1286 if (privacy != NULL) {
1288 g_free(compose->privacy_system);
1289 compose->privacy_system = NULL;
1290 g_free(compose->encdata);
1291 compose->encdata = NULL;
1293 if (compose->privacy_system == NULL)
1294 compose->privacy_system = g_strdup(privacy);
1295 else if (*(compose->privacy_system) == '\0') {
1296 g_free(compose->privacy_system);
1297 g_free(compose->encdata);
1298 compose->encdata = NULL;
1299 compose->privacy_system = g_strdup(privacy);
1301 compose_update_privacy_system_menu_item(compose, FALSE);
1302 compose_use_encryption(compose, TRUE);
1306 static void compose_force_signing(Compose *compose, PrefsAccount *account, const gchar *system)
1308 const gchar *privacy = NULL;
1310 if (account->default_privacy_system && strlen(account->default_privacy_system))
1311 privacy = account->default_privacy_system;
1315 GSList *privacy_avail = privacy_get_system_ids();
1316 if (privacy_avail && g_slist_length(privacy_avail)) {
1317 privacy = (gchar *)(privacy_avail->data);
1321 if (privacy != NULL) {
1323 g_free(compose->privacy_system);
1324 compose->privacy_system = NULL;
1325 g_free(compose->encdata);
1326 compose->encdata = NULL;
1328 if (compose->privacy_system == NULL)
1329 compose->privacy_system = g_strdup(privacy);
1330 compose_update_privacy_system_menu_item(compose, FALSE);
1331 compose_use_signing(compose, TRUE);
1335 static Compose *compose_reply_mode(ComposeMode mode, GSList *msginfo_list, gchar *body)
1339 Compose *compose = NULL;
1341 cm_return_val_if_fail(msginfo_list != NULL, NULL);
1343 msginfo = (MsgInfo*)g_slist_nth_data(msginfo_list, 0);
1344 cm_return_val_if_fail(msginfo != NULL, NULL);
1346 list_len = g_slist_length(msginfo_list);
1350 case COMPOSE_REPLY_TO_ADDRESS:
1351 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1352 FALSE, prefs_common.default_reply_list, FALSE, body);
1354 case COMPOSE_REPLY_WITH_QUOTE:
1355 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1356 FALSE, prefs_common.default_reply_list, FALSE, body);
1358 case COMPOSE_REPLY_WITHOUT_QUOTE:
1359 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1360 FALSE, prefs_common.default_reply_list, FALSE, NULL);
1362 case COMPOSE_REPLY_TO_SENDER:
1363 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1364 FALSE, FALSE, TRUE, body);
1366 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1367 compose = compose_followup_and_reply_to(msginfo,
1368 COMPOSE_QUOTE_CHECK,
1369 FALSE, FALSE, body);
1371 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1372 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1373 FALSE, FALSE, TRUE, body);
1375 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1376 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1377 FALSE, FALSE, TRUE, NULL);
1379 case COMPOSE_REPLY_TO_ALL:
1380 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1381 TRUE, FALSE, FALSE, body);
1383 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1384 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1385 TRUE, FALSE, FALSE, body);
1387 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1388 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1389 TRUE, FALSE, FALSE, NULL);
1391 case COMPOSE_REPLY_TO_LIST:
1392 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1393 FALSE, TRUE, FALSE, body);
1395 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1396 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1397 FALSE, TRUE, FALSE, body);
1399 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1400 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1401 FALSE, TRUE, FALSE, NULL);
1403 case COMPOSE_FORWARD:
1404 if (prefs_common.forward_as_attachment) {
1405 compose = compose_reply_mode(COMPOSE_FORWARD_AS_ATTACH, msginfo_list, body);
1408 compose = compose_reply_mode(COMPOSE_FORWARD_INLINE, msginfo_list, body);
1412 case COMPOSE_FORWARD_INLINE:
1413 /* check if we reply to more than one Message */
1414 if (list_len == 1) {
1415 compose = compose_forward(NULL, msginfo, FALSE, body, FALSE, FALSE);
1418 /* more messages FALL THROUGH */
1419 case COMPOSE_FORWARD_AS_ATTACH:
1420 compose = compose_forward_multiple(NULL, msginfo_list);
1422 case COMPOSE_REDIRECT:
1423 compose = compose_redirect(NULL, msginfo, FALSE);
1426 g_warning("compose_reply_mode(): invalid Compose Mode: %d", mode);
1429 if (compose == NULL) {
1430 alertpanel_error(_("Unable to reply. The original email probably doesn't exist."));
1434 compose->rmode = mode;
1435 switch (compose->rmode) {
1437 case COMPOSE_REPLY_WITH_QUOTE:
1438 case COMPOSE_REPLY_WITHOUT_QUOTE:
1439 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1440 debug_print("reply mode Normal\n");
1441 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/Normal", TRUE);
1442 compose_reply_change_mode(compose, COMPOSE_REPLY); /* force update */
1444 case COMPOSE_REPLY_TO_SENDER:
1445 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1446 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1447 debug_print("reply mode Sender\n");
1448 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/Sender", TRUE);
1450 case COMPOSE_REPLY_TO_ALL:
1451 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1452 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1453 debug_print("reply mode All\n");
1454 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/All", TRUE);
1456 case COMPOSE_REPLY_TO_LIST:
1457 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1458 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1459 debug_print("reply mode List\n");
1460 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/List", TRUE);
1462 case COMPOSE_REPLY_TO_ADDRESS:
1463 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", FALSE);
1471 static Compose *compose_reply(MsgInfo *msginfo,
1472 ComposeQuoteMode quote_mode,
1478 return compose_generic_reply(msginfo, quote_mode, to_all, to_ml,
1479 to_sender, FALSE, body);
1482 static Compose *compose_followup_and_reply_to(MsgInfo *msginfo,
1483 ComposeQuoteMode quote_mode,
1488 return compose_generic_reply(msginfo, quote_mode, to_all, FALSE,
1489 to_sender, TRUE, body);
1492 static void compose_extract_original_charset(Compose *compose)
1494 MsgInfo *info = NULL;
1495 if (compose->replyinfo) {
1496 info = compose->replyinfo;
1497 } else if (compose->fwdinfo) {
1498 info = compose->fwdinfo;
1499 } else if (compose->targetinfo) {
1500 info = compose->targetinfo;
1503 MimeInfo *mimeinfo = procmime_scan_message_short(info);
1504 MimeInfo *partinfo = mimeinfo;
1505 while (partinfo && partinfo->type != MIMETYPE_TEXT)
1506 partinfo = procmime_mimeinfo_next(partinfo);
1508 compose->orig_charset =
1509 g_strdup(procmime_mimeinfo_get_parameter(
1510 partinfo, "charset"));
1512 procmime_mimeinfo_free_all(&mimeinfo);
1516 #define SIGNAL_BLOCK(buffer) { \
1517 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1518 G_CALLBACK(compose_changed_cb), \
1520 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1521 G_CALLBACK(text_inserted), \
1525 #define SIGNAL_UNBLOCK(buffer) { \
1526 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1527 G_CALLBACK(compose_changed_cb), \
1529 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1530 G_CALLBACK(text_inserted), \
1534 static Compose *compose_generic_reply(MsgInfo *msginfo,
1535 ComposeQuoteMode quote_mode,
1536 gboolean to_all, gboolean to_ml,
1538 gboolean followup_and_reply_to,
1542 PrefsAccount *account = NULL;
1543 GtkTextView *textview;
1544 GtkTextBuffer *textbuf;
1545 gboolean quote = FALSE;
1546 const gchar *qmark = NULL;
1547 const gchar *body_fmt = NULL;
1548 gchar *s_system = NULL;
1550 cm_return_val_if_fail(msginfo != NULL, NULL);
1551 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
1553 account = account_get_reply_account(msginfo, prefs_common.reply_account_autosel);
1555 cm_return_val_if_fail(account != NULL, NULL);
1557 compose = compose_create(account, msginfo->folder, COMPOSE_REPLY, FALSE);
1559 compose->updating = TRUE;
1561 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
1562 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", TRUE);
1564 compose->replyinfo = procmsg_msginfo_get_full_info(msginfo);
1565 if (!compose->replyinfo)
1566 compose->replyinfo = procmsg_msginfo_copy(msginfo);
1568 compose_extract_original_charset(compose);
1570 if (msginfo->folder && msginfo->folder->ret_rcpt)
1571 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
1573 /* Set save folder */
1574 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1575 gchar *folderidentifier;
1577 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1578 folderidentifier = folder_item_get_identifier(msginfo->folder);
1579 compose_set_save_to(compose, folderidentifier);
1580 g_free(folderidentifier);
1583 if (compose_parse_header(compose, msginfo) < 0) {
1584 compose->updating = FALSE;
1585 compose_destroy(compose);
1589 /* override from name according to folder properties */
1590 if (msginfo->folder && msginfo->folder->prefs &&
1591 msginfo->folder->prefs->reply_with_format &&
1592 msginfo->folder->prefs->reply_override_from_format &&
1593 *msginfo->folder->prefs->reply_override_from_format != '\0') {
1598 /* decode \-escape sequences in the internal representation of the quote format */
1599 tmp = g_malloc(strlen(msginfo->folder->prefs->reply_override_from_format)+1);
1600 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->reply_override_from_format);
1603 quote_fmt_init(compose->replyinfo, NULL, NULL, FALSE, compose->account, FALSE,
1604 compose->gtkaspell);
1606 quote_fmt_init(compose->replyinfo, NULL, NULL, FALSE, compose->account, FALSE);
1608 quote_fmt_scan_string(tmp);
1611 buf = quote_fmt_get_buffer();
1613 alertpanel_error(_("The \"From\" field of the \"Reply\" template contains an invalid email address."));
1615 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1616 quote_fmt_reset_vartable();
1617 quote_fmtlex_destroy();
1622 textview = (GTK_TEXT_VIEW(compose->text));
1623 textbuf = gtk_text_view_get_buffer(textview);
1624 compose_create_tags(textview, compose);
1626 undo_block(compose->undostruct);
1628 compose_set_dictionaries_from_folder_prefs(compose, msginfo->folder);
1629 gtkaspell_block_check(compose->gtkaspell);
1632 if (quote_mode == COMPOSE_QUOTE_FORCED ||
1633 (quote_mode == COMPOSE_QUOTE_CHECK && prefs_common.reply_with_quote)) {
1634 /* use the reply format of folder (if enabled), or the account's one
1635 (if enabled) or fallback to the global reply format, which is always
1636 enabled (even if empty), and use the relevant quotemark */
1638 if (msginfo->folder && msginfo->folder->prefs &&
1639 msginfo->folder->prefs->reply_with_format) {
1640 qmark = msginfo->folder->prefs->reply_quotemark;
1641 body_fmt = msginfo->folder->prefs->reply_body_format;
1643 } else if (account->reply_with_format) {
1644 qmark = account->reply_quotemark;
1645 body_fmt = account->reply_body_format;
1648 qmark = prefs_common.quotemark;
1649 if (prefs_common.quotefmt && *prefs_common.quotefmt)
1650 body_fmt = gettext(prefs_common.quotefmt);
1657 /* empty quotemark is not allowed */
1658 if (qmark == NULL || *qmark == '\0')
1660 compose_quote_fmt(compose, compose->replyinfo,
1661 body_fmt, qmark, body, FALSE, TRUE,
1662 _("The body of the \"Reply\" template has an error at line %d."));
1663 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1664 quote_fmt_reset_vartable();
1667 if (MSG_IS_ENCRYPTED(compose->replyinfo->flags)) {
1668 compose_force_encryption(compose, account, FALSE, s_system);
1671 privacy_msginfo_get_signed_state(compose->replyinfo, &s_system);
1672 if (MSG_IS_SIGNED(compose->replyinfo->flags) && account->default_sign_reply) {
1673 compose_force_signing(compose, account, s_system);
1677 SIGNAL_BLOCK(textbuf);
1679 if (account->auto_sig)
1680 compose_insert_sig(compose, FALSE);
1682 compose_wrap_all(compose);
1685 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1686 gtkaspell_highlight_all(compose->gtkaspell);
1687 gtkaspell_unblock_check(compose->gtkaspell);
1689 SIGNAL_UNBLOCK(textbuf);
1691 gtk_widget_grab_focus(compose->text);
1693 undo_unblock(compose->undostruct);
1695 if (prefs_common.auto_exteditor)
1696 compose_exec_ext_editor(compose);
1698 compose->modified = FALSE;
1699 compose_set_title(compose);
1701 compose->updating = FALSE;
1702 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
1703 SCROLL_TO_CURSOR(compose);
1705 if (compose->deferred_destroy) {
1706 compose_destroy(compose);
1714 #define INSERT_FW_HEADER(var, hdr) \
1715 if (msginfo->var && *msginfo->var) { \
1716 gtk_stext_insert(text, NULL, NULL, NULL, hdr, -1); \
1717 gtk_stext_insert(text, NULL, NULL, NULL, msginfo->var, -1); \
1718 gtk_stext_insert(text, NULL, NULL, NULL, "\n", 1); \
1721 Compose *compose_forward(PrefsAccount *account, MsgInfo *msginfo,
1722 gboolean as_attach, const gchar *body,
1723 gboolean no_extedit,
1727 GtkTextView *textview;
1728 GtkTextBuffer *textbuf;
1729 gint cursor_pos = -1;
1732 cm_return_val_if_fail(msginfo != NULL, NULL);
1733 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
1735 if (!account && !(account = compose_find_account(msginfo)))
1736 account = cur_account;
1738 if (!prefs_common.forward_as_attachment)
1739 mode = COMPOSE_FORWARD_INLINE;
1741 mode = COMPOSE_FORWARD;
1742 compose = compose_create(account, msginfo->folder, mode, batch);
1744 compose->updating = TRUE;
1745 compose->fwdinfo = procmsg_msginfo_get_full_info(msginfo);
1746 if (!compose->fwdinfo)
1747 compose->fwdinfo = procmsg_msginfo_copy(msginfo);
1749 compose_extract_original_charset(compose);
1751 if (msginfo->subject && *msginfo->subject) {
1752 gchar *buf, *buf2, *p;
1754 buf = p = g_strdup(msginfo->subject);
1755 p += subject_get_prefix_length(p);
1756 memmove(buf, p, strlen(p) + 1);
1758 buf2 = g_strdup_printf("Fw: %s", buf);
1759 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
1765 /* override from name according to folder properties */
1766 if (msginfo->folder && msginfo->folder->prefs &&
1767 msginfo->folder->prefs->forward_with_format &&
1768 msginfo->folder->prefs->forward_override_from_format &&
1769 *msginfo->folder->prefs->forward_override_from_format != '\0') {
1773 MsgInfo *full_msginfo = NULL;
1776 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1778 full_msginfo = procmsg_msginfo_copy(msginfo);
1780 /* decode \-escape sequences in the internal representation of the quote format */
1781 tmp = g_malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
1782 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
1785 gtkaspell_block_check(compose->gtkaspell);
1786 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE,
1787 compose->gtkaspell);
1789 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE);
1791 quote_fmt_scan_string(tmp);
1794 buf = quote_fmt_get_buffer();
1796 alertpanel_error(_("The \"From\" field of the \"Forward\" template contains an invalid email address."));
1798 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1799 quote_fmt_reset_vartable();
1800 quote_fmtlex_destroy();
1803 procmsg_msginfo_free(&full_msginfo);
1806 textview = GTK_TEXT_VIEW(compose->text);
1807 textbuf = gtk_text_view_get_buffer(textview);
1808 compose_create_tags(textview, compose);
1810 undo_block(compose->undostruct);
1814 msgfile = procmsg_get_message_file(msginfo);
1815 if (!is_file_exist(msgfile))
1816 g_warning("%s: file does not exist", msgfile);
1818 compose_attach_append(compose, msgfile, msgfile,
1819 "message/rfc822", NULL);
1823 const gchar *qmark = NULL;
1824 const gchar *body_fmt = NULL;
1825 MsgInfo *full_msginfo;
1827 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1829 full_msginfo = procmsg_msginfo_copy(msginfo);
1831 /* use the forward format of folder (if enabled), or the account's one
1832 (if enabled) or fallback to the global forward format, which is always
1833 enabled (even if empty), and use the relevant quotemark */
1834 if (msginfo->folder && msginfo->folder->prefs &&
1835 msginfo->folder->prefs->forward_with_format) {
1836 qmark = msginfo->folder->prefs->forward_quotemark;
1837 body_fmt = msginfo->folder->prefs->forward_body_format;
1839 } else if (account->forward_with_format) {
1840 qmark = account->forward_quotemark;
1841 body_fmt = account->forward_body_format;
1844 qmark = prefs_common.fw_quotemark;
1845 if (prefs_common.fw_quotefmt && *prefs_common.fw_quotefmt)
1846 body_fmt = gettext(prefs_common.fw_quotefmt);
1851 /* empty quotemark is not allowed */
1852 if (qmark == NULL || *qmark == '\0')
1855 compose_quote_fmt(compose, full_msginfo,
1856 body_fmt, qmark, body, FALSE, TRUE,
1857 _("The body of the \"Forward\" template has an error at line %d."));
1858 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1859 quote_fmt_reset_vartable();
1860 compose_attach_parts(compose, msginfo);
1862 procmsg_msginfo_free(&full_msginfo);
1865 SIGNAL_BLOCK(textbuf);
1867 if (account->auto_sig)
1868 compose_insert_sig(compose, FALSE);
1870 compose_wrap_all(compose);
1873 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1874 gtkaspell_highlight_all(compose->gtkaspell);
1875 gtkaspell_unblock_check(compose->gtkaspell);
1877 SIGNAL_UNBLOCK(textbuf);
1879 cursor_pos = quote_fmt_get_cursor_pos();
1880 if (cursor_pos == -1)
1881 gtk_widget_grab_focus(compose->header_last->entry);
1883 gtk_widget_grab_focus(compose->text);
1885 if (!no_extedit && prefs_common.auto_exteditor)
1886 compose_exec_ext_editor(compose);
1889 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1890 gchar *folderidentifier;
1892 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1893 folderidentifier = folder_item_get_identifier(msginfo->folder);
1894 compose_set_save_to(compose, folderidentifier);
1895 g_free(folderidentifier);
1898 undo_unblock(compose->undostruct);
1900 compose->modified = FALSE;
1901 compose_set_title(compose);
1903 compose->updating = FALSE;
1904 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
1905 SCROLL_TO_CURSOR(compose);
1907 if (compose->deferred_destroy) {
1908 compose_destroy(compose);
1912 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
1917 #undef INSERT_FW_HEADER
1919 static Compose *compose_forward_multiple(PrefsAccount *account, GSList *msginfo_list)
1922 GtkTextView *textview;
1923 GtkTextBuffer *textbuf;
1927 gboolean single_mail = TRUE;
1929 cm_return_val_if_fail(msginfo_list != NULL, NULL);
1931 if (g_slist_length(msginfo_list) > 1)
1932 single_mail = FALSE;
1934 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next)
1935 if (((MsgInfo *)msginfo->data)->folder == NULL)
1938 /* guess account from first selected message */
1940 !(account = compose_find_account(msginfo_list->data)))
1941 account = cur_account;
1943 cm_return_val_if_fail(account != NULL, NULL);
1945 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
1946 if (msginfo->data) {
1947 MSG_UNSET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_REPLIED);
1948 MSG_SET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_FORWARDED);
1952 if (msginfo_list == NULL || msginfo_list->data == NULL) {
1953 g_warning("no msginfo_list");
1957 compose = compose_create(account, ((MsgInfo *)msginfo_list->data)->folder, COMPOSE_FORWARD, FALSE);
1959 compose->updating = TRUE;
1961 /* override from name according to folder properties */
1962 if (msginfo_list->data) {
1963 MsgInfo *msginfo = msginfo_list->data;
1965 if (msginfo->folder && msginfo->folder->prefs &&
1966 msginfo->folder->prefs->forward_with_format &&
1967 msginfo->folder->prefs->forward_override_from_format &&
1968 *msginfo->folder->prefs->forward_override_from_format != '\0') {
1973 /* decode \-escape sequences in the internal representation of the quote format */
1974 tmp = g_malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
1975 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
1978 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
1979 compose->gtkaspell);
1981 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
1983 quote_fmt_scan_string(tmp);
1986 buf = quote_fmt_get_buffer();
1988 alertpanel_error(_("The \"From\" field of the \"Forward\" template contains an invalid email address."));
1990 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1991 quote_fmt_reset_vartable();
1992 quote_fmtlex_destroy();
1998 textview = GTK_TEXT_VIEW(compose->text);
1999 textbuf = gtk_text_view_get_buffer(textview);
2000 compose_create_tags(textview, compose);
2002 undo_block(compose->undostruct);
2003 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
2004 msgfile = procmsg_get_message_file((MsgInfo *)msginfo->data);
2006 if (!is_file_exist(msgfile))
2007 g_warning("%s: file does not exist", msgfile);
2009 compose_attach_append(compose, msgfile, msgfile,
2010 "message/rfc822", NULL);
2015 MsgInfo *info = (MsgInfo *)msginfo_list->data;
2016 if (info->subject && *info->subject) {
2017 gchar *buf, *buf2, *p;
2019 buf = p = g_strdup(info->subject);
2020 p += subject_get_prefix_length(p);
2021 memmove(buf, p, strlen(p) + 1);
2023 buf2 = g_strdup_printf("Fw: %s", buf);
2024 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
2030 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2031 _("Fw: multiple emails"));
2034 SIGNAL_BLOCK(textbuf);
2036 if (account->auto_sig)
2037 compose_insert_sig(compose, FALSE);
2039 compose_wrap_all(compose);
2041 SIGNAL_UNBLOCK(textbuf);
2043 gtk_text_buffer_get_start_iter(textbuf, &iter);
2044 gtk_text_buffer_place_cursor(textbuf, &iter);
2046 if (prefs_common.auto_exteditor)
2047 compose_exec_ext_editor(compose);
2049 gtk_widget_grab_focus(compose->header_last->entry);
2050 undo_unblock(compose->undostruct);
2051 compose->modified = FALSE;
2052 compose_set_title(compose);
2054 compose->updating = FALSE;
2055 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2056 SCROLL_TO_CURSOR(compose);
2058 if (compose->deferred_destroy) {
2059 compose_destroy(compose);
2063 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2068 static gboolean compose_is_sig_separator(Compose *compose, GtkTextBuffer *textbuf, GtkTextIter *iter)
2070 GtkTextIter start = *iter;
2071 GtkTextIter end_iter;
2072 int start_pos = gtk_text_iter_get_offset(&start);
2074 if (!compose->account->sig_sep)
2077 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
2078 start_pos+strlen(compose->account->sig_sep));
2080 /* check sig separator */
2081 str = gtk_text_iter_get_text(&start, &end_iter);
2082 if (!strcmp(str, compose->account->sig_sep)) {
2084 /* check end of line (\n) */
2085 gtk_text_buffer_get_iter_at_offset(textbuf, &start,
2086 start_pos+strlen(compose->account->sig_sep));
2087 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
2088 start_pos+strlen(compose->account->sig_sep)+1);
2089 tmp = gtk_text_iter_get_text(&start, &end_iter);
2090 if (!strcmp(tmp,"\n")) {
2102 static gboolean compose_update_folder_hook(gpointer source, gpointer data)
2104 FolderUpdateData *hookdata = (FolderUpdateData *)source;
2105 Compose *compose = (Compose *)data;
2106 FolderItem *old_item = NULL;
2107 FolderItem *new_item = NULL;
2108 gchar *old_id, *new_id;
2110 if (!(hookdata->update_flags & FOLDER_REMOVE_FOLDERITEM)
2111 && !(hookdata->update_flags & FOLDER_MOVE_FOLDERITEM))
2114 old_item = hookdata->item;
2115 new_item = hookdata->item2;
2117 old_id = folder_item_get_identifier(old_item);
2118 new_id = new_item ? folder_item_get_identifier(new_item) : g_strdup("NULL");
2120 if (compose->targetinfo && compose->targetinfo->folder == old_item) {
2121 debug_print("updating targetinfo folder: %s -> %s\n", old_id, new_id);
2122 compose->targetinfo->folder = new_item;
2125 if (compose->replyinfo && compose->replyinfo->folder == old_item) {
2126 debug_print("updating replyinfo folder: %s -> %s\n", old_id, new_id);
2127 compose->replyinfo->folder = new_item;
2130 if (compose->fwdinfo && compose->fwdinfo->folder == old_item) {
2131 debug_print("updating fwdinfo folder: %s -> %s\n", old_id, new_id);
2132 compose->fwdinfo->folder = new_item;
2140 static void compose_colorize_signature(Compose *compose)
2142 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
2144 GtkTextIter end_iter;
2145 gtk_text_buffer_get_start_iter(buffer, &iter);
2146 while (gtk_text_iter_forward_line(&iter))
2147 if (compose_is_sig_separator(compose, buffer, &iter)) {
2148 gtk_text_buffer_get_end_iter(buffer, &end_iter);
2149 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &end_iter);
2153 #define BLOCK_WRAP() { \
2154 prev_autowrap = compose->autowrap; \
2155 buffer = gtk_text_view_get_buffer( \
2156 GTK_TEXT_VIEW(compose->text)); \
2157 compose->autowrap = FALSE; \
2159 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2160 G_CALLBACK(compose_changed_cb), \
2162 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2163 G_CALLBACK(text_inserted), \
2166 #define UNBLOCK_WRAP() { \
2167 compose->autowrap = prev_autowrap; \
2168 if (compose->autowrap) { \
2169 gint old = compose->draft_timeout_tag; \
2170 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN; \
2171 compose_wrap_all(compose); \
2172 compose->draft_timeout_tag = old; \
2175 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2176 G_CALLBACK(compose_changed_cb), \
2178 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2179 G_CALLBACK(text_inserted), \
2183 Compose *compose_reedit(MsgInfo *msginfo, gboolean batch)
2185 Compose *compose = NULL;
2186 PrefsAccount *account = NULL;
2187 GtkTextView *textview;
2188 GtkTextBuffer *textbuf;
2192 gboolean use_signing = FALSE;
2193 gboolean use_encryption = FALSE;
2194 gchar *privacy_system = NULL;
2195 int priority = PRIORITY_NORMAL;
2196 MsgInfo *replyinfo = NULL, *fwdinfo = NULL;
2197 gboolean autowrap = prefs_common.autowrap;
2198 gboolean autoindent = prefs_common.auto_indent;
2199 HeaderEntry *manual_headers = NULL;
2201 cm_return_val_if_fail(msginfo != NULL, NULL);
2202 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
2204 if (compose_put_existing_to_front(msginfo)) {
2208 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2209 folder_has_parent_of_type(msginfo->folder, F_DRAFT) ||
2210 folder_has_parent_of_type(msginfo->folder, F_OUTBOX)) {
2211 gchar *queueheader_buf = NULL;
2214 /* Select Account from queue headers */
2215 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2216 "X-Claws-Account-Id:")) {
2217 id = atoi(&queueheader_buf[strlen("X-Claws-Account-Id:")]);
2218 account = account_find_from_id(id);
2219 g_free(queueheader_buf);
2221 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2222 "X-Sylpheed-Account-Id:")) {
2223 id = atoi(&queueheader_buf[strlen("X-Sylpheed-Account-Id:")]);
2224 account = account_find_from_id(id);
2225 g_free(queueheader_buf);
2227 if (!account && !procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2229 id = atoi(&queueheader_buf[strlen("NAID:")]);
2230 account = account_find_from_id(id);
2231 g_free(queueheader_buf);
2233 if (!account && !procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2235 id = atoi(&queueheader_buf[strlen("MAID:")]);
2236 account = account_find_from_id(id);
2237 g_free(queueheader_buf);
2239 if (!account && !procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2241 account = account_find_from_address(queueheader_buf, FALSE);
2242 g_free(queueheader_buf);
2244 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2246 param = atoi(&queueheader_buf[strlen("X-Claws-Sign:")]);
2247 use_signing = param;
2248 g_free(queueheader_buf);
2250 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2251 "X-Sylpheed-Sign:")) {
2252 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Sign:")]);
2253 use_signing = param;
2254 g_free(queueheader_buf);
2256 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2257 "X-Claws-Encrypt:")) {
2258 param = atoi(&queueheader_buf[strlen("X-Claws-Encrypt:")]);
2259 use_encryption = param;
2260 g_free(queueheader_buf);
2262 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2263 "X-Sylpheed-Encrypt:")) {
2264 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Encrypt:")]);
2265 use_encryption = param;
2266 g_free(queueheader_buf);
2268 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2269 "X-Claws-Auto-Wrapping:")) {
2270 param = atoi(&queueheader_buf[strlen("X-Claws-Auto-Wrapping:")]);
2272 g_free(queueheader_buf);
2274 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2275 "X-Claws-Auto-Indent:")) {
2276 param = atoi(&queueheader_buf[strlen("X-Claws-Auto-Indent:")]);
2278 g_free(queueheader_buf);
2280 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2281 "X-Claws-Privacy-System:")) {
2282 privacy_system = g_strdup(&queueheader_buf[strlen("X-Claws-Privacy-System:")]);
2283 g_free(queueheader_buf);
2285 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2286 "X-Sylpheed-Privacy-System:")) {
2287 privacy_system = g_strdup(&queueheader_buf[strlen("X-Sylpheed-Privacy-System:")]);
2288 g_free(queueheader_buf);
2290 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2292 param = atoi(&queueheader_buf[strlen("X-Priority: ")]); /* mind the space */
2294 g_free(queueheader_buf);
2296 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2298 gchar **tokens = g_strsplit(&queueheader_buf[strlen("RMID:")], "\t", 0);
2299 if (tokens && tokens[0] && tokens[1] && tokens[2]) {
2300 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2301 if (orig_item != NULL) {
2302 replyinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2306 g_free(queueheader_buf);
2308 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2310 gchar **tokens = g_strsplit(&queueheader_buf[strlen("FMID:")], "\t", 0);
2311 if (tokens && tokens[0] && tokens[1] && tokens[2]) {
2312 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2313 if (orig_item != NULL) {
2314 fwdinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2318 g_free(queueheader_buf);
2320 /* Get manual headers */
2321 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2322 "X-Claws-Manual-Headers:")) {
2323 gchar *listmh = g_strdup(&queueheader_buf[strlen("X-Claws-Manual-Headers:")]);
2324 if (listmh && *listmh != '\0') {
2325 debug_print("Got manual headers: %s\n", listmh);
2326 manual_headers = procheader_entries_from_str(listmh);
2329 g_free(queueheader_buf);
2332 account = msginfo->folder->folder->account;
2335 if (!account && prefs_common.reedit_account_autosel) {
2337 if (!procheader_get_header_from_msginfo(msginfo, &from, "FROM:")) {
2338 extract_address(from);
2339 account = account_find_from_address(from, FALSE);
2344 account = cur_account;
2346 cm_return_val_if_fail(account != NULL, NULL);
2348 compose = compose_create(account, msginfo->folder, COMPOSE_REEDIT, batch);
2350 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", autowrap);
2351 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", autoindent);
2352 compose->autowrap = autowrap;
2353 compose->replyinfo = replyinfo;
2354 compose->fwdinfo = fwdinfo;
2356 compose->updating = TRUE;
2357 compose->priority = priority;
2359 if (privacy_system != NULL) {
2360 compose->privacy_system = privacy_system;
2361 compose_use_signing(compose, use_signing);
2362 compose_use_encryption(compose, use_encryption);
2363 compose_update_privacy_system_menu_item(compose, FALSE);
2365 activate_privacy_system(compose, account, FALSE);
2368 compose->targetinfo = procmsg_msginfo_copy(msginfo);
2370 compose_extract_original_charset(compose);
2372 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2373 folder_has_parent_of_type(msginfo->folder, F_DRAFT) ||
2374 folder_has_parent_of_type(msginfo->folder, F_OUTBOX)) {
2375 gchar *queueheader_buf = NULL;
2377 /* Set message save folder */
2378 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf, "SCF:")) {
2379 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
2380 compose_set_save_to(compose, &queueheader_buf[4]);
2381 g_free(queueheader_buf);
2383 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf, "RRCPT:")) {
2384 gint active = atoi(&queueheader_buf[strlen("RRCPT:")]);
2386 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
2388 g_free(queueheader_buf);
2392 if (compose_parse_header(compose, msginfo) < 0) {
2393 compose->updating = FALSE;
2394 compose_destroy(compose);
2397 compose_reedit_set_entry(compose, msginfo);
2399 textview = GTK_TEXT_VIEW(compose->text);
2400 textbuf = gtk_text_view_get_buffer(textview);
2401 compose_create_tags(textview, compose);
2403 mark = gtk_text_buffer_get_insert(textbuf);
2404 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
2406 g_signal_handlers_block_by_func(G_OBJECT(textbuf),
2407 G_CALLBACK(compose_changed_cb),
2410 if (MSG_IS_ENCRYPTED(msginfo->flags)) {
2411 fp = procmime_get_first_encrypted_text_content(msginfo);
2413 compose_force_encryption(compose, account, TRUE, NULL);
2416 fp = procmime_get_first_text_content(msginfo);
2419 g_warning("Can't get text part");
2423 gchar buf[BUFFSIZE];
2424 gboolean prev_autowrap;
2425 GtkTextBuffer *buffer;
2427 while (fgets(buf, sizeof(buf), fp) != NULL) {
2429 gtk_text_buffer_insert(textbuf, &iter, buf, -1);
2435 compose_attach_parts(compose, msginfo);
2437 compose_colorize_signature(compose);
2439 g_signal_handlers_unblock_by_func(G_OBJECT(textbuf),
2440 G_CALLBACK(compose_changed_cb),
2443 if (manual_headers != NULL) {
2444 if (compose_parse_manual_headers(compose, msginfo, manual_headers) < 0) {
2445 procheader_entries_free(manual_headers);
2446 compose->updating = FALSE;
2447 compose_destroy(compose);
2450 procheader_entries_free(manual_headers);
2453 gtk_widget_grab_focus(compose->text);
2455 if (prefs_common.auto_exteditor) {
2456 compose_exec_ext_editor(compose);
2458 compose->modified = FALSE;
2459 compose_set_title(compose);
2461 compose->updating = FALSE;
2462 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2463 SCROLL_TO_CURSOR(compose);
2465 if (compose->deferred_destroy) {
2466 compose_destroy(compose);
2470 compose->sig_str = account_get_signature_str(compose->account);
2472 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2477 Compose *compose_redirect(PrefsAccount *account, MsgInfo *msginfo,
2484 cm_return_val_if_fail(msginfo != NULL, NULL);
2487 account = account_get_reply_account(msginfo,
2488 prefs_common.reply_account_autosel);
2489 cm_return_val_if_fail(account != NULL, NULL);
2491 compose = compose_create(account, msginfo->folder, COMPOSE_REDIRECT, batch);
2493 compose->updating = TRUE;
2495 compose_create_tags(GTK_TEXT_VIEW(compose->text), compose);
2496 compose->replyinfo = NULL;
2497 compose->fwdinfo = NULL;
2499 compose_show_first_last_header(compose, TRUE);
2501 gtk_widget_grab_focus(compose->header_last->entry);
2503 filename = procmsg_get_message_file(msginfo);
2505 if (filename == NULL) {
2506 compose->updating = FALSE;
2507 compose_destroy(compose);
2512 compose->redirect_filename = filename;
2514 /* Set save folder */
2515 item = msginfo->folder;
2516 if (item && item->prefs && item->prefs->save_copy_to_folder) {
2517 gchar *folderidentifier;
2519 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
2520 folderidentifier = folder_item_get_identifier(item);
2521 compose_set_save_to(compose, folderidentifier);
2522 g_free(folderidentifier);
2525 compose_attach_parts(compose, msginfo);
2527 if (msginfo->subject)
2528 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2530 gtk_editable_set_editable(GTK_EDITABLE(compose->subject_entry), FALSE);
2532 compose_quote_fmt(compose, msginfo, "%M", NULL, NULL, FALSE, FALSE,
2533 _("The body of the \"Redirect\" template has an error at line %d."));
2534 quote_fmt_reset_vartable();
2535 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), FALSE);
2537 compose_colorize_signature(compose);
2540 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Add", FALSE);
2541 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", FALSE);
2542 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", FALSE);
2544 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/Save", FALSE);
2545 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertFile", FALSE);
2546 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/AttachFile", FALSE);
2547 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertSig", FALSE);
2548 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/ReplaceSig", FALSE);
2549 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", FALSE);
2550 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", FALSE);
2551 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/ShowRuler", FALSE);
2552 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Actions", FALSE);
2554 if (compose->toolbar->draft_btn)
2555 gtk_widget_set_sensitive(compose->toolbar->draft_btn, FALSE);
2556 if (compose->toolbar->insert_btn)
2557 gtk_widget_set_sensitive(compose->toolbar->insert_btn, FALSE);
2558 if (compose->toolbar->attach_btn)
2559 gtk_widget_set_sensitive(compose->toolbar->attach_btn, FALSE);
2560 if (compose->toolbar->sig_btn)
2561 gtk_widget_set_sensitive(compose->toolbar->sig_btn, FALSE);
2562 if (compose->toolbar->exteditor_btn)
2563 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, FALSE);
2564 if (compose->toolbar->linewrap_current_btn)
2565 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, FALSE);
2566 if (compose->toolbar->linewrap_all_btn)
2567 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, FALSE);
2569 compose->modified = FALSE;
2570 compose_set_title(compose);
2571 compose->updating = FALSE;
2572 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2573 SCROLL_TO_CURSOR(compose);
2575 if (compose->deferred_destroy) {
2576 compose_destroy(compose);
2580 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2585 const GList *compose_get_compose_list(void)
2587 return compose_list;
2590 void compose_entry_append(Compose *compose, const gchar *address,
2591 ComposeEntryType type, ComposePrefType pref_type)
2593 const gchar *header;
2595 gboolean in_quote = FALSE;
2596 if (!address || *address == '\0') return;
2603 header = N_("Bcc:");
2605 case COMPOSE_REPLYTO:
2606 header = N_("Reply-To:");
2608 case COMPOSE_NEWSGROUPS:
2609 header = N_("Newsgroups:");
2611 case COMPOSE_FOLLOWUPTO:
2612 header = N_( "Followup-To:");
2614 case COMPOSE_INREPLYTO:
2615 header = N_( "In-Reply-To:");
2622 header = prefs_common_translated_header_name(header);
2624 cur = begin = (gchar *)address;
2626 /* we separate the line by commas, but not if we're inside a quoted
2628 while (*cur != '\0') {
2630 in_quote = !in_quote;
2631 if (*cur == ',' && !in_quote) {
2632 gchar *tmp = g_strdup(begin);
2634 tmp[cur-begin]='\0';
2637 while (*tmp == ' ' || *tmp == '\t')
2639 compose_add_header_entry(compose, header, tmp, pref_type);
2640 compose_entry_indicate(compose, tmp);
2647 gchar *tmp = g_strdup(begin);
2649 tmp[cur-begin]='\0';
2650 while (*tmp == ' ' || *tmp == '\t')
2652 compose_add_header_entry(compose, header, tmp, pref_type);
2653 compose_entry_indicate(compose, tmp);
2658 static void compose_entry_indicate(Compose *compose, const gchar *mailto)
2663 for (h_list = compose->header_list; h_list != NULL; h_list = h_list->next) {
2664 entry = GTK_ENTRY(((ComposeHeaderEntry *)h_list->data)->entry);
2665 if (gtk_entry_get_text(entry) &&
2666 !g_utf8_collate(gtk_entry_get_text(entry), mailto)) {
2667 gtk_widget_modify_base(
2668 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2669 GTK_STATE_NORMAL, &default_header_bgcolor);
2670 gtk_widget_modify_text(
2671 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2672 GTK_STATE_NORMAL, &default_header_color);
2677 void compose_toolbar_cb(gint action, gpointer data)
2679 ToolbarItem *toolbar_item = (ToolbarItem*)data;
2680 Compose *compose = (Compose*)toolbar_item->parent;
2682 cm_return_if_fail(compose != NULL);
2686 compose_send_cb(NULL, compose);
2689 compose_send_later_cb(NULL, compose);
2692 compose_draft(compose, COMPOSE_QUIT_EDITING);
2695 compose_insert_file_cb(NULL, compose);
2698 compose_attach_cb(NULL, compose);
2701 compose_insert_sig(compose, FALSE);
2704 compose_insert_sig(compose, TRUE);
2707 compose_ext_editor_cb(NULL, compose);
2709 case A_LINEWRAP_CURRENT:
2710 compose_beautify_paragraph(compose, NULL, TRUE);
2712 case A_LINEWRAP_ALL:
2713 compose_wrap_all_full(compose, TRUE);
2716 compose_address_cb(NULL, compose);
2719 case A_CHECK_SPELLING:
2720 compose_check_all(NULL, compose);
2723 case A_PRIVACY_SIGN:
2725 case A_PRIVACY_ENCRYPT:
2732 static MailField compose_entries_set(Compose *compose, const gchar *mailto, ComposeEntryType to_type)
2737 gchar *subject = NULL;
2741 gchar **attach = NULL;
2742 gchar *inreplyto = NULL;
2743 MailField mfield = NO_FIELD_PRESENT;
2745 /* get mailto parts but skip from */
2746 scan_mailto_url(mailto, NULL, &to, &cc, &bcc, &subject, &body, &attach, &inreplyto);
2749 compose_entry_append(compose, to, to_type, PREF_MAILTO);
2750 mfield = TO_FIELD_PRESENT;
2753 compose_entry_append(compose, cc, COMPOSE_CC, PREF_MAILTO);
2755 compose_entry_append(compose, bcc, COMPOSE_BCC, PREF_MAILTO);
2757 if (!g_utf8_validate (subject, -1, NULL)) {
2758 temp = g_locale_to_utf8 (subject, -1, NULL, &len, NULL);
2759 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), temp);
2762 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), subject);
2764 mfield = SUBJECT_FIELD_PRESENT;
2767 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2768 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2771 gboolean prev_autowrap = compose->autowrap;
2773 compose->autowrap = FALSE;
2775 mark = gtk_text_buffer_get_insert(buffer);
2776 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2778 if (!g_utf8_validate (body, -1, NULL)) {
2779 temp = g_locale_to_utf8 (body, -1, NULL, &len, NULL);
2780 gtk_text_buffer_insert(buffer, &iter, temp, -1);
2783 gtk_text_buffer_insert(buffer, &iter, body, -1);
2785 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
2787 compose->autowrap = prev_autowrap;
2788 if (compose->autowrap)
2789 compose_wrap_all(compose);
2790 mfield = BODY_FIELD_PRESENT;
2794 gint i = 0, att = 0;
2795 gchar *warn_files = NULL;
2796 while (attach[i] != NULL) {
2797 gchar *utf8_filename = conv_filename_to_utf8(attach[i]);
2798 if (utf8_filename) {
2799 if (compose_attach_append(compose, attach[i], utf8_filename, NULL, NULL)) {
2800 gchar *tmp = g_strdup_printf("%s%s\n",
2801 warn_files?warn_files:"",
2807 g_free(utf8_filename);
2809 alertpanel_error(_("Couldn't attach a file (charset conversion failed)."));
2814 alertpanel_notice(ngettext(
2815 "The following file has been attached: \n%s",
2816 "The following files have been attached: \n%s", att), warn_files);
2821 compose_entry_append(compose, inreplyto, COMPOSE_INREPLYTO, PREF_MAILTO);
2834 static gint compose_parse_header(Compose *compose, MsgInfo *msginfo)
2836 static HeaderEntry hentry[] = {
2837 {"Reply-To:", NULL, TRUE },
2838 {"Cc:", NULL, TRUE },
2839 {"References:", NULL, FALSE },
2840 {"Bcc:", NULL, TRUE },
2841 {"Newsgroups:", NULL, TRUE },
2842 {"Followup-To:", NULL, TRUE },
2843 {"List-Post:", NULL, FALSE },
2844 {"X-Priority:", NULL, FALSE },
2845 {NULL, NULL, FALSE }
2862 cm_return_val_if_fail(msginfo != NULL, -1);
2864 if ((fp = procmsg_open_message(msginfo)) == NULL) return -1;
2865 procheader_get_header_fields(fp, hentry);
2868 if (hentry[H_REPLY_TO].body != NULL) {
2869 if (hentry[H_REPLY_TO].body[0] != '\0') {
2871 conv_unmime_header(hentry[H_REPLY_TO].body,
2874 g_free(hentry[H_REPLY_TO].body);
2875 hentry[H_REPLY_TO].body = NULL;
2877 if (hentry[H_CC].body != NULL) {
2878 compose->cc = conv_unmime_header(hentry[H_CC].body, NULL, TRUE);
2879 g_free(hentry[H_CC].body);
2880 hentry[H_CC].body = NULL;
2882 if (hentry[H_REFERENCES].body != NULL) {
2883 if (compose->mode == COMPOSE_REEDIT)
2884 compose->references = hentry[H_REFERENCES].body;
2886 compose->references = compose_parse_references
2887 (hentry[H_REFERENCES].body, msginfo->msgid);
2888 g_free(hentry[H_REFERENCES].body);
2890 hentry[H_REFERENCES].body = NULL;
2892 if (hentry[H_BCC].body != NULL) {
2893 if (compose->mode == COMPOSE_REEDIT)
2895 conv_unmime_header(hentry[H_BCC].body, NULL, TRUE);
2896 g_free(hentry[H_BCC].body);
2897 hentry[H_BCC].body = NULL;
2899 if (hentry[H_NEWSGROUPS].body != NULL) {
2900 compose->newsgroups = hentry[H_NEWSGROUPS].body;
2901 hentry[H_NEWSGROUPS].body = NULL;
2903 if (hentry[H_FOLLOWUP_TO].body != NULL) {
2904 if (hentry[H_FOLLOWUP_TO].body[0] != '\0') {
2905 compose->followup_to =
2906 conv_unmime_header(hentry[H_FOLLOWUP_TO].body,
2909 g_free(hentry[H_FOLLOWUP_TO].body);
2910 hentry[H_FOLLOWUP_TO].body = NULL;
2912 if (hentry[H_LIST_POST].body != NULL) {
2913 gchar *to = NULL, *start = NULL;
2915 extract_address(hentry[H_LIST_POST].body);
2916 if (hentry[H_LIST_POST].body[0] != '\0') {
2917 start = strstr(hentry[H_LIST_POST].body, "mailto:");
2919 scan_mailto_url(start ? start : hentry[H_LIST_POST].body,
2920 NULL, &to, NULL, NULL, NULL, NULL, NULL, NULL);
2923 g_free(compose->ml_post);
2924 compose->ml_post = to;
2927 g_free(hentry[H_LIST_POST].body);
2928 hentry[H_LIST_POST].body = NULL;
2931 /* CLAWS - X-Priority */
2932 if (compose->mode == COMPOSE_REEDIT)
2933 if (hentry[H_X_PRIORITY].body != NULL) {
2936 priority = atoi(hentry[H_X_PRIORITY].body);
2937 g_free(hentry[H_X_PRIORITY].body);
2939 hentry[H_X_PRIORITY].body = NULL;
2941 if (priority < PRIORITY_HIGHEST ||
2942 priority > PRIORITY_LOWEST)
2943 priority = PRIORITY_NORMAL;
2945 compose->priority = priority;
2948 if (compose->mode == COMPOSE_REEDIT) {
2949 if (msginfo->inreplyto && *msginfo->inreplyto)
2950 compose->inreplyto = g_strdup(msginfo->inreplyto);
2952 if (msginfo->msgid && *msginfo->msgid &&
2953 compose->folder != NULL &&
2954 compose->folder->stype == F_DRAFT)
2955 compose->msgid = g_strdup(msginfo->msgid);
2957 if (msginfo->msgid && *msginfo->msgid)
2958 compose->inreplyto = g_strdup(msginfo->msgid);
2960 if (!compose->references) {
2961 if (msginfo->msgid && *msginfo->msgid) {
2962 if (msginfo->inreplyto && *msginfo->inreplyto)
2963 compose->references =
2964 g_strdup_printf("<%s>\n\t<%s>",
2968 compose->references =
2969 g_strconcat("<", msginfo->msgid, ">",
2971 } else if (msginfo->inreplyto && *msginfo->inreplyto) {
2972 compose->references =
2973 g_strconcat("<", msginfo->inreplyto, ">",
2982 static gint compose_parse_manual_headers(Compose *compose, MsgInfo *msginfo, HeaderEntry *entries)
2987 cm_return_val_if_fail(msginfo != NULL, -1);
2989 if ((fp = procmsg_open_message(msginfo)) == NULL) return -1;
2990 procheader_get_header_fields(fp, entries);
2994 while (he != NULL && he->name != NULL) {
2996 GtkListStore *model = NULL;
2998 debug_print("Adding manual header: %s with value %s\n", he->name, he->body);
2999 model = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(compose->header_last->combo)));
3000 COMBOBOX_ADD(model, he->name, COMPOSE_TO);
3001 gtk_combo_box_set_active_iter(GTK_COMBO_BOX(compose->header_last->combo), &iter);
3002 gtk_entry_set_text(GTK_ENTRY(compose->header_last->entry), he->body);
3009 static gchar *compose_parse_references(const gchar *ref, const gchar *msgid)
3011 GSList *ref_id_list, *cur;
3015 ref_id_list = references_list_append(NULL, ref);
3016 if (!ref_id_list) return NULL;
3017 if (msgid && *msgid)
3018 ref_id_list = g_slist_append(ref_id_list, g_strdup(msgid));
3023 for (cur = ref_id_list; cur != NULL; cur = cur->next)
3024 /* "<" + Message-ID + ">" + CR+LF+TAB */
3025 len += strlen((gchar *)cur->data) + 5;
3027 if (len > MAX_REFERENCES_LEN) {
3028 /* remove second message-ID */
3029 if (ref_id_list && ref_id_list->next &&
3030 ref_id_list->next->next) {
3031 g_free(ref_id_list->next->data);
3032 ref_id_list = g_slist_remove
3033 (ref_id_list, ref_id_list->next->data);
3035 slist_free_strings_full(ref_id_list);
3042 new_ref = g_string_new("");
3043 for (cur = ref_id_list; cur != NULL; cur = cur->next) {
3044 if (new_ref->len > 0)
3045 g_string_append(new_ref, "\n\t");
3046 g_string_append_printf(new_ref, "<%s>", (gchar *)cur->data);
3049 slist_free_strings_full(ref_id_list);
3051 new_ref_str = new_ref->str;
3052 g_string_free(new_ref, FALSE);
3057 static gchar *compose_quote_fmt(Compose *compose, MsgInfo *msginfo,
3058 const gchar *fmt, const gchar *qmark,
3059 const gchar *body, gboolean rewrap,
3060 gboolean need_unescape,
3061 const gchar *err_msg)
3063 MsgInfo* dummyinfo = NULL;
3064 gchar *quote_str = NULL;
3066 gboolean prev_autowrap;
3067 const gchar *trimmed_body = body;
3068 gint cursor_pos = -1;
3069 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3070 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3075 SIGNAL_BLOCK(buffer);
3078 dummyinfo = compose_msginfo_new_from_compose(compose);
3079 msginfo = dummyinfo;
3082 if (qmark != NULL) {
3084 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
3085 compose->gtkaspell);
3087 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
3089 quote_fmt_scan_string(qmark);
3092 buf = quote_fmt_get_buffer();
3095 alertpanel_error(_("The \"Quotation mark\" of the template is invalid."));
3097 Xstrdup_a(quote_str, buf, goto error)
3100 if (fmt && *fmt != '\0') {
3103 while (*trimmed_body == '\n')
3107 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE,
3108 compose->gtkaspell);
3110 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE);
3112 if (need_unescape) {
3115 /* decode \-escape sequences in the internal representation of the quote format */
3116 tmp = g_malloc(strlen(fmt)+1);
3117 pref_get_unescaped_pref(tmp, fmt);
3118 quote_fmt_scan_string(tmp);
3122 quote_fmt_scan_string(fmt);
3126 buf = quote_fmt_get_buffer();
3129 gint line = quote_fmt_get_line();
3130 alertpanel_error(err_msg, line);
3138 prev_autowrap = compose->autowrap;
3139 compose->autowrap = FALSE;
3141 mark = gtk_text_buffer_get_insert(buffer);
3142 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3143 if (g_utf8_validate(buf, -1, NULL)) {
3144 gtk_text_buffer_insert(buffer, &iter, buf, -1);
3146 gchar *tmpout = NULL;
3147 tmpout = conv_codeset_strdup
3148 (buf, conv_get_locale_charset_str_no_utf8(),
3150 if (!tmpout || !g_utf8_validate(tmpout, -1, NULL)) {
3152 tmpout = g_malloc(strlen(buf)*2+1);
3153 conv_localetodisp(tmpout, strlen(buf)*2+1, buf);
3155 gtk_text_buffer_insert(buffer, &iter, tmpout, -1);
3159 cursor_pos = quote_fmt_get_cursor_pos();
3160 if (cursor_pos == -1)
3161 cursor_pos = gtk_text_iter_get_offset(&iter);
3162 compose->set_cursor_pos = cursor_pos;
3164 gtk_text_buffer_get_start_iter(buffer, &iter);
3165 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
3166 gtk_text_buffer_place_cursor(buffer, &iter);
3168 compose->autowrap = prev_autowrap;
3169 if (compose->autowrap && rewrap)
3170 compose_wrap_all(compose);
3177 SIGNAL_UNBLOCK(buffer);
3179 procmsg_msginfo_free( &dummyinfo );
3184 /* if ml_post is of type addr@host and from is of type
3185 * addr-anything@host, return TRUE
3187 static gboolean is_subscription(const gchar *ml_post, const gchar *from)
3189 gchar *left_ml = NULL;
3190 gchar *right_ml = NULL;
3191 gchar *left_from = NULL;
3192 gchar *right_from = NULL;
3193 gboolean result = FALSE;
3195 if (!ml_post || !from)
3198 left_ml = g_strdup(ml_post);
3199 if (strstr(left_ml, "@")) {
3200 right_ml = strstr(left_ml, "@")+1;
3201 *(strstr(left_ml, "@")) = '\0';
3204 left_from = g_strdup(from);
3205 if (strstr(left_from, "@")) {
3206 right_from = strstr(left_from, "@")+1;
3207 *(strstr(left_from, "@")) = '\0';
3210 if (right_ml && right_from
3211 && !strncmp(left_from, left_ml, strlen(left_ml))
3212 && !strcmp(right_from, right_ml)) {
3221 static void compose_set_folder_prefs(Compose *compose, FolderItem *folder,
3222 gboolean respect_default_to)
3226 if (!folder || !folder->prefs)
3229 if (respect_default_to && folder->prefs->enable_default_to) {
3230 compose_entry_append(compose, folder->prefs->default_to,
3231 COMPOSE_TO, PREF_FOLDER);
3232 compose_entry_indicate(compose, folder->prefs->default_to);
3234 if (folder->prefs->enable_default_cc) {
3235 compose_entry_append(compose, folder->prefs->default_cc,
3236 COMPOSE_CC, PREF_FOLDER);
3237 compose_entry_indicate(compose, folder->prefs->default_cc);
3239 if (folder->prefs->enable_default_bcc) {
3240 compose_entry_append(compose, folder->prefs->default_bcc,
3241 COMPOSE_BCC, PREF_FOLDER);
3242 compose_entry_indicate(compose, folder->prefs->default_bcc);
3244 if (folder->prefs->enable_default_replyto) {
3245 compose_entry_append(compose, folder->prefs->default_replyto,
3246 COMPOSE_REPLYTO, PREF_FOLDER);
3247 compose_entry_indicate(compose, folder->prefs->default_replyto);
3251 static void compose_reply_set_subject(Compose *compose, MsgInfo *msginfo)
3256 if (!compose || !msginfo)
3259 if (msginfo->subject && *msginfo->subject) {
3260 buf = p = g_strdup(msginfo->subject);
3261 p += subject_get_prefix_length(p);
3262 memmove(buf, p, strlen(p) + 1);
3264 buf2 = g_strdup_printf("Re: %s", buf);
3265 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
3270 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), "Re: ");
3273 static void compose_reply_set_entry(Compose *compose, MsgInfo *msginfo,
3274 gboolean to_all, gboolean to_ml,
3276 gboolean followup_and_reply_to)
3278 GSList *cc_list = NULL;
3281 gchar *replyto = NULL;
3282 gchar *ac_email = NULL;
3284 gboolean reply_to_ml = FALSE;
3285 gboolean default_reply_to = FALSE;
3287 cm_return_if_fail(compose->account != NULL);
3288 cm_return_if_fail(msginfo != NULL);
3290 reply_to_ml = to_ml && compose->ml_post;
3292 default_reply_to = msginfo->folder &&
3293 msginfo->folder->prefs->enable_default_reply_to;
3295 if (compose->account->protocol != A_NNTP) {
3296 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
3298 if (reply_to_ml && !default_reply_to) {
3300 gboolean is_subscr = is_subscription(compose->ml_post,
3303 /* normal answer to ml post with a reply-to */
3304 compose_entry_append(compose,
3306 COMPOSE_TO, PREF_ML);
3307 if (compose->replyto)
3308 compose_entry_append(compose,
3310 COMPOSE_CC, PREF_ML);
3312 /* answer to subscription confirmation */
3313 if (compose->replyto)
3314 compose_entry_append(compose,
3316 COMPOSE_TO, PREF_ML);
3317 else if (msginfo->from)
3318 compose_entry_append(compose,
3320 COMPOSE_TO, PREF_ML);
3323 else if (!(to_all || to_sender) && default_reply_to) {
3324 compose_entry_append(compose,
3325 msginfo->folder->prefs->default_reply_to,
3326 COMPOSE_TO, PREF_FOLDER);
3327 compose_entry_indicate(compose,
3328 msginfo->folder->prefs->default_reply_to);
3334 compose_entry_append(compose, msginfo->from,
3335 COMPOSE_TO, PREF_NONE);
3337 Xstrdup_a(tmp1, msginfo->from, return);
3338 extract_address(tmp1);
3339 compose_entry_append(compose,
3340 (!account_find_from_address(tmp1, FALSE))
3343 COMPOSE_TO, PREF_NONE);
3344 if (compose->replyto)
3345 compose_entry_append(compose,
3347 COMPOSE_CC, PREF_NONE);
3349 if (!folder_has_parent_of_type(msginfo->folder, F_QUEUE) &&
3350 !folder_has_parent_of_type(msginfo->folder, F_OUTBOX) &&
3351 !folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
3352 if (compose->replyto) {
3353 compose_entry_append(compose,
3355 COMPOSE_TO, PREF_NONE);
3357 compose_entry_append(compose,
3358 msginfo->from ? msginfo->from : "",
3359 COMPOSE_TO, PREF_NONE);
3362 /* replying to own mail, use original recp */
3363 compose_entry_append(compose,
3364 msginfo->to ? msginfo->to : "",
3365 COMPOSE_TO, PREF_NONE);
3366 compose_entry_append(compose,
3367 msginfo->cc ? msginfo->cc : "",
3368 COMPOSE_CC, PREF_NONE);
3373 if (to_sender || (compose->followup_to &&
3374 !strncmp(compose->followup_to, "poster", 6)))
3375 compose_entry_append
3377 (compose->replyto ? compose->replyto :
3378 msginfo->from ? msginfo->from : ""),
3379 COMPOSE_TO, PREF_NONE);
3381 else if (followup_and_reply_to || to_all) {
3382 compose_entry_append
3384 (compose->replyto ? compose->replyto :
3385 msginfo->from ? msginfo->from : ""),
3386 COMPOSE_TO, PREF_NONE);
3388 compose_entry_append
3390 compose->followup_to ? compose->followup_to :
3391 compose->newsgroups ? compose->newsgroups : "",
3392 COMPOSE_NEWSGROUPS, PREF_NONE);
3394 compose_entry_append
3396 msginfo->cc ? msginfo->cc : "",
3397 COMPOSE_CC, PREF_NONE);
3400 compose_entry_append
3402 compose->followup_to ? compose->followup_to :
3403 compose->newsgroups ? compose->newsgroups : "",
3404 COMPOSE_NEWSGROUPS, PREF_NONE);
3406 compose_reply_set_subject(compose, msginfo);
3408 if (to_ml && compose->ml_post) return;
3409 if (!to_all || compose->account->protocol == A_NNTP) return;
3411 if (compose->replyto) {
3412 Xstrdup_a(replyto, compose->replyto, return);
3413 extract_address(replyto);
3415 if (msginfo->from) {
3416 Xstrdup_a(from, msginfo->from, return);
3417 extract_address(from);
3420 if (replyto && from)
3421 cc_list = address_list_append_with_comments(cc_list, from);
3422 if (to_all && msginfo->folder &&
3423 msginfo->folder->prefs->enable_default_reply_to)
3424 cc_list = address_list_append_with_comments(cc_list,
3425 msginfo->folder->prefs->default_reply_to);
3426 cc_list = address_list_append_with_comments(cc_list, msginfo->to);
3427 cc_list = address_list_append_with_comments(cc_list, compose->cc);
3429 ac_email = g_utf8_strdown(compose->account->address, -1);
3432 for (cur = cc_list; cur != NULL; cur = cur->next) {
3433 gchar *addr = g_utf8_strdown(cur->data, -1);
3434 extract_address(addr);
3436 if (strcmp(ac_email, addr))
3437 compose_entry_append(compose, (gchar *)cur->data,
3438 COMPOSE_CC, PREF_NONE);
3440 debug_print("Cc address same as compose account's, ignoring\n");
3445 slist_free_strings_full(cc_list);
3451 #define SET_ENTRY(entry, str) \
3454 gtk_entry_set_text(GTK_ENTRY(compose->entry), str); \
3457 #define SET_ADDRESS(type, str) \
3460 compose_entry_append(compose, str, type, PREF_NONE); \
3463 static void compose_reedit_set_entry(Compose *compose, MsgInfo *msginfo)
3465 cm_return_if_fail(msginfo != NULL);
3467 SET_ENTRY(subject_entry, msginfo->subject);
3468 SET_ENTRY(from_name, msginfo->from);
3469 SET_ADDRESS(COMPOSE_TO, msginfo->to);
3470 SET_ADDRESS(COMPOSE_CC, compose->cc);
3471 SET_ADDRESS(COMPOSE_BCC, compose->bcc);
3472 SET_ADDRESS(COMPOSE_REPLYTO, compose->replyto);
3473 SET_ADDRESS(COMPOSE_NEWSGROUPS, compose->newsgroups);
3474 SET_ADDRESS(COMPOSE_FOLLOWUPTO, compose->followup_to);
3476 compose_update_priority_menu_item(compose);
3477 compose_update_privacy_system_menu_item(compose, FALSE);
3478 compose_show_first_last_header(compose, TRUE);
3484 static void compose_insert_sig(Compose *compose, gboolean replace)
3486 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3487 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3489 GtkTextIter iter, iter_end;
3490 gint cur_pos, ins_pos;
3491 gboolean prev_autowrap;
3492 gboolean found = FALSE;
3493 gboolean exists = FALSE;
3495 cm_return_if_fail(compose->account != NULL);
3499 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3500 G_CALLBACK(compose_changed_cb),
3503 mark = gtk_text_buffer_get_insert(buffer);
3504 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3505 cur_pos = gtk_text_iter_get_offset (&iter);
3508 gtk_text_buffer_get_end_iter(buffer, &iter);
3510 exists = (compose->sig_str != NULL);
3513 GtkTextIter first_iter, start_iter, end_iter;
3515 gtk_text_buffer_get_start_iter(buffer, &first_iter);
3517 if (!exists || compose->sig_str[0] == '\0')
3520 found = gtk_text_iter_forward_to_tag_toggle(&first_iter,
3521 compose->signature_tag);
3524 /* include previous \n\n */
3525 gtk_text_iter_backward_chars(&first_iter, 1);
3526 start_iter = first_iter;
3527 end_iter = first_iter;
3529 found = gtk_text_iter_forward_to_tag_toggle(&end_iter,
3530 compose->signature_tag);
3531 found &= gtk_text_iter_forward_to_tag_toggle(&end_iter,
3532 compose->signature_tag);
3534 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
3540 g_free(compose->sig_str);
3541 compose->sig_str = account_get_signature_str(compose->account);
3543 cur_pos = gtk_text_iter_get_offset(&iter);
3545 if (!compose->sig_str || (replace && !compose->account->auto_sig)) {
3546 g_free(compose->sig_str);
3547 compose->sig_str = NULL;
3549 if (compose->sig_inserted == FALSE)
3550 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
3551 compose->sig_inserted = TRUE;
3553 cur_pos = gtk_text_iter_get_offset(&iter);
3554 gtk_text_buffer_insert(buffer, &iter, compose->sig_str, -1);
3556 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cur_pos);
3557 gtk_text_iter_forward_chars(&iter, 1);
3558 gtk_text_buffer_get_end_iter(buffer, &iter_end);
3559 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &iter_end);
3561 if (cur_pos > gtk_text_buffer_get_char_count (buffer))
3562 cur_pos = gtk_text_buffer_get_char_count (buffer);
3565 /* put the cursor where it should be
3566 * either where the quote_fmt says, either where it was */
3567 if (compose->set_cursor_pos < 0)
3568 gtk_text_buffer_get_iter_at_offset(buffer, &iter, ins_pos);
3570 gtk_text_buffer_get_iter_at_offset(buffer, &iter,
3571 compose->set_cursor_pos);
3573 compose->set_cursor_pos = -1;
3574 gtk_text_buffer_place_cursor(buffer, &iter);
3575 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3576 G_CALLBACK(compose_changed_cb),
3582 static ComposeInsertResult compose_insert_file(Compose *compose, const gchar *file)
3585 GtkTextBuffer *buffer;
3588 const gchar *cur_encoding;
3589 gchar buf[BUFFSIZE];
3592 gboolean prev_autowrap;
3596 GError *error = NULL;
3602 GString *file_contents = NULL;
3603 ComposeInsertResult result = COMPOSE_INSERT_SUCCESS;
3605 cm_return_val_if_fail(file != NULL, COMPOSE_INSERT_NO_FILE);
3607 /* get the size of the file we are about to insert */
3609 f = g_file_new_for_path(file);
3610 fi = g_file_query_info(f, "standard::size",
3611 G_FILE_QUERY_INFO_NONE, NULL, &error);
3613 if (error != NULL) {
3614 g_warning(error->message);
3616 g_error_free(error);
3620 ret = g_stat(file, &file_stat);
3623 gchar *shortfile = g_path_get_basename(file);
3624 alertpanel_error(_("Could not get size of file '%s'."), shortfile);
3626 return COMPOSE_INSERT_NO_FILE;
3627 } else if (prefs_common.warn_large_insert == TRUE) {
3629 size = g_file_info_get_size(fi);
3633 size = file_stat.st_size;
3636 /* ask user for confirmation if the file is large */
3637 if (prefs_common.warn_large_insert_size < 0 ||
3638 size > ((goffset) prefs_common.warn_large_insert_size * 1024)) {
3642 msg = g_strdup_printf(_("You are about to insert a file of %s "
3643 "in the message body. Are you sure you want to do that?"),
3644 to_human_readable(size));
3645 aval = alertpanel_full(_("Are you sure?"), msg, GTK_STOCK_CANCEL,
3646 _("_Insert"), NULL, ALERTFOCUS_SECOND, TRUE,
3647 NULL, ALERT_QUESTION);
3650 /* do we ask for confirmation next time? */
3651 if (aval & G_ALERTDISABLE) {
3652 /* no confirmation next time, disable feature in preferences */
3653 aval &= ~G_ALERTDISABLE;
3654 prefs_common.warn_large_insert = FALSE;
3657 /* abort file insertion if user canceled action */
3658 if (aval != G_ALERTALTERNATE) {
3659 return COMPOSE_INSERT_NO_FILE;
3665 if ((fp = g_fopen(file, "rb")) == NULL) {
3666 FILE_OP_ERROR(file, "fopen");
3667 return COMPOSE_INSERT_READ_ERROR;
3670 prev_autowrap = compose->autowrap;
3671 compose->autowrap = FALSE;
3673 text = GTK_TEXT_VIEW(compose->text);
3674 buffer = gtk_text_view_get_buffer(text);
3675 mark = gtk_text_buffer_get_insert(buffer);
3676 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3678 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3679 G_CALLBACK(text_inserted),
3682 cur_encoding = conv_get_locale_charset_str_no_utf8();
3684 file_contents = g_string_new("");
3685 while (fgets(buf, sizeof(buf), fp) != NULL) {
3688 if (g_utf8_validate(buf, -1, NULL) == TRUE)
3689 str = g_strdup(buf);
3691 codeconv_set_strict(TRUE);
3692 str = conv_codeset_strdup
3693 (buf, cur_encoding, CS_INTERNAL);
3694 codeconv_set_strict(FALSE);
3697 result = COMPOSE_INSERT_INVALID_CHARACTER;
3703 /* strip <CR> if DOS/Windows file,
3704 replace <CR> with <LF> if Macintosh file. */
3707 if (len > 0 && str[len - 1] != '\n') {
3709 if (str[len] == '\r') str[len] = '\n';
3712 file_contents = g_string_append(file_contents, str);
3716 if (result == COMPOSE_INSERT_SUCCESS) {
3717 gtk_text_buffer_insert(buffer, &iter, file_contents->str, -1);
3719 compose_changed_cb(NULL, compose);
3720 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3721 G_CALLBACK(text_inserted),
3723 compose->autowrap = prev_autowrap;
3724 if (compose->autowrap)
3725 compose_wrap_all(compose);
3728 g_string_free(file_contents, TRUE);
3734 static gboolean compose_attach_append(Compose *compose, const gchar *file,
3735 const gchar *filename,
3736 const gchar *content_type,
3737 const gchar *charset)
3745 GtkListStore *store;
3747 gboolean has_binary = FALSE;
3749 if (!is_file_exist(file)) {
3750 gchar *file_from_uri = g_filename_from_uri(file, NULL, NULL);
3751 gboolean result = FALSE;
3752 if (file_from_uri && is_file_exist(file_from_uri)) {
3753 result = compose_attach_append(
3754 compose, file_from_uri,
3755 filename, content_type,
3758 g_free(file_from_uri);
3761 alertpanel_error("File %s doesn't exist or permission denied\n", filename);
3764 if ((size = get_file_size(file)) < 0) {
3765 alertpanel_error("Can't get file size of %s\n", filename);
3769 /* In batch mode, we allow 0-length files to be attached no questions asked */
3770 if (size == 0 && !compose->batch) {
3771 gchar * msg = g_strdup_printf(_("File %s is empty."), filename);
3772 AlertValue aval = alertpanel_full(_("Empty file"), msg,
3773 GTK_STOCK_CANCEL, _("_Attach anyway"), NULL,
3774 ALERTFOCUS_SECOND, FALSE, NULL, ALERT_WARNING);
3777 if (aval != G_ALERTALTERNATE) {
3781 if ((fp = g_fopen(file, "rb")) == NULL) {
3782 alertpanel_error(_("Can't read %s."), filename);
3787 ainfo = g_new0(AttachInfo, 1);
3788 auto_ainfo = g_auto_pointer_new_with_free
3789 (ainfo, (GFreeFunc) compose_attach_info_free);
3790 ainfo->file = g_strdup(file);
3793 ainfo->content_type = g_strdup(content_type);
3794 if (!g_ascii_strcasecmp(content_type, "message/rfc822")) {
3796 MsgFlags flags = {0, 0};
3798 if (procmime_get_encoding_for_text_file(file, &has_binary) == ENC_7BIT)
3799 ainfo->encoding = ENC_7BIT;
3801 ainfo->encoding = ENC_8BIT;
3803 msginfo = procheader_parse_file(file, flags, FALSE, FALSE);
3804 if (msginfo && msginfo->subject)
3805 name = g_strdup(msginfo->subject);
3807 name = g_path_get_basename(filename ? filename : file);
3809 ainfo->name = g_strdup_printf(_("Message: %s"), name);
3811 procmsg_msginfo_free(&msginfo);
3813 if (!g_ascii_strncasecmp(content_type, "text/", 5)) {
3814 ainfo->charset = g_strdup(charset);
3815 ainfo->encoding = procmime_get_encoding_for_text_file(file, &has_binary);
3817 ainfo->encoding = ENC_BASE64;
3819 name = g_path_get_basename(filename ? filename : file);
3820 ainfo->name = g_strdup(name);
3824 ainfo->content_type = procmime_get_mime_type(file);
3825 if (!ainfo->content_type) {
3826 ainfo->content_type =
3827 g_strdup("application/octet-stream");
3828 ainfo->encoding = ENC_BASE64;
3829 } else if (!g_ascii_strncasecmp(ainfo->content_type, "text/", 5))
3831 procmime_get_encoding_for_text_file(file, &has_binary);
3833 ainfo->encoding = ENC_BASE64;
3834 name = g_path_get_basename(filename ? filename : file);
3835 ainfo->name = g_strdup(name);
3839 if (ainfo->name != NULL
3840 && !strcmp(ainfo->name, ".")) {
3841 g_free(ainfo->name);
3845 if (!strcmp(ainfo->content_type, "unknown") || has_binary) {
3846 g_free(ainfo->content_type);
3847 ainfo->content_type = g_strdup("application/octet-stream");
3848 g_free(ainfo->charset);
3849 ainfo->charset = NULL;
3852 ainfo->size = (goffset)size;
3853 size_text = to_human_readable((goffset)size);
3855 store = GTK_LIST_STORE(gtk_tree_view_get_model
3856 (GTK_TREE_VIEW(compose->attach_clist)));
3858 gtk_list_store_append(store, &iter);
3859 gtk_list_store_set(store, &iter,
3860 COL_MIMETYPE, ainfo->content_type,
3861 COL_SIZE, size_text,
3862 COL_NAME, ainfo->name,
3863 COL_CHARSET, ainfo->charset,
3865 COL_AUTODATA, auto_ainfo,
3868 g_auto_pointer_free(auto_ainfo);
3869 compose_attach_update_label(compose);
3873 void compose_use_signing(Compose *compose, gboolean use_signing)
3875 compose->use_signing = use_signing;
3876 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", use_signing);
3879 void compose_use_encryption(Compose *compose, gboolean use_encryption)
3881 compose->use_encryption = use_encryption;
3882 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", use_encryption);
3885 #define NEXT_PART_NOT_CHILD(info) \
3887 node = info->node; \
3888 while (node->children) \
3889 node = g_node_last_child(node); \
3890 info = procmime_mimeinfo_next((MimeInfo *)node->data); \
3893 static void compose_attach_parts(Compose *compose, MsgInfo *msginfo)
3897 MimeInfo *firsttext = NULL;
3898 MimeInfo *encrypted = NULL;
3901 const gchar *partname = NULL;
3903 mimeinfo = procmime_scan_message(msginfo);
3904 if (!mimeinfo) return;
3906 if (mimeinfo->node->children == NULL) {
3907 procmime_mimeinfo_free_all(&mimeinfo);
3911 /* find first content part */
3912 child = (MimeInfo *) mimeinfo->node->children->data;
3913 while (child && child->node->children && (child->type == MIMETYPE_MULTIPART))
3914 child = (MimeInfo *)child->node->children->data;
3917 if (child->type == MIMETYPE_TEXT) {
3919 debug_print("First text part found\n");
3920 } else if (compose->mode == COMPOSE_REEDIT &&
3921 child->type == MIMETYPE_APPLICATION &&
3922 !g_ascii_strcasecmp(child->subtype, "pgp-encrypted")) {
3923 encrypted = (MimeInfo *)child->node->parent->data;
3926 child = (MimeInfo *) mimeinfo->node->children->data;
3927 while (child != NULL) {
3930 if (child == encrypted) {
3931 /* skip this part of tree */
3932 NEXT_PART_NOT_CHILD(child);
3936 if (child->type == MIMETYPE_MULTIPART) {
3937 /* get the actual content */
3938 child = procmime_mimeinfo_next(child);
3942 if (child == firsttext) {
3943 child = procmime_mimeinfo_next(child);
3947 outfile = procmime_get_tmp_file_name(child);
3948 if ((err = procmime_get_part(outfile, child)) < 0)
3949 g_warning("Can't get the part of multipart message. (%s)", g_strerror(-err));
3951 gchar *content_type;
3953 content_type = procmime_get_content_type_str(child->type, child->subtype);
3955 /* if we meet a pgp signature, we don't attach it, but
3956 * we force signing. */
3957 if ((strcmp(content_type, "application/pgp-signature") &&
3958 strcmp(content_type, "application/pkcs7-signature") &&
3959 strcmp(content_type, "application/x-pkcs7-signature"))
3960 || compose->mode == COMPOSE_REDIRECT) {
3961 partname = procmime_mimeinfo_get_parameter(child, "filename");
3962 if (partname == NULL)
3963 partname = procmime_mimeinfo_get_parameter(child, "name");
3964 if (partname == NULL)
3966 compose_attach_append(compose, outfile,
3967 partname, content_type,
3968 procmime_mimeinfo_get_parameter(child, "charset"));
3970 compose_force_signing(compose, compose->account, NULL);
3972 g_free(content_type);
3975 NEXT_PART_NOT_CHILD(child);
3977 procmime_mimeinfo_free_all(&mimeinfo);
3980 #undef NEXT_PART_NOT_CHILD
3985 WAIT_FOR_INDENT_CHAR,
3986 WAIT_FOR_INDENT_CHAR_OR_SPACE,
3989 /* return indent length, we allow:
3990 indent characters followed by indent characters or spaces/tabs,
3991 alphabets and numbers immediately followed by indent characters,
3992 and the repeating sequences of the above
3993 If quote ends with multiple spaces, only the first one is included. */
3994 static gchar *compose_get_quote_str(GtkTextBuffer *buffer,
3995 const GtkTextIter *start, gint *len)
3997 GtkTextIter iter = *start;
4001 IndentState state = WAIT_FOR_INDENT_CHAR;
4004 gint alnum_count = 0;
4005 gint space_count = 0;
4008 if (prefs_common.quote_chars == NULL) {
4012 while (!gtk_text_iter_ends_line(&iter)) {
4013 wc = gtk_text_iter_get_char(&iter);
4014 if (g_unichar_iswide(wc))
4016 clen = g_unichar_to_utf8(wc, ch);
4020 is_indent = strchr(prefs_common.quote_chars, ch[0]) ? TRUE : FALSE;
4021 is_space = g_unichar_isspace(wc);
4023 if (state == WAIT_FOR_INDENT_CHAR) {
4024 if (!is_indent && !g_unichar_isalnum(wc))
4027 quote_len += alnum_count + space_count + 1;
4028 alnum_count = space_count = 0;
4029 state = WAIT_FOR_INDENT_CHAR_OR_SPACE;
4032 } else if (state == WAIT_FOR_INDENT_CHAR_OR_SPACE) {
4033 if (!is_indent && !is_space && !g_unichar_isalnum(wc))
4037 else if (is_indent) {
4038 quote_len += alnum_count + space_count + 1;
4039 alnum_count = space_count = 0;
4042 state = WAIT_FOR_INDENT_CHAR;
4046 gtk_text_iter_forward_char(&iter);
4049 if (quote_len > 0 && space_count > 0)
4055 if (quote_len > 0) {
4057 gtk_text_iter_forward_chars(&iter, quote_len);
4058 return gtk_text_buffer_get_text(buffer, start, &iter, FALSE);
4064 /* return >0 if the line is itemized */
4065 static int compose_itemized_length(GtkTextBuffer *buffer,
4066 const GtkTextIter *start)
4068 GtkTextIter iter = *start;
4073 if (gtk_text_iter_ends_line(&iter))
4078 wc = gtk_text_iter_get_char(&iter);
4079 if (!g_unichar_isspace(wc))
4081 gtk_text_iter_forward_char(&iter);
4082 if (gtk_text_iter_ends_line(&iter))
4086 clen = g_unichar_to_utf8(wc, ch);
4087 if (!((clen == 1 && strchr("*-+", ch[0])) ||
4089 wc == 0x2022 || /* BULLET */
4090 wc == 0x2023 || /* TRIANGULAR BULLET */
4091 wc == 0x2043 || /* HYPHEN BULLET */
4092 wc == 0x204c || /* BLACK LEFTWARDS BULLET */
4093 wc == 0x204d || /* BLACK RIGHTWARDS BULLET */
4094 wc == 0x2219 || /* BULLET OPERATOR */
4095 wc == 0x25d8 || /* INVERSE BULLET */
4096 wc == 0x25e6 || /* WHITE BULLET */
4097 wc == 0x2619 || /* REVERSED ROTATED FLORAL HEART BULLET */
4098 wc == 0x2765 || /* ROTATED HEAVY BLACK HEART BULLET */
4099 wc == 0x2767 || /* ROTATED FLORAL HEART BULLET */
4100 wc == 0x29be || /* CIRCLED WHITE BULLET */
4101 wc == 0x29bf /* CIRCLED BULLET */
4105 gtk_text_iter_forward_char(&iter);
4106 if (gtk_text_iter_ends_line(&iter))
4108 wc = gtk_text_iter_get_char(&iter);
4109 if (g_unichar_isspace(wc)) {
4115 /* return the string at the start of the itemization */
4116 static gchar * compose_get_itemized_chars(GtkTextBuffer *buffer,
4117 const GtkTextIter *start)
4119 GtkTextIter iter = *start;
4122 GString *item_chars = g_string_new("");
4125 if (gtk_text_iter_ends_line(&iter))
4130 wc = gtk_text_iter_get_char(&iter);
4131 if (!g_unichar_isspace(wc))
4133 gtk_text_iter_forward_char(&iter);
4134 if (gtk_text_iter_ends_line(&iter))
4136 g_string_append_unichar(item_chars, wc);
4139 str = item_chars->str;
4140 g_string_free(item_chars, FALSE);
4144 /* return the number of spaces at a line's start */
4145 static int compose_left_offset_length(GtkTextBuffer *buffer,
4146 const GtkTextIter *start)
4148 GtkTextIter iter = *start;
4151 if (gtk_text_iter_ends_line(&iter))
4155 wc = gtk_text_iter_get_char(&iter);
4156 if (!g_unichar_isspace(wc))
4159 gtk_text_iter_forward_char(&iter);
4160 if (gtk_text_iter_ends_line(&iter))
4164 gtk_text_iter_forward_char(&iter);
4165 if (gtk_text_iter_ends_line(&iter))
4170 static gboolean compose_get_line_break_pos(GtkTextBuffer *buffer,
4171 const GtkTextIter *start,
4172 GtkTextIter *break_pos,
4176 GtkTextIter iter = *start, line_end = *start;
4177 PangoLogAttr *attrs;
4184 gboolean can_break = FALSE;
4185 gboolean do_break = FALSE;
4186 gboolean was_white = FALSE;
4187 gboolean prev_dont_break = FALSE;
4189 gtk_text_iter_forward_to_line_end(&line_end);
4190 str = gtk_text_buffer_get_text(buffer, &iter, &line_end, FALSE);
4191 len = g_utf8_strlen(str, -1);
4195 g_warning("compose_get_line_break_pos: len = 0!");
4199 /* g_print("breaking line: %d: %s (len = %d)\n",
4200 gtk_text_iter_get_line(&iter), str, len); */
4202 attrs = g_new(PangoLogAttr, len + 1);
4204 pango_default_break(str, -1, NULL, attrs, len + 1);
4208 /* skip quote and leading spaces */
4209 for (i = 0; *p != '\0' && i < len; i++) {
4212 wc = g_utf8_get_char(p);
4213 if (i >= quote_len && !g_unichar_isspace(wc))
4215 if (g_unichar_iswide(wc))
4217 else if (*p == '\t')
4221 p = g_utf8_next_char(p);
4224 for (; *p != '\0' && i < len; i++) {
4225 PangoLogAttr *attr = attrs + i;
4229 if (attr->is_line_break && can_break && was_white && !prev_dont_break)
4232 was_white = attr->is_white;
4234 /* don't wrap URI */
4235 if ((uri_len = get_uri_len(p)) > 0) {
4237 if (pos > 0 && col > max_col) {
4247 wc = g_utf8_get_char(p);
4248 if (g_unichar_iswide(wc)) {
4250 if (prev_dont_break && can_break && attr->is_line_break)
4252 } else if (*p == '\t')
4256 if (pos > 0 && col > max_col) {
4261 if (*p == '-' || *p == '/')
4262 prev_dont_break = TRUE;
4264 prev_dont_break = FALSE;
4266 p = g_utf8_next_char(p);
4270 /* debug_print("compose_get_line_break_pos(): do_break = %d, pos = %d, col = %d\n", do_break, pos, col); */
4275 *break_pos = *start;
4276 gtk_text_iter_set_line_offset(break_pos, pos);
4281 static gboolean compose_join_next_line(Compose *compose,
4282 GtkTextBuffer *buffer,
4284 const gchar *quote_str)
4286 GtkTextIter iter_ = *iter, cur, prev, next, end;
4287 PangoLogAttr attrs[3];
4289 gchar *next_quote_str;
4292 gboolean keep_cursor = FALSE;
4294 if (!gtk_text_iter_forward_line(&iter_) ||
4295 gtk_text_iter_ends_line(&iter_)) {
4298 next_quote_str = compose_get_quote_str(buffer, &iter_, "e_len);
4300 if ((quote_str || next_quote_str) &&
4301 strcmp2(quote_str, next_quote_str) != 0) {
4302 g_free(next_quote_str);
4305 g_free(next_quote_str);
4308 if (quote_len > 0) {
4309 gtk_text_iter_forward_chars(&end, quote_len);
4310 if (gtk_text_iter_ends_line(&end)) {
4315 /* don't join itemized lines */
4316 if (compose_itemized_length(buffer, &end) > 0) {
4320 /* don't join signature separator */
4321 if (compose_is_sig_separator(compose, buffer, &iter_)) {
4324 /* delete quote str */
4326 gtk_text_buffer_delete(buffer, &iter_, &end);
4328 /* don't join line breaks put by the user */
4330 gtk_text_iter_backward_char(&cur);
4331 if (gtk_text_iter_has_tag(&cur, compose->no_join_tag)) {
4332 gtk_text_iter_forward_char(&cur);
4336 gtk_text_iter_forward_char(&cur);
4337 /* delete linebreak and extra spaces */
4338 while (gtk_text_iter_backward_char(&cur)) {
4339 wc1 = gtk_text_iter_get_char(&cur);
4340 if (!g_unichar_isspace(wc1))
4345 while (!gtk_text_iter_ends_line(&cur)) {
4346 wc1 = gtk_text_iter_get_char(&cur);
4347 if (!g_unichar_isspace(wc1))
4349 gtk_text_iter_forward_char(&cur);
4352 if (!gtk_text_iter_equal(&prev, &next)) {
4355 mark = gtk_text_buffer_get_insert(buffer);
4356 gtk_text_buffer_get_iter_at_mark(buffer, &cur, mark);
4357 if (gtk_text_iter_equal(&prev, &cur))
4359 gtk_text_buffer_delete(buffer, &prev, &next);
4363 /* insert space if required */
4364 gtk_text_iter_backward_char(&prev);
4365 wc1 = gtk_text_iter_get_char(&prev);
4366 wc2 = gtk_text_iter_get_char(&next);
4367 gtk_text_iter_forward_char(&next);
4368 str = gtk_text_buffer_get_text(buffer, &prev, &next, FALSE);
4369 pango_default_break(str, -1, NULL, attrs, 3);
4370 if (!attrs[1].is_line_break ||
4371 (!g_unichar_iswide(wc1) || !g_unichar_iswide(wc2))) {
4372 gtk_text_buffer_insert(buffer, &iter_, " ", 1);
4374 gtk_text_iter_backward_char(&iter_);
4375 gtk_text_buffer_place_cursor(buffer, &iter_);
4384 #define ADD_TXT_POS(bp_, ep_, pti_) \
4385 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
4386 last = last->next; \
4387 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
4388 last->next = NULL; \
4390 g_warning("alloc error scanning URIs"); \
4393 static gboolean compose_beautify_paragraph(Compose *compose, GtkTextIter *par_iter, gboolean force)
4395 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4396 GtkTextBuffer *buffer;
4397 GtkTextIter iter, break_pos, end_of_line;
4398 gchar *quote_str = NULL;
4400 gboolean wrap_quote = force || prefs_common.linewrap_quote;
4401 gboolean prev_autowrap = compose->autowrap;
4402 gint startq_offset = -1, noq_offset = -1;
4403 gint uri_start = -1, uri_stop = -1;
4404 gint nouri_start = -1, nouri_stop = -1;
4405 gint num_blocks = 0;
4406 gint quotelevel = -1;
4407 gboolean modified = force;
4408 gboolean removed = FALSE;
4409 gboolean modified_before_remove = FALSE;
4411 gboolean start = TRUE;
4412 gint itemized_len = 0, rem_item_len = 0;
4413 gchar *itemized_chars = NULL;
4414 gboolean item_continuation = FALSE;
4419 if (compose->draft_timeout_tag == COMPOSE_DRAFT_TIMEOUT_FORBIDDEN) {
4423 compose->autowrap = FALSE;
4425 buffer = gtk_text_view_get_buffer(text);
4426 undo_wrapping(compose->undostruct, TRUE);
4431 mark = gtk_text_buffer_get_insert(buffer);
4432 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
4436 if (compose->draft_timeout_tag == COMPOSE_DRAFT_TIMEOUT_FORBIDDEN) {
4437 if (gtk_text_iter_ends_line(&iter)) {
4438 while (gtk_text_iter_ends_line(&iter) &&
4439 gtk_text_iter_forward_line(&iter))
4442 while (gtk_text_iter_backward_line(&iter)) {
4443 if (gtk_text_iter_ends_line(&iter)) {
4444 gtk_text_iter_forward_line(&iter);
4450 /* move to line start */
4451 gtk_text_iter_set_line_offset(&iter, 0);
4454 itemized_len = compose_itemized_length(buffer, &iter);
4456 if (!itemized_len) {
4457 itemized_len = compose_left_offset_length(buffer, &iter);
4458 item_continuation = TRUE;
4462 itemized_chars = compose_get_itemized_chars(buffer, &iter);
4464 /* go until paragraph end (empty line) */
4465 while (start || !gtk_text_iter_ends_line(&iter)) {
4466 gchar *scanpos = NULL;
4467 /* parse table - in order of priority */
4469 const gchar *needle; /* token */
4471 /* token search function */
4472 gchar *(*search) (const gchar *haystack,
4473 const gchar *needle);
4474 /* part parsing function */
4475 gboolean (*parse) (const gchar *start,
4476 const gchar *scanpos,
4480 /* part to URI function */
4481 gchar *(*build_uri) (const gchar *bp,
4485 static struct table parser[] = {
4486 {"http://", strcasestr, get_uri_part, make_uri_string},
4487 {"https://", strcasestr, get_uri_part, make_uri_string},
4488 {"ftp://", strcasestr, get_uri_part, make_uri_string},
4489 {"sftp://", strcasestr, get_uri_part, make_uri_string},
4490 {"gopher://",strcasestr, get_uri_part, make_uri_string},
4491 {"www.", strcasestr, get_uri_part, make_http_string},
4492 {"mailto:", strcasestr, get_uri_part, make_uri_string},
4493 {"@", strcasestr, get_email_part, make_email_string}
4495 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
4496 gint last_index = PARSE_ELEMS;
4498 gchar *o_walk = NULL, *walk = NULL, *bp = NULL, *ep = NULL;
4502 if (!prev_autowrap && num_blocks == 0) {
4504 g_signal_handlers_block_by_func(G_OBJECT(buffer),
4505 G_CALLBACK(text_inserted),
4508 if (gtk_text_iter_has_tag(&iter, compose->no_wrap_tag) && !force)
4511 uri_start = uri_stop = -1;
4513 quote_str = compose_get_quote_str(buffer, &iter, "e_len);
4516 /* debug_print("compose_beautify_paragraph(): quote_str = '%s'\n", quote_str); */
4517 if (startq_offset == -1)
4518 startq_offset = gtk_text_iter_get_offset(&iter);
4519 quotelevel = get_quote_level(quote_str, prefs_common.quote_chars);
4520 if (quotelevel > 2) {
4521 /* recycle colors */
4522 if (prefs_common.recycle_quote_colors)
4531 if (startq_offset == -1)
4532 noq_offset = gtk_text_iter_get_offset(&iter);
4536 if (prev_autowrap == FALSE && !force && !wrap_quote) {
4539 if (gtk_text_iter_ends_line(&iter)) {
4541 } else if (compose_get_line_break_pos(buffer, &iter, &break_pos,
4542 prefs_common.linewrap_len,
4544 GtkTextIter prev, next, cur;
4545 if (prev_autowrap != FALSE || force) {
4546 compose->automatic_break = TRUE;
4548 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4549 compose->automatic_break = FALSE;
4550 if (itemized_len && compose->autoindent) {
4551 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4552 if (!item_continuation)
4553 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4555 } else if (quote_str && wrap_quote) {
4556 compose->automatic_break = TRUE;
4558 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4559 compose->automatic_break = FALSE;
4560 if (itemized_len && compose->autoindent) {
4561 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4562 if (!item_continuation)
4563 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4567 /* remove trailing spaces */
4569 rem_item_len = itemized_len;
4570 while (compose->autoindent && rem_item_len-- > 0)
4571 gtk_text_iter_backward_char(&cur);
4572 gtk_text_iter_backward_char(&cur);
4575 while (!gtk_text_iter_starts_line(&cur)) {
4578 gtk_text_iter_backward_char(&cur);
4579 wc = gtk_text_iter_get_char(&cur);
4580 if (!g_unichar_isspace(wc))
4584 if (!gtk_text_iter_equal(&prev, &next)) {
4585 gtk_text_buffer_delete(buffer, &prev, &next);
4587 gtk_text_iter_forward_char(&break_pos);
4591 gtk_text_buffer_insert(buffer, &break_pos,
4595 modified |= compose_join_next_line(compose, buffer, &iter, quote_str);
4597 /* move iter to current line start */
4598 gtk_text_iter_set_line_offset(&iter, 0);
4605 /* move iter to next line start */
4611 if (!prev_autowrap && num_blocks > 0) {
4613 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
4614 G_CALLBACK(text_inserted),
4618 while (!gtk_text_iter_ends_line(&end_of_line)) {
4619 gtk_text_iter_forward_char(&end_of_line);
4621 o_walk = walk = gtk_text_buffer_get_text(buffer, &iter, &end_of_line, FALSE);
4623 nouri_start = gtk_text_iter_get_offset(&iter);
4624 nouri_stop = gtk_text_iter_get_offset(&end_of_line);
4626 walk_pos = gtk_text_iter_get_offset(&iter);
4627 /* FIXME: this looks phony. scanning for anything in the parse table */
4628 for (n = 0; n < PARSE_ELEMS; n++) {
4631 tmp = parser[n].search(walk, parser[n].needle);
4633 if (scanpos == NULL || tmp < scanpos) {
4642 /* check if URI can be parsed */
4643 if (parser[last_index].parse(walk, scanpos, (const gchar **)&bp,
4644 (const gchar **)&ep, FALSE)
4645 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
4649 strlen(parser[last_index].needle);
4652 uri_start = walk_pos + (bp - o_walk);
4653 uri_stop = walk_pos + (ep - o_walk);
4657 gtk_text_iter_forward_line(&iter);
4660 if (startq_offset != -1) {
4661 GtkTextIter startquote, endquote;
4662 gtk_text_buffer_get_iter_at_offset(
4663 buffer, &startquote, startq_offset);
4666 switch (quotelevel) {
4668 if (!gtk_text_iter_has_tag(&startquote, compose->quote0_tag) ||
4669 !gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) {
4670 gtk_text_buffer_apply_tag_by_name(
4671 buffer, "quote0", &startquote, &endquote);
4672 gtk_text_buffer_remove_tag_by_name(
4673 buffer, "quote1", &startquote, &endquote);
4674 gtk_text_buffer_remove_tag_by_name(
4675 buffer, "quote2", &startquote, &endquote);
4680 if (!gtk_text_iter_has_tag(&startquote, compose->quote1_tag) ||
4681 !gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) {
4682 gtk_text_buffer_apply_tag_by_name(
4683 buffer, "quote1", &startquote, &endquote);
4684 gtk_text_buffer_remove_tag_by_name(
4685 buffer, "quote0", &startquote, &endquote);
4686 gtk_text_buffer_remove_tag_by_name(
4687 buffer, "quote2", &startquote, &endquote);
4692 if (!gtk_text_iter_has_tag(&startquote, compose->quote2_tag) ||
4693 !gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag)) {
4694 gtk_text_buffer_apply_tag_by_name(
4695 buffer, "quote2", &startquote, &endquote);
4696 gtk_text_buffer_remove_tag_by_name(
4697 buffer, "quote0", &startquote, &endquote);
4698 gtk_text_buffer_remove_tag_by_name(
4699 buffer, "quote1", &startquote, &endquote);
4705 } else if (noq_offset != -1) {
4706 GtkTextIter startnoquote, endnoquote;
4707 gtk_text_buffer_get_iter_at_offset(
4708 buffer, &startnoquote, noq_offset);
4711 if ((gtk_text_iter_has_tag(&startnoquote, compose->quote0_tag)
4712 && gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) ||
4713 (gtk_text_iter_has_tag(&startnoquote, compose->quote1_tag)
4714 && gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) ||
4715 (gtk_text_iter_has_tag(&startnoquote, compose->quote2_tag)
4716 && gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag))) {
4717 gtk_text_buffer_remove_tag_by_name(
4718 buffer, "quote0", &startnoquote, &endnoquote);
4719 gtk_text_buffer_remove_tag_by_name(
4720 buffer, "quote1", &startnoquote, &endnoquote);
4721 gtk_text_buffer_remove_tag_by_name(
4722 buffer, "quote2", &startnoquote, &endnoquote);
4728 if (uri_start != nouri_start && uri_stop != nouri_stop) {
4729 GtkTextIter nouri_start_iter, nouri_end_iter;
4730 gtk_text_buffer_get_iter_at_offset(
4731 buffer, &nouri_start_iter, nouri_start);
4732 gtk_text_buffer_get_iter_at_offset(
4733 buffer, &nouri_end_iter, nouri_stop);
4734 if (gtk_text_iter_has_tag(&nouri_start_iter, compose->uri_tag) &&
4735 gtk_text_iter_has_tag(&nouri_end_iter, compose->uri_tag)) {
4736 gtk_text_buffer_remove_tag_by_name(
4737 buffer, "link", &nouri_start_iter, &nouri_end_iter);
4738 modified_before_remove = modified;
4743 if (uri_start >= 0 && uri_stop > 0) {
4744 GtkTextIter uri_start_iter, uri_end_iter, back;
4745 gtk_text_buffer_get_iter_at_offset(
4746 buffer, &uri_start_iter, uri_start);
4747 gtk_text_buffer_get_iter_at_offset(
4748 buffer, &uri_end_iter, uri_stop);
4749 back = uri_end_iter;
4750 gtk_text_iter_backward_char(&back);
4751 if (!gtk_text_iter_has_tag(&uri_start_iter, compose->uri_tag) ||
4752 !gtk_text_iter_has_tag(&back, compose->uri_tag)) {
4753 gtk_text_buffer_apply_tag_by_name(
4754 buffer, "link", &uri_start_iter, &uri_end_iter);
4756 if (removed && !modified_before_remove) {
4762 /* debug_print("not modified, out after %d lines\n", lines); */
4766 /* debug_print("modified, out after %d lines\n", lines); */
4768 g_free(itemized_chars);
4771 undo_wrapping(compose->undostruct, FALSE);
4772 compose->autowrap = prev_autowrap;
4777 void compose_action_cb(void *data)
4779 Compose *compose = (Compose *)data;
4780 compose_wrap_all(compose);
4783 static void compose_wrap_all(Compose *compose)
4785 compose_wrap_all_full(compose, FALSE);
4788 static void compose_wrap_all_full(Compose *compose, gboolean force)
4790 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4791 GtkTextBuffer *buffer;
4793 gboolean modified = TRUE;
4795 buffer = gtk_text_view_get_buffer(text);
4797 gtk_text_buffer_get_start_iter(buffer, &iter);
4799 undo_wrapping(compose->undostruct, TRUE);
4801 while (!gtk_text_iter_is_end(&iter) && modified)
4802 modified = compose_beautify_paragraph(compose, &iter, force);
4804 undo_wrapping(compose->undostruct, FALSE);
4808 static void compose_set_title(Compose *compose)
4814 edited = compose->modified ? _(" [Edited]") : "";
4816 subject = gtk_editable_get_chars(
4817 GTK_EDITABLE(compose->subject_entry), 0, -1);
4819 #ifndef GENERIC_UMPC
4820 if (subject && strlen(subject))
4821 str = g_strdup_printf(_("%s - Compose message%s"),
4824 str = g_strdup_printf(_("[no subject] - Compose message%s"), edited);
4826 str = g_strdup(_("Compose message"));
4829 gtk_window_set_title(GTK_WINDOW(compose->window), str);
4835 * compose_current_mail_account:
4837 * Find a current mail account (the currently selected account, or the
4838 * default account, if a news account is currently selected). If a
4839 * mail account cannot be found, display an error message.
4841 * Return value: Mail account, or NULL if not found.
4843 static PrefsAccount *
4844 compose_current_mail_account(void)
4848 if (cur_account && cur_account->protocol != A_NNTP)
4851 ac = account_get_default();
4852 if (!ac || ac->protocol == A_NNTP) {
4853 alertpanel_error(_("Account for sending mail is not specified.\n"
4854 "Please select a mail account before sending."));
4861 #define QUOTE_IF_REQUIRED(out, str) \
4863 if (*str != '"' && strpbrk(str, ",.:;[]<>()@\\\"")) { \
4867 len = strlen(str) + 3; \
4868 if ((__tmp = alloca(len)) == NULL) { \
4869 g_warning("can't allocate memory"); \
4870 g_string_free(header, TRUE); \
4873 g_snprintf(__tmp, len, "\"%s\"", str); \
4878 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4879 g_warning("can't allocate memory"); \
4880 g_string_free(header, TRUE); \
4883 strcpy(__tmp, str); \
4889 #define QUOTE_IF_REQUIRED_NORMAL(out, str, errret) \
4891 if (*str != '"' && strpbrk(str, ",.:;[]<>()@\\\"")) { \
4895 len = strlen(str) + 3; \
4896 if ((__tmp = alloca(len)) == NULL) { \
4897 g_warning("can't allocate memory"); \
4900 g_snprintf(__tmp, len, "\"%s\"", str); \
4905 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4906 g_warning("can't allocate memory"); \
4909 strcpy(__tmp, str); \
4915 static void compose_select_account(Compose *compose, PrefsAccount *account,
4918 gchar *from = NULL, *header = NULL;
4919 ComposeHeaderEntry *header_entry;
4922 cm_return_if_fail(account != NULL);
4924 compose->account = account;
4925 if (account->name && *account->name) {
4927 QUOTE_IF_REQUIRED_NORMAL(buf, account->name, return);
4928 qbuf = escape_internal_quotes(buf, '"');
4929 from = g_strdup_printf("%s <%s>",
4930 qbuf, account->address);
4933 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4935 from = g_strdup_printf("<%s>",
4937 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4942 compose_set_title(compose);
4944 if (account->default_sign && compose->mode != COMPOSE_REDIRECT)
4945 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", TRUE);
4947 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", FALSE);
4948 if (account->default_encrypt && compose->mode != COMPOSE_REDIRECT)
4949 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", TRUE);
4951 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", FALSE);
4953 activate_privacy_system(compose, account, FALSE);
4955 if (!init && compose->mode != COMPOSE_REDIRECT) {
4956 undo_block(compose->undostruct);
4957 compose_insert_sig(compose, TRUE);
4958 undo_unblock(compose->undostruct);
4961 header_entry = (ComposeHeaderEntry *) compose->header_list->data;
4962 if (gtk_combo_box_get_active_iter(GTK_COMBO_BOX(header_entry->combo), &iter))
4963 gtk_tree_model_get(gtk_combo_box_get_model(GTK_COMBO_BOX(
4964 header_entry->combo)), &iter, COMBOBOX_TEXT, &header, -1);
4966 if (header && !strlen(gtk_entry_get_text(GTK_ENTRY(header_entry->entry)))) {
4967 if (account->protocol == A_NNTP) {
4968 if (!strcmp(header, _("To:")))
4969 combobox_select_by_text(
4970 GTK_COMBO_BOX(header_entry->combo),
4973 if (!strcmp(header, _("Newsgroups:")))
4974 combobox_select_by_text(
4975 GTK_COMBO_BOX(header_entry->combo),
4983 /* use account's dict info if set */
4984 if (compose->gtkaspell) {
4985 if (account->enable_default_dictionary)
4986 gtkaspell_change_dict(compose->gtkaspell,
4987 account->default_dictionary, FALSE);
4988 if (account->enable_default_alt_dictionary)
4989 gtkaspell_change_alt_dict(compose->gtkaspell,
4990 account->default_alt_dictionary);
4991 if (account->enable_default_dictionary
4992 || account->enable_default_alt_dictionary)
4993 compose_spell_menu_changed(compose);
4998 gboolean compose_check_for_valid_recipient(Compose *compose) {
4999 gchar *recipient_headers_mail[] = {"To:", "Cc:", "Bcc:", NULL};
5000 gchar *recipient_headers_news[] = {"Newsgroups:", NULL};
5001 gboolean recipient_found = FALSE;
5005 /* free to and newsgroup list */
5006 slist_free_strings_full(compose->to_list);
5007 compose->to_list = NULL;
5009 slist_free_strings_full(compose->newsgroup_list);
5010 compose->newsgroup_list = NULL;
5012 /* search header entries for to and newsgroup entries */
5013 for (list = compose->header_list; list; list = list->next) {
5016 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5017 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5020 if (entry[0] != '\0') {
5021 for (strptr = recipient_headers_mail; *strptr != NULL; strptr++) {
5022 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
5023 compose->to_list = address_list_append(compose->to_list, entry);
5024 recipient_found = TRUE;
5027 for (strptr = recipient_headers_news; *strptr != NULL; strptr++) {
5028 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
5029 compose->newsgroup_list = newsgroup_list_append(compose->newsgroup_list, entry);
5030 recipient_found = TRUE;
5037 return recipient_found;
5040 static gboolean compose_check_for_set_recipients(Compose *compose)
5042 if (compose->account->set_autocc && compose->account->auto_cc) {
5043 gboolean found_other = FALSE;
5045 /* search header entries for to and newsgroup entries */
5046 for (list = compose->header_list; list; list = list->next) {
5049 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5050 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5053 if (strcmp(entry, compose->account->auto_cc)
5054 || strcmp(header, prefs_common_translated_header_name("Cc:"))) {
5065 if (compose->batch) {
5066 gtk_widget_show_all(compose->window);
5068 text = g_strdup_printf(_("The only recipient is the default '%s' address. Send anyway?"),
5069 prefs_common_translated_header_name("Cc"));
5070 aval = alertpanel(_("Send"),
5072 GTK_STOCK_CANCEL, _("_Send"), NULL, ALERTFOCUS_SECOND);
5074 if (aval != G_ALERTALTERNATE)
5078 if (compose->account->set_autobcc && compose->account->auto_bcc) {
5079 gboolean found_other = FALSE;
5081 /* search header entries for to and newsgroup entries */
5082 for (list = compose->header_list; list; list = list->next) {
5085 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5086 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5089 if (strcmp(entry, compose->account->auto_bcc)
5090 || strcmp(header, prefs_common_translated_header_name("Bcc:"))) {
5102 if (compose->batch) {
5103 gtk_widget_show_all(compose->window);
5105 text = g_strdup_printf(_("The only recipient is the default '%s' address. Send anyway?"),
5106 prefs_common_translated_header_name("Bcc"));
5107 aval = alertpanel(_("Send"),
5109 GTK_STOCK_CANCEL, _("_Send"), NULL, ALERTFOCUS_SECOND);
5111 if (aval != G_ALERTALTERNATE)
5118 static gboolean compose_check_entries(Compose *compose, gboolean check_everything)
5122 if (compose_check_for_valid_recipient(compose) == FALSE) {
5123 if (compose->batch) {
5124 gtk_widget_show_all(compose->window);
5126 alertpanel_error(_("Recipient is not specified."));
5130 if (compose_check_for_set_recipients(compose) == FALSE) {
5134 if (!compose->batch && prefs_common.warn_empty_subj == TRUE) {
5135 str = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5136 if (*str == '\0' && check_everything == TRUE &&
5137 compose->mode != COMPOSE_REDIRECT) {
5141 message = g_strdup_printf(_("Subject is empty. %s"),
5142 compose->sending?_("Send it anyway?"):
5143 _("Queue it anyway?"));
5145 aval = alertpanel_full(compose->sending?_("Send"):_("Send later"), message,
5146 GTK_STOCK_CANCEL, compose->sending?_("_Send"):_("_Queue"), NULL,
5147 ALERTFOCUS_FIRST, TRUE, NULL, ALERT_QUESTION);
5149 if (aval & G_ALERTDISABLE) {
5150 aval &= ~G_ALERTDISABLE;
5151 prefs_common.warn_empty_subj = FALSE;
5153 if (aval != G_ALERTALTERNATE)
5158 if (!compose->batch && prefs_common.warn_sending_many_recipients_num > 0
5159 && check_everything == TRUE) {
5163 /* count To and Cc recipients */
5164 for (list = compose->header_list; list; list = list->next) {
5168 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5169 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5172 if ((entry[0] != '\0') &&
5173 (!strcmp(header, prefs_common_translated_header_name("To:")) ||
5174 !strcmp(header, prefs_common_translated_header_name("Cc:")))) {
5180 if (cnt > prefs_common.warn_sending_many_recipients_num) {
5184 message = g_strdup_printf(_("Sending to %d recipients. %s"), cnt,
5185 compose->sending?_("Send it anyway?"):
5186 _("Queue it anyway?"));
5188 aval = alertpanel_full(compose->sending?_("Send"):_("Send later"), message,
5189 GTK_STOCK_CANCEL, compose->sending?_("_Send"):_("_Queue"), NULL,
5190 ALERTFOCUS_FIRST, TRUE, NULL, ALERT_QUESTION);
5192 if (aval & G_ALERTDISABLE) {
5193 aval &= ~G_ALERTDISABLE;
5194 prefs_common.warn_sending_many_recipients_num = 0;
5196 if (aval != G_ALERTALTERNATE)
5201 if (check_everything && hooks_invoke(COMPOSE_CHECK_BEFORE_SEND_HOOKLIST, compose))
5207 static void _display_queue_error(ComposeQueueResult val)
5210 case COMPOSE_QUEUE_SUCCESS:
5212 case COMPOSE_QUEUE_ERROR_NO_MSG:
5213 alertpanel_error(_("Could not queue message."));
5215 case COMPOSE_QUEUE_ERROR_WITH_ERRNO:
5216 alertpanel_error(_("Could not queue message:\n\n%s."),
5219 case COMPOSE_QUEUE_ERROR_SIGNING_FAILED:
5220 alertpanel_error(_("Could not queue message for sending:\n\n"
5221 "Signature failed: %s"), privacy_get_error());
5223 case COMPOSE_QUEUE_ERROR_ENCRYPT_FAILED:
5224 alertpanel_error(_("Could not queue message for sending:\n\n"
5225 "Encryption failed: %s"), privacy_get_error());
5227 case COMPOSE_QUEUE_ERROR_CHAR_CONVERSION:
5228 alertpanel_error(_("Could not queue message for sending:\n\n"
5229 "Charset conversion failed."));
5231 case COMPOSE_QUEUE_ERROR_NO_ENCRYPTION_KEY:
5232 alertpanel_error(_("Could not queue message for sending:\n\n"
5233 "Couldn't get recipient encryption key."));
5236 /* unhandled error */
5237 debug_print("oops, unhandled compose_queue() return value %d\n",
5243 gint compose_send(Compose *compose)
5246 FolderItem *folder = NULL;
5247 ComposeQueueResult val = COMPOSE_QUEUE_ERROR_NO_MSG;
5248 gchar *msgpath = NULL;
5249 gboolean discard_window = FALSE;
5250 gchar *errstr = NULL;
5251 gchar *tmsgid = NULL;
5252 MainWindow *mainwin = mainwindow_get_mainwindow();
5253 gboolean queued_removed = FALSE;
5255 if (prefs_common.send_dialog_invisible
5256 || compose->batch == TRUE)
5257 discard_window = TRUE;
5259 compose_allow_user_actions (compose, FALSE);
5260 compose->sending = TRUE;
5262 if (compose_check_entries(compose, TRUE) == FALSE) {
5263 if (compose->batch) {
5264 gtk_widget_show_all(compose->window);
5270 val = compose_queue(compose, &msgnum, &folder, &msgpath, TRUE);
5272 if (val != COMPOSE_QUEUE_SUCCESS) {
5273 if (compose->batch) {
5274 gtk_widget_show_all(compose->window);
5277 _display_queue_error(val);
5282 tmsgid = compose->msgid ? g_strdup(compose->msgid) : NULL;
5283 if (discard_window) {
5284 compose->sending = FALSE;
5285 compose_close(compose);
5286 /* No more compose access in the normal codepath
5287 * after this point! */
5292 alertpanel_error(_("The message was queued but could not be "
5293 "sent.\nUse \"Send queued messages\" from "
5294 "the main window to retry."));
5295 if (!discard_window) {
5302 if (msgpath == NULL) {
5303 msgpath = folder_item_fetch_msg(folder, msgnum);
5304 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5307 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5308 claws_unlink(msgpath);
5311 if (!discard_window) {
5313 if (!queued_removed)
5314 folder_item_remove_msg(folder, msgnum);
5315 folder_item_scan(folder);
5317 /* make sure we delete that */
5318 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5320 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5321 folder_item_remove_msg(folder, tmp->msgnum);
5322 procmsg_msginfo_free(&tmp);
5329 if (!queued_removed)
5330 folder_item_remove_msg(folder, msgnum);
5331 folder_item_scan(folder);
5333 /* make sure we delete that */
5334 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5336 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5337 folder_item_remove_msg(folder, tmp->msgnum);
5338 procmsg_msginfo_free(&tmp);
5341 if (!discard_window) {
5342 compose->sending = FALSE;
5343 compose_allow_user_actions (compose, TRUE);
5344 compose_close(compose);
5348 alertpanel_error_log(_("%s\nUse \"Send queued messages\" from "
5349 "the main window to retry."), errstr);
5352 alertpanel_error_log(_("The message was queued but could not be "
5353 "sent.\nUse \"Send queued messages\" from "
5354 "the main window to retry."));
5356 if (!discard_window) {
5365 toolbar_main_set_sensitive(mainwin);
5366 main_window_set_menu_sensitive(mainwin);
5372 compose_allow_user_actions (compose, TRUE);
5373 compose->sending = FALSE;
5374 compose->modified = TRUE;
5375 toolbar_main_set_sensitive(mainwin);
5376 main_window_set_menu_sensitive(mainwin);
5381 static gboolean compose_use_attach(Compose *compose)
5383 GtkTreeModel *model = gtk_tree_view_get_model
5384 (GTK_TREE_VIEW(compose->attach_clist));
5385 return gtk_tree_model_iter_n_children(model, NULL) > 0;
5388 static gint compose_redirect_write_headers_from_headerlist(Compose *compose,
5391 gchar buf[BUFFSIZE];
5393 gboolean first_to_address;
5394 gboolean first_cc_address;
5396 ComposeHeaderEntry *headerentry;
5397 const gchar *headerentryname;
5398 const gchar *cc_hdr;
5399 const gchar *to_hdr;
5400 gboolean err = FALSE;
5402 debug_print("Writing redirect header\n");
5404 cc_hdr = prefs_common_translated_header_name("Cc:");
5405 to_hdr = prefs_common_translated_header_name("To:");
5407 first_to_address = TRUE;
5408 for (list = compose->header_list; list; list = list->next) {
5409 headerentry = ((ComposeHeaderEntry *)list->data);
5410 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5412 if (g_utf8_collate(headerentryname, to_hdr) == 0) {
5413 const gchar *entstr = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5414 Xstrdup_a(str, entstr, return -1);
5416 if (str[0] != '\0') {
5417 compose_convert_header
5418 (compose, buf, sizeof(buf), str,
5419 strlen("Resent-To") + 2, TRUE);
5421 if (first_to_address) {
5422 err |= (fprintf(fp, "Resent-To: ") < 0);
5423 first_to_address = FALSE;
5425 err |= (fprintf(fp, ",") < 0);
5427 err |= (fprintf(fp, "%s", buf) < 0);
5431 if (!first_to_address) {
5432 err |= (fprintf(fp, "\n") < 0);
5435 first_cc_address = TRUE;
5436 for (list = compose->header_list; list; list = list->next) {
5437 headerentry = ((ComposeHeaderEntry *)list->data);
5438 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5440 if (g_utf8_collate(headerentryname, cc_hdr) == 0) {
5441 const gchar *strg = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5442 Xstrdup_a(str, strg, return -1);
5444 if (str[0] != '\0') {
5445 compose_convert_header
5446 (compose, buf, sizeof(buf), str,
5447 strlen("Resent-Cc") + 2, TRUE);
5449 if (first_cc_address) {
5450 err |= (fprintf(fp, "Resent-Cc: ") < 0);
5451 first_cc_address = FALSE;
5453 err |= (fprintf(fp, ",") < 0);
5455 err |= (fprintf(fp, "%s", buf) < 0);
5459 if (!first_cc_address) {
5460 err |= (fprintf(fp, "\n") < 0);
5463 return (err ? -1:0);
5466 static gint compose_redirect_write_headers(Compose *compose, FILE *fp)
5468 gchar date[RFC822_DATE_BUFFSIZE];
5469 gchar buf[BUFFSIZE];
5471 const gchar *entstr;
5472 /* struct utsname utsbuf; */
5473 gboolean err = FALSE;
5475 cm_return_val_if_fail(fp != NULL, -1);
5476 cm_return_val_if_fail(compose->account != NULL, -1);
5477 cm_return_val_if_fail(compose->account->address != NULL, -1);
5480 if (prefs_common.hide_timezone)
5481 get_rfc822_date_hide_tz(date, sizeof(date));
5483 get_rfc822_date(date, sizeof(date));
5484 err |= (fprintf(fp, "Resent-Date: %s\n", date) < 0);
5487 if (compose->account->name && *compose->account->name) {
5488 compose_convert_header
5489 (compose, buf, sizeof(buf), compose->account->name,
5490 strlen("From: "), TRUE);
5491 err |= (fprintf(fp, "Resent-From: %s <%s>\n",
5492 buf, compose->account->address) < 0);
5494 err |= (fprintf(fp, "Resent-From: %s\n", compose->account->address) < 0);
5497 entstr = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5498 if (*entstr != '\0') {
5499 Xstrdup_a(str, entstr, return -1);
5502 compose_convert_header(compose, buf, sizeof(buf), str,
5503 strlen("Subject: "), FALSE);
5504 err |= (fprintf(fp, "Subject: %s\n", buf) < 0);
5508 /* Resent-Message-ID */
5509 if (compose->account->gen_msgid) {
5510 gchar *addr = prefs_account_generate_msgid(compose->account);
5511 err |= (fprintf(fp, "Resent-Message-ID: <%s>\n", addr) < 0);
5513 g_free(compose->msgid);
5514 compose->msgid = addr;
5516 compose->msgid = NULL;
5519 if (compose_redirect_write_headers_from_headerlist(compose, fp))
5522 /* separator between header and body */
5523 err |= (fputs("\n", fp) == EOF);
5525 return (err ? -1:0);
5528 static gint compose_redirect_write_to_file(Compose *compose, FILE *fdest)
5533 gchar rewrite_buf[BUFFSIZE];
5535 gboolean skip = FALSE;
5536 gboolean err = FALSE;
5537 gchar *not_included[]={
5538 "Return-Path:", "Delivered-To:", "Received:",
5539 "Subject:", "X-UIDL:", "AF:",
5540 "NF:", "PS:", "SRH:",
5541 "SFN:", "DSR:", "MID:",
5542 "CFG:", "PT:", "S:",
5543 "RQ:", "SSV:", "NSV:",
5544 "SSH:", "R:", "MAID:",
5545 "NAID:", "RMID:", "FMID:",
5546 "SCF:", "RRCPT:", "NG:",
5547 "X-Claws-Privacy", "X-Claws-Sign:", "X-Claws-Encrypt",
5548 "X-Claws-End-Special-Headers:", "X-Claws-Account-Id:",
5549 "X-Sylpheed-Privacy", "X-Sylpheed-Sign:", "X-Sylpheed-Encrypt",
5550 "X-Sylpheed-End-Special-Headers:", "X-Sylpheed-Account-Id:",
5551 "X-Claws-Auto-Wrapping:", "X-Claws-Auto-Indent:",
5556 if ((fp = g_fopen(compose->redirect_filename, "rb")) == NULL) {
5557 FILE_OP_ERROR(compose->redirect_filename, "fopen");
5561 while ((ret = procheader_get_one_field_asis(&buf, fp)) != -1) {
5563 for (i = 0; not_included[i] != NULL; i++) {
5564 if (g_ascii_strncasecmp(buf, not_included[i],
5565 strlen(not_included[i])) == 0) {
5575 if (fputs(buf, fdest) == -1) {
5581 if (!prefs_common.redirect_keep_from) {
5582 if (g_ascii_strncasecmp(buf, "From:",
5583 strlen("From:")) == 0) {
5584 err |= (fputs(" (by way of ", fdest) == EOF);
5585 if (compose->account->name
5586 && *compose->account->name) {
5587 gchar buffer[BUFFSIZE];
5589 compose_convert_header
5590 (compose, buffer, sizeof(buffer),
5591 compose->account->name,
5594 err |= (fprintf(fdest, "%s <%s>",
5596 compose->account->address) < 0);
5598 err |= (fprintf(fdest, "%s",
5599 compose->account->address) < 0);
5600 err |= (fputs(")", fdest) == EOF);
5606 if (fputs("\n", fdest) == -1)
5613 if (compose_redirect_write_headers(compose, fdest))
5616 while ((len = fread(rewrite_buf, sizeof(gchar), sizeof(rewrite_buf), fp)) > 0) {
5617 if (fwrite(rewrite_buf, sizeof(gchar), len, fdest) != len)
5631 static gint compose_write_to_file(Compose *compose, FILE *fp, gint action, gboolean attach_parts)
5633 GtkTextBuffer *buffer;
5634 GtkTextIter start, end, tmp;
5635 gchar *chars, *tmp_enc_file, *content;
5637 const gchar *out_codeset;
5638 EncodingType encoding = ENC_UNKNOWN;
5639 MimeInfo *mimemsg, *mimetext;
5641 const gchar *src_codeset = CS_INTERNAL;
5642 gchar *from_addr = NULL;
5643 gchar *from_name = NULL;
5646 if (action == COMPOSE_WRITE_FOR_SEND) {
5647 attach_parts = TRUE;
5649 /* We're sending the message, generate a Message-ID
5651 if (compose->msgid == NULL &&
5652 compose->account->gen_msgid) {
5653 compose->msgid = prefs_account_generate_msgid(compose->account);
5657 /* create message MimeInfo */
5658 mimemsg = procmime_mimeinfo_new();
5659 mimemsg->type = MIMETYPE_MESSAGE;
5660 mimemsg->subtype = g_strdup("rfc822");
5661 mimemsg->content = MIMECONTENT_MEM;
5662 mimemsg->tmp = TRUE; /* must free content later */
5663 mimemsg->data.mem = compose_get_header(compose);
5665 /* Create text part MimeInfo */
5666 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5667 gtk_text_buffer_get_end_iter(buffer, &end);
5670 /* We make sure that there is a newline at the end. */
5671 if (action == COMPOSE_WRITE_FOR_SEND && gtk_text_iter_backward_char(&tmp)) {
5672 chars = gtk_text_buffer_get_text(buffer, &tmp, &end, FALSE);
5673 if (*chars != '\n') {
5674 gtk_text_buffer_insert(buffer, &end, "\n", 1);
5679 /* get all composed text */
5680 gtk_text_buffer_get_start_iter(buffer, &start);
5681 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5683 out_codeset = conv_get_charset_str(compose->out_encoding);
5685 if (!out_codeset && is_ascii_str(chars)) {
5686 out_codeset = CS_US_ASCII;
5687 } else if (prefs_common.outgoing_fallback_to_ascii &&
5688 is_ascii_str(chars)) {
5689 out_codeset = CS_US_ASCII;
5690 encoding = ENC_7BIT;
5694 gchar *test_conv_global_out = NULL;
5695 gchar *test_conv_reply = NULL;
5697 /* automatic mode. be automatic. */
5698 codeconv_set_strict(TRUE);
5700 out_codeset = conv_get_outgoing_charset_str();
5702 debug_print("trying to convert to %s\n", out_codeset);
5703 test_conv_global_out = conv_codeset_strdup(chars, src_codeset, out_codeset);
5706 if (!test_conv_global_out && compose->orig_charset
5707 && strcmp(compose->orig_charset, CS_US_ASCII)) {
5708 out_codeset = compose->orig_charset;
5709 debug_print("failure; trying to convert to %s\n", out_codeset);
5710 test_conv_reply = conv_codeset_strdup(chars, src_codeset, out_codeset);
5713 if (!test_conv_global_out && !test_conv_reply) {
5715 out_codeset = CS_INTERNAL;
5716 debug_print("failure; finally using %s\n", out_codeset);
5718 g_free(test_conv_global_out);
5719 g_free(test_conv_reply);
5720 codeconv_set_strict(FALSE);
5723 if (encoding == ENC_UNKNOWN) {
5724 if (prefs_common.encoding_method == CTE_BASE64)
5725 encoding = ENC_BASE64;
5726 else if (prefs_common.encoding_method == CTE_QUOTED_PRINTABLE)
5727 encoding = ENC_QUOTED_PRINTABLE;
5728 else if (prefs_common.encoding_method == CTE_8BIT)
5729 encoding = ENC_8BIT;
5731 encoding = procmime_get_encoding_for_charset(out_codeset);
5734 debug_print("src encoding = %s, out encoding = %s, transfer encoding = %s\n",
5735 src_codeset, out_codeset, procmime_get_encoding_str(encoding));
5737 if (action == COMPOSE_WRITE_FOR_SEND) {
5738 codeconv_set_strict(TRUE);
5739 buf = conv_codeset_strdup(chars, src_codeset, out_codeset);
5740 codeconv_set_strict(FALSE);
5745 msg = g_strdup_printf(_("Can't convert the character encoding of the message \n"
5746 "to the specified %s charset.\n"
5747 "Send it as %s?"), out_codeset, src_codeset);
5748 aval = alertpanel_full(_("Error"), msg, GTK_STOCK_CANCEL,
5749 _("_Send"), NULL, ALERTFOCUS_SECOND, FALSE,
5753 if (aval != G_ALERTALTERNATE) {
5755 return COMPOSE_QUEUE_ERROR_CHAR_CONVERSION;
5758 out_codeset = src_codeset;
5764 out_codeset = src_codeset;
5769 if (prefs_common.rewrite_first_from && (encoding == ENC_8BIT || encoding == ENC_7BIT)) {
5770 if (!strncmp(buf, "From ", sizeof("From ")-1) ||
5771 strstr(buf, "\nFrom ") != NULL) {
5772 encoding = ENC_QUOTED_PRINTABLE;
5776 mimetext = procmime_mimeinfo_new();
5777 mimetext->content = MIMECONTENT_MEM;
5778 mimetext->tmp = TRUE; /* must free content later */
5779 /* dup'ed because procmime_encode_content can turn it into a tmpfile
5780 * and free the data, which we need later. */
5781 mimetext->data.mem = g_strdup(buf);
5782 mimetext->type = MIMETYPE_TEXT;
5783 mimetext->subtype = g_strdup("plain");
5784 g_hash_table_insert(mimetext->typeparameters, g_strdup("charset"),
5785 g_strdup(out_codeset));
5787 /* protect trailing spaces when signing message */
5788 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5789 privacy_system_can_sign(compose->privacy_system)) {
5790 encoding = ENC_QUOTED_PRINTABLE;
5794 debug_print("main text: %Id bytes encoded as %s in %d\n",
5796 debug_print("main text: %zd bytes encoded as %s in %d\n",
5798 strlen(buf), out_codeset, encoding);
5800 /* check for line length limit */
5801 if (action == COMPOSE_WRITE_FOR_SEND &&
5802 encoding != ENC_QUOTED_PRINTABLE && encoding != ENC_BASE64 &&
5803 check_line_length(buf, 1000, &line) < 0) {
5806 msg = g_strdup_printf
5807 (_("Line %d exceeds the line length limit (998 bytes).\n"
5808 "The contents of the message might be broken on the way to the delivery.\n"
5810 "Send it anyway?"), line + 1);
5811 aval = alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, GTK_STOCK_OK, NULL,
5814 if (aval != G_ALERTALTERNATE) {
5816 return COMPOSE_QUEUE_ERROR_NO_MSG;
5820 if (encoding != ENC_UNKNOWN)
5821 procmime_encode_content(mimetext, encoding);
5823 /* append attachment parts */
5824 if (compose_use_attach(compose) && attach_parts) {
5825 MimeInfo *mimempart;
5826 gchar *boundary = NULL;
5827 mimempart = procmime_mimeinfo_new();
5828 mimempart->content = MIMECONTENT_EMPTY;
5829 mimempart->type = MIMETYPE_MULTIPART;
5830 mimempart->subtype = g_strdup("mixed");
5834 boundary = generate_mime_boundary(NULL);
5835 } while (strstr(buf, boundary) != NULL);
5837 g_hash_table_insert(mimempart->typeparameters, g_strdup("boundary"),
5840 mimetext->disposition = DISPOSITIONTYPE_INLINE;
5842 g_node_append(mimempart->node, mimetext->node);
5843 g_node_append(mimemsg->node, mimempart->node);
5845 if (compose_add_attachments(compose, mimempart) < 0)
5848 g_node_append(mimemsg->node, mimetext->node);
5852 if (strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) != 0) {
5853 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
5854 /* extract name and address */
5855 if (strstr(spec, " <") && strstr(spec, ">")) {
5856 from_addr = g_strdup(strrchr(spec, '<')+1);
5857 *(strrchr(from_addr, '>')) = '\0';
5858 from_name = g_strdup(spec);
5859 *(strrchr(from_name, '<')) = '\0';
5866 /* sign message if sending */
5867 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5868 privacy_system_can_sign(compose->privacy_system))
5869 if (!privacy_sign(compose->privacy_system, mimemsg,
5870 compose->account, from_addr)) {
5878 if (compose->use_encryption) {
5879 if (compose->encdata != NULL &&
5880 strcmp(compose->encdata, "_DONT_ENCRYPT_")) {
5882 /* First, write an unencrypted copy and save it to outbox, if
5883 * user wants that. */
5884 if (compose->account->save_encrypted_as_clear_text) {
5885 debug_print("saving sent message unencrypted...\n");
5886 FILE *tmpfp = get_tmpfile_in_dir(get_mime_tmp_dir(), &tmp_enc_file);
5890 /* fp now points to a file with headers written,
5891 * let's make a copy. */
5893 content = file_read_stream_to_str(fp);
5895 str_write_to_file(content, tmp_enc_file);
5898 /* Now write the unencrypted body. */
5899 if ((tmpfp = g_fopen(tmp_enc_file, "a")) != NULL) {
5900 procmime_write_mimeinfo(mimemsg, tmpfp);
5903 outbox = folder_find_item_from_identifier(compose_get_save_to(compose));
5905 outbox = folder_get_default_outbox();
5907 procmsg_save_to_outbox(outbox, tmp_enc_file, TRUE);
5908 claws_unlink(tmp_enc_file);
5910 g_warning("Can't open file '%s'", tmp_enc_file);
5913 g_warning("couldn't get tempfile");
5916 if (!privacy_encrypt(compose->privacy_system, mimemsg, compose->encdata)) {
5917 debug_print("Couldn't encrypt mime structure: %s.\n",
5918 privacy_get_error());
5919 return COMPOSE_QUEUE_ERROR_ENCRYPT_FAILED;
5924 procmime_write_mimeinfo(mimemsg, fp);
5926 procmime_mimeinfo_free_all(&mimemsg);
5931 static gint compose_write_body_to_file(Compose *compose, const gchar *file)
5933 GtkTextBuffer *buffer;
5934 GtkTextIter start, end;
5939 if ((fp = g_fopen(file, "wb")) == NULL) {
5940 FILE_OP_ERROR(file, "fopen");
5944 /* chmod for security */
5945 if (change_file_mode_rw(fp, file) < 0) {
5946 FILE_OP_ERROR(file, "chmod");
5947 g_warning("can't change file mode");
5950 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5951 gtk_text_buffer_get_start_iter(buffer, &start);
5952 gtk_text_buffer_get_end_iter(buffer, &end);
5953 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5955 chars = conv_codeset_strdup
5956 (tmp, CS_INTERNAL, conv_get_locale_charset_str());
5965 len = strlen(chars);
5966 if (fwrite(chars, sizeof(gchar), len, fp) != len) {
5967 FILE_OP_ERROR(file, "fwrite");
5976 if (fclose(fp) == EOF) {
5977 FILE_OP_ERROR(file, "fclose");
5984 static gint compose_remove_reedit_target(Compose *compose, gboolean force)
5987 MsgInfo *msginfo = compose->targetinfo;
5989 cm_return_val_if_fail(compose->mode == COMPOSE_REEDIT, -1);
5990 if (!msginfo) return -1;
5992 if (!force && MSG_IS_LOCKED(msginfo->flags))
5995 item = msginfo->folder;
5996 cm_return_val_if_fail(item != NULL, -1);
5998 if (procmsg_msg_exist(msginfo) &&
5999 (folder_has_parent_of_type(item, F_QUEUE) ||
6000 folder_has_parent_of_type(item, F_DRAFT)
6001 || msginfo == compose->autosaved_draft)) {
6002 if (folder_item_remove_msg(item, msginfo->msgnum) < 0) {
6003 g_warning("can't remove the old message");
6006 debug_print("removed reedit target %d\n", msginfo->msgnum);
6013 static void compose_remove_draft(Compose *compose)
6016 MsgInfo *msginfo = compose->targetinfo;
6017 drafts = account_get_special_folder(compose->account, F_DRAFT);
6019 if (procmsg_msg_exist(msginfo)) {
6020 folder_item_remove_msg(drafts, msginfo->msgnum);
6025 ComposeQueueResult compose_queue(Compose *compose, gint *msgnum, FolderItem **item, gchar **msgpath,
6026 gboolean remove_reedit_target)
6028 return compose_queue_sub (compose, msgnum, item, msgpath, FALSE, remove_reedit_target);
6031 static gboolean compose_warn_encryption(Compose *compose)
6033 const gchar *warning = privacy_get_encrypt_warning(compose->privacy_system);
6034 AlertValue val = G_ALERTALTERNATE;
6036 if (warning == NULL)
6039 val = alertpanel_full(_("Encryption warning"), warning,
6040 GTK_STOCK_CANCEL, _("C_ontinue"), NULL, ALERTFOCUS_SECOND,
6041 TRUE, NULL, ALERT_WARNING);
6042 if (val & G_ALERTDISABLE) {
6043 val &= ~G_ALERTDISABLE;
6044 if (val == G_ALERTALTERNATE)
6045 privacy_inhibit_encrypt_warning(compose->privacy_system,
6049 if (val == G_ALERTALTERNATE) {
6056 static ComposeQueueResult compose_queue_sub(Compose *compose, gint *msgnum, FolderItem **item,
6057 gchar **msgpath, gboolean perform_checks,
6058 gboolean remove_reedit_target)
6065 PrefsAccount *mailac = NULL, *newsac = NULL;
6066 gboolean err = FALSE;
6068 debug_print("queueing message...\n");
6069 cm_return_val_if_fail(compose->account != NULL, -1);
6071 if (compose_check_entries(compose, perform_checks) == FALSE) {
6072 if (compose->batch) {
6073 gtk_widget_show_all(compose->window);
6075 return COMPOSE_QUEUE_ERROR_NO_MSG;
6078 if (!compose->to_list && !compose->newsgroup_list) {
6079 g_warning("can't get recipient list.");
6080 return COMPOSE_QUEUE_ERROR_NO_MSG;
6083 if (compose->to_list) {
6084 if (compose->account->protocol != A_NNTP)
6085 mailac = compose->account;
6086 else if (cur_account && cur_account->protocol != A_NNTP)
6087 mailac = cur_account;
6088 else if (!(mailac = compose_current_mail_account())) {
6089 alertpanel_error(_("No account for sending mails available!"));
6090 return COMPOSE_QUEUE_ERROR_NO_MSG;
6094 if (compose->newsgroup_list) {
6095 if (compose->account->protocol == A_NNTP)
6096 newsac = compose->account;
6098 alertpanel_error(_("Selected account isn't NNTP: Posting is impossible."));
6099 return COMPOSE_QUEUE_ERROR_NO_MSG;
6103 /* write queue header */
6104 tmp = g_strdup_printf("%s%cqueue.%p%08x", get_tmp_dir(),
6105 G_DIR_SEPARATOR, compose, (guint) rand());
6106 debug_print("queuing to %s\n", tmp);
6107 if ((fp = g_fopen(tmp, "w+b")) == NULL) {
6108 FILE_OP_ERROR(tmp, "fopen");
6110 return COMPOSE_QUEUE_ERROR_WITH_ERRNO;
6113 if (change_file_mode_rw(fp, tmp) < 0) {
6114 FILE_OP_ERROR(tmp, "chmod");
6115 g_warning("can't change file mode");
6118 /* queueing variables */
6119 err |= (fprintf(fp, "AF:\n") < 0);
6120 err |= (fprintf(fp, "NF:0\n") < 0);
6121 err |= (fprintf(fp, "PS:10\n") < 0);
6122 err |= (fprintf(fp, "SRH:1\n") < 0);
6123 err |= (fprintf(fp, "SFN:\n") < 0);
6124 err |= (fprintf(fp, "DSR:\n") < 0);
6126 err |= (fprintf(fp, "MID:<%s>\n", compose->msgid) < 0);
6128 err |= (fprintf(fp, "MID:\n") < 0);
6129 err |= (fprintf(fp, "CFG:\n") < 0);
6130 err |= (fprintf(fp, "PT:0\n") < 0);
6131 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
6132 err |= (fprintf(fp, "RQ:\n") < 0);
6134 err |= (fprintf(fp, "SSV:%s\n", mailac->smtp_server) < 0);
6136 err |= (fprintf(fp, "SSV:\n") < 0);
6138 err |= (fprintf(fp, "NSV:%s\n", newsac->nntp_server) < 0);
6140 err |= (fprintf(fp, "NSV:\n") < 0);
6141 err |= (fprintf(fp, "SSH:\n") < 0);
6142 /* write recepient list */
6143 if (compose->to_list) {
6144 err |= (fprintf(fp, "R:<%s>", (gchar *)compose->to_list->data) < 0);
6145 for (cur = compose->to_list->next; cur != NULL;
6147 err |= (fprintf(fp, ",<%s>", (gchar *)cur->data) < 0);
6148 err |= (fprintf(fp, "\n") < 0);
6150 /* write newsgroup list */
6151 if (compose->newsgroup_list) {
6152 err |= (fprintf(fp, "NG:") < 0);
6153 err |= (fprintf(fp, "%s", (gchar *)compose->newsgroup_list->data) < 0);
6154 for (cur = compose->newsgroup_list->next; cur != NULL; cur = cur->next)
6155 err |= (fprintf(fp, ",%s", (gchar *)cur->data) < 0);
6156 err |= (fprintf(fp, "\n") < 0);
6158 /* Sylpheed account IDs */
6160 err |= (fprintf(fp, "MAID:%d\n", mailac->account_id) < 0);
6162 err |= (fprintf(fp, "NAID:%d\n", newsac->account_id) < 0);
6165 if (compose->privacy_system != NULL) {
6166 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
6167 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
6168 if (compose->use_encryption) {
6169 if (!compose_warn_encryption(compose)) {
6173 return COMPOSE_QUEUE_ERROR_NO_MSG;
6175 if (mailac && mailac->encrypt_to_self) {
6176 GSList *tmp_list = g_slist_copy(compose->to_list);
6177 tmp_list = g_slist_append(tmp_list, compose->account->address);
6178 compose->encdata = privacy_get_encrypt_data(compose->privacy_system, tmp_list);
6179 g_slist_free(tmp_list);
6181 compose->encdata = privacy_get_encrypt_data(compose->privacy_system, compose->to_list);
6183 if (compose->encdata != NULL) {
6184 if (strcmp(compose->encdata, "_DONT_ENCRYPT_")) {
6185 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
6186 err |= (fprintf(fp, "X-Claws-Encrypt-Data:%s\n",
6187 compose->encdata) < 0);
6188 } /* else we finally dont want to encrypt */
6190 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
6191 /* and if encdata was null, it means there's been a problem in
6194 g_warning("failed to write queue message");
6198 return COMPOSE_QUEUE_ERROR_NO_ENCRYPTION_KEY;
6203 /* Save copy folder */
6204 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
6205 gchar *savefolderid;
6207 savefolderid = compose_get_save_to(compose);
6208 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
6209 g_free(savefolderid);
6211 /* Save copy folder */
6212 if (compose->return_receipt) {
6213 err |= (fprintf(fp, "RRCPT:1\n") < 0);
6215 /* Message-ID of message replying to */
6216 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
6217 gchar *folderid = NULL;
6219 if (compose->replyinfo->folder)
6220 folderid = folder_item_get_identifier(compose->replyinfo->folder);
6221 if (folderid == NULL)
6222 folderid = g_strdup("NULL");
6224 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
6227 /* Message-ID of message forwarding to */
6228 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
6229 gchar *folderid = NULL;
6231 if (compose->fwdinfo->folder)
6232 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
6233 if (folderid == NULL)
6234 folderid = g_strdup("NULL");
6236 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
6240 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
6241 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
6243 /* end of headers */
6244 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
6246 if (compose->redirect_filename != NULL) {
6247 if (compose_redirect_write_to_file(compose, fp) < 0) {
6251 return COMPOSE_QUEUE_ERROR_WITH_ERRNO;
6255 if ((result = compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_SEND, TRUE)) < 0) {
6263 g_warning("failed to write queue message");
6267 return COMPOSE_QUEUE_ERROR_WITH_ERRNO;
6269 if (fclose(fp) == EOF) {
6270 FILE_OP_ERROR(tmp, "fclose");
6273 return COMPOSE_QUEUE_ERROR_WITH_ERRNO;
6276 if (item && *item) {
6279 queue = account_get_special_folder(compose->account, F_QUEUE);
6282 g_warning("can't find queue folder");
6285 return COMPOSE_QUEUE_ERROR_NO_MSG;
6287 folder_item_scan(queue);
6288 if ((num = folder_item_add_msg(queue, tmp, NULL, FALSE)) < 0) {
6289 g_warning("can't queue the message");
6292 return COMPOSE_QUEUE_ERROR_NO_MSG;
6295 if (msgpath == NULL) {
6301 if (compose->mode == COMPOSE_REEDIT && remove_reedit_target) {
6302 compose_remove_reedit_target(compose, FALSE);
6305 if ((msgnum != NULL) && (item != NULL)) {
6310 return COMPOSE_QUEUE_SUCCESS;
6313 static int compose_add_attachments(Compose *compose, MimeInfo *parent)
6316 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
6321 GError *error = NULL;
6326 gchar *type, *subtype;
6327 GtkTreeModel *model;
6330 model = gtk_tree_view_get_model(tree_view);
6332 if (!gtk_tree_model_get_iter_first(model, &iter))
6335 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
6337 if (!is_file_exist(ainfo->file)) {
6338 gchar *msg = g_strdup_printf(_("Attachment %s doesn't exist anymore. Ignore?"), ainfo->file);
6339 AlertValue val = alertpanel_full(_("Warning"), msg,
6340 _("Cancel sending"), _("Ignore attachment"), NULL,
6341 ALERTFOCUS_FIRST, FALSE, NULL, ALERT_WARNING);
6343 if (val == G_ALERTDEFAULT) {
6349 f = g_file_new_for_path(ainfo->file);
6350 fi = g_file_query_info(f, "standard::size",
6351 G_FILE_QUERY_INFO_NONE, NULL, &error);
6352 if (error != NULL) {
6353 g_warning(error->message);
6354 g_error_free(error);
6358 size = g_file_info_get_size(fi);
6362 if (g_stat(ainfo->file, &statbuf) < 0)
6364 size = statbuf.st_size;
6367 mimepart = procmime_mimeinfo_new();
6368 mimepart->content = MIMECONTENT_FILE;
6369 mimepart->data.filename = g_strdup(ainfo->file);
6370 mimepart->tmp = FALSE; /* or we destroy our attachment */
6371 mimepart->offset = 0;
6372 mimepart->length = size;
6374 type = g_strdup(ainfo->content_type);
6376 if (!strchr(type, '/')) {
6378 type = g_strdup("application/octet-stream");
6381 subtype = strchr(type, '/') + 1;
6382 *(subtype - 1) = '\0';
6383 mimepart->type = procmime_get_media_type(type);
6384 mimepart->subtype = g_strdup(subtype);
6387 if (mimepart->type == MIMETYPE_MESSAGE &&
6388 !g_ascii_strcasecmp(mimepart->subtype, "rfc822")) {
6389 mimepart->disposition = DISPOSITIONTYPE_INLINE;
6390 } else if (mimepart->type == MIMETYPE_TEXT) {
6391 if (!ainfo->name && g_ascii_strcasecmp(mimepart->subtype, "plain")) {
6392 /* Text parts with no name come from multipart/alternative
6393 * forwards. Make sure the recipient won't look at the
6394 * original HTML part by mistake. */
6395 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6396 ainfo->name = g_strdup_printf(_("Original %s part"),
6400 g_hash_table_insert(mimepart->typeparameters,
6401 g_strdup("charset"), g_strdup(ainfo->charset));
6403 if (ainfo->name && mimepart->type != MIMETYPE_MESSAGE) {
6404 if (mimepart->type == MIMETYPE_APPLICATION &&
6405 !strcmp2(mimepart->subtype, "octet-stream"))
6406 g_hash_table_insert(mimepart->typeparameters,
6407 g_strdup("name"), g_strdup(ainfo->name));
6408 g_hash_table_insert(mimepart->dispositionparameters,
6409 g_strdup("filename"), g_strdup(ainfo->name));
6410 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6413 if (mimepart->type == MIMETYPE_MESSAGE
6414 || mimepart->type == MIMETYPE_MULTIPART)
6415 ainfo->encoding = ENC_BINARY;
6416 else if (compose->use_signing) {
6417 if (ainfo->encoding == ENC_7BIT)
6418 ainfo->encoding = ENC_QUOTED_PRINTABLE;
6419 else if (ainfo->encoding == ENC_8BIT)
6420 ainfo->encoding = ENC_BASE64;
6423 procmime_encode_content(mimepart, ainfo->encoding);
6425 g_node_append(parent->node, mimepart->node);
6426 } while (gtk_tree_model_iter_next(model, &iter));
6431 static gchar *compose_quote_list_of_addresses(gchar *str)
6433 GSList *list = NULL, *item = NULL;
6434 gchar *qname = NULL, *faddr = NULL, *result = NULL;
6436 list = address_list_append_with_comments(list, str);
6437 for (item = list; item != NULL; item = item->next) {
6438 gchar *spec = item->data;
6439 gchar *endofname = strstr(spec, " <");
6440 if (endofname != NULL) {
6443 QUOTE_IF_REQUIRED_NORMAL(qname, spec, return NULL);
6444 qqname = escape_internal_quotes(qname, '"');
6446 if (*qname != *spec || qqname != qname) { /* has been quoted, compute new */
6447 gchar *addr = g_strdup(endofname);
6448 gchar *name = (qqname != qname)? qqname: g_strdup(qname);
6449 faddr = g_strconcat(name, addr, NULL);
6452 debug_print("new auto-quoted address: '%s'\n", faddr);
6456 result = g_strdup((faddr != NULL)? faddr: spec);
6458 result = g_strconcat(result,
6460 (faddr != NULL)? faddr: spec,
6463 if (faddr != NULL) {
6468 slist_free_strings_full(list);
6473 #define IS_IN_CUSTOM_HEADER(header) \
6474 (compose->account->add_customhdr && \
6475 custom_header_find(compose->account->customhdr_list, header) != NULL)
6477 static const gchar *compose_untranslated_header_name(gchar *header_name)
6479 /* return the untranslated header name, if header_name is a known
6480 header name, in either its translated or untranslated form, with
6481 or without trailing colon. otherwise, returns header_name. */
6482 gchar *translated_header_name;
6483 gchar *translated_header_name_wcolon;
6484 const gchar *untranslated_header_name;
6485 const gchar *untranslated_header_name_wcolon;
6488 cm_return_val_if_fail(header_name != NULL, NULL);
6490 for (i = 0; HEADERS[i].header_name != NULL; i++) {
6491 untranslated_header_name = HEADERS[i].header_name;
6492 untranslated_header_name_wcolon = HEADERS[i].header_name_w_colon;
6494 translated_header_name = gettext(untranslated_header_name);
6495 translated_header_name_wcolon = gettext(untranslated_header_name_wcolon);
6497 if (!strcmp(header_name, untranslated_header_name) ||
6498 !strcmp(header_name, translated_header_name)) {
6499 return untranslated_header_name;
6501 if (!strcmp(header_name, untranslated_header_name_wcolon) ||
6502 !strcmp(header_name, translated_header_name_wcolon)) {
6503 return untranslated_header_name_wcolon;
6507 debug_print("compose_untranslated_header_name: unknown header '%s'\n", header_name);
6511 static void compose_add_headerfield_from_headerlist(Compose *compose,
6513 const gchar *fieldname,
6514 const gchar *seperator)
6516 gchar *str, *fieldname_w_colon;
6517 gboolean add_field = FALSE;
6519 ComposeHeaderEntry *headerentry;
6520 const gchar *headerentryname;
6521 const gchar *trans_fieldname;
6524 if (IS_IN_CUSTOM_HEADER(fieldname))
6527 debug_print("Adding %s-fields\n", fieldname);
6529 fieldstr = g_string_sized_new(64);
6531 fieldname_w_colon = g_strconcat(fieldname, ":", NULL);
6532 trans_fieldname = prefs_common_translated_header_name(fieldname_w_colon);
6534 for (list = compose->header_list; list; list = list->next) {
6535 headerentry = ((ComposeHeaderEntry *)list->data);
6536 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
6538 if (!g_utf8_collate(trans_fieldname, headerentryname)) {
6539 gchar * ustr = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
6541 str = compose_quote_list_of_addresses(ustr);
6543 if (str != NULL && str[0] != '\0') {
6545 g_string_append(fieldstr, seperator);
6546 g_string_append(fieldstr, str);
6555 buf = g_new0(gchar, fieldstr->len * 4 + 256);
6556 compose_convert_header
6557 (compose, buf, fieldstr->len * 4 + 256, fieldstr->str,
6558 strlen(fieldname) + 2, TRUE);
6559 g_string_append_printf(header, "%s: %s\n", fieldname, buf);
6563 g_free(fieldname_w_colon);
6564 g_string_free(fieldstr, TRUE);
6569 static gchar *compose_get_manual_headers_info(Compose *compose)
6571 GString *sh_header = g_string_new(" ");
6573 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6575 for (list = compose->header_list; list; list = list->next) {
6576 ComposeHeaderEntry *headerentry;
6579 gchar *headername_wcolon;
6580 const gchar *headername_trans;
6582 gboolean standard_header = FALSE;
6584 headerentry = ((ComposeHeaderEntry *)list->data);
6586 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6588 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6593 if (!strstr(tmp, ":")) {
6594 headername_wcolon = g_strconcat(tmp, ":", NULL);
6595 headername = g_strdup(tmp);
6597 headername_wcolon = g_strdup(tmp);
6598 headername = g_strdup(strtok(tmp, ":"));
6602 string = std_headers;
6603 while (*string != NULL) {
6604 headername_trans = prefs_common_translated_header_name(*string);
6605 if (!strcmp(headername_trans, headername_wcolon))
6606 standard_header = TRUE;
6609 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
6610 g_string_append_printf(sh_header, "%s ", headername);
6612 g_free(headername_wcolon);
6614 g_string_truncate(sh_header, strlen(sh_header->str) - 1); /* remove last space */
6615 return g_string_free(sh_header, FALSE);
6618 static gchar *compose_get_header(Compose *compose)
6620 gchar date[RFC822_DATE_BUFFSIZE];
6621 gchar buf[BUFFSIZE];
6622 const gchar *entry_str;
6626 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6628 gchar *from_name = NULL, *from_address = NULL;
6631 cm_return_val_if_fail(compose->account != NULL, NULL);
6632 cm_return_val_if_fail(compose->account->address != NULL, NULL);
6634 header = g_string_sized_new(64);
6637 if (prefs_common.hide_timezone)
6638 get_rfc822_date_hide_tz(date, sizeof(date));
6640 get_rfc822_date(date, sizeof(date));
6641 g_string_append_printf(header, "Date: %s\n", date);
6645 if (compose->account->name && *compose->account->name) {
6647 QUOTE_IF_REQUIRED(buf, compose->account->name);
6648 tmp = g_strdup_printf("%s <%s>",
6649 buf, compose->account->address);
6651 tmp = g_strdup_printf("%s",
6652 compose->account->address);
6654 if (!strcmp(gtk_entry_get_text(GTK_ENTRY(compose->from_name)), tmp)
6655 || strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) == 0) {
6657 from_name = compose->account->name ? g_strdup(compose->account->name):NULL;
6658 from_address = g_strdup(compose->account->address);
6660 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
6661 /* extract name and address */
6662 if (strstr(spec, " <") && strstr(spec, ">")) {
6663 from_address = g_strdup(strrchr(spec, '<')+1);
6664 *(strrchr(from_address, '>')) = '\0';
6665 from_name = g_strdup(spec);
6666 *(strrchr(from_name, '<')) = '\0';
6669 from_address = g_strdup(spec);
6676 if (from_name && *from_name) {
6678 compose_convert_header
6679 (compose, buf, sizeof(buf), from_name,
6680 strlen("From: "), TRUE);
6681 QUOTE_IF_REQUIRED(name, buf);
6682 qname = escape_internal_quotes(name, '"');
6684 g_string_append_printf(header, "From: %s <%s>\n",
6685 qname, from_address);
6686 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To") &&
6687 compose->return_receipt) {
6688 compose_convert_header(compose, buf, sizeof(buf), from_name,
6689 strlen("Disposition-Notification-To: "),
6691 g_string_append_printf(header, "Disposition-Notification-To: %s <%s>\n", buf, from_address);
6696 g_string_append_printf(header, "From: %s\n", from_address);
6697 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To") &&
6698 compose->return_receipt)
6699 g_string_append_printf(header, "Disposition-Notification-To: %s\n", from_address);
6703 g_free(from_address);
6706 compose_add_headerfield_from_headerlist(compose, header, "To", ", ");
6709 compose_add_headerfield_from_headerlist(compose, header, "Newsgroups", ",");
6712 compose_add_headerfield_from_headerlist(compose, header, "Cc", ", ");
6716 * If this account is a NNTP account remove Bcc header from
6717 * message body since it otherwise will be publicly shown
6719 if (compose->account->protocol != A_NNTP)
6720 compose_add_headerfield_from_headerlist(compose, header, "Bcc", ", ");
6723 str = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
6725 if (*str != '\0' && !IS_IN_CUSTOM_HEADER("Subject")) {
6728 compose_convert_header(compose, buf, sizeof(buf), str,
6729 strlen("Subject: "), FALSE);
6730 g_string_append_printf(header, "Subject: %s\n", buf);
6736 if (compose->msgid != NULL && strlen(compose->msgid) > 0) {
6737 g_string_append_printf(header, "Message-ID: <%s>\n",
6741 if (compose->remove_references == FALSE) {
6743 if (compose->inreplyto && compose->to_list)
6744 g_string_append_printf(header, "In-Reply-To: <%s>\n", compose->inreplyto);
6747 if (compose->references)
6748 g_string_append_printf(header, "References: %s\n", compose->references);
6752 compose_add_headerfield_from_headerlist(compose, header, "Followup-To", ",");
6755 compose_add_headerfield_from_headerlist(compose, header, "Reply-To", ", ");
6758 if (compose->account->organization &&
6759 strlen(compose->account->organization) &&
6760 !IS_IN_CUSTOM_HEADER("Organization")) {
6761 compose_convert_header(compose, buf, sizeof(buf),
6762 compose->account->organization,
6763 strlen("Organization: "), FALSE);
6764 g_string_append_printf(header, "Organization: %s\n", buf);
6767 /* Program version and system info */
6768 if (compose->account->gen_xmailer &&
6769 g_slist_length(compose->to_list) && !IS_IN_CUSTOM_HEADER("X-Mailer") &&
6770 !compose->newsgroup_list) {
6771 g_string_append_printf(header, "X-Mailer: %s (GTK+ %d.%d.%d; %s)\n",
6773 gtk_major_version, gtk_minor_version, gtk_micro_version,
6776 if (compose->account->gen_xmailer &&
6777 g_slist_length(compose->newsgroup_list) && !IS_IN_CUSTOM_HEADER("X-Newsreader")) {
6778 g_string_append_printf(header, "X-Newsreader: %s (GTK+ %d.%d.%d; %s)\n",
6780 gtk_major_version, gtk_minor_version, gtk_micro_version,
6784 /* custom headers */
6785 if (compose->account->add_customhdr) {
6788 for (cur = compose->account->customhdr_list; cur != NULL;
6790 CustomHeader *chdr = (CustomHeader *)cur->data;
6792 if (custom_header_is_allowed(chdr->name)
6793 && chdr->value != NULL
6794 && *(chdr->value) != '\0') {
6795 compose_convert_header
6796 (compose, buf, sizeof(buf),
6798 strlen(chdr->name) + 2, FALSE);
6799 g_string_append_printf(header, "%s: %s\n", chdr->name, buf);
6804 /* Automatic Faces and X-Faces */
6805 if (get_account_xface (buf, sizeof(buf), compose->account->account_name) == 0) {
6806 g_string_append_printf(header, "X-Face: %s\n", buf);
6808 else if (get_default_xface (buf, sizeof(buf)) == 0) {
6809 g_string_append_printf(header, "X-Face: %s\n", buf);
6811 if (get_account_face (buf, sizeof(buf), compose->account->account_name) == 0) {
6812 g_string_append_printf(header, "Face: %s\n", buf);
6814 else if (get_default_face (buf, sizeof(buf)) == 0) {
6815 g_string_append_printf(header, "Face: %s\n", buf);
6819 switch (compose->priority) {
6820 case PRIORITY_HIGHEST: g_string_append_printf(header, "Importance: high\n"
6821 "X-Priority: 1 (Highest)\n");
6823 case PRIORITY_HIGH: g_string_append_printf(header, "Importance: high\n"
6824 "X-Priority: 2 (High)\n");
6826 case PRIORITY_NORMAL: break;
6827 case PRIORITY_LOW: g_string_append_printf(header, "Importance: low\n"
6828 "X-Priority: 4 (Low)\n");
6830 case PRIORITY_LOWEST: g_string_append_printf(header, "Importance: low\n"
6831 "X-Priority: 5 (Lowest)\n");
6833 default: debug_print("compose: priority unknown : %d\n",
6837 /* get special headers */
6838 for (list = compose->header_list; list; list = list->next) {
6839 ComposeHeaderEntry *headerentry;
6842 gchar *headername_wcolon;
6843 const gchar *headername_trans;
6846 gboolean standard_header = FALSE;
6848 headerentry = ((ComposeHeaderEntry *)list->data);
6850 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6852 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6857 if (!strstr(tmp, ":")) {
6858 headername_wcolon = g_strconcat(tmp, ":", NULL);
6859 headername = g_strdup(tmp);
6861 headername_wcolon = g_strdup(tmp);
6862 headername = g_strdup(strtok(tmp, ":"));
6866 entry_str = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
6867 Xstrdup_a(headervalue, entry_str, return NULL);
6868 subst_char(headervalue, '\r', ' ');
6869 subst_char(headervalue, '\n', ' ');
6870 g_strstrip(headervalue);
6871 if (*headervalue != '\0') {
6872 string = std_headers;
6873 while (*string != NULL && !standard_header) {
6874 headername_trans = prefs_common_translated_header_name(*string);
6875 /* support mixed translated and untranslated headers */
6876 if (!strcmp(headername_trans, headername_wcolon) || !strcmp(*string, headername_wcolon))
6877 standard_header = TRUE;
6880 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername)) {
6881 /* store untranslated header name */
6882 g_string_append_printf(header, "%s %s\n",
6883 compose_untranslated_header_name(headername_wcolon), headervalue);
6887 g_free(headername_wcolon);
6891 g_string_free(header, FALSE);
6896 #undef IS_IN_CUSTOM_HEADER
6898 static void compose_convert_header(Compose *compose, gchar *dest, gint len, gchar *src,
6899 gint header_len, gboolean addr_field)
6901 gchar *tmpstr = NULL;
6902 const gchar *out_codeset = NULL;
6904 cm_return_if_fail(src != NULL);
6905 cm_return_if_fail(dest != NULL);
6907 if (len < 1) return;
6909 tmpstr = g_strdup(src);
6911 subst_char(tmpstr, '\n', ' ');
6912 subst_char(tmpstr, '\r', ' ');
6915 if (!g_utf8_validate(tmpstr, -1, NULL)) {
6916 gchar *mybuf = g_malloc(strlen(tmpstr)*2 +1);
6917 conv_localetodisp(mybuf, strlen(tmpstr)*2 +1, tmpstr);
6922 codeconv_set_strict(TRUE);
6923 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6924 conv_get_charset_str(compose->out_encoding));
6925 codeconv_set_strict(FALSE);
6927 if (!dest || *dest == '\0') {
6928 gchar *test_conv_global_out = NULL;
6929 gchar *test_conv_reply = NULL;
6931 /* automatic mode. be automatic. */
6932 codeconv_set_strict(TRUE);
6934 out_codeset = conv_get_outgoing_charset_str();
6936 debug_print("trying to convert to %s\n", out_codeset);
6937 test_conv_global_out = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6940 if (!test_conv_global_out && compose->orig_charset
6941 && strcmp(compose->orig_charset, CS_US_ASCII)) {
6942 out_codeset = compose->orig_charset;
6943 debug_print("failure; trying to convert to %s\n", out_codeset);
6944 test_conv_reply = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6947 if (!test_conv_global_out && !test_conv_reply) {
6949 out_codeset = CS_INTERNAL;
6950 debug_print("finally using %s\n", out_codeset);
6952 g_free(test_conv_global_out);
6953 g_free(test_conv_reply);
6954 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6956 codeconv_set_strict(FALSE);
6961 static void compose_add_to_addressbook_cb(GtkMenuItem *menuitem, gpointer user_data)
6965 cm_return_if_fail(user_data != NULL);
6967 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(user_data)));
6968 g_strstrip(address);
6969 if (*address != '\0') {
6970 gchar *name = procheader_get_fromname(address);
6971 extract_address(address);
6972 #ifndef USE_ALT_ADDRBOOK
6973 addressbook_add_contact(name, address, NULL, NULL);
6975 debug_print("%s: %s\n", name, address);
6976 if (addressadd_selection(name, address, NULL, NULL)) {
6977 debug_print( "addressbook_add_contact - added\n" );
6984 static void compose_entry_popup_extend(GtkEntry *entry, GtkMenu *menu, gpointer user_data)
6986 GtkWidget *menuitem;
6989 cm_return_if_fail(menu != NULL);
6990 cm_return_if_fail(GTK_IS_MENU_SHELL(menu));
6992 menuitem = gtk_separator_menu_item_new();
6993 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6994 gtk_widget_show(menuitem);
6996 menuitem = gtk_menu_item_new_with_mnemonic(_("Add to address _book"));
6997 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6999 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
7000 g_strstrip(address);
7001 if (*address == '\0') {
7002 gtk_widget_set_sensitive(GTK_WIDGET(menuitem), FALSE);
7005 g_signal_connect(G_OBJECT(menuitem), "activate",
7006 G_CALLBACK(compose_add_to_addressbook_cb), entry);
7007 gtk_widget_show(menuitem);
7010 void compose_add_extra_header(gchar *header, GtkListStore *model)
7013 if (strcmp(header, "")) {
7014 COMBOBOX_ADD(model, header, COMPOSE_TO);
7018 void compose_add_extra_header_entries(GtkListStore *model)
7022 gchar buf[BUFFSIZE];
7025 if (extra_headers == NULL) {
7026 exhrc = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, "extraheaderrc", NULL);
7027 if ((exh = g_fopen(exhrc, "rb")) == NULL) {
7028 debug_print("extra headers file not found\n");
7029 goto extra_headers_done;
7031 while (fgets(buf, BUFFSIZE, exh) != NULL) {
7032 lastc = strlen(buf) - 1; /* remove trailing control chars */
7033 while (lastc >= 0 && buf[lastc] != ':')
7034 buf[lastc--] = '\0';
7035 if (lastc > 0 && buf[0] != '#' && buf[lastc] == ':') {
7036 buf[lastc] = '\0'; /* remove trailing : for comparison */
7037 if (custom_header_is_allowed(buf)) {
7039 extra_headers = g_slist_prepend(extra_headers, g_strdup(buf));
7042 g_message("disallowed extra header line: %s\n", buf);
7046 g_message("invalid extra header line: %s\n", buf);
7052 extra_headers = g_slist_prepend(extra_headers, g_strdup("")); /* end of list */
7053 extra_headers = g_slist_reverse(extra_headers);
7055 g_slist_foreach(extra_headers, (GFunc)compose_add_extra_header, (gpointer)model);
7059 static void _ldap_srv_func(gpointer data, gpointer user_data)
7061 LdapServer *server = (LdapServer *)data;
7062 gboolean *enable = (gboolean *)user_data;
7064 debug_print("%s server '%s'\n", (*enable == TRUE ? "enabling" : "disabling"), server->control->hostName);
7065 server->searchFlag = *enable;
7069 static void compose_create_header_entry(Compose *compose)
7071 gchar *headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
7078 const gchar *header = NULL;
7079 ComposeHeaderEntry *headerentry;
7080 gboolean standard_header = FALSE;
7081 GtkListStore *model;
7084 headerentry = g_new0(ComposeHeaderEntry, 1);
7086 /* Combo box model */
7087 model = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_BOOLEAN);
7088 COMBOBOX_ADD(model, prefs_common_translated_header_name("To:"),
7090 COMBOBOX_ADD(model, prefs_common_translated_header_name("Cc:"),
7092 COMBOBOX_ADD(model, prefs_common_translated_header_name("Bcc:"),
7094 COMBOBOX_ADD(model, prefs_common_translated_header_name("Newsgroups:"),
7095 COMPOSE_NEWSGROUPS);
7096 COMBOBOX_ADD(model, prefs_common_translated_header_name("Reply-To:"),
7098 COMBOBOX_ADD(model, prefs_common_translated_header_name("Followup-To:"),
7099 COMPOSE_FOLLOWUPTO);
7100 compose_add_extra_header_entries(model);
7103 combo = gtk_combo_box_new_with_model_and_entry(GTK_TREE_MODEL(model));
7104 GtkCellRenderer *cell = gtk_cell_renderer_text_new();
7105 gtk_cell_renderer_set_alignment(cell, 0.0, 0.5);
7106 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), cell, TRUE);
7107 gtk_combo_box_set_entry_text_column(GTK_COMBO_BOX(combo), 0);
7108 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
7109 g_signal_connect(G_OBJECT(gtk_bin_get_child(GTK_BIN(combo))), "grab_focus",
7110 G_CALLBACK(compose_grab_focus_cb), compose);
7111 gtk_widget_show(combo);
7113 /* Putting only the combobox child into focus chain of its parent causes
7114 * the parent to be skipped when changing focus via Tab or Shift+Tab.
7115 * This eliminates need to pres Tab twice in order to really get from the
7116 * combobox to next widget. */
7118 l = g_list_prepend(l, gtk_bin_get_child(GTK_BIN(combo)));
7119 gtk_container_set_focus_chain(GTK_CONTAINER(combo), l);
7122 gtk_table_attach(GTK_TABLE(compose->header_table), combo, 0, 1,
7123 compose->header_nextrow, compose->header_nextrow+1,
7124 GTK_SHRINK, GTK_FILL, 0, 0);
7125 if (compose->header_last && (compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN)) {
7126 const gchar *last_header_entry = gtk_entry_get_text(
7127 GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
7129 while (*string != NULL) {
7130 if (!strcmp(prefs_common_translated_header_name(*string), last_header_entry))
7131 standard_header = TRUE;
7134 if (standard_header)
7135 header = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
7137 if (!compose->header_last || !standard_header) {
7138 switch(compose->account->protocol) {
7140 header = prefs_common_translated_header_name("Newsgroups:");
7143 header = prefs_common_translated_header_name("To:");
7148 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((combo)))), header);
7150 gtk_editable_set_editable(
7151 GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((combo)))),
7152 prefs_common.type_any_header);
7154 g_signal_connect_after(G_OBJECT(gtk_bin_get_child(GTK_BIN((combo)))), "grab_focus",
7155 G_CALLBACK(compose_grab_focus_cb), compose);
7157 /* Entry field with cleanup button */
7158 button = gtk_button_new();
7159 gtk_button_set_image(GTK_BUTTON(button),
7160 gtk_image_new_from_stock(GTK_STOCK_CLEAR, GTK_ICON_SIZE_MENU));
7161 gtk_widget_show(button);
7162 CLAWS_SET_TIP(button,
7163 _("Delete entry contents"));
7164 entry = gtk_entry_new();
7165 gtk_widget_show(entry);
7166 CLAWS_SET_TIP(entry,
7167 _("Use <tab> to autocomplete from addressbook"));
7168 hbox = gtk_hbox_new (FALSE, 0);
7169 gtk_widget_show(hbox);
7170 gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, TRUE, 0);
7171 gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
7172 gtk_table_attach(GTK_TABLE(compose->header_table), hbox, 1, 2,
7173 compose->header_nextrow, compose->header_nextrow+1,
7174 GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
7176 g_signal_connect(G_OBJECT(entry), "key-press-event",
7177 G_CALLBACK(compose_headerentry_key_press_event_cb),
7179 g_signal_connect(G_OBJECT(entry), "changed",
7180 G_CALLBACK(compose_headerentry_changed_cb),
7182 g_signal_connect_after(G_OBJECT(entry), "grab_focus",
7183 G_CALLBACK(compose_grab_focus_cb), compose);
7185 g_signal_connect(G_OBJECT(button), "clicked",
7186 G_CALLBACK(compose_headerentry_button_clicked_cb),
7190 gtk_drag_dest_set(entry, GTK_DEST_DEFAULT_ALL, compose_mime_types,
7191 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7192 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7193 g_signal_connect(G_OBJECT(entry), "drag_data_received",
7194 G_CALLBACK(compose_header_drag_received_cb),
7196 g_signal_connect(G_OBJECT(entry), "drag-drop",
7197 G_CALLBACK(compose_drag_drop),
7199 g_signal_connect(G_OBJECT(entry), "populate-popup",
7200 G_CALLBACK(compose_entry_popup_extend),
7204 #ifndef PASSWORD_CRYPTO_OLD
7205 GSList *pwd_servers = addrindex_get_password_protected_ldap_servers();
7206 if (pwd_servers != NULL && master_passphrase() == NULL) {
7207 gboolean enable = FALSE;
7208 debug_print("Master passphrase not available, disabling password-protected LDAP servers for this compose window.\n");
7209 /* Temporarily disable password-protected LDAP servers,
7210 * because user did not provide a master passphrase.
7211 * We can safely enable searchFlag on all servers in this list
7212 * later, since addrindex_get_password_protected_ldap_servers()
7213 * includes servers which have it enabled initially. */
7214 g_slist_foreach(pwd_servers, _ldap_srv_func, &enable);
7215 compose->passworded_ldap_servers = pwd_servers;
7217 #endif /* PASSWORD_CRYPTO_OLD */
7218 #endif /* USE_LDAP */
7220 address_completion_register_entry(GTK_ENTRY(entry), TRUE);
7222 headerentry->compose = compose;
7223 headerentry->combo = combo;
7224 headerentry->entry = entry;
7225 headerentry->button = button;
7226 headerentry->hbox = hbox;
7227 headerentry->headernum = compose->header_nextrow;
7228 headerentry->type = PREF_NONE;
7230 compose->header_nextrow++;
7231 compose->header_last = headerentry;
7232 compose->header_list =
7233 g_slist_append(compose->header_list,
7237 static void compose_add_header_entry(Compose *compose, const gchar *header,
7238 gchar *text, ComposePrefType pref_type)
7240 ComposeHeaderEntry *last_header = compose->header_last;
7241 gchar *tmp = g_strdup(text), *email;
7242 gboolean replyto_hdr;
7244 replyto_hdr = (!strcasecmp(header,
7245 prefs_common_translated_header_name("Reply-To:")) ||
7247 prefs_common_translated_header_name("Followup-To:")) ||
7249 prefs_common_translated_header_name("In-Reply-To:")));
7251 extract_address(tmp);
7252 email = g_utf8_strdown(tmp, -1);
7254 if (replyto_hdr == FALSE &&
7255 g_hash_table_lookup(compose->email_hashtable, email) != NULL)
7257 debug_print("Ignoring duplicate address - %s %s, pref_type: %d\n",
7258 header, text, (gint) pref_type);
7264 if (!strcasecmp(header, prefs_common_translated_header_name("In-Reply-To:")))
7265 gtk_entry_set_text(GTK_ENTRY(
7266 gtk_bin_get_child(GTK_BIN(last_header->combo))), header);
7268 combobox_select_by_text(GTK_COMBO_BOX(last_header->combo), header);
7269 gtk_entry_set_text(GTK_ENTRY(last_header->entry), text);
7270 last_header->type = pref_type;
7272 if (replyto_hdr == FALSE)
7273 g_hash_table_insert(compose->email_hashtable, email,
7274 GUINT_TO_POINTER(1));
7281 static void compose_destroy_headerentry(Compose *compose,
7282 ComposeHeaderEntry *headerentry)
7284 gchar *text = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
7287 extract_address(text);
7288 email = g_utf8_strdown(text, -1);
7289 g_hash_table_remove(compose->email_hashtable, email);
7293 gtk_widget_destroy(headerentry->combo);
7294 gtk_widget_destroy(headerentry->entry);
7295 gtk_widget_destroy(headerentry->button);
7296 gtk_widget_destroy(headerentry->hbox);
7297 g_free(headerentry);
7300 static void compose_remove_header_entries(Compose *compose)
7303 for (list = compose->header_list; list; list = list->next)
7304 compose_destroy_headerentry(compose, (ComposeHeaderEntry *)list->data);
7306 compose->header_last = NULL;
7307 g_slist_free(compose->header_list);
7308 compose->header_list = NULL;
7309 compose->header_nextrow = 1;
7310 compose_create_header_entry(compose);
7313 static GtkWidget *compose_create_header(Compose *compose)
7315 GtkWidget *from_optmenu_hbox;
7316 GtkWidget *header_table_main;
7317 GtkWidget *header_scrolledwin;
7318 GtkWidget *header_table;
7320 /* parent with account selection and from header */
7321 header_table_main = gtk_table_new(2, 2, FALSE);
7322 gtk_widget_show(header_table_main);
7323 gtk_container_set_border_width(GTK_CONTAINER(header_table_main), BORDER_WIDTH);
7325 from_optmenu_hbox = compose_account_option_menu_create(compose);
7326 gtk_table_attach(GTK_TABLE(header_table_main), from_optmenu_hbox,
7327 0, 2, 0, 1, GTK_EXPAND | GTK_FILL, GTK_SHRINK, 0, 0);
7329 /* child with header labels and entries */
7330 header_scrolledwin = gtk_scrolled_window_new(NULL, NULL);
7331 gtk_widget_show(header_scrolledwin);
7332 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
7334 header_table = gtk_table_new(2, 2, FALSE);
7335 gtk_widget_show(header_table);
7336 gtk_container_set_border_width(GTK_CONTAINER(header_table), 0);
7337 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin), header_table);
7338 gtk_container_set_focus_vadjustment(GTK_CONTAINER(header_table),
7339 gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(header_scrolledwin)));
7340 gtk_viewport_set_shadow_type(GTK_VIEWPORT(gtk_bin_get_child(GTK_BIN(header_scrolledwin))), GTK_SHADOW_NONE);
7342 gtk_table_attach(GTK_TABLE(header_table_main), header_scrolledwin,
7343 0, 2, 1, 2, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 2);
7345 compose->header_table = header_table;
7346 compose->header_list = NULL;
7347 compose->header_nextrow = 0;
7349 compose_create_header_entry(compose);
7351 compose->table = NULL;
7353 return header_table_main;
7356 static gboolean popup_attach_button_pressed(GtkWidget *widget, gpointer data)
7358 Compose *compose = (Compose *)data;
7359 GdkEventButton event;
7362 event.time = gtk_get_current_event_time();
7364 return attach_button_pressed(compose->attach_clist, &event, compose);
7367 static GtkWidget *compose_create_attach(Compose *compose)
7369 GtkWidget *attach_scrwin;
7370 GtkWidget *attach_clist;
7372 GtkListStore *store;
7373 GtkCellRenderer *renderer;
7374 GtkTreeViewColumn *column;
7375 GtkTreeSelection *selection;
7377 /* attachment list */
7378 attach_scrwin = gtk_scrolled_window_new(NULL, NULL);
7379 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(attach_scrwin),
7380 GTK_POLICY_AUTOMATIC,
7381 GTK_POLICY_AUTOMATIC);
7382 gtk_widget_set_size_request(attach_scrwin, -1, 80);
7384 store = gtk_list_store_new(N_ATTACH_COLS,
7390 G_TYPE_AUTO_POINTER,
7392 attach_clist = GTK_WIDGET(gtk_tree_view_new_with_model
7393 (GTK_TREE_MODEL(store)));
7394 gtk_container_add(GTK_CONTAINER(attach_scrwin), attach_clist);
7395 g_object_unref(store);
7397 renderer = gtk_cell_renderer_text_new();
7398 column = gtk_tree_view_column_new_with_attributes
7399 (_("Mime type"), renderer, "text",
7400 COL_MIMETYPE, NULL);
7401 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7403 renderer = gtk_cell_renderer_text_new();
7404 column = gtk_tree_view_column_new_with_attributes
7405 (_("Size"), renderer, "text",
7407 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7409 renderer = gtk_cell_renderer_text_new();
7410 column = gtk_tree_view_column_new_with_attributes
7411 (_("Name"), renderer, "text",
7413 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7415 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(attach_clist),
7416 prefs_common.use_stripes_everywhere);
7417 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(attach_clist));
7418 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
7420 g_signal_connect(G_OBJECT(attach_clist), "row_activated",
7421 G_CALLBACK(attach_selected), compose);
7422 g_signal_connect(G_OBJECT(attach_clist), "button_press_event",
7423 G_CALLBACK(attach_button_pressed), compose);
7424 g_signal_connect(G_OBJECT(attach_clist), "popup-menu",
7425 G_CALLBACK(popup_attach_button_pressed), compose);
7426 g_signal_connect(G_OBJECT(attach_clist), "key_press_event",
7427 G_CALLBACK(attach_key_pressed), compose);
7430 gtk_drag_dest_set(attach_clist,
7431 GTK_DEST_DEFAULT_ALL, compose_mime_types,
7432 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7433 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7434 g_signal_connect(G_OBJECT(attach_clist), "drag_data_received",
7435 G_CALLBACK(compose_attach_drag_received_cb),
7437 g_signal_connect(G_OBJECT(attach_clist), "drag-drop",
7438 G_CALLBACK(compose_drag_drop),
7441 compose->attach_scrwin = attach_scrwin;
7442 compose->attach_clist = attach_clist;
7444 return attach_scrwin;
7447 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose);
7448 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose);
7450 static GtkWidget *compose_create_others(Compose *compose)
7453 GtkWidget *savemsg_checkbtn;
7454 GtkWidget *savemsg_combo;
7455 GtkWidget *savemsg_select;
7458 gchar *folderidentifier;
7460 /* Table for settings */
7461 table = gtk_table_new(3, 1, FALSE);
7462 gtk_container_set_border_width(GTK_CONTAINER(table), BORDER_WIDTH);
7463 gtk_widget_show(table);
7464 gtk_table_set_row_spacings(GTK_TABLE(table), VSPACING_NARROW);
7467 /* Save Message to folder */
7468 savemsg_checkbtn = gtk_check_button_new_with_label(_("Save Message to "));
7469 gtk_widget_show(savemsg_checkbtn);
7470 gtk_table_attach(GTK_TABLE(table), savemsg_checkbtn, 0, 1, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7471 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7472 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), prefs_common.savemsg);
7474 g_signal_connect(G_OBJECT(savemsg_checkbtn), "toggled",
7475 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
7477 savemsg_combo = gtk_combo_box_text_new_with_entry();
7478 compose->savemsg_checkbtn = savemsg_checkbtn;
7479 compose->savemsg_combo = savemsg_combo;
7480 gtk_widget_show(savemsg_combo);
7482 if (prefs_common.compose_save_to_history)
7483 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(savemsg_combo),
7484 prefs_common.compose_save_to_history);
7485 gtk_table_attach(GTK_TABLE(table), savemsg_combo, 1, 2, rowcount, rowcount + 1, GTK_FILL|GTK_EXPAND, GTK_SHRINK, 0, 0);
7486 gtk_widget_set_sensitive(GTK_WIDGET(savemsg_combo), prefs_common.savemsg);
7487 g_signal_connect_after(G_OBJECT(savemsg_combo), "grab_focus",
7488 G_CALLBACK(compose_grab_focus_cb), compose);
7489 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7490 folderidentifier = folder_item_get_identifier(account_get_special_folder
7491 (compose->account, F_OUTBOX));
7492 compose_set_save_to(compose, folderidentifier);
7493 g_free(folderidentifier);
7496 savemsg_select = gtkut_get_browse_file_btn(_("_Browse"));
7497 gtk_widget_show(savemsg_select);
7498 gtk_table_attach(GTK_TABLE(table), savemsg_select, 2, 3, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7499 g_signal_connect(G_OBJECT(savemsg_select), "clicked",
7500 G_CALLBACK(compose_savemsg_select_cb),
7506 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose)
7508 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo),
7509 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn)));
7512 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose)
7517 dest = foldersel_folder_sel(NULL, FOLDER_SEL_COPY, NULL, FALSE,
7518 _("Select folder to save message to"));
7521 path = folder_item_get_identifier(dest);
7523 compose_set_save_to(compose, path);
7527 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry, gboolean wrap,
7528 GdkAtom clip, GtkTextIter *insert_place);
7531 static gboolean text_clicked(GtkWidget *text, GdkEventButton *event,
7535 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
7537 if (event->button == 3) {
7539 GtkTextIter sel_start, sel_end;
7540 gboolean stuff_selected;
7542 /* move the cursor to allow GtkAspell to check the word
7543 * under the mouse */
7544 if (event->x && event->y) {
7545 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7546 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7548 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7551 GtkTextMark *mark = gtk_text_buffer_get_insert(buffer);
7552 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7555 stuff_selected = gtk_text_buffer_get_selection_bounds(
7557 &sel_start, &sel_end);
7559 gtk_text_buffer_place_cursor (buffer, &iter);
7560 /* reselect stuff */
7562 && gtk_text_iter_in_range(&iter, &sel_start, &sel_end)) {
7563 gtk_text_buffer_select_range(buffer,
7564 &sel_start, &sel_end);
7566 return FALSE; /* pass the event so that the right-click goes through */
7569 if (event->button == 2) {
7574 /* get the middle-click position to paste at the correct place */
7575 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7576 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7578 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7581 entry_paste_clipboard(compose, text,
7582 prefs_common.linewrap_pastes,
7583 GDK_SELECTION_PRIMARY, &iter);
7591 static void compose_spell_menu_changed(void *data)
7593 Compose *compose = (Compose *)data;
7595 GtkWidget *menuitem;
7596 GtkWidget *parent_item;
7597 GtkMenu *menu = GTK_MENU(gtk_menu_new());
7600 if (compose->gtkaspell == NULL)
7603 parent_item = gtk_ui_manager_get_widget(compose->ui_manager,
7604 "/Menu/Spelling/Options");
7606 /* setting the submenu removes /Spelling/Options from the factory
7607 * so we need to save it */
7609 if (parent_item == NULL) {
7610 parent_item = compose->aspell_options_menu;
7611 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), NULL);
7613 compose->aspell_options_menu = parent_item;
7615 spell_menu = gtkaspell_make_config_menu(compose->gtkaspell);
7617 spell_menu = g_slist_reverse(spell_menu);
7618 for (items = spell_menu;
7619 items; items = items->next) {
7620 menuitem = GTK_WIDGET(GTK_MENU_ITEM(items->data));
7621 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), GTK_WIDGET(menuitem));
7622 gtk_widget_show(GTK_WIDGET(menuitem));
7624 g_slist_free(spell_menu);
7626 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), GTK_WIDGET(menu));
7627 gtk_widget_show(parent_item);
7630 static void compose_dict_changed(void *data)
7632 Compose *compose = (Compose *) data;
7634 if(!compose->gtkaspell)
7636 if(compose->gtkaspell->recheck_when_changing_dict == FALSE)
7639 gtkaspell_highlight_all(compose->gtkaspell);
7640 claws_spell_entry_recheck_all(CLAWS_SPELL_ENTRY(compose->subject_entry));
7644 static gboolean compose_popup_menu(GtkWidget *widget, gpointer data)
7646 Compose *compose = (Compose *)data;
7647 GdkEventButton event;
7650 event.time = gtk_get_current_event_time();
7654 return text_clicked(compose->text, &event, compose);
7657 static gboolean compose_force_window_origin = TRUE;
7658 static Compose *compose_create(PrefsAccount *account,
7667 GtkWidget *handlebox;
7669 GtkWidget *notebook;
7671 GtkWidget *attach_hbox;
7672 GtkWidget *attach_lab1;
7673 GtkWidget *attach_lab2;
7678 GtkWidget *subject_hbox;
7679 GtkWidget *subject_frame;
7680 GtkWidget *subject_entry;
7684 GtkWidget *edit_vbox;
7685 GtkWidget *ruler_hbox;
7687 GtkWidget *scrolledwin;
7689 GtkTextBuffer *buffer;
7690 GtkClipboard *clipboard;
7692 UndoMain *undostruct;
7694 GtkWidget *popupmenu;
7695 GtkWidget *tmpl_menu;
7696 GtkActionGroup *action_group = NULL;
7699 GtkAspell * gtkaspell = NULL;
7702 static GdkGeometry geometry;
7704 cm_return_val_if_fail(account != NULL, NULL);
7706 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_DEFAULT_HEADER_BG],
7707 &default_header_bgcolor);
7708 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_DEFAULT_HEADER],
7709 &default_header_color);
7711 debug_print("Creating compose window...\n");
7712 compose = g_new0(Compose, 1);
7714 compose->batch = batch;
7715 compose->account = account;
7716 compose->folder = folder;
7718 compose->mutex = cm_mutex_new();
7719 compose->set_cursor_pos = -1;
7721 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose");
7723 gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
7724 gtk_widget_set_size_request(window, prefs_common.compose_width,
7725 prefs_common.compose_height);
7727 if (!geometry.max_width) {
7728 geometry.max_width = gdk_screen_width();
7729 geometry.max_height = gdk_screen_height();
7732 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7733 &geometry, GDK_HINT_MAX_SIZE);
7734 if (!geometry.min_width) {
7735 geometry.min_width = 600;
7736 geometry.min_height = 440;
7738 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7739 &geometry, GDK_HINT_MIN_SIZE);
7741 #ifndef GENERIC_UMPC
7742 if (compose_force_window_origin)
7743 gtk_window_move(GTK_WINDOW(window), prefs_common.compose_x,
7744 prefs_common.compose_y);
7746 g_signal_connect(G_OBJECT(window), "delete_event",
7747 G_CALLBACK(compose_delete_cb), compose);
7748 MANAGE_WINDOW_SIGNALS_CONNECT(window);
7749 gtk_widget_realize(window);
7751 gtkut_widget_set_composer_icon(window);
7753 vbox = gtk_vbox_new(FALSE, 0);
7754 gtk_container_add(GTK_CONTAINER(window), vbox);
7756 compose->ui_manager = gtk_ui_manager_new();
7757 action_group = cm_menu_create_action_group_full(compose->ui_manager,"Menu", compose_entries,
7758 G_N_ELEMENTS(compose_entries), (gpointer)compose);
7759 gtk_action_group_add_toggle_actions(action_group, compose_toggle_entries,
7760 G_N_ELEMENTS(compose_toggle_entries), (gpointer)compose);
7761 gtk_action_group_add_radio_actions(action_group, compose_radio_rm_entries,
7762 G_N_ELEMENTS(compose_radio_rm_entries), COMPOSE_REPLY, G_CALLBACK(compose_reply_change_mode_cb), (gpointer)compose);
7763 gtk_action_group_add_radio_actions(action_group, compose_radio_prio_entries,
7764 G_N_ELEMENTS(compose_radio_prio_entries), PRIORITY_NORMAL, G_CALLBACK(compose_set_priority_cb), (gpointer)compose);
7765 gtk_action_group_add_radio_actions(action_group, compose_radio_enc_entries,
7766 G_N_ELEMENTS(compose_radio_enc_entries), C_AUTO, G_CALLBACK(compose_set_encoding_cb), (gpointer)compose);
7768 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Menu", NULL, GTK_UI_MANAGER_MENUBAR)
7770 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Message", "Message", GTK_UI_MANAGER_MENU)
7771 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Edit", "Edit", GTK_UI_MANAGER_MENU)
7773 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Spelling", "Spelling", GTK_UI_MANAGER_MENU)
7775 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Options", "Options", GTK_UI_MANAGER_MENU)
7776 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Tools", "Tools", GTK_UI_MANAGER_MENU)
7777 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Help", "Help", GTK_UI_MANAGER_MENU)
7780 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Send", "Message/Send", GTK_UI_MANAGER_MENUITEM)
7781 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "SendLater", "Message/SendLater", GTK_UI_MANAGER_MENUITEM)
7782 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator1", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7783 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "AttachFile", "Message/AttachFile", GTK_UI_MANAGER_MENUITEM)
7784 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertFile", "Message/InsertFile", GTK_UI_MANAGER_MENUITEM)
7785 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertSig", "Message/InsertSig", GTK_UI_MANAGER_MENUITEM)
7786 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "ReplaceSig", "Message/ReplaceSig", GTK_UI_MANAGER_MENUITEM)
7787 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator2", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7788 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Save", "Message/Save", GTK_UI_MANAGER_MENUITEM)
7789 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator3", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7790 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Print", "Message/Print", GTK_UI_MANAGER_MENUITEM)
7791 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator4", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7792 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Close", "Message/Close", GTK_UI_MANAGER_MENUITEM)
7795 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Undo", "Edit/Undo", GTK_UI_MANAGER_MENUITEM)
7796 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Redo", "Edit/Redo", GTK_UI_MANAGER_MENUITEM)
7797 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator1", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7799 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Cut", "Edit/Cut", GTK_UI_MANAGER_MENUITEM)
7800 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Copy", "Edit/Copy", GTK_UI_MANAGER_MENUITEM)
7801 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Paste", "Edit/Paste", GTK_UI_MANAGER_MENUITEM)
7803 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SpecialPaste", "Edit/SpecialPaste", GTK_UI_MANAGER_MENU)
7804 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "AsQuotation", "Edit/SpecialPaste/AsQuotation", GTK_UI_MANAGER_MENUITEM)
7805 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Wrapped", "Edit/SpecialPaste/Wrapped", GTK_UI_MANAGER_MENUITEM)
7806 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Unwrapped", "Edit/SpecialPaste/Unwrapped", GTK_UI_MANAGER_MENUITEM)
7808 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SelectAll", "Edit/SelectAll", GTK_UI_MANAGER_MENUITEM)
7810 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Advanced", "Edit/Advanced", GTK_UI_MANAGER_MENU)
7811 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackChar", "Edit/Advanced/BackChar", GTK_UI_MANAGER_MENUITEM)
7812 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwChar", "Edit/Advanced/ForwChar", GTK_UI_MANAGER_MENUITEM)
7813 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackWord", "Edit/Advanced/BackWord", GTK_UI_MANAGER_MENUITEM)
7814 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwWord", "Edit/Advanced/ForwWord", GTK_UI_MANAGER_MENUITEM)
7815 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BegLine", "Edit/Advanced/BegLine", GTK_UI_MANAGER_MENUITEM)
7816 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "EndLine", "Edit/Advanced/EndLine", GTK_UI_MANAGER_MENUITEM)
7817 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "PrevLine", "Edit/Advanced/PrevLine", GTK_UI_MANAGER_MENUITEM)
7818 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "NextLine", "Edit/Advanced/NextLine", GTK_UI_MANAGER_MENUITEM)
7819 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackChar", "Edit/Advanced/DelBackChar", GTK_UI_MANAGER_MENUITEM)
7820 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwChar", "Edit/Advanced/DelForwChar", GTK_UI_MANAGER_MENUITEM)
7821 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackWord", "Edit/Advanced/DelBackWord", GTK_UI_MANAGER_MENUITEM)
7822 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwWord", "Edit/Advanced/DelForwWord", GTK_UI_MANAGER_MENUITEM)
7823 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelLine", "Edit/Advanced/DelLine", GTK_UI_MANAGER_MENUITEM)
7824 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelEndLine", "Edit/Advanced/DelEndLine", GTK_UI_MANAGER_MENUITEM)
7826 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator2", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7828 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Find", "Edit/Find", GTK_UI_MANAGER_MENUITEM)
7829 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapPara", "Edit/WrapPara", GTK_UI_MANAGER_MENUITEM)
7830 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapAllLines", "Edit/WrapAllLines", GTK_UI_MANAGER_MENUITEM)
7831 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoWrap", "Edit/AutoWrap", GTK_UI_MANAGER_MENUITEM)
7832 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoIndent", "Edit/AutoIndent", GTK_UI_MANAGER_MENUITEM)
7834 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator3", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7836 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "ExtEditor", "Edit/ExtEditor", GTK_UI_MANAGER_MENUITEM)
7840 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckAllSel", "Spelling/CheckAllSel", GTK_UI_MANAGER_MENUITEM)
7841 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "HighlightAll", "Spelling/HighlightAll", GTK_UI_MANAGER_MENUITEM)
7842 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckBackwards", "Spelling/CheckBackwards", GTK_UI_MANAGER_MENUITEM)
7843 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "ForwardNext", "Spelling/ForwardNext", GTK_UI_MANAGER_MENUITEM)
7844 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Separator1", "Spelling/---", GTK_UI_MANAGER_SEPARATOR)
7845 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Options", "Spelling/Options", GTK_UI_MANAGER_MENU)
7849 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "ReplyMode", "Options/ReplyMode", GTK_UI_MANAGER_MENU)
7850 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Normal", "Options/ReplyMode/Normal", GTK_UI_MANAGER_MENUITEM)
7851 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "All", "Options/ReplyMode/All", GTK_UI_MANAGER_MENUITEM)
7852 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Sender", "Options/ReplyMode/Sender", GTK_UI_MANAGER_MENUITEM)
7853 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "List", "Options/ReplyMode/List", GTK_UI_MANAGER_MENUITEM)
7855 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator1", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7856 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "PrivacySystem", "Options/PrivacySystem", GTK_UI_MANAGER_MENU)
7857 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/PrivacySystem", "PlaceHolder", "Options/PrivacySystem/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7858 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Sign", "Options/Sign", GTK_UI_MANAGER_MENUITEM)
7859 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encrypt", "Options/Encrypt", GTK_UI_MANAGER_MENUITEM)
7862 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator2", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7863 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Priority", "Options/Priority", GTK_UI_MANAGER_MENU)
7864 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Highest", "Options/Priority/Highest", GTK_UI_MANAGER_MENUITEM)
7865 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "High", "Options/Priority/High", GTK_UI_MANAGER_MENUITEM)
7866 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Normal", "Options/Priority/Normal", GTK_UI_MANAGER_MENUITEM)
7867 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Low", "Options/Priority/Low", GTK_UI_MANAGER_MENUITEM)
7868 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Lowest", "Options/Priority/Lowest", GTK_UI_MANAGER_MENUITEM)
7870 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator3", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7871 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RequestRetRcpt", "Options/RequestRetRcpt", GTK_UI_MANAGER_MENUITEM)
7872 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator4", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7873 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RemoveReferences", "Options/RemoveReferences", GTK_UI_MANAGER_MENUITEM)
7874 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator5", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7876 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encoding", "Options/Encoding", GTK_UI_MANAGER_MENU)
7878 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_AUTO, "Options/Encoding/"CS_AUTO, GTK_UI_MANAGER_MENUITEM)
7879 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator1", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7880 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_US_ASCII, "Options/Encoding/"CS_US_ASCII, GTK_UI_MANAGER_MENUITEM)
7881 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_UTF_8, "Options/Encoding/"CS_UTF_8, GTK_UI_MANAGER_MENUITEM)
7882 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator2", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7884 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Western", "Options/Encoding/Western", GTK_UI_MANAGER_MENU)
7885 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)
7886 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)
7887 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Western", CS_WINDOWS_1252, "Options/Encoding/Western/"CS_WINDOWS_1252, GTK_UI_MANAGER_MENUITEM)
7889 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_2, "Options/Encoding/"CS_ISO_8859_2, GTK_UI_MANAGER_MENUITEM)
7891 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Baltic", "Options/Encoding/Baltic", GTK_UI_MANAGER_MENU)
7892 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)
7893 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)
7895 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_7, "Options/Encoding/"CS_ISO_8859_7, GTK_UI_MANAGER_MENUITEM)
7897 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Hebrew", "Options/Encoding/Hebrew", GTK_UI_MANAGER_MENU)
7898 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)
7899 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Hebrew", CS_WINDOWS_1255, "Options/Encoding/Hebrew/"CS_WINDOWS_1255, GTK_UI_MANAGER_MENUITEM)
7901 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Arabic", "Options/Encoding/Arabic", GTK_UI_MANAGER_MENU)
7902 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)
7903 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Arabic", CS_WINDOWS_1256, "Options/Encoding/Arabic/"CS_WINDOWS_1256, GTK_UI_MANAGER_MENUITEM)
7905 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_9, "Options/Encoding/"CS_ISO_8859_9, GTK_UI_MANAGER_MENUITEM)
7907 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Cyrillic", "Options/Encoding/Cyrillic", GTK_UI_MANAGER_MENU)
7908 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)
7909 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_R, "Options/Encoding/Cyrillic/"CS_KOI8_R, GTK_UI_MANAGER_MENUITEM)
7910 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_MACCYR, "Options/Encoding/Cyrillic/"CS_MACCYR, GTK_UI_MANAGER_MENUITEM)
7911 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_U, "Options/Encoding/Cyrillic/"CS_KOI8_U, GTK_UI_MANAGER_MENUITEM)
7912 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_WINDOWS_1251, "Options/Encoding/Cyrillic/"CS_WINDOWS_1251, GTK_UI_MANAGER_MENUITEM)
7914 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Japanese", "Options/Encoding/Japanese", GTK_UI_MANAGER_MENU)
7915 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)
7916 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)
7917 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_EUC_JP, "Options/Encoding/Japanese/"CS_EUC_JP, GTK_UI_MANAGER_MENUITEM)
7918 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_SHIFT_JIS, "Options/Encoding/Japanese/"CS_SHIFT_JIS, GTK_UI_MANAGER_MENUITEM)
7920 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Chinese", "Options/Encoding/Chinese", GTK_UI_MANAGER_MENU)
7921 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB18030, "Options/Encoding/Chinese/"CS_GB18030, GTK_UI_MANAGER_MENUITEM)
7922 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB2312, "Options/Encoding/Chinese/"CS_GB2312, GTK_UI_MANAGER_MENUITEM)
7923 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GBK, "Options/Encoding/Chinese/"CS_GBK, GTK_UI_MANAGER_MENUITEM)
7924 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_BIG5, "Options/Encoding/Chinese/"CS_BIG5, GTK_UI_MANAGER_MENUITEM)
7925 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_EUC_TW, "Options/Encoding/Chinese/"CS_EUC_TW, GTK_UI_MANAGER_MENUITEM)
7927 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Korean", "Options/Encoding/Korean", GTK_UI_MANAGER_MENU)
7928 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Korean", CS_EUC_KR, "Options/Encoding/Korean/"CS_EUC_KR, GTK_UI_MANAGER_MENUITEM)
7929 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)
7931 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Thai", "Options/Encoding/Thai", GTK_UI_MANAGER_MENU)
7932 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_TIS_620, "Options/Encoding/Thai/"CS_TIS_620, GTK_UI_MANAGER_MENUITEM)
7933 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_WINDOWS_874, "Options/Encoding/Thai/"CS_WINDOWS_874, GTK_UI_MANAGER_MENUITEM)
7937 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "ShowRuler", "Tools/ShowRuler", GTK_UI_MANAGER_MENUITEM)
7938 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "AddressBook", "Tools/AddressBook", GTK_UI_MANAGER_MENUITEM)
7939 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Template", "Tools/Template", GTK_UI_MANAGER_MENU)
7940 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Template", "PlaceHolder", "Tools/Template/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7941 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Actions", "Tools/Actions", GTK_UI_MANAGER_MENU)
7942 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Actions", "PlaceHolder", "Tools/Actions/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7945 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Help", "About", "Help/About", GTK_UI_MANAGER_MENUITEM)
7947 menubar = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu");
7948 gtk_widget_show_all(menubar);
7950 gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(compose->ui_manager));
7951 gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, TRUE, 0);
7953 if (prefs_common.toolbar_detachable) {
7954 handlebox = gtk_handle_box_new();
7956 handlebox = gtk_hbox_new(FALSE, 0);
7958 gtk_box_pack_start(GTK_BOX(vbox), handlebox, FALSE, FALSE, 0);
7960 gtk_widget_realize(handlebox);
7961 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, handlebox,
7964 vbox2 = gtk_vbox_new(FALSE, 2);
7965 gtk_box_pack_start(GTK_BOX(vbox), vbox2, TRUE, TRUE, 0);
7966 gtk_container_set_border_width(GTK_CONTAINER(vbox2), 0);
7969 notebook = gtk_notebook_new();
7970 gtk_widget_show(notebook);
7972 /* header labels and entries */
7973 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7974 compose_create_header(compose),
7975 gtk_label_new_with_mnemonic(_("Hea_der")));
7976 /* attachment list */
7977 attach_hbox = gtk_hbox_new(FALSE, 0);
7978 gtk_widget_show(attach_hbox);
7980 attach_lab1 = gtk_label_new_with_mnemonic(_("_Attachments"));
7981 gtk_widget_show(attach_lab1);
7982 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab1, TRUE, TRUE, 0);
7984 attach_lab2 = gtk_label_new("");
7985 gtk_widget_show(attach_lab2);
7986 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab2, FALSE, FALSE, 0);
7988 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7989 compose_create_attach(compose),
7992 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7993 compose_create_others(compose),
7994 gtk_label_new_with_mnemonic(_("Othe_rs")));
7997 subject_hbox = gtk_hbox_new(FALSE, 0);
7998 gtk_widget_show(subject_hbox);
8000 subject_frame = gtk_frame_new(NULL);
8001 gtk_frame_set_shadow_type(GTK_FRAME(subject_frame), GTK_SHADOW_NONE);
8002 gtk_box_pack_start(GTK_BOX(subject_hbox), subject_frame, TRUE, TRUE, 0);
8003 gtk_widget_show(subject_frame);
8005 subject = gtk_hbox_new(FALSE, HSPACING_NARROW);
8006 gtk_container_set_border_width(GTK_CONTAINER(subject), 0);
8007 gtk_widget_show(subject);
8009 label = gtk_label_new_with_mnemonic(_("S_ubject:"));
8010 gtk_box_pack_start(GTK_BOX(subject), label, FALSE, FALSE, 0);
8011 gtk_widget_show(label);
8014 subject_entry = claws_spell_entry_new();
8016 subject_entry = gtk_entry_new();
8018 gtk_box_pack_start(GTK_BOX(subject), subject_entry, TRUE, TRUE, 0);
8019 g_signal_connect_after(G_OBJECT(subject_entry), "grab_focus",
8020 G_CALLBACK(compose_grab_focus_cb), compose);
8021 gtk_label_set_mnemonic_widget(GTK_LABEL(label), subject_entry);
8022 gtk_widget_show(subject_entry);
8023 compose->subject_entry = subject_entry;
8024 gtk_container_add(GTK_CONTAINER(subject_frame), subject);
8026 edit_vbox = gtk_vbox_new(FALSE, 0);
8028 gtk_box_pack_start(GTK_BOX(edit_vbox), subject_hbox, FALSE, FALSE, 0);
8031 ruler_hbox = gtk_hbox_new(FALSE, 0);
8032 gtk_box_pack_start(GTK_BOX(edit_vbox), ruler_hbox, FALSE, FALSE, 0);
8034 ruler = gtk_shruler_new(GTK_ORIENTATION_HORIZONTAL);
8035 gtk_shruler_set_range(GTK_SHRULER(ruler), 0.0, 100.0, 1.0);
8036 gtk_box_pack_start(GTK_BOX(ruler_hbox), ruler, TRUE, TRUE,
8040 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
8041 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
8042 GTK_POLICY_AUTOMATIC,
8043 GTK_POLICY_AUTOMATIC);
8044 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
8046 gtk_box_pack_start(GTK_BOX(edit_vbox), scrolledwin, TRUE, TRUE, 0);
8048 text = gtk_text_view_new();
8049 if (prefs_common.show_compose_margin) {
8050 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text), 6);
8051 gtk_text_view_set_right_margin(GTK_TEXT_VIEW(text), 6);
8053 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
8054 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
8055 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), TRUE);
8056 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
8057 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
8059 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
8060 g_signal_connect_after(G_OBJECT(text), "size_allocate",
8061 G_CALLBACK(compose_edit_size_alloc),
8063 g_signal_connect(G_OBJECT(buffer), "changed",
8064 G_CALLBACK(compose_changed_cb), compose);
8065 g_signal_connect(G_OBJECT(text), "grab_focus",
8066 G_CALLBACK(compose_grab_focus_cb), compose);
8067 g_signal_connect(G_OBJECT(buffer), "insert_text",
8068 G_CALLBACK(text_inserted), compose);
8069 g_signal_connect(G_OBJECT(text), "button_press_event",
8070 G_CALLBACK(text_clicked), compose);
8071 g_signal_connect(G_OBJECT(text), "popup-menu",
8072 G_CALLBACK(compose_popup_menu), compose);
8073 g_signal_connect(G_OBJECT(subject_entry), "changed",
8074 G_CALLBACK(compose_changed_cb), compose);
8075 g_signal_connect(G_OBJECT(subject_entry), "activate",
8076 G_CALLBACK(compose_subject_entry_activated), compose);
8079 gtk_drag_dest_set(text, GTK_DEST_DEFAULT_ALL, compose_mime_types,
8080 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
8081 GDK_ACTION_COPY | GDK_ACTION_MOVE);
8082 g_signal_connect(G_OBJECT(text), "drag_data_received",
8083 G_CALLBACK(compose_insert_drag_received_cb),
8085 g_signal_connect(G_OBJECT(text), "drag-drop",
8086 G_CALLBACK(compose_drag_drop),
8088 g_signal_connect(G_OBJECT(text), "key-press-event",
8089 G_CALLBACK(completion_set_focus_to_subject),
8091 gtk_widget_show_all(vbox);
8093 /* pane between attach clist and text */
8094 paned = gtk_vpaned_new();
8095 gtk_container_add(GTK_CONTAINER(vbox2), paned);
8096 gtk_paned_pack1(GTK_PANED(paned), notebook, FALSE, FALSE);
8097 gtk_paned_pack2(GTK_PANED(paned), edit_vbox, TRUE, FALSE);
8098 gtk_paned_set_position(GTK_PANED(paned), prefs_common.compose_notebook_height);
8099 g_signal_connect(G_OBJECT(notebook), "size_allocate",
8100 G_CALLBACK(compose_notebook_size_alloc), paned);
8102 gtk_widget_show_all(paned);
8105 if (prefs_common.textfont) {
8106 PangoFontDescription *font_desc;
8108 font_desc = pango_font_description_from_string
8109 (prefs_common.textfont);
8111 gtk_widget_modify_font(text, font_desc);
8112 pango_font_description_free(font_desc);
8116 gtk_action_group_add_actions(action_group, compose_popup_entries,
8117 G_N_ELEMENTS(compose_popup_entries), (gpointer)compose);
8118 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Popup", NULL, GTK_UI_MANAGER_MENUBAR)
8119 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup", "Compose", "Compose", GTK_UI_MANAGER_MENU)
8120 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Add", "Compose/Add", GTK_UI_MANAGER_MENUITEM)
8121 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Remove", "Compose/Remove", GTK_UI_MANAGER_MENUITEM)
8122 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Separator1", "Compose/---", GTK_UI_MANAGER_SEPARATOR)
8123 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Properties", "Compose/Properties", GTK_UI_MANAGER_MENUITEM)
8125 popupmenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(gtk_ui_manager_get_widget(compose->ui_manager, "/Popup/Compose")));
8127 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
8128 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
8129 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
8131 tmpl_menu = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu/Tools/Template");
8133 undostruct = undo_init(text);
8134 undo_set_change_state_func(undostruct, &compose_undo_state_changed,
8137 address_completion_start(window);
8139 compose->window = window;
8140 compose->vbox = vbox;
8141 compose->menubar = menubar;
8142 compose->handlebox = handlebox;
8144 compose->vbox2 = vbox2;
8146 compose->paned = paned;
8148 compose->attach_label = attach_lab2;
8150 compose->notebook = notebook;
8151 compose->edit_vbox = edit_vbox;
8152 compose->ruler_hbox = ruler_hbox;
8153 compose->ruler = ruler;
8154 compose->scrolledwin = scrolledwin;
8155 compose->text = text;
8157 compose->focused_editable = NULL;
8159 compose->popupmenu = popupmenu;
8161 compose->tmpl_menu = tmpl_menu;
8163 compose->mode = mode;
8164 compose->rmode = mode;
8166 compose->targetinfo = NULL;
8167 compose->replyinfo = NULL;
8168 compose->fwdinfo = NULL;
8170 compose->email_hashtable = g_hash_table_new_full(g_str_hash,
8171 g_str_equal, (GDestroyNotify) g_free, NULL);
8173 compose->replyto = NULL;
8175 compose->bcc = NULL;
8176 compose->followup_to = NULL;
8178 compose->ml_post = NULL;
8180 compose->inreplyto = NULL;
8181 compose->references = NULL;
8182 compose->msgid = NULL;
8183 compose->boundary = NULL;
8185 compose->autowrap = prefs_common.autowrap;
8186 compose->autoindent = prefs_common.auto_indent;
8187 compose->use_signing = FALSE;
8188 compose->use_encryption = FALSE;
8189 compose->privacy_system = NULL;
8190 compose->encdata = NULL;
8192 compose->modified = FALSE;
8194 compose->return_receipt = FALSE;
8196 compose->to_list = NULL;
8197 compose->newsgroup_list = NULL;
8199 compose->undostruct = undostruct;
8201 compose->sig_str = NULL;
8203 compose->exteditor_file = NULL;
8204 compose->exteditor_pid = -1;
8205 compose->exteditor_tag = -1;
8206 compose->exteditor_socket = NULL;
8207 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN; /* inhibit auto-drafting while loading */
8209 compose->folder_update_callback_id =
8210 hooks_register_hook(FOLDER_UPDATE_HOOKLIST,
8211 compose_update_folder_hook,
8212 (gpointer) compose);
8215 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
8216 if (mode != COMPOSE_REDIRECT) {
8217 if (prefs_common.enable_aspell && prefs_common.dictionary &&
8218 strcmp(prefs_common.dictionary, "")) {
8219 gtkaspell = gtkaspell_new(prefs_common.dictionary,
8220 prefs_common.alt_dictionary,
8221 conv_get_locale_charset_str(),
8222 prefs_common.color[COL_MISSPELLED],
8223 prefs_common.check_while_typing,
8224 prefs_common.recheck_when_changing_dict,
8225 prefs_common.use_alternate,
8226 prefs_common.use_both_dicts,
8227 GTK_TEXT_VIEW(text),
8228 GTK_WINDOW(compose->window),
8229 compose_dict_changed,
8230 compose_spell_menu_changed,
8233 alertpanel_error(_("Spell checker could not "
8235 gtkaspell_checkers_strerror());
8236 gtkaspell_checkers_reset_error();
8238 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", TRUE);
8242 compose->gtkaspell = gtkaspell;
8243 compose_spell_menu_changed(compose);
8244 claws_spell_entry_set_gtkaspell(CLAWS_SPELL_ENTRY(subject_entry), gtkaspell);
8247 compose_select_account(compose, account, TRUE);
8249 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", prefs_common.autowrap);
8250 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", prefs_common.auto_indent);
8252 if (account->set_autocc && account->auto_cc && mode != COMPOSE_REEDIT)
8253 compose_entry_append(compose, account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8255 if (account->set_autobcc && account->auto_bcc && mode != COMPOSE_REEDIT)
8256 compose_entry_append(compose, account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8258 if (account->set_autoreplyto && account->auto_replyto && mode != COMPOSE_REEDIT)
8259 compose_entry_append(compose, account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8261 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", compose->mode == COMPOSE_REPLY);
8262 if (account->protocol != A_NNTP)
8263 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
8264 prefs_common_translated_header_name("To:"));
8266 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
8267 prefs_common_translated_header_name("Newsgroups:"));
8269 #ifndef USE_ALT_ADDRBOOK
8270 addressbook_set_target_compose(compose);
8272 if (mode != COMPOSE_REDIRECT)
8273 compose_set_template_menu(compose);
8275 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Template", FALSE);
8278 compose_list = g_list_append(compose_list, compose);
8280 if (!prefs_common.show_ruler)
8281 gtk_widget_hide(ruler_hbox);
8283 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Tools/ShowRuler", prefs_common.show_ruler);
8286 compose->priority = PRIORITY_NORMAL;
8287 compose_update_priority_menu_item(compose);
8289 compose_set_out_encoding(compose);
8292 compose_update_actions_menu(compose);
8294 /* Privacy Systems menu */
8295 compose_update_privacy_systems_menu(compose);
8297 activate_privacy_system(compose, account, TRUE);
8298 toolbar_set_style(compose->toolbar->toolbar, compose->handlebox, prefs_common.toolbar_style);
8300 gtk_widget_realize(window);
8302 gtk_widget_show(window);
8308 static GtkWidget *compose_account_option_menu_create(Compose *compose)
8313 GtkWidget *optmenubox;
8314 GtkWidget *fromlabel;
8317 GtkWidget *from_name = NULL;
8319 gint num = 0, def_menu = 0;
8321 accounts = account_get_list();
8322 cm_return_val_if_fail(accounts != NULL, NULL);
8324 optmenubox = gtk_event_box_new();
8325 optmenu = gtkut_sc_combobox_create(optmenubox, FALSE);
8326 menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
8328 hbox = gtk_hbox_new(FALSE, 4);
8329 from_name = gtk_entry_new();
8331 g_signal_connect_after(G_OBJECT(from_name), "grab_focus",
8332 G_CALLBACK(compose_grab_focus_cb), compose);
8333 g_signal_connect_after(G_OBJECT(from_name), "activate",
8334 G_CALLBACK(from_name_activate_cb), optmenu);
8336 for (; accounts != NULL; accounts = accounts->next, num++) {
8337 PrefsAccount *ac = (PrefsAccount *)accounts->data;
8338 gchar *name, *from = NULL;
8340 if (ac == compose->account) def_menu = num;
8342 name = g_markup_printf_escaped("<i>%s</i>",
8345 if (ac == compose->account) {
8346 if (ac->name && *ac->name) {
8348 QUOTE_IF_REQUIRED_NORMAL(buf, ac->name, return NULL);
8349 from = g_strdup_printf("%s <%s>",
8351 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8353 from = g_strdup_printf("%s",
8355 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8357 if (cur_account != compose->account) {
8358 gtk_widget_modify_base(
8359 GTK_WIDGET(from_name),
8360 GTK_STATE_NORMAL, &default_header_bgcolor);
8361 gtk_widget_modify_text(
8362 GTK_WIDGET(from_name),
8363 GTK_STATE_NORMAL, &default_header_color);
8366 COMBOBOX_ADD(menu, name, ac->account_id);
8371 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), def_menu);
8373 g_signal_connect(G_OBJECT(optmenu), "changed",
8374 G_CALLBACK(account_activated),
8376 g_signal_connect(G_OBJECT(from_name), "populate-popup",
8377 G_CALLBACK(compose_entry_popup_extend),
8380 fromlabel = gtk_label_new_with_mnemonic(_("_From:"));
8381 gtk_label_set_mnemonic_widget(GTK_LABEL(fromlabel), from_name);
8383 gtk_box_pack_start(GTK_BOX(hbox), fromlabel, FALSE, FALSE, 4);
8384 gtk_box_pack_start(GTK_BOX(hbox), optmenubox, FALSE, FALSE, 0);
8385 gtk_box_pack_start(GTK_BOX(hbox), from_name, TRUE, TRUE, 0);
8387 /* Putting only the GtkEntry into focus chain of parent hbox causes
8388 * the account selector combobox next to it to be unreachable when
8389 * navigating widgets in GtkTable with up/down arrow keys.
8390 * Note: gtk_widget_set_can_focus() was not enough. */
8392 l = g_list_prepend(l, from_name);
8393 gtk_container_set_focus_chain(GTK_CONTAINER(hbox), l);
8396 CLAWS_SET_TIP(optmenubox,
8397 _("Account to use for this email"));
8398 CLAWS_SET_TIP(from_name,
8399 _("Sender address to be used"));
8401 compose->account_combo = optmenu;
8402 compose->from_name = from_name;
8407 static void compose_set_priority_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8409 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8410 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8411 Compose *compose = (Compose *) data;
8413 compose->priority = value;
8417 static void compose_reply_change_mode(Compose *compose,
8420 gboolean was_modified = compose->modified;
8422 gboolean all = FALSE, ml = FALSE, sender = FALSE, followup = FALSE;
8424 cm_return_if_fail(compose->replyinfo != NULL);
8426 if (action == COMPOSE_REPLY && prefs_common.default_reply_list)
8428 if (action == COMPOSE_REPLY && compose->rmode == COMPOSE_FOLLOWUP_AND_REPLY_TO)
8430 if (action == COMPOSE_REPLY_TO_ALL)
8432 if (action == COMPOSE_REPLY_TO_SENDER)
8434 if (action == COMPOSE_REPLY_TO_LIST)
8437 compose_remove_header_entries(compose);
8438 compose_reply_set_entry(compose, compose->replyinfo, all, ml, sender, followup);
8439 if (compose->account->set_autocc && compose->account->auto_cc)
8440 compose_entry_append(compose, compose->account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8442 if (compose->account->set_autobcc && compose->account->auto_bcc)
8443 compose_entry_append(compose, compose->account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8445 if (compose->account->set_autoreplyto && compose->account->auto_replyto)
8446 compose_entry_append(compose, compose->account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8447 compose_show_first_last_header(compose, TRUE);
8448 compose->modified = was_modified;
8449 compose_set_title(compose);
8452 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8454 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8455 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8456 Compose *compose = (Compose *) data;
8459 compose_reply_change_mode(compose, value);
8462 static void compose_update_priority_menu_item(Compose * compose)
8464 GtkWidget *menuitem = NULL;
8465 switch (compose->priority) {
8466 case PRIORITY_HIGHEST:
8467 menuitem = gtk_ui_manager_get_widget
8468 (compose->ui_manager, "/Menu/Options/Priority/Highest");
8471 menuitem = gtk_ui_manager_get_widget
8472 (compose->ui_manager, "/Menu/Options/Priority/High");
8474 case PRIORITY_NORMAL:
8475 menuitem = gtk_ui_manager_get_widget
8476 (compose->ui_manager, "/Menu/Options/Priority/Normal");
8479 menuitem = gtk_ui_manager_get_widget
8480 (compose->ui_manager, "/Menu/Options/Priority/Low");
8482 case PRIORITY_LOWEST:
8483 menuitem = gtk_ui_manager_get_widget
8484 (compose->ui_manager, "/Menu/Options/Priority/Lowest");
8487 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8490 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data)
8492 Compose *compose = (Compose *) data;
8494 gboolean can_sign = FALSE, can_encrypt = FALSE;
8496 cm_return_if_fail(GTK_IS_CHECK_MENU_ITEM(widget));
8498 if (!gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget)))
8501 systemid = g_object_get_data(G_OBJECT(widget), "privacy_system");
8502 g_free(compose->privacy_system);
8503 compose->privacy_system = NULL;
8504 g_free(compose->encdata);
8505 compose->encdata = NULL;
8506 if (systemid != NULL) {
8507 compose->privacy_system = g_strdup(systemid);
8509 can_sign = privacy_system_can_sign(systemid);
8510 can_encrypt = privacy_system_can_encrypt(systemid);
8513 debug_print("activated privacy system: %s\n", systemid != NULL ? systemid : "None");
8515 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8516 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8517 if (compose->toolbar->privacy_sign_btn != NULL) {
8518 gtk_widget_set_sensitive(
8519 GTK_WIDGET(compose->toolbar->privacy_sign_btn),
8521 gtk_toggle_tool_button_set_active(
8522 GTK_TOGGLE_TOOL_BUTTON(compose->toolbar->privacy_sign_btn),
8523 can_sign ? compose->use_signing : FALSE);
8525 if (compose->toolbar->privacy_encrypt_btn != NULL) {
8526 gtk_widget_set_sensitive(
8527 GTK_WIDGET(compose->toolbar->privacy_encrypt_btn),
8529 gtk_toggle_tool_button_set_active(
8530 GTK_TOGGLE_TOOL_BUTTON(compose->toolbar->privacy_encrypt_btn),
8531 can_encrypt ? compose->use_encryption : FALSE);
8535 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn)
8537 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8538 GtkWidget *menuitem = NULL;
8539 GList *children, *amenu;
8540 gboolean can_sign = FALSE, can_encrypt = FALSE;
8541 gboolean found = FALSE;
8543 if (compose->privacy_system != NULL) {
8545 menuitem = gtk_menu_item_get_submenu(GTK_MENU_ITEM(
8546 gtk_ui_manager_get_widget(compose->ui_manager, branch_path)));
8547 cm_return_if_fail(menuitem != NULL);
8549 children = gtk_container_get_children(GTK_CONTAINER(GTK_MENU_SHELL(menuitem)));
8552 while (amenu != NULL) {
8553 systemid = g_object_get_data(G_OBJECT(amenu->data), "privacy_system");
8554 if (systemid != NULL) {
8555 if (strcmp(systemid, compose->privacy_system) == 0 &&
8556 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8557 menuitem = GTK_WIDGET(amenu->data);
8559 can_sign = privacy_system_can_sign(systemid);
8560 can_encrypt = privacy_system_can_encrypt(systemid);
8564 } else if (strlen(compose->privacy_system) == 0 &&
8565 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8566 menuitem = GTK_WIDGET(amenu->data);
8569 can_encrypt = FALSE;
8574 amenu = amenu->next;
8576 g_list_free(children);
8577 if (menuitem != NULL)
8578 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8580 if (warn && !found && strlen(compose->privacy_system)) {
8581 alertpanel_warning(_("The privacy system '%s' cannot be loaded. You "
8582 "will not be able to sign or encrypt this message."),
8583 compose->privacy_system);
8587 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8588 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8589 if (compose->toolbar->privacy_sign_btn != NULL) {
8590 gtk_widget_set_sensitive(
8591 GTK_WIDGET(compose->toolbar->privacy_sign_btn),
8594 if (compose->toolbar->privacy_encrypt_btn != NULL) {
8595 gtk_widget_set_sensitive(
8596 GTK_WIDGET(compose->toolbar->privacy_encrypt_btn),
8601 static void compose_set_out_encoding(Compose *compose)
8603 CharSet out_encoding;
8604 const gchar *branch = NULL;
8605 out_encoding = conv_get_charset_from_str(prefs_common.outgoing_charset);
8607 switch(out_encoding) {
8608 case C_AUTO: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8609 case C_US_ASCII: branch = "Menu/Options/Encoding/" CS_US_ASCII; break;
8610 case C_UTF_8: branch = "Menu/Options/Encoding/" CS_UTF_8; break;
8611 case C_ISO_8859_2: branch = "Menu/Options/Encoding/" CS_ISO_8859_2; break;
8612 case C_ISO_8859_7: branch = "Menu/Options/Encoding/" CS_ISO_8859_7; break;
8613 case C_ISO_8859_9: branch = "Menu/Options/Encoding/" CS_ISO_8859_9; break;
8614 case C_ISO_8859_1: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_1; break;
8615 case C_ISO_8859_15: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_15; break;
8616 case C_WINDOWS_1252: branch = "Menu/Options/Encoding/Western/" CS_WINDOWS_1252; break;
8617 case C_ISO_8859_13: branch = "Menu/Options/Encoding/Baltic/" CS_ISO_8859_13; break;
8618 case C_ISO_8859_4: branch = "Menu/Options/Encoding/Baltic" CS_ISO_8859_4; break;
8619 case C_ISO_8859_8: branch = "Menu/Options/Encoding/Hebrew/" CS_ISO_8859_8; break;
8620 case C_WINDOWS_1255: branch = "Menu/Options/Encoding/Hebrew/" CS_WINDOWS_1255; break;
8621 case C_ISO_8859_6: branch = "Menu/Options/Encoding/Arabic/" CS_ISO_8859_6; break;
8622 case C_WINDOWS_1256: branch = "Menu/Options/Encoding/Arabic/" CS_WINDOWS_1256; break;
8623 case C_ISO_8859_5: branch = "Menu/Options/Encoding/Cyrillic/" CS_ISO_8859_5; break;
8624 case C_KOI8_R: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_R; break;
8625 case C_MACCYR: branch = "Menu/Options/Encoding/Cyrillic/" CS_MACCYR; break;
8626 case C_KOI8_U: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_U; break;
8627 case C_WINDOWS_1251: branch = "Menu/Options/Encoding/Cyrillic/" CS_WINDOWS_1251; break;
8628 case C_ISO_2022_JP: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP; break;
8629 case C_ISO_2022_JP_2: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP_2; break;
8630 case C_EUC_JP: branch = "Menu/Options/Encoding/Japanese/" CS_EUC_JP; break;
8631 case C_SHIFT_JIS: branch = "Menu/Options/Encoding/Japanese/" CS_SHIFT_JIS; break;
8632 case C_GB18030: branch = "Menu/Options/Encoding/Chinese/" CS_GB18030; break;
8633 case C_GB2312: branch = "Menu/Options/Encoding/Chinese/" CS_GB2312; break;
8634 case C_GBK: branch = "Menu/Options/Encoding/Chinese/" CS_GBK; break;
8635 case C_BIG5: branch = "Menu/Options/Encoding/Chinese/" CS_BIG5; break;
8636 case C_EUC_TW: branch = "Menu/Options/Encoding/Chinese/" CS_EUC_TW; break;
8637 case C_EUC_KR: branch = "Menu/Options/Encoding/Korean/" CS_EUC_KR; break;
8638 case C_ISO_2022_KR: branch = "Menu/Options/Encoding/Korean/" CS_ISO_2022_KR; break;
8639 case C_TIS_620: branch = "Menu/Options/Encoding/Thai/" CS_TIS_620; break;
8640 case C_WINDOWS_874: branch = "Menu/Options/Encoding/Thai/" CS_WINDOWS_874; break;
8641 default: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8643 cm_toggle_menu_set_active_full(compose->ui_manager, (gchar *)branch, TRUE);
8646 static void compose_set_template_menu(Compose *compose)
8648 GSList *tmpl_list, *cur;
8652 tmpl_list = template_get_config();
8654 menu = gtk_menu_new();
8656 gtk_menu_set_accel_group (GTK_MENU (menu),
8657 gtk_ui_manager_get_accel_group(compose->ui_manager));
8658 for (cur = tmpl_list; cur != NULL; cur = cur->next) {
8659 Template *tmpl = (Template *)cur->data;
8660 gchar *accel_path = NULL;
8661 item = gtk_menu_item_new_with_label(tmpl->name);
8662 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
8663 g_signal_connect(G_OBJECT(item), "activate",
8664 G_CALLBACK(compose_template_activate_cb),
8666 g_object_set_data(G_OBJECT(item), "template", tmpl);
8667 gtk_widget_show(item);
8668 accel_path = g_strconcat("<ComposeTemplates>" , "/", tmpl->name, NULL);
8669 gtk_menu_item_set_accel_path(GTK_MENU_ITEM(item), accel_path);
8673 gtk_widget_show(menu);
8674 gtk_menu_item_set_submenu(GTK_MENU_ITEM(compose->tmpl_menu), menu);
8677 void compose_update_actions_menu(Compose *compose)
8679 action_update_compose_menu(compose->ui_manager, "/Menu/Tools/Actions", compose);
8682 static void compose_update_privacy_systems_menu(Compose *compose)
8684 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8685 GSList *systems, *cur;
8687 GtkWidget *system_none;
8689 GtkWidget *privacy_menuitem = gtk_ui_manager_get_widget(compose->ui_manager, branch_path);
8690 GtkWidget *privacy_menu = gtk_menu_new();
8692 system_none = gtk_radio_menu_item_new_with_mnemonic(NULL, _("_None"));
8693 g_object_set_data_full(G_OBJECT(system_none), "privacy_system", NULL, NULL);
8695 g_signal_connect(G_OBJECT(system_none), "activate",
8696 G_CALLBACK(compose_set_privacy_system_cb), compose);
8698 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), system_none);
8699 gtk_widget_show(system_none);
8701 systems = privacy_get_system_ids();
8702 for (cur = systems; cur != NULL; cur = g_slist_next(cur)) {
8703 gchar *systemid = cur->data;
8705 group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(system_none));
8706 widget = gtk_radio_menu_item_new_with_label(group,
8707 privacy_system_get_name(systemid));
8708 g_object_set_data_full(G_OBJECT(widget), "privacy_system",
8709 g_strdup(systemid), g_free);
8710 g_signal_connect(G_OBJECT(widget), "activate",
8711 G_CALLBACK(compose_set_privacy_system_cb), compose);
8713 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), widget);
8714 gtk_widget_show(widget);
8717 g_slist_free(systems);
8718 gtk_menu_item_set_submenu(GTK_MENU_ITEM(privacy_menuitem), privacy_menu);
8719 gtk_widget_show_all(privacy_menu);
8720 gtk_widget_show_all(privacy_menuitem);
8723 void compose_reflect_prefs_all(void)
8728 for (cur = compose_list; cur != NULL; cur = cur->next) {
8729 compose = (Compose *)cur->data;
8730 compose_set_template_menu(compose);
8734 void compose_reflect_prefs_pixmap_theme(void)
8739 for (cur = compose_list; cur != NULL; cur = cur->next) {
8740 compose = (Compose *)cur->data;
8741 toolbar_update(TOOLBAR_COMPOSE, compose);
8745 static const gchar *compose_quote_char_from_context(Compose *compose)
8747 const gchar *qmark = NULL;
8749 cm_return_val_if_fail(compose != NULL, NULL);
8751 switch (compose->mode) {
8752 /* use forward-specific quote char */
8753 case COMPOSE_FORWARD:
8754 case COMPOSE_FORWARD_AS_ATTACH:
8755 case COMPOSE_FORWARD_INLINE:
8756 if (compose->folder && compose->folder->prefs &&
8757 compose->folder->prefs->forward_with_format)
8758 qmark = compose->folder->prefs->forward_quotemark;
8759 else if (compose->account->forward_with_format)
8760 qmark = compose->account->forward_quotemark;
8762 qmark = prefs_common.fw_quotemark;
8765 /* use reply-specific quote char in all other modes */
8767 if (compose->folder && compose->folder->prefs &&
8768 compose->folder->prefs->reply_with_format)
8769 qmark = compose->folder->prefs->reply_quotemark;
8770 else if (compose->account->reply_with_format)
8771 qmark = compose->account->reply_quotemark;
8773 qmark = prefs_common.quotemark;
8777 if (qmark == NULL || *qmark == '\0')
8783 static void compose_template_apply(Compose *compose, Template *tmpl,
8787 GtkTextBuffer *buffer;
8791 gchar *parsed_str = NULL;
8792 gint cursor_pos = 0;
8793 const gchar *err_msg = _("The body of the template has an error at line %d.");
8796 /* process the body */
8798 text = GTK_TEXT_VIEW(compose->text);
8799 buffer = gtk_text_view_get_buffer(text);
8802 qmark = compose_quote_char_from_context(compose);
8804 if (compose->replyinfo != NULL) {
8807 gtk_text_buffer_set_text(buffer, "", -1);
8808 mark = gtk_text_buffer_get_insert(buffer);
8809 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8811 parsed_str = compose_quote_fmt(compose, compose->replyinfo,
8812 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8814 } else if (compose->fwdinfo != NULL) {
8817 gtk_text_buffer_set_text(buffer, "", -1);
8818 mark = gtk_text_buffer_get_insert(buffer);
8819 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8821 parsed_str = compose_quote_fmt(compose, compose->fwdinfo,
8822 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8825 MsgInfo* dummyinfo = compose_msginfo_new_from_compose(compose);
8827 GtkTextIter start, end;
8830 gtk_text_buffer_get_start_iter(buffer, &start);
8831 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
8832 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
8834 /* clear the buffer now */
8836 gtk_text_buffer_set_text(buffer, "", -1);
8838 parsed_str = compose_quote_fmt(compose, dummyinfo,
8839 tmpl->value, qmark, tmp, FALSE, FALSE, err_msg);
8840 procmsg_msginfo_free( &dummyinfo );
8846 gtk_text_buffer_set_text(buffer, "", -1);
8847 mark = gtk_text_buffer_get_insert(buffer);
8848 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8851 if (replace && parsed_str && compose->account->auto_sig)
8852 compose_insert_sig(compose, FALSE);
8854 if (replace && parsed_str) {
8855 gtk_text_buffer_get_start_iter(buffer, &iter);
8856 gtk_text_buffer_place_cursor(buffer, &iter);
8860 cursor_pos = quote_fmt_get_cursor_pos();
8861 compose->set_cursor_pos = cursor_pos;
8862 if (cursor_pos == -1)
8864 gtk_text_buffer_get_start_iter(buffer, &iter);
8865 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
8866 gtk_text_buffer_place_cursor(buffer, &iter);
8869 /* process the other fields */
8871 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
8872 compose_template_apply_fields(compose, tmpl);
8873 quote_fmt_reset_vartable();
8874 quote_fmtlex_destroy();
8876 compose_changed_cb(NULL, compose);
8879 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
8880 gtkaspell_highlight_all(compose->gtkaspell);
8884 static void compose_template_apply_fields_error(const gchar *header)
8889 tr = g_strdup(C_("'%s' stands for a header name",
8890 "Template '%s' format error."));
8891 text = g_strdup_printf(tr, prefs_common_translated_header_name(header));
8892 alertpanel_error("%s", text);
8898 static void compose_template_apply_fields(Compose *compose, Template *tmpl)
8900 MsgInfo* dummyinfo = NULL;
8901 MsgInfo *msginfo = NULL;
8904 if (compose->replyinfo != NULL)
8905 msginfo = compose->replyinfo;
8906 else if (compose->fwdinfo != NULL)
8907 msginfo = compose->fwdinfo;
8909 dummyinfo = compose_msginfo_new_from_compose(compose);
8910 msginfo = dummyinfo;
8913 if (tmpl->from && *tmpl->from != '\0') {
8915 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8916 compose->gtkaspell);
8918 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8920 quote_fmt_scan_string(tmpl->from);
8923 buf = quote_fmt_get_buffer();
8925 compose_template_apply_fields_error("From");
8927 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
8930 quote_fmt_reset_vartable();
8931 quote_fmtlex_destroy();
8934 if (tmpl->to && *tmpl->to != '\0') {
8936 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8937 compose->gtkaspell);
8939 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8941 quote_fmt_scan_string(tmpl->to);
8944 buf = quote_fmt_get_buffer();
8946 compose_template_apply_fields_error("To");
8948 compose_entry_append(compose, buf, COMPOSE_TO, PREF_TEMPLATE);
8951 quote_fmt_reset_vartable();
8952 quote_fmtlex_destroy();
8955 if (tmpl->cc && *tmpl->cc != '\0') {
8957 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8958 compose->gtkaspell);
8960 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8962 quote_fmt_scan_string(tmpl->cc);
8965 buf = quote_fmt_get_buffer();
8967 compose_template_apply_fields_error("Cc");
8969 compose_entry_append(compose, buf, COMPOSE_CC, PREF_TEMPLATE);
8972 quote_fmt_reset_vartable();
8973 quote_fmtlex_destroy();
8976 if (tmpl->bcc && *tmpl->bcc != '\0') {
8978 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8979 compose->gtkaspell);
8981 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8983 quote_fmt_scan_string(tmpl->bcc);
8986 buf = quote_fmt_get_buffer();
8988 compose_template_apply_fields_error("Bcc");
8990 compose_entry_append(compose, buf, COMPOSE_BCC, PREF_TEMPLATE);
8993 quote_fmt_reset_vartable();
8994 quote_fmtlex_destroy();
8997 if (tmpl->replyto && *tmpl->replyto != '\0') {
8999 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
9000 compose->gtkaspell);
9002 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
9004 quote_fmt_scan_string(tmpl->replyto);
9007 buf = quote_fmt_get_buffer();
9009 compose_template_apply_fields_error("Reply-To");
9011 compose_entry_append(compose, buf, COMPOSE_REPLYTO, PREF_TEMPLATE);
9014 quote_fmt_reset_vartable();
9015 quote_fmtlex_destroy();
9018 /* process the subject */
9019 if (tmpl->subject && *tmpl->subject != '\0') {
9021 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
9022 compose->gtkaspell);
9024 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
9026 quote_fmt_scan_string(tmpl->subject);
9029 buf = quote_fmt_get_buffer();
9031 compose_template_apply_fields_error("Subject");
9033 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
9036 quote_fmt_reset_vartable();
9037 quote_fmtlex_destroy();
9040 procmsg_msginfo_free( &dummyinfo );
9043 static void compose_destroy(Compose *compose)
9045 GtkAllocation allocation;
9046 GtkTextBuffer *buffer;
9047 GtkClipboard *clipboard;
9049 compose_list = g_list_remove(compose_list, compose);
9052 gboolean enable = TRUE;
9053 g_slist_foreach(compose->passworded_ldap_servers,
9054 _ldap_srv_func, &enable);
9055 g_slist_free(compose->passworded_ldap_servers);
9058 if (compose->updating) {
9059 debug_print("danger, not destroying anything now\n");
9060 compose->deferred_destroy = TRUE;
9064 /* NOTE: address_completion_end() does nothing with the window
9065 * however this may change. */
9066 address_completion_end(compose->window);
9068 slist_free_strings_full(compose->to_list);
9069 slist_free_strings_full(compose->newsgroup_list);
9070 slist_free_strings_full(compose->header_list);
9072 slist_free_strings_full(extra_headers);
9073 extra_headers = NULL;
9075 compose->header_list = compose->newsgroup_list = compose->to_list = NULL;
9077 g_hash_table_destroy(compose->email_hashtable);
9079 hooks_unregister_hook(FOLDER_UPDATE_HOOKLIST,
9080 compose->folder_update_callback_id);
9082 procmsg_msginfo_free(&(compose->targetinfo));
9083 procmsg_msginfo_free(&(compose->replyinfo));
9084 procmsg_msginfo_free(&(compose->fwdinfo));
9086 g_free(compose->replyto);
9087 g_free(compose->cc);
9088 g_free(compose->bcc);
9089 g_free(compose->newsgroups);
9090 g_free(compose->followup_to);
9092 g_free(compose->ml_post);
9094 g_free(compose->inreplyto);
9095 g_free(compose->references);
9096 g_free(compose->msgid);
9097 g_free(compose->boundary);
9099 g_free(compose->redirect_filename);
9100 if (compose->undostruct)
9101 undo_destroy(compose->undostruct);
9103 g_free(compose->sig_str);
9105 g_free(compose->exteditor_file);
9107 g_free(compose->orig_charset);
9109 g_free(compose->privacy_system);
9110 g_free(compose->encdata);
9112 #ifndef USE_ALT_ADDRBOOK
9113 if (addressbook_get_target_compose() == compose)
9114 addressbook_set_target_compose(NULL);
9117 if (compose->gtkaspell) {
9118 gtkaspell_delete(compose->gtkaspell);
9119 compose->gtkaspell = NULL;
9123 if (!compose->batch) {
9124 gtk_widget_get_allocation(compose->window, &allocation);
9125 prefs_common.compose_width = allocation.width;
9126 prefs_common.compose_height = allocation.height;
9129 if (!gtk_widget_get_parent(compose->paned))
9130 gtk_widget_destroy(compose->paned);
9131 gtk_widget_destroy(compose->popupmenu);
9133 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
9134 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
9135 gtk_text_buffer_remove_selection_clipboard(buffer, clipboard);
9137 gtk_widget_destroy(compose->window);
9138 toolbar_destroy(compose->toolbar);
9139 g_free(compose->toolbar);
9140 cm_mutex_free(compose->mutex);
9144 static void compose_attach_info_free(AttachInfo *ainfo)
9146 g_free(ainfo->file);
9147 g_free(ainfo->content_type);
9148 g_free(ainfo->name);
9149 g_free(ainfo->charset);
9153 static void compose_attach_update_label(Compose *compose)
9158 GtkTreeModel *model;
9162 if (compose == NULL)
9165 model = gtk_tree_view_get_model(GTK_TREE_VIEW(compose->attach_clist));
9166 if (!gtk_tree_model_get_iter_first(model, &iter)) {
9167 gtk_label_set_text(GTK_LABEL(compose->attach_label), "");
9171 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
9172 total_size = ainfo->size;
9173 while(gtk_tree_model_iter_next(model, &iter)) {
9174 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
9175 total_size += ainfo->size;
9178 text = g_strdup_printf(" (%d/%s)", i, to_human_readable(total_size));
9179 gtk_label_set_text(GTK_LABEL(compose->attach_label), text);
9183 static void compose_attach_remove_selected(GtkAction *action, gpointer data)
9185 Compose *compose = (Compose *)data;
9186 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
9187 GtkTreeSelection *selection;
9189 GtkTreeModel *model;
9191 selection = gtk_tree_view_get_selection(tree_view);
9192 sel = gtk_tree_selection_get_selected_rows(selection, &model);
9197 for (cur = sel; cur != NULL; cur = cur->next) {
9198 GtkTreePath *path = cur->data;
9199 GtkTreeRowReference *ref = gtk_tree_row_reference_new
9202 gtk_tree_path_free(path);
9205 for (cur = sel; cur != NULL; cur = cur->next) {
9206 GtkTreeRowReference *ref = cur->data;
9207 GtkTreePath *path = gtk_tree_row_reference_get_path(ref);
9210 if (gtk_tree_model_get_iter(model, &iter, path))
9211 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
9213 gtk_tree_path_free(path);
9214 gtk_tree_row_reference_free(ref);
9218 compose_attach_update_label(compose);
9221 static struct _AttachProperty
9224 GtkWidget *mimetype_entry;
9225 GtkWidget *encoding_optmenu;
9226 GtkWidget *path_entry;
9227 GtkWidget *filename_entry;
9229 GtkWidget *cancel_btn;
9232 static void gtk_tree_path_free_(gpointer ptr, gpointer data)
9234 gtk_tree_path_free((GtkTreePath *)ptr);
9237 static void compose_attach_property(GtkAction *action, gpointer data)
9239 Compose *compose = (Compose *)data;
9240 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
9242 GtkComboBox *optmenu;
9243 GtkTreeSelection *selection;
9245 GtkTreeModel *model;
9248 static gboolean cancelled;
9250 /* only if one selected */
9251 selection = gtk_tree_view_get_selection(tree_view);
9252 if (gtk_tree_selection_count_selected_rows(selection) != 1)
9255 sel = gtk_tree_selection_get_selected_rows(selection, &model);
9259 path = (GtkTreePath *) sel->data;
9260 gtk_tree_model_get_iter(model, &iter, path);
9261 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
9264 g_list_foreach(sel, gtk_tree_path_free_, NULL);
9270 if (!attach_prop.window)
9271 compose_attach_property_create(&cancelled);
9272 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), TRUE);
9273 gtk_widget_grab_focus(attach_prop.ok_btn);
9274 gtk_widget_show(attach_prop.window);
9275 gtk_window_set_transient_for(GTK_WINDOW(attach_prop.window),
9276 GTK_WINDOW(compose->window));
9278 optmenu = GTK_COMBO_BOX(attach_prop.encoding_optmenu);
9279 if (ainfo->encoding == ENC_UNKNOWN)
9280 combobox_select_by_data(optmenu, ENC_BASE64);
9282 combobox_select_by_data(optmenu, ainfo->encoding);
9284 gtk_entry_set_text(GTK_ENTRY(attach_prop.mimetype_entry),
9285 ainfo->content_type ? ainfo->content_type : "");
9286 gtk_entry_set_text(GTK_ENTRY(attach_prop.path_entry),
9287 ainfo->file ? ainfo->file : "");
9288 gtk_entry_set_text(GTK_ENTRY(attach_prop.filename_entry),
9289 ainfo->name ? ainfo->name : "");
9292 const gchar *entry_text;
9294 gchar *cnttype = NULL;
9301 gtk_widget_hide(attach_prop.window);
9302 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), FALSE);
9307 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.mimetype_entry));
9308 if (*entry_text != '\0') {
9311 text = g_strstrip(g_strdup(entry_text));
9312 if ((p = strchr(text, '/')) && !strchr(p + 1, '/')) {
9313 cnttype = g_strdup(text);
9316 alertpanel_error(_("Invalid MIME type."));
9322 ainfo->encoding = combobox_get_active_data(optmenu);
9324 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.path_entry));
9325 if (*entry_text != '\0') {
9326 if (is_file_exist(entry_text) &&
9327 (size = get_file_size(entry_text)) > 0)
9328 file = g_strdup(entry_text);
9331 (_("File doesn't exist or is empty."));
9337 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.filename_entry));
9338 if (*entry_text != '\0') {
9339 g_free(ainfo->name);
9340 ainfo->name = g_strdup(entry_text);
9344 g_free(ainfo->content_type);
9345 ainfo->content_type = cnttype;
9348 g_free(ainfo->file);
9352 ainfo->size = (goffset)size;
9354 /* update tree store */
9355 text = to_human_readable(ainfo->size);
9356 gtk_tree_model_get_iter(model, &iter, path);
9357 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
9358 COL_MIMETYPE, ainfo->content_type,
9360 COL_NAME, ainfo->name,
9361 COL_CHARSET, ainfo->charset,
9367 gtk_tree_path_free(path);
9370 #define SET_LABEL_AND_ENTRY(str, entry, top) \
9372 label = gtk_label_new(str); \
9373 gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), \
9374 GTK_FILL, 0, 0, 0); \
9375 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); \
9377 entry = gtk_entry_new(); \
9378 gtk_table_attach(GTK_TABLE(table), entry, 1, 2, top, (top + 1), \
9379 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); \
9382 static void compose_attach_property_create(gboolean *cancelled)
9388 GtkWidget *mimetype_entry;
9391 GtkListStore *optmenu_menu;
9392 GtkWidget *path_entry;
9393 GtkWidget *filename_entry;
9396 GtkWidget *cancel_btn;
9397 GList *mime_type_list, *strlist;
9400 debug_print("Creating attach_property window...\n");
9402 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose_attach_property");
9403 gtk_widget_set_size_request(window, 480, -1);
9404 gtk_container_set_border_width(GTK_CONTAINER(window), 8);
9405 gtk_window_set_title(GTK_WINDOW(window), _("Properties"));
9406 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
9407 g_signal_connect(G_OBJECT(window), "delete_event",
9408 G_CALLBACK(attach_property_delete_event),
9410 g_signal_connect(G_OBJECT(window), "key_press_event",
9411 G_CALLBACK(attach_property_key_pressed),
9414 vbox = gtk_vbox_new(FALSE, 8);
9415 gtk_container_add(GTK_CONTAINER(window), vbox);
9417 table = gtk_table_new(4, 2, FALSE);
9418 gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
9419 gtk_table_set_row_spacings(GTK_TABLE(table), 8);
9420 gtk_table_set_col_spacings(GTK_TABLE(table), 8);
9422 label = gtk_label_new(_("MIME type"));
9423 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, (0 + 1),
9425 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
9426 mimetype_entry = gtk_combo_box_text_new_with_entry();
9427 gtk_table_attach(GTK_TABLE(table), mimetype_entry, 1, 2, 0, (0 + 1),
9428 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
9430 /* stuff with list */
9431 mime_type_list = procmime_get_mime_type_list();
9433 for (; mime_type_list != NULL; mime_type_list = mime_type_list->next) {
9434 MimeType *type = (MimeType *) mime_type_list->data;
9437 tmp = g_strdup_printf("%s/%s", type->type, type->sub_type);
9439 if (g_list_find_custom(strlist, tmp, (GCompareFunc)strcmp2))
9442 strlist = g_list_insert_sorted(strlist, (gpointer)tmp,
9443 (GCompareFunc)strcmp2);
9446 for (mime_type_list = strlist; mime_type_list != NULL;
9447 mime_type_list = mime_type_list->next) {
9448 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(mimetype_entry), mime_type_list->data);
9449 g_free(mime_type_list->data);
9451 g_list_free(strlist);
9452 gtk_combo_box_set_active(GTK_COMBO_BOX(mimetype_entry), 0);
9453 mimetype_entry = gtk_bin_get_child(GTK_BIN((mimetype_entry)));
9455 label = gtk_label_new(_("Encoding"));
9456 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2,
9458 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
9460 hbox = gtk_hbox_new(FALSE, 0);
9461 gtk_table_attach(GTK_TABLE(table), hbox, 1, 2, 1, 2,
9462 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
9464 optmenu = gtkut_sc_combobox_create(NULL, TRUE);
9465 optmenu_menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
9467 COMBOBOX_ADD(optmenu_menu, "7bit", ENC_7BIT);
9468 COMBOBOX_ADD(optmenu_menu, "8bit", ENC_8BIT);
9469 COMBOBOX_ADD(optmenu_menu, "quoted-printable", ENC_QUOTED_PRINTABLE);
9470 COMBOBOX_ADD(optmenu_menu, "base64", ENC_BASE64);
9471 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), 0);
9473 gtk_box_pack_start(GTK_BOX(hbox), optmenu, TRUE, TRUE, 0);
9475 SET_LABEL_AND_ENTRY(_("Path"), path_entry, 2);
9476 SET_LABEL_AND_ENTRY(_("File name"), filename_entry, 3);
9478 gtkut_stock_button_set_create(&hbbox, &cancel_btn, GTK_STOCK_CANCEL,
9479 &ok_btn, GTK_STOCK_OK,
9481 gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
9482 gtk_widget_grab_default(ok_btn);
9484 g_signal_connect(G_OBJECT(ok_btn), "clicked",
9485 G_CALLBACK(attach_property_ok),
9487 g_signal_connect(G_OBJECT(cancel_btn), "clicked",
9488 G_CALLBACK(attach_property_cancel),
9491 gtk_widget_show_all(vbox);
9493 attach_prop.window = window;
9494 attach_prop.mimetype_entry = mimetype_entry;
9495 attach_prop.encoding_optmenu = optmenu;
9496 attach_prop.path_entry = path_entry;
9497 attach_prop.filename_entry = filename_entry;
9498 attach_prop.ok_btn = ok_btn;
9499 attach_prop.cancel_btn = cancel_btn;
9502 #undef SET_LABEL_AND_ENTRY
9504 static void attach_property_ok(GtkWidget *widget, gboolean *cancelled)
9510 static void attach_property_cancel(GtkWidget *widget, gboolean *cancelled)
9516 static gint attach_property_delete_event(GtkWidget *widget, GdkEventAny *event,
9517 gboolean *cancelled)
9525 static gboolean attach_property_key_pressed(GtkWidget *widget,
9527 gboolean *cancelled)
9529 if (event && event->keyval == GDK_KEY_Escape) {
9533 if (event && event->keyval == GDK_KEY_Return) {
9541 static void compose_exec_ext_editor(Compose *compose)
9546 GdkNativeWindow socket_wid = 0;
9550 tmp = g_strdup_printf("%s%ctmpmsg.%p", get_tmp_dir(),
9551 G_DIR_SEPARATOR, compose);
9553 if (compose_get_ext_editor_uses_socket()) {
9554 /* Only allow one socket */
9555 if (compose->exteditor_socket != NULL) {
9556 if (gtk_widget_is_focus(compose->exteditor_socket)) {
9557 /* Move the focus off of the socket */
9558 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9563 /* Create the receiving GtkSocket */
9564 socket = gtk_socket_new ();
9565 g_signal_connect (GTK_OBJECT(socket), "plug-removed",
9566 G_CALLBACK(compose_ext_editor_plug_removed_cb),
9568 gtk_box_pack_start(GTK_BOX(compose->edit_vbox), socket, TRUE, TRUE, 0);
9569 gtk_widget_set_size_request(socket, prefs_common.compose_width, -1);
9570 /* Realize the socket so that we can use its ID */
9571 gtk_widget_realize(socket);
9572 socket_wid = gtk_socket_get_id(GTK_SOCKET (socket));
9573 compose->exteditor_socket = socket;
9576 if (pipe(pipe_fds) < 0) {
9582 if ((pid = fork()) < 0) {
9589 /* close the write side of the pipe */
9592 compose->exteditor_file = g_strdup(tmp);
9593 compose->exteditor_pid = pid;
9595 compose_set_ext_editor_sensitive(compose, FALSE);
9598 compose->exteditor_ch = g_io_channel_unix_new(pipe_fds[0]);
9600 compose->exteditor_ch = g_io_channel_win32_new_fd(pipe_fds[0]);
9602 compose->exteditor_tag = g_io_add_watch(compose->exteditor_ch,
9606 } else { /* process-monitoring process */
9612 /* close the read side of the pipe */
9615 if (compose_write_body_to_file(compose, tmp) < 0) {
9616 fd_write_all(pipe_fds[1], "2\n", 2);
9620 pid_ed = compose_exec_ext_editor_real(tmp, socket_wid);
9622 fd_write_all(pipe_fds[1], "1\n", 2);
9626 /* wait until editor is terminated */
9627 waitpid(pid_ed, NULL, 0);
9629 fd_write_all(pipe_fds[1], "0\n", 2);
9636 #endif /* G_OS_UNIX */
9639 static gboolean compose_can_autosave(Compose *compose)
9641 if (compose->privacy_system && compose->use_encryption)
9642 return prefs_common.autosave && prefs_common.autosave_encrypted;
9644 return prefs_common.autosave;
9648 static gboolean compose_get_ext_editor_cmd_valid()
9650 gboolean has_s = FALSE;
9651 gboolean has_w = FALSE;
9652 const gchar *p = prefs_common_get_ext_editor_cmd();
9655 while ((p = strchr(p, '%'))) {
9661 } else if (*p == 'w') {
9672 static gint compose_exec_ext_editor_real(const gchar *file, GdkNativeWindow socket_wid)
9679 cm_return_val_if_fail(file != NULL, -1);
9681 if ((pid = fork()) < 0) {
9686 if (pid != 0) return pid;
9688 /* grandchild process */
9690 if (setpgid(0, getppid()))
9693 if (compose_get_ext_editor_cmd_valid()) {
9694 if (compose_get_ext_editor_uses_socket()) {
9695 p = g_strdup(prefs_common_get_ext_editor_cmd());
9696 s = strstr(p, "%w");
9698 if (strstr(p, "%s") < s)
9699 buf = g_strdup_printf(p, file, socket_wid);
9701 buf = g_strdup_printf(p, socket_wid, file);
9704 buf = g_strdup_printf(prefs_common_get_ext_editor_cmd(), file);
9707 if (prefs_common_get_ext_editor_cmd())
9708 g_warning("External editor command-line is invalid: '%s'",
9709 prefs_common_get_ext_editor_cmd());
9710 buf = g_strdup_printf(DEFAULT_EDITOR_CMD, file);
9713 cmdline = strsplit_with_quote(buf, " ", 0);
9715 execvp(cmdline[0], cmdline);
9718 g_strfreev(cmdline);
9723 static gboolean compose_ext_editor_kill(Compose *compose)
9725 pid_t pgid = compose->exteditor_pid * -1;
9728 ret = kill(pgid, 0);
9730 if (ret == 0 || (ret == -1 && EPERM == errno)) {
9734 msg = g_strdup_printf
9735 (_("The external editor is still working.\n"
9736 "Force terminating the process?\n"
9737 "process group id: %d"), -pgid);
9738 val = alertpanel_full(_("Notice"), msg, GTK_STOCK_NO, GTK_STOCK_YES,
9739 NULL, ALERTFOCUS_FIRST, FALSE, NULL,
9744 if (val == G_ALERTALTERNATE) {
9745 g_source_remove(compose->exteditor_tag);
9746 g_io_channel_shutdown(compose->exteditor_ch,
9748 g_io_channel_unref(compose->exteditor_ch);
9750 if (kill(pgid, SIGTERM) < 0) perror("kill");
9751 waitpid(compose->exteditor_pid, NULL, 0);
9753 g_warning("Terminated process group id: %d. "
9754 "Temporary file: %s", -pgid, compose->exteditor_file);
9756 compose_set_ext_editor_sensitive(compose, TRUE);
9758 g_free(compose->exteditor_file);
9759 compose->exteditor_file = NULL;
9760 compose->exteditor_pid = -1;
9761 compose->exteditor_ch = NULL;
9762 compose->exteditor_tag = -1;
9770 static gboolean compose_input_cb(GIOChannel *source, GIOCondition condition,
9774 Compose *compose = (Compose *)data;
9777 debug_print("Compose: input from monitoring process\n");
9779 if (g_io_channel_read_chars(source, buf, sizeof(buf), &bytes_read, NULL) != G_IO_STATUS_NORMAL) {
9784 g_io_channel_shutdown(source, FALSE, NULL);
9785 g_io_channel_unref(source);
9787 waitpid(compose->exteditor_pid, NULL, 0);
9789 if (buf[0] == '0') { /* success */
9790 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9791 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
9792 GtkTextIter start, end;
9795 gtk_text_buffer_set_text(buffer, "", -1);
9796 compose_insert_file(compose, compose->exteditor_file);
9797 compose_changed_cb(NULL, compose);
9799 /* Check if we should save the draft or not */
9800 if (compose_can_autosave(compose))
9801 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
9803 if (claws_unlink(compose->exteditor_file) < 0)
9804 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9806 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
9807 gtk_text_buffer_get_start_iter(buffer, &start);
9808 gtk_text_buffer_get_end_iter(buffer, &end);
9809 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
9810 if (chars && strlen(chars) > 0)
9811 compose->modified = TRUE;
9813 } else if (buf[0] == '1') { /* failed */
9814 g_warning("Couldn't exec external editor");
9815 if (claws_unlink(compose->exteditor_file) < 0)
9816 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9817 } else if (buf[0] == '2') {
9818 g_warning("Couldn't write to file");
9819 } else if (buf[0] == '3') {
9820 g_warning("Pipe read failed");
9823 compose_set_ext_editor_sensitive(compose, TRUE);
9825 g_free(compose->exteditor_file);
9826 compose->exteditor_file = NULL;
9827 compose->exteditor_pid = -1;
9828 compose->exteditor_ch = NULL;
9829 compose->exteditor_tag = -1;
9830 if (compose->exteditor_socket) {
9831 gtk_widget_destroy(compose->exteditor_socket);
9832 compose->exteditor_socket = NULL;
9839 static char *ext_editor_menu_entries[] = {
9840 "Menu/Message/Send",
9841 "Menu/Message/SendLater",
9842 "Menu/Message/InsertFile",
9843 "Menu/Message/InsertSig",
9844 "Menu/Message/ReplaceSig",
9845 "Menu/Message/Save",
9846 "Menu/Message/Print",
9851 "Menu/Tools/ShowRuler",
9852 "Menu/Tools/Actions",
9857 static void compose_set_ext_editor_sensitive(Compose *compose,
9862 for (i = 0; ext_editor_menu_entries[i]; ++i) {
9863 cm_menu_set_sensitive_full(compose->ui_manager,
9864 ext_editor_menu_entries[i], sensitive);
9867 if (compose_get_ext_editor_uses_socket()) {
9869 if (compose->exteditor_socket)
9870 gtk_widget_hide(compose->exteditor_socket);
9871 gtk_widget_show(compose->scrolledwin);
9872 if (prefs_common.show_ruler)
9873 gtk_widget_show(compose->ruler_hbox);
9874 /* Fix the focus, as it doesn't go anywhere when the
9875 * socket is hidden or destroyed */
9876 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9878 g_assert (compose->exteditor_socket != NULL);
9879 /* Fix the focus, as it doesn't go anywhere when the
9880 * edit box is hidden */
9881 if (gtk_widget_is_focus(compose->text))
9882 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9883 gtk_widget_hide(compose->scrolledwin);
9884 gtk_widget_hide(compose->ruler_hbox);
9885 gtk_widget_show(compose->exteditor_socket);
9888 gtk_widget_set_sensitive(compose->text, sensitive);
9890 if (compose->toolbar->send_btn)
9891 gtk_widget_set_sensitive(compose->toolbar->send_btn, sensitive);
9892 if (compose->toolbar->sendl_btn)
9893 gtk_widget_set_sensitive(compose->toolbar->sendl_btn, sensitive);
9894 if (compose->toolbar->draft_btn)
9895 gtk_widget_set_sensitive(compose->toolbar->draft_btn, sensitive);
9896 if (compose->toolbar->insert_btn)
9897 gtk_widget_set_sensitive(compose->toolbar->insert_btn, sensitive);
9898 if (compose->toolbar->sig_btn)
9899 gtk_widget_set_sensitive(compose->toolbar->sig_btn, sensitive);
9900 if (compose->toolbar->exteditor_btn)
9901 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, sensitive);
9902 if (compose->toolbar->linewrap_current_btn)
9903 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, sensitive);
9904 if (compose->toolbar->linewrap_all_btn)
9905 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, sensitive);
9908 static gboolean compose_get_ext_editor_uses_socket()
9910 return (prefs_common_get_ext_editor_cmd() &&
9911 strstr(prefs_common_get_ext_editor_cmd(), "%w"));
9914 static gboolean compose_ext_editor_plug_removed_cb(GtkSocket *socket, Compose *compose)
9916 compose->exteditor_socket = NULL;
9917 /* returning FALSE allows destruction of the socket */
9920 #endif /* G_OS_UNIX */
9923 * compose_undo_state_changed:
9925 * Change the sensivity of the menuentries undo and redo
9927 static void compose_undo_state_changed(UndoMain *undostruct, gint undo_state,
9928 gint redo_state, gpointer data)
9930 Compose *compose = (Compose *)data;
9932 switch (undo_state) {
9933 case UNDO_STATE_TRUE:
9934 if (!undostruct->undo_state) {
9935 undostruct->undo_state = TRUE;
9936 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", TRUE);
9939 case UNDO_STATE_FALSE:
9940 if (undostruct->undo_state) {
9941 undostruct->undo_state = FALSE;
9942 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
9945 case UNDO_STATE_UNCHANGED:
9947 case UNDO_STATE_REFRESH:
9948 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", undostruct->undo_state);
9951 g_warning("Undo state not recognized");
9955 switch (redo_state) {
9956 case UNDO_STATE_TRUE:
9957 if (!undostruct->redo_state) {
9958 undostruct->redo_state = TRUE;
9959 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", TRUE);
9962 case UNDO_STATE_FALSE:
9963 if (undostruct->redo_state) {
9964 undostruct->redo_state = FALSE;
9965 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
9968 case UNDO_STATE_UNCHANGED:
9970 case UNDO_STATE_REFRESH:
9971 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", undostruct->redo_state);
9974 g_warning("Redo state not recognized");
9979 /* callback functions */
9981 static void compose_notebook_size_alloc(GtkNotebook *notebook,
9982 GtkAllocation *allocation,
9985 prefs_common.compose_notebook_height = gtk_paned_get_position(paned);
9988 /* compose_edit_size_alloc() - called when resized. don't know whether Gtk
9989 * includes "non-client" (windows-izm) in calculation, so this calculation
9990 * may not be accurate.
9992 static gboolean compose_edit_size_alloc(GtkEditable *widget,
9993 GtkAllocation *allocation,
9994 GtkSHRuler *shruler)
9996 if (prefs_common.show_ruler) {
9997 gint char_width = 0, char_height = 0;
9998 gint line_width_in_chars;
10000 gtkut_get_font_size(GTK_WIDGET(widget),
10001 &char_width, &char_height);
10002 line_width_in_chars =
10003 (allocation->width - allocation->x) / char_width;
10005 /* got the maximum */
10006 gtk_shruler_set_range(GTK_SHRULER(shruler),
10007 0.0, line_width_in_chars, 0);
10016 ComposePrefType type;
10017 gboolean entry_marked;
10018 } HeaderEntryState;
10020 static void account_activated(GtkComboBox *optmenu, gpointer data)
10022 Compose *compose = (Compose *)data;
10025 gchar *folderidentifier;
10026 gint account_id = 0;
10027 GtkTreeModel *menu;
10029 GSList *list, *saved_list = NULL;
10030 HeaderEntryState *state;
10032 /* Get ID of active account in the combo box */
10033 menu = gtk_combo_box_get_model(optmenu);
10034 cm_return_if_fail(gtk_combo_box_get_active_iter(optmenu, &iter));
10035 gtk_tree_model_get(menu, &iter, 1, &account_id, -1);
10037 ac = account_find_from_id(account_id);
10038 cm_return_if_fail(ac != NULL);
10040 if (ac != compose->account) {
10041 compose_select_account(compose, ac, FALSE);
10043 for (list = compose->header_list; list; list = list->next) {
10044 ComposeHeaderEntry *hentry=(ComposeHeaderEntry *)list->data;
10046 if (hentry->type == PREF_ACCOUNT || !list->next) {
10047 compose_destroy_headerentry(compose, hentry);
10050 state = g_malloc0(sizeof(HeaderEntryState));
10051 state->header = gtk_editable_get_chars(GTK_EDITABLE(
10052 gtk_bin_get_child(GTK_BIN(hentry->combo))), 0, -1);
10053 state->entry = gtk_editable_get_chars(
10054 GTK_EDITABLE(hentry->entry), 0, -1);
10055 state->type = hentry->type;
10057 saved_list = g_slist_append(saved_list, state);
10058 compose_destroy_headerentry(compose, hentry);
10061 compose->header_last = NULL;
10062 g_slist_free(compose->header_list);
10063 compose->header_list = NULL;
10064 compose->header_nextrow = 1;
10065 compose_create_header_entry(compose);
10067 if (ac->set_autocc && ac->auto_cc)
10068 compose_entry_append(compose, ac->auto_cc,
10069 COMPOSE_CC, PREF_ACCOUNT);
10070 if (ac->set_autobcc && ac->auto_bcc)
10071 compose_entry_append(compose, ac->auto_bcc,
10072 COMPOSE_BCC, PREF_ACCOUNT);
10073 if (ac->set_autoreplyto && ac->auto_replyto)
10074 compose_entry_append(compose, ac->auto_replyto,
10075 COMPOSE_REPLYTO, PREF_ACCOUNT);
10077 for (list = saved_list; list; list = list->next) {
10078 state = (HeaderEntryState *) list->data;
10080 compose_add_header_entry(compose, state->header,
10081 state->entry, state->type);
10083 g_free(state->header);
10084 g_free(state->entry);
10087 g_slist_free(saved_list);
10089 combobox_select_by_data(GTK_COMBO_BOX(compose->header_last->combo),
10090 (ac->protocol == A_NNTP) ?
10091 COMPOSE_NEWSGROUPS : COMPOSE_TO);
10094 /* Set message save folder */
10095 if (account_get_special_folder(compose->account, F_OUTBOX)) {
10096 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
10098 g_signal_connect(G_OBJECT(compose->savemsg_checkbtn), "toggled",
10099 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
10101 compose_set_save_to(compose, NULL);
10102 if (account_get_special_folder(compose->account, F_OUTBOX)) {
10103 folderidentifier = folder_item_get_identifier(account_get_special_folder
10104 (compose->account, F_OUTBOX));
10105 compose_set_save_to(compose, folderidentifier);
10106 g_free(folderidentifier);
10110 static void attach_selected(GtkTreeView *tree_view, GtkTreePath *tree_path,
10111 GtkTreeViewColumn *column, Compose *compose)
10113 compose_attach_property(NULL, compose);
10116 static gboolean attach_button_pressed(GtkWidget *widget, GdkEventButton *event,
10119 Compose *compose = (Compose *)data;
10120 GtkTreeSelection *attach_selection;
10121 gint attach_nr_selected;
10124 if (!event) return FALSE;
10126 if (event->button == 3) {
10127 attach_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
10128 attach_nr_selected = gtk_tree_selection_count_selected_rows(attach_selection);
10130 /* If no rows, or just one row is selected, right-click should
10131 * open menu relevant to the row being right-clicked on. We
10132 * achieve that by selecting the clicked row first. If more
10133 * than one row is selected, we shouldn't modify the selection,
10134 * as user may want to remove selected rows (attachments). */
10135 if (attach_nr_selected < 2) {
10136 gtk_tree_selection_unselect_all(attach_selection);
10137 attach_nr_selected = 0;
10138 gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget),
10139 event->x, event->y, &path, NULL, NULL, NULL);
10140 if (path != NULL) {
10141 gtk_tree_selection_select_path(attach_selection, path);
10142 gtk_tree_path_free(path);
10143 attach_nr_selected++;
10147 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", (attach_nr_selected > 0));
10148 /* Properties menu item makes no sense with more than one row
10149 * selected, the properties dialog can only edit one attachment. */
10150 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", (attach_nr_selected == 1));
10152 gtk_menu_popup(GTK_MENU(compose->popupmenu), NULL, NULL,
10153 NULL, NULL, event->button, event->time);
10160 static gboolean attach_key_pressed(GtkWidget *widget, GdkEventKey *event,
10163 Compose *compose = (Compose *)data;
10165 if (!event) return FALSE;
10167 switch (event->keyval) {
10168 case GDK_KEY_Delete:
10169 compose_attach_remove_selected(NULL, compose);
10175 static void compose_allow_user_actions (Compose *compose, gboolean allow)
10177 toolbar_comp_set_sensitive(compose, allow);
10178 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message", allow);
10179 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", allow);
10181 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", allow);
10183 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", allow);
10184 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools", allow);
10185 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Help", allow);
10187 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), allow);
10191 static void compose_send_cb(GtkAction *action, gpointer data)
10193 Compose *compose = (Compose *)data;
10196 if (compose->exteditor_tag != -1) {
10197 debug_print("ignoring send: external editor still open\n");
10201 if (prefs_common.work_offline &&
10202 !inc_offline_should_override(TRUE,
10203 _("Claws Mail needs network access in order "
10204 "to send this email.")))
10207 if (compose->draft_timeout_tag >= 0) { /* CLAWS: disable draft timeout */
10208 g_source_remove(compose->draft_timeout_tag);
10209 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
10212 compose_send(compose);
10215 static void compose_send_later_cb(GtkAction *action, gpointer data)
10217 Compose *compose = (Compose *)data;
10218 ComposeQueueResult val;
10221 compose_allow_user_actions(compose, FALSE);
10222 val = compose_queue_sub(compose, NULL, NULL, NULL, TRUE, TRUE);
10223 compose_allow_user_actions(compose, TRUE);
10226 if (val == COMPOSE_QUEUE_SUCCESS) {
10227 compose_close(compose);
10229 _display_queue_error(val);
10232 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
10235 #define DRAFTED_AT_EXIT "drafted_at_exit"
10236 static void compose_register_draft(MsgInfo *info)
10238 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10239 DRAFTED_AT_EXIT, NULL);
10240 FILE *fp = g_fopen(filepath, "ab");
10243 fprintf(fp, "%s\t%d\n", folder_item_get_identifier(info->folder),
10251 gboolean compose_draft (gpointer data, guint action)
10253 Compose *compose = (Compose *)data;
10258 MsgFlags flag = {0, 0};
10259 static gboolean lock = FALSE;
10260 MsgInfo *newmsginfo;
10262 gboolean target_locked = FALSE;
10263 gboolean err = FALSE;
10265 if (lock) return FALSE;
10267 if (compose->sending)
10270 draft = account_get_special_folder(compose->account, F_DRAFT);
10271 cm_return_val_if_fail(draft != NULL, FALSE);
10273 if (!g_mutex_trylock(compose->mutex)) {
10274 /* we don't want to lock the mutex once it's available,
10275 * because as the only other part of compose.c locking
10276 * it is compose_close - which means once unlocked,
10277 * the compose struct will be freed */
10278 debug_print("couldn't lock mutex, probably sending\n");
10284 tmp = g_strdup_printf("%s%cdraft.%p", get_tmp_dir(),
10285 G_DIR_SEPARATOR, compose);
10286 if ((fp = g_fopen(tmp, "wb")) == NULL) {
10287 FILE_OP_ERROR(tmp, "fopen");
10291 /* chmod for security */
10292 if (change_file_mode_rw(fp, tmp) < 0) {
10293 FILE_OP_ERROR(tmp, "chmod");
10294 g_warning("can't change file mode");
10297 /* Save draft infos */
10298 err |= (fprintf(fp, "X-Claws-Account-Id:%d\n", compose->account->account_id) < 0);
10299 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
10301 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
10302 gchar *savefolderid;
10304 savefolderid = compose_get_save_to(compose);
10305 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
10306 g_free(savefolderid);
10308 if (compose->return_receipt) {
10309 err |= (fprintf(fp, "RRCPT:1\n") < 0);
10311 if (compose->privacy_system) {
10312 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
10313 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
10314 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
10317 /* Message-ID of message replying to */
10318 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
10319 gchar *folderid = NULL;
10321 if (compose->replyinfo->folder)
10322 folderid = folder_item_get_identifier(compose->replyinfo->folder);
10323 if (folderid == NULL)
10324 folderid = g_strdup("NULL");
10326 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
10329 /* Message-ID of message forwarding to */
10330 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
10331 gchar *folderid = NULL;
10333 if (compose->fwdinfo->folder)
10334 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
10335 if (folderid == NULL)
10336 folderid = g_strdup("NULL");
10338 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
10342 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
10343 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
10345 sheaders = compose_get_manual_headers_info(compose);
10346 err |= (fprintf(fp, "X-Claws-Manual-Headers:%s\n", sheaders) < 0);
10349 /* end of headers */
10350 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
10357 if (compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_STORE, action != COMPOSE_AUTO_SAVE) < 0) {
10361 if (fclose(fp) == EOF) {
10365 flag.perm_flags = MSG_NEW|MSG_UNREAD;
10366 if (compose->targetinfo) {
10367 target_locked = MSG_IS_LOCKED(compose->targetinfo->flags);
10369 flag.perm_flags |= MSG_LOCKED;
10371 flag.tmp_flags = MSG_DRAFT;
10373 folder_item_scan(draft);
10374 if ((msgnum = folder_item_add_msg(draft, tmp, &flag, TRUE)) < 0) {
10375 MsgInfo *tmpinfo = NULL;
10376 debug_print("didn't get msgnum after adding draft [%s]\n", compose->msgid?compose->msgid:"no msgid");
10377 if (compose->msgid) {
10378 tmpinfo = folder_item_get_msginfo_by_msgid(draft, compose->msgid);
10381 msgnum = tmpinfo->msgnum;
10382 procmsg_msginfo_free(&tmpinfo);
10383 debug_print("got draft msgnum %d from scanning\n", msgnum);
10385 debug_print("didn't get draft msgnum after scanning\n");
10388 debug_print("got draft msgnum %d from adding\n", msgnum);
10394 if (action != COMPOSE_AUTO_SAVE) {
10395 if (action != COMPOSE_DRAFT_FOR_EXIT)
10396 alertpanel_error(_("Could not save draft."));
10399 gtkut_window_popup(compose->window);
10400 val = alertpanel_full(_("Could not save draft"),
10401 _("Could not save draft.\n"
10402 "Do you want to cancel exit or discard this email?"),
10403 _("_Cancel exit"), _("_Discard email"), NULL, ALERTFOCUS_FIRST,
10404 FALSE, NULL, ALERT_QUESTION);
10405 if (val == G_ALERTALTERNATE) {
10407 g_mutex_unlock(compose->mutex); /* must be done before closing */
10408 compose_close(compose);
10412 g_mutex_unlock(compose->mutex); /* must be done before closing */
10421 if (compose->mode == COMPOSE_REEDIT) {
10422 compose_remove_reedit_target(compose, TRUE);
10425 newmsginfo = folder_item_get_msginfo(draft, msgnum);
10428 procmsg_msginfo_unset_flags(newmsginfo, ~0, ~0);
10430 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD|MSG_LOCKED, MSG_DRAFT);
10432 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD, MSG_DRAFT);
10433 if (compose_use_attach(compose) && action != COMPOSE_AUTO_SAVE)
10434 procmsg_msginfo_set_flags(newmsginfo, 0,
10435 MSG_HAS_ATTACHMENT);
10437 if (action == COMPOSE_DRAFT_FOR_EXIT) {
10438 compose_register_draft(newmsginfo);
10440 procmsg_msginfo_free(&newmsginfo);
10443 folder_item_scan(draft);
10445 if (action == COMPOSE_QUIT_EDITING || action == COMPOSE_DRAFT_FOR_EXIT) {
10447 g_mutex_unlock(compose->mutex); /* must be done before closing */
10448 compose_close(compose);
10455 GError *error = NULL;
10460 goffset size, mtime;
10462 path = folder_item_fetch_msg(draft, msgnum);
10463 if (path == NULL) {
10464 debug_print("can't fetch %s:%d\n", draft->path, msgnum);
10468 f = g_file_new_for_path(path);
10469 fi = g_file_query_info(f, "standard::size,time::modified",
10470 G_FILE_QUERY_INFO_NONE, NULL, &error);
10471 if (error != NULL) {
10472 debug_print("couldn't query file info for '%s': %s\n",
10473 path, error->message);
10474 g_error_free(error);
10479 size = g_file_info_get_size(fi);
10480 g_file_info_get_modification_time(fi, &tv);
10482 g_object_unref(fi);
10485 if (g_stat(path, &s) < 0) {
10486 FILE_OP_ERROR(path, "stat");
10491 mtime = s.st_mtime;
10495 procmsg_msginfo_free(&(compose->targetinfo));
10496 compose->targetinfo = procmsg_msginfo_new();
10497 compose->targetinfo->msgnum = msgnum;
10498 compose->targetinfo->size = size;
10499 compose->targetinfo->mtime = mtime;
10500 compose->targetinfo->folder = draft;
10502 procmsg_msginfo_set_flags(compose->targetinfo, MSG_LOCKED, 0);
10503 compose->mode = COMPOSE_REEDIT;
10505 if (action == COMPOSE_AUTO_SAVE) {
10506 compose->autosaved_draft = compose->targetinfo;
10508 compose->modified = FALSE;
10509 compose_set_title(compose);
10513 g_mutex_unlock(compose->mutex);
10517 void compose_clear_exit_drafts(void)
10519 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10520 DRAFTED_AT_EXIT, NULL);
10521 if (is_file_exist(filepath))
10522 claws_unlink(filepath);
10527 void compose_reopen_exit_drafts(void)
10529 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10530 DRAFTED_AT_EXIT, NULL);
10531 FILE *fp = g_fopen(filepath, "rb");
10535 while (fgets(buf, sizeof(buf), fp)) {
10536 gchar **parts = g_strsplit(buf, "\t", 2);
10537 const gchar *folder = parts[0];
10538 int msgnum = parts[1] ? atoi(parts[1]):-1;
10540 if (folder && *folder && msgnum > -1) {
10541 FolderItem *item = folder_find_item_from_identifier(folder);
10542 MsgInfo *info = folder_item_get_msginfo(item, msgnum);
10544 compose_reedit(info, FALSE);
10551 compose_clear_exit_drafts();
10554 static void compose_save_cb(GtkAction *action, gpointer data)
10556 Compose *compose = (Compose *)data;
10557 compose_draft(compose, COMPOSE_KEEP_EDITING);
10558 compose->rmode = COMPOSE_REEDIT;
10561 void compose_attach_from_list(Compose *compose, GList *file_list, gboolean free_data)
10563 if (compose && file_list) {
10566 for ( tmp = file_list; tmp; tmp = tmp->next) {
10567 gchar *file = (gchar *) tmp->data;
10568 gchar *utf8_filename = conv_filename_to_utf8(file);
10569 compose_attach_append(compose, file, utf8_filename, NULL, NULL);
10570 compose_changed_cb(NULL, compose);
10575 g_free(utf8_filename);
10580 static void compose_attach_cb(GtkAction *action, gpointer data)
10582 Compose *compose = (Compose *)data;
10585 if (compose->redirect_filename != NULL)
10588 /* Set focus_window properly, in case we were called via popup menu,
10589 * which unsets it (via focus_out_event callback on compose window). */
10590 manage_window_focus_in(compose->window, NULL, NULL);
10592 file_list = filesel_select_multiple_files_open(_("Select file"), NULL);
10595 compose_attach_from_list(compose, file_list, TRUE);
10596 g_list_free(file_list);
10600 static void compose_insert_file_cb(GtkAction *action, gpointer data)
10602 Compose *compose = (Compose *)data;
10604 gint files_inserted = 0;
10606 file_list = filesel_select_multiple_files_open(_("Select file"), NULL);
10611 for ( tmp = file_list; tmp; tmp = tmp->next) {
10612 gchar *file = (gchar *) tmp->data;
10613 gchar *filedup = g_strdup(file);
10614 gchar *shortfile = g_path_get_basename(filedup);
10615 ComposeInsertResult res;
10616 /* insert the file if the file is short or if the user confirmed that
10617 he/she wants to insert the large file */
10618 res = compose_insert_file(compose, file);
10619 if (res == COMPOSE_INSERT_READ_ERROR) {
10620 alertpanel_error(_("File '%s' could not be read."), shortfile);
10621 } else if (res == COMPOSE_INSERT_INVALID_CHARACTER) {
10622 alertpanel_error(_("File '%s' contained invalid characters\n"
10623 "for the current encoding, insertion may be incorrect."),
10625 } else if (res == COMPOSE_INSERT_SUCCESS)
10632 g_list_free(file_list);
10636 if (files_inserted > 0 && compose->gtkaspell &&
10637 compose->gtkaspell->check_while_typing)
10638 gtkaspell_highlight_all(compose->gtkaspell);
10642 static void compose_insert_sig_cb(GtkAction *action, gpointer data)
10644 Compose *compose = (Compose *)data;
10646 compose_insert_sig(compose, FALSE);
10649 static void compose_replace_sig_cb(GtkAction *action, gpointer data)
10651 Compose *compose = (Compose *)data;
10653 compose_insert_sig(compose, TRUE);
10656 static gint compose_delete_cb(GtkWidget *widget, GdkEventAny *event,
10660 Compose *compose = (Compose *)data;
10662 gtkut_widget_get_uposition(widget, &x, &y);
10663 if (!compose->batch) {
10664 prefs_common.compose_x = x;
10665 prefs_common.compose_y = y;
10667 if (compose->sending || compose->updating)
10669 compose_close_cb(NULL, compose);
10673 void compose_close_toolbar(Compose *compose)
10675 compose_close_cb(NULL, compose);
10678 static void compose_close_cb(GtkAction *action, gpointer data)
10680 Compose *compose = (Compose *)data;
10684 if (compose->exteditor_tag != -1) {
10685 if (!compose_ext_editor_kill(compose))
10690 if (compose->modified) {
10691 gboolean reedit = (compose->rmode == COMPOSE_REEDIT);
10692 if (!g_mutex_trylock(compose->mutex)) {
10693 /* we don't want to lock the mutex once it's available,
10694 * because as the only other part of compose.c locking
10695 * it is compose_close - which means once unlocked,
10696 * the compose struct will be freed */
10697 debug_print("couldn't lock mutex, probably sending\n");
10701 val = alertpanel(_("Discard message"),
10702 _("This message has been modified. Discard it?"),
10703 _("_Discard"), _("_Save to Drafts"), GTK_STOCK_CANCEL,
10706 val = alertpanel(_("Save changes"),
10707 _("This message has been modified. Save the latest changes?"),
10708 _("_Don't save"), _("_Save to Drafts"), GTK_STOCK_CANCEL,
10709 ALERTFOCUS_SECOND);
10711 g_mutex_unlock(compose->mutex);
10713 case G_ALERTDEFAULT:
10714 if (compose_can_autosave(compose) && !reedit)
10715 compose_remove_draft(compose);
10717 case G_ALERTALTERNATE:
10718 compose_draft(data, COMPOSE_QUIT_EDITING);
10725 compose_close(compose);
10728 static void compose_print_cb(GtkAction *action, gpointer data)
10730 Compose *compose = (Compose *) data;
10732 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
10733 if (compose->targetinfo)
10734 messageview_print(compose->targetinfo, FALSE, -1, -1, 0);
10737 static void compose_set_encoding_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
10739 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
10740 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
10741 Compose *compose = (Compose *) data;
10744 compose->out_encoding = (CharSet)value;
10747 static void compose_address_cb(GtkAction *action, gpointer data)
10749 Compose *compose = (Compose *)data;
10751 #ifndef USE_ALT_ADDRBOOK
10752 addressbook_open(compose);
10754 GError* error = NULL;
10755 addressbook_connect_signals(compose);
10756 addressbook_dbus_open(TRUE, &error);
10758 g_warning("%s", error->message);
10759 g_error_free(error);
10764 static void about_show_cb(GtkAction *action, gpointer data)
10769 static void compose_template_activate_cb(GtkWidget *widget, gpointer data)
10771 Compose *compose = (Compose *)data;
10776 tmpl = g_object_get_data(G_OBJECT(widget), "template");
10777 cm_return_if_fail(tmpl != NULL);
10779 msg = g_strdup_printf(_("Do you want to apply the template '%s'?"),
10781 val = alertpanel(_("Apply template"), msg,
10782 _("_Replace"), _("_Insert"), GTK_STOCK_CANCEL, ALERTFOCUS_FIRST);
10785 if (val == G_ALERTDEFAULT)
10786 compose_template_apply(compose, tmpl, TRUE);
10787 else if (val == G_ALERTALTERNATE)
10788 compose_template_apply(compose, tmpl, FALSE);
10791 static void compose_ext_editor_cb(GtkAction *action, gpointer data)
10793 Compose *compose = (Compose *)data;
10796 if (compose->exteditor_tag != -1) {
10797 debug_print("ignoring open external editor: external editor still open\n");
10801 compose_exec_ext_editor(compose);
10804 static void compose_undo_cb(GtkAction *action, gpointer data)
10806 Compose *compose = (Compose *)data;
10807 gboolean prev_autowrap = compose->autowrap;
10809 compose->autowrap = FALSE;
10810 undo_undo(compose->undostruct);
10811 compose->autowrap = prev_autowrap;
10814 static void compose_redo_cb(GtkAction *action, gpointer data)
10816 Compose *compose = (Compose *)data;
10817 gboolean prev_autowrap = compose->autowrap;
10819 compose->autowrap = FALSE;
10820 undo_redo(compose->undostruct);
10821 compose->autowrap = prev_autowrap;
10824 static void entry_cut_clipboard(GtkWidget *entry)
10826 if (GTK_IS_EDITABLE(entry))
10827 gtk_editable_cut_clipboard (GTK_EDITABLE(entry));
10828 else if (GTK_IS_TEXT_VIEW(entry))
10829 gtk_text_buffer_cut_clipboard(
10830 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10831 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
10835 static void entry_copy_clipboard(GtkWidget *entry)
10837 if (GTK_IS_EDITABLE(entry))
10838 gtk_editable_copy_clipboard (GTK_EDITABLE(entry));
10839 else if (GTK_IS_TEXT_VIEW(entry))
10840 gtk_text_buffer_copy_clipboard(
10841 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10842 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
10845 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry,
10846 gboolean wrap, GdkAtom clip, GtkTextIter *insert_place)
10848 if (GTK_IS_TEXT_VIEW(entry)) {
10849 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10850 GtkTextMark *mark_start = gtk_text_buffer_get_insert(buffer);
10851 GtkTextIter start_iter, end_iter;
10853 gchar *contents = gtk_clipboard_wait_for_text(gtk_clipboard_get(clip));
10855 if (contents == NULL)
10858 /* we shouldn't delete the selection when middle-click-pasting, or we
10859 * can't mid-click-paste our own selection */
10860 if (clip != GDK_SELECTION_PRIMARY) {
10861 undo_paste_clipboard(GTK_TEXT_VIEW(compose->text), compose->undostruct);
10862 gtk_text_buffer_delete_selection(buffer, FALSE, TRUE);
10865 if (insert_place == NULL) {
10866 /* if insert_place isn't specified, insert at the cursor.
10867 * used for Ctrl-V pasting */
10868 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10869 start = gtk_text_iter_get_offset(&start_iter);
10870 gtk_text_buffer_insert(buffer, &start_iter, contents, strlen(contents));
10872 /* if insert_place is specified, paste here.
10873 * used for mid-click-pasting */
10874 start = gtk_text_iter_get_offset(insert_place);
10875 gtk_text_buffer_insert(buffer, insert_place, contents, strlen(contents));
10876 if (prefs_common.primary_paste_unselects)
10877 gtk_text_buffer_select_range(buffer, insert_place, insert_place);
10881 /* paste unwrapped: mark the paste so it's not wrapped later */
10882 end = start + strlen(contents);
10883 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, start);
10884 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, end);
10885 gtk_text_buffer_apply_tag_by_name(buffer, "no_wrap", &start_iter, &end_iter);
10886 } else if (wrap && clip == GDK_SELECTION_PRIMARY) {
10887 /* rewrap paragraph now (after a mid-click-paste) */
10888 mark_start = gtk_text_buffer_get_insert(buffer);
10889 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10890 gtk_text_iter_backward_char(&start_iter);
10891 compose_beautify_paragraph(compose, &start_iter, TRUE);
10893 } else if (GTK_IS_EDITABLE(entry))
10894 gtk_editable_paste_clipboard (GTK_EDITABLE(entry));
10896 compose->modified = TRUE;
10899 static void entry_allsel(GtkWidget *entry)
10901 if (GTK_IS_EDITABLE(entry))
10902 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
10903 else if (GTK_IS_TEXT_VIEW(entry)) {
10904 GtkTextIter startiter, enditer;
10905 GtkTextBuffer *textbuf;
10907 textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10908 gtk_text_buffer_get_start_iter(textbuf, &startiter);
10909 gtk_text_buffer_get_end_iter(textbuf, &enditer);
10911 gtk_text_buffer_move_mark_by_name(textbuf,
10912 "selection_bound", &startiter);
10913 gtk_text_buffer_move_mark_by_name(textbuf,
10914 "insert", &enditer);
10918 static void compose_cut_cb(GtkAction *action, gpointer data)
10920 Compose *compose = (Compose *)data;
10921 if (compose->focused_editable
10922 #ifndef GENERIC_UMPC
10923 && gtk_widget_has_focus(compose->focused_editable)
10926 entry_cut_clipboard(compose->focused_editable);
10929 static void compose_copy_cb(GtkAction *action, gpointer data)
10931 Compose *compose = (Compose *)data;
10932 if (compose->focused_editable
10933 #ifndef GENERIC_UMPC
10934 && gtk_widget_has_focus(compose->focused_editable)
10937 entry_copy_clipboard(compose->focused_editable);
10940 static void compose_paste_cb(GtkAction *action, gpointer data)
10942 Compose *compose = (Compose *)data;
10943 gint prev_autowrap;
10944 GtkTextBuffer *buffer;
10946 if (compose->focused_editable &&
10947 #ifndef GENERIC_UMPC
10948 gtk_widget_has_focus(compose->focused_editable)
10951 entry_paste_clipboard(compose, compose->focused_editable,
10952 prefs_common.linewrap_pastes,
10953 GDK_SELECTION_CLIPBOARD, NULL);
10958 #ifndef GENERIC_UMPC
10959 gtk_widget_has_focus(compose->text) &&
10961 compose->gtkaspell &&
10962 compose->gtkaspell->check_while_typing)
10963 gtkaspell_highlight_all(compose->gtkaspell);
10967 static void compose_paste_as_quote_cb(GtkAction *action, gpointer data)
10969 Compose *compose = (Compose *)data;
10970 gint wrap_quote = prefs_common.linewrap_quote;
10971 if (compose->focused_editable
10972 #ifndef GENERIC_UMPC
10973 && gtk_widget_has_focus(compose->focused_editable)
10976 /* let text_insert() (called directly or at a later time
10977 * after the gtk_editable_paste_clipboard) know that
10978 * text is to be inserted as a quotation. implemented
10979 * by using a simple refcount... */
10980 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data(
10981 G_OBJECT(compose->focused_editable),
10982 "paste_as_quotation"));
10983 g_object_set_data(G_OBJECT(compose->focused_editable),
10984 "paste_as_quotation",
10985 GINT_TO_POINTER(paste_as_quotation + 1));
10986 prefs_common.linewrap_quote = prefs_common.linewrap_pastes;
10987 entry_paste_clipboard(compose, compose->focused_editable,
10988 prefs_common.linewrap_pastes,
10989 GDK_SELECTION_CLIPBOARD, NULL);
10990 prefs_common.linewrap_quote = wrap_quote;
10994 static void compose_paste_no_wrap_cb(GtkAction *action, gpointer data)
10996 Compose *compose = (Compose *)data;
10997 gint prev_autowrap;
10998 GtkTextBuffer *buffer;
11000 if (compose->focused_editable
11001 #ifndef GENERIC_UMPC
11002 && gtk_widget_has_focus(compose->focused_editable)
11005 entry_paste_clipboard(compose, compose->focused_editable, FALSE,
11006 GDK_SELECTION_CLIPBOARD, NULL);
11011 #ifndef GENERIC_UMPC
11012 gtk_widget_has_focus(compose->text) &&
11014 compose->gtkaspell &&
11015 compose->gtkaspell->check_while_typing)
11016 gtkaspell_highlight_all(compose->gtkaspell);
11020 static void compose_paste_wrap_cb(GtkAction *action, gpointer data)
11022 Compose *compose = (Compose *)data;
11023 gint prev_autowrap;
11024 GtkTextBuffer *buffer;
11026 if (compose->focused_editable
11027 #ifndef GENERIC_UMPC
11028 && gtk_widget_has_focus(compose->focused_editable)
11031 entry_paste_clipboard(compose, compose->focused_editable, TRUE,
11032 GDK_SELECTION_CLIPBOARD, NULL);
11037 #ifndef GENERIC_UMPC
11038 gtk_widget_has_focus(compose->text) &&
11040 compose->gtkaspell &&
11041 compose->gtkaspell->check_while_typing)
11042 gtkaspell_highlight_all(compose->gtkaspell);
11046 static void compose_allsel_cb(GtkAction *action, gpointer data)
11048 Compose *compose = (Compose *)data;
11049 if (compose->focused_editable
11050 #ifndef GENERIC_UMPC
11051 && gtk_widget_has_focus(compose->focused_editable)
11054 entry_allsel(compose->focused_editable);
11057 static void textview_move_beginning_of_line (GtkTextView *text)
11059 GtkTextBuffer *buffer;
11063 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11065 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11066 mark = gtk_text_buffer_get_insert(buffer);
11067 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11068 gtk_text_iter_set_line_offset(&ins, 0);
11069 gtk_text_buffer_place_cursor(buffer, &ins);
11072 static void textview_move_forward_character (GtkTextView *text)
11074 GtkTextBuffer *buffer;
11078 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11080 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11081 mark = gtk_text_buffer_get_insert(buffer);
11082 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11083 if (gtk_text_iter_forward_cursor_position(&ins))
11084 gtk_text_buffer_place_cursor(buffer, &ins);
11087 static void textview_move_backward_character (GtkTextView *text)
11089 GtkTextBuffer *buffer;
11093 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11095 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11096 mark = gtk_text_buffer_get_insert(buffer);
11097 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11098 if (gtk_text_iter_backward_cursor_position(&ins))
11099 gtk_text_buffer_place_cursor(buffer, &ins);
11102 static void textview_move_forward_word (GtkTextView *text)
11104 GtkTextBuffer *buffer;
11109 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11111 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11112 mark = gtk_text_buffer_get_insert(buffer);
11113 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11114 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
11115 if (gtk_text_iter_forward_word_ends(&ins, count)) {
11116 gtk_text_iter_backward_word_start(&ins);
11117 gtk_text_buffer_place_cursor(buffer, &ins);
11121 static void textview_move_backward_word (GtkTextView *text)
11123 GtkTextBuffer *buffer;
11127 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11129 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11130 mark = gtk_text_buffer_get_insert(buffer);
11131 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11132 if (gtk_text_iter_backward_word_starts(&ins, 1))
11133 gtk_text_buffer_place_cursor(buffer, &ins);
11136 static void textview_move_end_of_line (GtkTextView *text)
11138 GtkTextBuffer *buffer;
11142 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11144 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11145 mark = gtk_text_buffer_get_insert(buffer);
11146 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11147 if (gtk_text_iter_forward_to_line_end(&ins))
11148 gtk_text_buffer_place_cursor(buffer, &ins);
11151 static void textview_move_next_line (GtkTextView *text)
11153 GtkTextBuffer *buffer;
11158 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11160 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11161 mark = gtk_text_buffer_get_insert(buffer);
11162 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11163 offset = gtk_text_iter_get_line_offset(&ins);
11164 if (gtk_text_iter_forward_line(&ins)) {
11165 gtk_text_iter_set_line_offset(&ins, offset);
11166 gtk_text_buffer_place_cursor(buffer, &ins);
11170 static void textview_move_previous_line (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 offset = gtk_text_iter_get_line_offset(&ins);
11183 if (gtk_text_iter_backward_line(&ins)) {
11184 gtk_text_iter_set_line_offset(&ins, offset);
11185 gtk_text_buffer_place_cursor(buffer, &ins);
11189 static void textview_delete_forward_character (GtkTextView *text)
11191 GtkTextBuffer *buffer;
11193 GtkTextIter ins, end_iter;
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);
11201 if (gtk_text_iter_forward_char(&end_iter)) {
11202 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11206 static void textview_delete_backward_character (GtkTextView *text)
11208 GtkTextBuffer *buffer;
11210 GtkTextIter ins, end_iter;
11212 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11214 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11215 mark = gtk_text_buffer_get_insert(buffer);
11216 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11218 if (gtk_text_iter_backward_char(&end_iter)) {
11219 gtk_text_buffer_delete(buffer, &end_iter, &ins);
11223 static void textview_delete_forward_word (GtkTextView *text)
11225 GtkTextBuffer *buffer;
11227 GtkTextIter ins, end_iter;
11229 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11231 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11232 mark = gtk_text_buffer_get_insert(buffer);
11233 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11235 if (gtk_text_iter_forward_word_end(&end_iter)) {
11236 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11240 static void textview_delete_backward_word (GtkTextView *text)
11242 GtkTextBuffer *buffer;
11244 GtkTextIter ins, end_iter;
11246 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11248 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11249 mark = gtk_text_buffer_get_insert(buffer);
11250 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11252 if (gtk_text_iter_backward_word_start(&end_iter)) {
11253 gtk_text_buffer_delete(buffer, &end_iter, &ins);
11257 static void textview_delete_line (GtkTextView *text)
11259 GtkTextBuffer *buffer;
11261 GtkTextIter ins, start_iter, 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);
11270 gtk_text_iter_set_line_offset(&start_iter, 0);
11273 if (gtk_text_iter_ends_line(&end_iter)){
11274 if (!gtk_text_iter_forward_char(&end_iter))
11275 gtk_text_iter_backward_char(&start_iter);
11278 gtk_text_iter_forward_to_line_end(&end_iter);
11279 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
11282 static void textview_delete_to_line_end (GtkTextView *text)
11284 GtkTextBuffer *buffer;
11286 GtkTextIter ins, end_iter;
11288 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11290 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11291 mark = gtk_text_buffer_get_insert(buffer);
11292 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11294 if (gtk_text_iter_ends_line(&end_iter))
11295 gtk_text_iter_forward_char(&end_iter);
11297 gtk_text_iter_forward_to_line_end(&end_iter);
11298 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11301 #define DO_ACTION(name, act) { \
11302 if(!strcmp(name, a_name)) { \
11306 static ComposeCallAdvancedAction compose_call_advanced_action_from_path(GtkAction *action)
11308 const gchar *a_name = gtk_action_get_name(action);
11309 DO_ACTION("Edit/Advanced/BackChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER);
11310 DO_ACTION("Edit/Advanced/ForwChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER);
11311 DO_ACTION("Edit/Advanced/BackWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD);
11312 DO_ACTION("Edit/Advanced/ForwWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD);
11313 DO_ACTION("Edit/Advanced/BegLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE);
11314 DO_ACTION("Edit/Advanced/EndLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE);
11315 DO_ACTION("Edit/Advanced/PrevLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE);
11316 DO_ACTION("Edit/Advanced/NextLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE);
11317 DO_ACTION("Edit/Advanced/DelBackChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER);
11318 DO_ACTION("Edit/Advanced/DelForwChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER);
11319 DO_ACTION("Edit/Advanced/DelBackWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD);
11320 DO_ACTION("Edit/Advanced/DelForwWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD);
11321 DO_ACTION("Edit/Advanced/DelLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE);
11322 DO_ACTION("Edit/Advanced/DelEndLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END);
11323 return COMPOSE_CALL_ADVANCED_ACTION_UNDEFINED;
11326 static void compose_advanced_action_cb(GtkAction *gaction, gpointer data)
11328 Compose *compose = (Compose *)data;
11329 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11330 ComposeCallAdvancedAction action = COMPOSE_CALL_ADVANCED_ACTION_UNDEFINED;
11332 action = compose_call_advanced_action_from_path(gaction);
11335 void (*do_action) (GtkTextView *text);
11336 } action_table[] = {
11337 {textview_move_beginning_of_line},
11338 {textview_move_forward_character},
11339 {textview_move_backward_character},
11340 {textview_move_forward_word},
11341 {textview_move_backward_word},
11342 {textview_move_end_of_line},
11343 {textview_move_next_line},
11344 {textview_move_previous_line},
11345 {textview_delete_forward_character},
11346 {textview_delete_backward_character},
11347 {textview_delete_forward_word},
11348 {textview_delete_backward_word},
11349 {textview_delete_line},
11350 {textview_delete_to_line_end}
11353 if (!gtk_widget_has_focus(GTK_WIDGET(text))) return;
11355 if (action >= COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE &&
11356 action <= COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END) {
11357 if (action_table[action].do_action)
11358 action_table[action].do_action(text);
11360 g_warning("Not implemented yet.");
11364 static void compose_grab_focus_cb(GtkWidget *widget, Compose *compose)
11366 GtkAllocation allocation;
11370 if (GTK_IS_EDITABLE(widget)) {
11371 str = gtk_editable_get_chars(GTK_EDITABLE(widget), 0, -1);
11372 gtk_editable_set_position(GTK_EDITABLE(widget),
11375 if ((parent = gtk_widget_get_parent(widget))
11376 && (parent = gtk_widget_get_parent(parent))
11377 && (parent = gtk_widget_get_parent(parent))) {
11378 if (GTK_IS_SCROLLED_WINDOW(parent)) {
11379 gtk_widget_get_allocation(widget, &allocation);
11380 gint y = allocation.y;
11381 gint height = allocation.height;
11382 GtkAdjustment *shown = gtk_scrolled_window_get_vadjustment
11383 (GTK_SCROLLED_WINDOW(parent));
11385 gfloat value = gtk_adjustment_get_value(shown);
11386 gfloat upper = gtk_adjustment_get_upper(shown);
11387 gfloat page_size = gtk_adjustment_get_page_size(shown);
11388 if (y < (int)value) {
11389 gtk_adjustment_set_value(shown, y - 1);
11391 if ((y + height) > ((int)value + (int)page_size)) {
11392 if ((y - height - 1) < ((int)upper - (int)page_size)) {
11393 gtk_adjustment_set_value(shown,
11394 y + height - (int)page_size - 1);
11396 gtk_adjustment_set_value(shown,
11397 (int)upper - (int)page_size - 1);
11404 if (GTK_IS_EDITABLE(widget) || GTK_IS_TEXT_VIEW(widget))
11405 compose->focused_editable = widget;
11407 #ifdef GENERIC_UMPC
11408 if (GTK_IS_TEXT_VIEW(widget)
11409 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->edit_vbox) {
11410 g_object_ref(compose->notebook);
11411 g_object_ref(compose->edit_vbox);
11412 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
11413 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
11414 gtk_paned_add1(GTK_PANED(compose->paned), compose->edit_vbox);
11415 gtk_paned_add2(GTK_PANED(compose->paned), compose->notebook);
11416 g_object_unref(compose->notebook);
11417 g_object_unref(compose->edit_vbox);
11418 g_signal_handlers_block_by_func(G_OBJECT(widget),
11419 G_CALLBACK(compose_grab_focus_cb),
11421 gtk_widget_grab_focus(widget);
11422 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
11423 G_CALLBACK(compose_grab_focus_cb),
11425 } else if (!GTK_IS_TEXT_VIEW(widget)
11426 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->notebook) {
11427 g_object_ref(compose->notebook);
11428 g_object_ref(compose->edit_vbox);
11429 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
11430 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
11431 gtk_paned_add1(GTK_PANED(compose->paned), compose->notebook);
11432 gtk_paned_add2(GTK_PANED(compose->paned), compose->edit_vbox);
11433 g_object_unref(compose->notebook);
11434 g_object_unref(compose->edit_vbox);
11435 g_signal_handlers_block_by_func(G_OBJECT(widget),
11436 G_CALLBACK(compose_grab_focus_cb),
11438 gtk_widget_grab_focus(widget);
11439 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
11440 G_CALLBACK(compose_grab_focus_cb),
11446 static void compose_changed_cb(GtkTextBuffer *textbuf, Compose *compose)
11448 compose->modified = TRUE;
11449 /* compose_beautify_paragraph(compose, NULL, TRUE); */
11450 #ifndef GENERIC_UMPC
11451 compose_set_title(compose);
11455 static void compose_wrap_cb(GtkAction *action, gpointer data)
11457 Compose *compose = (Compose *)data;
11458 compose_beautify_paragraph(compose, NULL, TRUE);
11461 static void compose_wrap_all_cb(GtkAction *action, gpointer data)
11463 Compose *compose = (Compose *)data;
11464 compose_wrap_all_full(compose, TRUE);
11467 static void compose_find_cb(GtkAction *action, gpointer data)
11469 Compose *compose = (Compose *)data;
11471 message_search_compose(compose);
11474 static void compose_toggle_autowrap_cb(GtkToggleAction *action,
11477 Compose *compose = (Compose *)data;
11478 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11479 if (compose->autowrap)
11480 compose_wrap_all_full(compose, TRUE);
11481 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11484 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
11487 Compose *compose = (Compose *)data;
11488 compose->autoindent = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11491 static void compose_toggle_sign_cb(GtkToggleAction *action, gpointer data)
11493 Compose *compose = (Compose *)data;
11495 compose->use_signing = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11496 gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(compose->toolbar->privacy_sign_btn), compose->use_signing);
11499 static void compose_toggle_encrypt_cb(GtkToggleAction *action, gpointer data)
11501 Compose *compose = (Compose *)data;
11503 compose->use_encryption = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11504 gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(compose->toolbar->privacy_encrypt_btn), compose->use_encryption);
11507 static void activate_privacy_system(Compose *compose, PrefsAccount *account, gboolean warn)
11509 g_free(compose->privacy_system);
11510 g_free(compose->encdata);
11512 compose->privacy_system = g_strdup(account->default_privacy_system);
11513 compose_update_privacy_system_menu_item(compose, warn);
11516 static void compose_toggle_ruler_cb(GtkToggleAction *action, gpointer data)
11518 Compose *compose = (Compose *)data;
11520 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action))) {
11521 gtk_widget_show(compose->ruler_hbox);
11522 prefs_common.show_ruler = TRUE;
11524 gtk_widget_hide(compose->ruler_hbox);
11525 gtk_widget_queue_resize(compose->edit_vbox);
11526 prefs_common.show_ruler = FALSE;
11530 static void compose_attach_drag_received_cb (GtkWidget *widget,
11531 GdkDragContext *context,
11534 GtkSelectionData *data,
11537 gpointer user_data)
11539 Compose *compose = (Compose *)user_data;
11543 type = gtk_selection_data_get_data_type(data);
11544 if ((gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list"))
11545 && gtk_drag_get_source_widget(context) !=
11546 summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
11547 list = uri_list_extract_filenames(
11548 (const gchar *)gtk_selection_data_get_data(data));
11549 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11550 gchar *utf8_filename = conv_filename_to_utf8((const gchar *)tmp->data);
11551 compose_attach_append
11552 (compose, (const gchar *)tmp->data,
11553 utf8_filename, NULL, NULL);
11554 g_free(utf8_filename);
11556 if (list) compose_changed_cb(NULL, compose);
11557 list_free_strings(list);
11559 } else if (gtk_drag_get_source_widget(context)
11560 == summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
11561 /* comes from our summaryview */
11562 SummaryView * summaryview = NULL;
11563 GSList * list = NULL, *cur = NULL;
11565 if (mainwindow_get_mainwindow())
11566 summaryview = mainwindow_get_mainwindow()->summaryview;
11569 list = summary_get_selected_msg_list(summaryview);
11571 for (cur = list; cur; cur = cur->next) {
11572 MsgInfo *msginfo = (MsgInfo *)cur->data;
11573 gchar *file = NULL;
11575 file = procmsg_get_message_file_full(msginfo,
11578 compose_attach_append(compose, (const gchar *)file,
11579 (const gchar *)file, "message/rfc822", NULL);
11583 g_slist_free(list);
11587 static gboolean compose_drag_drop(GtkWidget *widget,
11588 GdkDragContext *drag_context,
11590 guint time, gpointer user_data)
11592 /* not handling this signal makes compose_insert_drag_received_cb
11597 static gboolean completion_set_focus_to_subject
11598 (GtkWidget *widget,
11599 GdkEventKey *event,
11602 cm_return_val_if_fail(compose != NULL, FALSE);
11604 /* make backtab move to subject field */
11605 if(event->keyval == GDK_KEY_ISO_Left_Tab) {
11606 gtk_widget_grab_focus(compose->subject_entry);
11612 static void compose_insert_drag_received_cb (GtkWidget *widget,
11613 GdkDragContext *drag_context,
11616 GtkSelectionData *data,
11619 gpointer user_data)
11621 Compose *compose = (Compose *)user_data;
11627 /* strangely, testing data->type == gdk_atom_intern("text/uri-list", TRUE)
11629 type = gtk_selection_data_get_data_type(data);
11630 if (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list")) {
11631 AlertValue val = G_ALERTDEFAULT;
11632 const gchar* ddata = (const gchar *)gtk_selection_data_get_data(data);
11634 list = uri_list_extract_filenames(ddata);
11635 num_files = g_list_length(list);
11636 if (list == NULL && strstr(ddata, "://")) {
11637 /* Assume a list of no files, and data has ://, is a remote link */
11638 gchar *tmpdata = g_strstrip(g_strdup(ddata));
11639 gchar *tmpfile = get_tmp_file();
11640 str_write_to_file(tmpdata, tmpfile);
11642 compose_insert_file(compose, tmpfile);
11643 claws_unlink(tmpfile);
11645 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11646 compose_beautify_paragraph(compose, NULL, TRUE);
11649 switch (prefs_common.compose_dnd_mode) {
11650 case COMPOSE_DND_ASK:
11651 msg = g_strdup_printf(
11653 "Do you want to insert the contents of the file "
11654 "into the message body, or attach it to the email?",
11655 "Do you want to insert the contents of the %d files "
11656 "into the message body, or attach them to the email?",
11659 val = alertpanel_full(_("Insert or attach?"), msg,
11660 GTK_STOCK_CANCEL, _("_Insert"), _("_Attach"),
11662 TRUE, NULL, ALERT_QUESTION);
11665 case COMPOSE_DND_INSERT:
11666 val = G_ALERTALTERNATE;
11668 case COMPOSE_DND_ATTACH:
11669 val = G_ALERTOTHER;
11672 /* unexpected case */
11673 g_warning("error: unexpected compose_dnd_mode option value in compose_insert_drag_received_cb()");
11676 if (val & G_ALERTDISABLE) {
11677 val &= ~G_ALERTDISABLE;
11678 /* remember what action to perform by default, only if we don't click Cancel */
11679 if (val == G_ALERTALTERNATE)
11680 prefs_common.compose_dnd_mode = COMPOSE_DND_INSERT;
11681 else if (val == G_ALERTOTHER)
11682 prefs_common.compose_dnd_mode = COMPOSE_DND_ATTACH;
11685 if (val == G_ALERTDEFAULT || val == G_ALERTCANCEL) {
11686 gtk_drag_finish(drag_context, FALSE, FALSE, time);
11687 list_free_strings(list);
11690 } else if (val == G_ALERTOTHER) {
11691 compose_attach_drag_received_cb(widget, drag_context, x, y, data, info, time, user_data);
11692 list_free_strings(list);
11697 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11698 compose_insert_file(compose, (const gchar *)tmp->data);
11700 list_free_strings(list);
11702 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11707 static void compose_header_drag_received_cb (GtkWidget *widget,
11708 GdkDragContext *drag_context,
11711 GtkSelectionData *data,
11714 gpointer user_data)
11716 GtkEditable *entry = (GtkEditable *)user_data;
11717 const gchar *email = (const gchar *)gtk_selection_data_get_data(data);
11719 /* strangely, testing data->type == gdk_atom_intern("text/plain", TRUE)
11722 if (!strncmp(email, "mailto:", strlen("mailto:"))) {
11723 gchar *decoded=g_new(gchar, strlen(email));
11726 decode_uri(decoded, email + strlen("mailto:")); /* will fit */
11727 gtk_editable_delete_text(entry, 0, -1);
11728 gtk_editable_insert_text(entry, decoded, strlen(decoded), &start);
11729 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11733 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11736 static void compose_toggle_return_receipt_cb(GtkToggleAction *action, gpointer data)
11738 Compose *compose = (Compose *)data;
11740 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11741 compose->return_receipt = TRUE;
11743 compose->return_receipt = FALSE;
11746 static void compose_toggle_remove_refs_cb(GtkToggleAction *action, gpointer data)
11748 Compose *compose = (Compose *)data;
11750 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11751 compose->remove_references = TRUE;
11753 compose->remove_references = FALSE;
11756 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
11757 ComposeHeaderEntry *headerentry)
11759 gtk_entry_set_text(GTK_ENTRY(headerentry->entry), "");
11760 gtk_widget_modify_base(GTK_WIDGET(headerentry->entry), GTK_STATE_NORMAL, NULL);
11761 gtk_widget_modify_text(GTK_WIDGET(headerentry->entry), GTK_STATE_NORMAL, NULL);
11765 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
11766 GdkEventKey *event,
11767 ComposeHeaderEntry *headerentry)
11769 if ((g_slist_length(headerentry->compose->header_list) > 0) &&
11770 ((headerentry->headernum + 1) != headerentry->compose->header_nextrow) &&
11771 !(event->state & GDK_MODIFIER_MASK) &&
11772 (event->keyval == GDK_KEY_BackSpace) &&
11773 (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) == 0)) {
11774 gtk_container_remove
11775 (GTK_CONTAINER(headerentry->compose->header_table),
11776 headerentry->combo);
11777 gtk_container_remove
11778 (GTK_CONTAINER(headerentry->compose->header_table),
11779 headerentry->entry);
11780 headerentry->compose->header_list =
11781 g_slist_remove(headerentry->compose->header_list,
11783 g_free(headerentry);
11784 } else if (event->keyval == GDK_KEY_Tab) {
11785 if (headerentry->compose->header_last == headerentry) {
11786 /* Override default next focus, and give it to subject_entry
11787 * instead of notebook tabs
11789 g_signal_stop_emission_by_name(G_OBJECT(entry), "key-press-event");
11790 gtk_widget_grab_focus(headerentry->compose->subject_entry);
11797 static gboolean scroll_postpone(gpointer data)
11799 Compose *compose = (Compose *)data;
11801 if (compose->batch)
11804 GTK_EVENTS_FLUSH();
11805 compose_show_first_last_header(compose, FALSE);
11809 static void compose_headerentry_changed_cb(GtkWidget *entry,
11810 ComposeHeaderEntry *headerentry)
11812 if (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) != 0) {
11813 compose_create_header_entry(headerentry->compose);
11814 g_signal_handlers_disconnect_matched
11815 (G_OBJECT(entry), G_SIGNAL_MATCH_DATA,
11816 0, 0, NULL, NULL, headerentry);
11818 if (!headerentry->compose->batch)
11819 g_timeout_add(0, scroll_postpone, headerentry->compose);
11823 static gboolean compose_defer_auto_save_draft(Compose *compose)
11825 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
11826 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
11830 static void compose_show_first_last_header(Compose *compose, gboolean show_first)
11832 GtkAdjustment *vadj;
11834 cm_return_if_fail(compose);
11839 cm_return_if_fail(GTK_IS_WIDGET(compose->header_table));
11840 cm_return_if_fail(GTK_IS_VIEWPORT(gtk_widget_get_parent(compose->header_table)));
11841 vadj = gtk_viewport_get_vadjustment(GTK_VIEWPORT(
11842 gtk_widget_get_parent(compose->header_table)));
11843 gtk_adjustment_set_value(vadj, (show_first ?
11844 gtk_adjustment_get_lower(vadj) :
11845 (gtk_adjustment_get_upper(vadj) -
11846 gtk_adjustment_get_page_size(vadj))));
11847 gtk_adjustment_changed(vadj);
11850 static void text_inserted(GtkTextBuffer *buffer, GtkTextIter *iter,
11851 const gchar *text, gint len, Compose *compose)
11853 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data
11854 (G_OBJECT(compose->text), "paste_as_quotation"));
11857 cm_return_if_fail(text != NULL);
11859 g_signal_handlers_block_by_func(G_OBJECT(buffer),
11860 G_CALLBACK(text_inserted),
11862 if (paste_as_quotation) {
11864 const gchar *qmark;
11866 GtkTextIter start_iter;
11869 len = strlen(text);
11871 new_text = g_strndup(text, len);
11873 qmark = compose_quote_char_from_context(compose);
11875 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11876 gtk_text_buffer_place_cursor(buffer, iter);
11878 pos = gtk_text_iter_get_offset(iter);
11880 compose_quote_fmt(compose, NULL, "%Q", qmark, new_text, TRUE, FALSE,
11881 _("Quote format error at line %d."));
11882 quote_fmt_reset_vartable();
11884 g_object_set_data(G_OBJECT(compose->text), "paste_as_quotation",
11885 GINT_TO_POINTER(paste_as_quotation - 1));
11887 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11888 gtk_text_buffer_place_cursor(buffer, iter);
11889 gtk_text_buffer_delete_mark(buffer, mark);
11891 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, pos);
11892 mark = gtk_text_buffer_create_mark(buffer, NULL, &start_iter, FALSE);
11893 compose_beautify_paragraph(compose, &start_iter, FALSE);
11894 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark);
11895 gtk_text_buffer_delete_mark(buffer, mark);
11897 if (strcmp(text, "\n") || compose->automatic_break
11898 || gtk_text_iter_starts_line(iter)) {
11899 GtkTextIter before_ins;
11900 gtk_text_buffer_insert(buffer, iter, text, len);
11901 if (!strstr(text, "\n") && gtk_text_iter_has_tag(iter, compose->no_join_tag)) {
11902 before_ins = *iter;
11903 gtk_text_iter_backward_chars(&before_ins, len);
11904 gtk_text_buffer_remove_tag_by_name(buffer, "no_join", &before_ins, iter);
11907 /* check if the preceding is just whitespace or quote */
11908 GtkTextIter start_line;
11909 gchar *tmp = NULL, *quote = NULL;
11910 gint quote_len = 0, is_normal = 0;
11911 start_line = *iter;
11912 gtk_text_iter_set_line_offset(&start_line, 0);
11913 tmp = gtk_text_buffer_get_text(buffer, &start_line, iter, FALSE);
11916 if (*tmp == '\0') {
11919 quote = compose_get_quote_str(buffer, &start_line, "e_len);
11927 gtk_text_buffer_insert(buffer, iter, text, len);
11929 gtk_text_buffer_insert_with_tags_by_name(buffer,
11930 iter, text, len, "no_join", NULL);
11935 if (!paste_as_quotation) {
11936 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11937 compose_beautify_paragraph(compose, iter, FALSE);
11938 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11939 gtk_text_buffer_delete_mark(buffer, mark);
11942 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
11943 G_CALLBACK(text_inserted),
11945 g_signal_stop_emission_by_name(G_OBJECT(buffer), "insert-text");
11947 if (compose_can_autosave(compose) &&
11948 gtk_text_buffer_get_char_count(buffer) % prefs_common.autosave_length == 0 &&
11949 compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN /* disabled while loading */)
11950 compose->draft_timeout_tag = g_timeout_add
11951 (500, (GSourceFunc) compose_defer_auto_save_draft, compose);
11955 static void compose_check_all(GtkAction *action, gpointer data)
11957 Compose *compose = (Compose *)data;
11958 if (!compose->gtkaspell)
11961 if (gtk_widget_has_focus(compose->subject_entry))
11962 claws_spell_entry_check_all(
11963 CLAWS_SPELL_ENTRY(compose->subject_entry));
11965 gtkaspell_check_all(compose->gtkaspell);
11968 static void compose_highlight_all(GtkAction *action, gpointer data)
11970 Compose *compose = (Compose *)data;
11971 if (compose->gtkaspell) {
11972 claws_spell_entry_recheck_all(
11973 CLAWS_SPELL_ENTRY(compose->subject_entry));
11974 gtkaspell_highlight_all(compose->gtkaspell);
11978 static void compose_check_backwards(GtkAction *action, gpointer data)
11980 Compose *compose = (Compose *)data;
11981 if (!compose->gtkaspell) {
11982 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
11986 if (gtk_widget_has_focus(compose->subject_entry))
11987 claws_spell_entry_check_backwards(
11988 CLAWS_SPELL_ENTRY(compose->subject_entry));
11990 gtkaspell_check_backwards(compose->gtkaspell);
11993 static void compose_check_forwards_go(GtkAction *action, gpointer data)
11995 Compose *compose = (Compose *)data;
11996 if (!compose->gtkaspell) {
11997 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
12001 if (gtk_widget_has_focus(compose->subject_entry))
12002 claws_spell_entry_check_forwards_go(
12003 CLAWS_SPELL_ENTRY(compose->subject_entry));
12005 gtkaspell_check_forwards_go(compose->gtkaspell);
12010 *\brief Guess originating forward account from MsgInfo and several
12011 * "common preference" settings. Return NULL if no guess.
12013 static PrefsAccount *compose_find_account(MsgInfo *msginfo)
12015 PrefsAccount *account = NULL;
12017 cm_return_val_if_fail(msginfo, NULL);
12018 cm_return_val_if_fail(msginfo->folder, NULL);
12019 cm_return_val_if_fail(msginfo->folder->prefs, NULL);
12021 if (msginfo->folder->prefs->enable_default_account)
12022 account = account_find_from_id(msginfo->folder->prefs->default_account);
12024 if (!account && msginfo->to && prefs_common.forward_account_autosel) {
12026 Xstrdup_a(to, msginfo->to, return NULL);
12027 extract_address(to);
12028 account = account_find_from_address(to, FALSE);
12031 if (!account && prefs_common.forward_account_autosel) {
12033 if (!procheader_get_header_from_msginfo
12034 (msginfo, &cc, "Cc:")) {
12035 gchar *buf = cc + strlen("Cc:");
12036 extract_address(buf);
12037 account = account_find_from_address(buf, FALSE);
12042 if (!account && prefs_common.forward_account_autosel) {
12043 gchar *deliveredto = NULL;
12044 if (!procheader_get_header_from_msginfo
12045 (msginfo, &deliveredto, "Delivered-To:")) {
12046 gchar *buf = deliveredto + strlen("Delivered-To:");
12047 extract_address(buf);
12048 account = account_find_from_address(buf, FALSE);
12049 g_free(deliveredto);
12054 account = msginfo->folder->folder->account;
12059 gboolean compose_close(Compose *compose)
12063 cm_return_val_if_fail(compose, FALSE);
12065 if (!g_mutex_trylock(compose->mutex)) {
12066 /* we have to wait for the (possibly deferred by auto-save)
12067 * drafting to be done, before destroying the compose under
12069 debug_print("waiting for drafting to finish...\n");
12070 compose_allow_user_actions(compose, FALSE);
12071 if (compose->close_timeout_tag == 0) {
12072 compose->close_timeout_tag =
12073 g_timeout_add (500, (GSourceFunc) compose_close,
12079 if (compose->draft_timeout_tag >= 0) {
12080 g_source_remove(compose->draft_timeout_tag);
12081 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN;
12084 gtkut_widget_get_uposition(compose->window, &x, &y);
12085 if (!compose->batch) {
12086 prefs_common.compose_x = x;
12087 prefs_common.compose_y = y;
12089 g_mutex_unlock(compose->mutex);
12090 compose_destroy(compose);
12095 * Add entry field for each address in list.
12096 * \param compose E-Mail composition object.
12097 * \param listAddress List of (formatted) E-Mail addresses.
12099 static void compose_add_field_list( Compose *compose, GList *listAddress ) {
12102 node = listAddress;
12104 addr = ( gchar * ) node->data;
12105 compose_entry_append( compose, addr, COMPOSE_TO, PREF_NONE );
12106 node = g_list_next( node );
12110 static void compose_reply_from_messageview_real(MessageView *msgview, GSList *msginfo_list,
12111 guint action, gboolean opening_multiple)
12113 gchar *body = NULL;
12114 GSList *new_msglist = NULL;
12115 MsgInfo *tmp_msginfo = NULL;
12116 gboolean originally_enc = FALSE;
12117 gboolean originally_sig = FALSE;
12118 Compose *compose = NULL;
12119 gchar *s_system = NULL;
12121 cm_return_if_fail(msgview != NULL);
12123 cm_return_if_fail(msginfo_list != NULL);
12125 if (g_slist_length(msginfo_list) == 1 && !opening_multiple) {
12126 MimeInfo *mimeinfo = messageview_get_selected_mime_part(msgview);
12127 MsgInfo *orig_msginfo = (MsgInfo *)msginfo_list->data;
12129 if (mimeinfo != NULL && mimeinfo->type == MIMETYPE_MESSAGE &&
12130 !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
12131 tmp_msginfo = procmsg_msginfo_new_from_mimeinfo(
12132 orig_msginfo, mimeinfo);
12133 if (tmp_msginfo != NULL) {
12134 new_msglist = g_slist_append(NULL, tmp_msginfo);
12136 originally_enc = MSG_IS_ENCRYPTED(orig_msginfo->flags);
12137 privacy_msginfo_get_signed_state(orig_msginfo, &s_system);
12138 originally_sig = MSG_IS_SIGNED(orig_msginfo->flags);
12140 tmp_msginfo->folder = orig_msginfo->folder;
12141 tmp_msginfo->msgnum = orig_msginfo->msgnum;
12142 if (orig_msginfo->tags) {
12143 tmp_msginfo->tags = g_slist_copy(orig_msginfo->tags);
12144 tmp_msginfo->folder->tags_dirty = TRUE;
12150 if (!opening_multiple)
12151 body = messageview_get_selection(msgview);
12154 compose = compose_reply_mode((ComposeMode)action, new_msglist, body);
12155 procmsg_msginfo_free(&tmp_msginfo);
12156 g_slist_free(new_msglist);
12158 compose = compose_reply_mode((ComposeMode)action, msginfo_list, body);
12160 if (compose && originally_enc) {
12161 compose_force_encryption(compose, compose->account, FALSE, s_system);
12164 if (compose && originally_sig && compose->account->default_sign_reply) {
12165 compose_force_signing(compose, compose->account, s_system);
12169 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
12172 void compose_reply_from_messageview(MessageView *msgview, GSList *msginfo_list,
12175 if ((!prefs_common.forward_as_attachment || action != COMPOSE_FORWARD)
12176 && action != COMPOSE_FORWARD_AS_ATTACH && g_slist_length(msginfo_list) > 1) {
12177 GSList *cur = msginfo_list;
12178 gchar *msg = g_strdup_printf(_("You are about to reply to %d "
12179 "messages. Opening the windows "
12180 "could take some time. Do you "
12181 "want to continue?"),
12182 g_slist_length(msginfo_list));
12183 if (g_slist_length(msginfo_list) > 9
12184 && alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, GTK_STOCK_YES, NULL,
12185 ALERTFOCUS_SECOND) != G_ALERTALTERNATE) {
12190 /* We'll open multiple compose windows */
12191 /* let the WM place the next windows */
12192 compose_force_window_origin = FALSE;
12193 for (; cur; cur = cur->next) {
12195 tmplist.data = cur->data;
12196 tmplist.next = NULL;
12197 compose_reply_from_messageview_real(msgview, &tmplist, action, TRUE);
12199 compose_force_window_origin = TRUE;
12201 /* forwarding multiple mails as attachments is done via a
12202 * single compose window */
12203 compose_reply_from_messageview_real(msgview, msginfo_list, action, FALSE);
12207 void compose_check_for_email_account(Compose *compose)
12209 PrefsAccount *ac = NULL, *curr = NULL;
12215 if (compose->account && compose->account->protocol == A_NNTP) {
12216 ac = account_get_cur_account();
12217 if (ac->protocol == A_NNTP) {
12218 list = account_get_list();
12220 for( ; list != NULL ; list = g_list_next(list)) {
12221 curr = (PrefsAccount *) list->data;
12222 if (curr->protocol != A_NNTP) {
12228 combobox_select_by_data(GTK_COMBO_BOX(compose->account_combo),
12233 void compose_reply_to_address(MessageView *msgview, MsgInfo *msginfo,
12234 const gchar *address)
12236 GSList *msginfo_list = NULL;
12237 gchar *body = messageview_get_selection(msgview);
12240 msginfo_list = g_slist_prepend(msginfo_list, msginfo);
12242 compose = compose_reply_mode(COMPOSE_REPLY_TO_ADDRESS, msginfo_list, body);
12243 compose_check_for_email_account(compose);
12244 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
12245 compose_entry_append(compose, address, COMPOSE_TO, PREF_NONE);
12246 compose_reply_set_subject(compose, msginfo);
12249 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
12252 void compose_set_position(Compose *compose, gint pos)
12254 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
12256 gtkut_text_view_set_position(text, pos);
12259 gboolean compose_search_string(Compose *compose,
12260 const gchar *str, gboolean case_sens)
12262 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
12264 return gtkut_text_view_search_string(text, str, case_sens);
12267 gboolean compose_search_string_backward(Compose *compose,
12268 const gchar *str, gboolean case_sens)
12270 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
12272 return gtkut_text_view_search_string_backward(text, str, case_sens);
12275 /* allocate a msginfo structure and populate its data from a compose data structure */
12276 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose)
12278 MsgInfo *newmsginfo;
12280 gchar date[RFC822_DATE_BUFFSIZE];
12282 cm_return_val_if_fail( compose != NULL, NULL );
12284 newmsginfo = procmsg_msginfo_new();
12287 get_rfc822_date(date, sizeof(date));
12288 newmsginfo->date = g_strdup(date);
12291 if (compose->from_name) {
12292 newmsginfo->from = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
12293 newmsginfo->fromname = procheader_get_fromname(newmsginfo->from);
12297 if (compose->subject_entry)
12298 newmsginfo->subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
12300 /* to, cc, reply-to, newsgroups */
12301 for (list = compose->header_list; list; list = list->next) {
12302 gchar *header = gtk_editable_get_chars(
12304 gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
12305 gchar *entry = gtk_editable_get_chars(
12306 GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
12308 if ( strcasecmp(header, prefs_common_translated_header_name("To:")) == 0 ) {
12309 if ( newmsginfo->to == NULL ) {
12310 newmsginfo->to = g_strdup(entry);
12311 } else if (entry && *entry) {
12312 gchar *tmp = g_strconcat(newmsginfo->to, ", ", entry, NULL);
12313 g_free(newmsginfo->to);
12314 newmsginfo->to = tmp;
12317 if ( strcasecmp(header, prefs_common_translated_header_name("Cc:")) == 0 ) {
12318 if ( newmsginfo->cc == NULL ) {
12319 newmsginfo->cc = g_strdup(entry);
12320 } else if (entry && *entry) {
12321 gchar *tmp = g_strconcat(newmsginfo->cc, ", ", entry, NULL);
12322 g_free(newmsginfo->cc);
12323 newmsginfo->cc = tmp;
12326 if ( strcasecmp(header,
12327 prefs_common_translated_header_name("Newsgroups:")) == 0 ) {
12328 if ( newmsginfo->newsgroups == NULL ) {
12329 newmsginfo->newsgroups = g_strdup(entry);
12330 } else if (entry && *entry) {
12331 gchar *tmp = g_strconcat(newmsginfo->newsgroups, ", ", entry, NULL);
12332 g_free(newmsginfo->newsgroups);
12333 newmsginfo->newsgroups = tmp;
12341 /* other data is unset */
12347 /* update compose's dictionaries from folder dict settings */
12348 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
12349 FolderItem *folder_item)
12351 cm_return_if_fail(compose != NULL);
12353 if (compose->gtkaspell && folder_item && folder_item->prefs) {
12354 FolderItemPrefs *prefs = folder_item->prefs;
12356 if (prefs->enable_default_dictionary)
12357 gtkaspell_change_dict(compose->gtkaspell,
12358 prefs->default_dictionary, FALSE);
12359 if (folder_item->prefs->enable_default_alt_dictionary)
12360 gtkaspell_change_alt_dict(compose->gtkaspell,
12361 prefs->default_alt_dictionary);
12362 if (prefs->enable_default_dictionary
12363 || prefs->enable_default_alt_dictionary)
12364 compose_spell_menu_changed(compose);
12369 static void compose_subject_entry_activated(GtkWidget *widget, gpointer data)
12371 Compose *compose = (Compose *)data;
12373 cm_return_if_fail(compose != NULL);
12375 gtk_widget_grab_focus(compose->text);
12378 static void from_name_activate_cb(GtkWidget *widget, gpointer data)
12380 gtk_combo_box_popup(GTK_COMBO_BOX(data));