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"),
5222 privacy_peek_error() ? privacy_get_error() : _("Unknown error"));
5224 case COMPOSE_QUEUE_ERROR_ENCRYPT_FAILED:
5225 alertpanel_error(_("Could not queue message for sending:\n\n"
5226 "Encryption failed: %s"),
5227 privacy_peek_error() ? privacy_get_error() : _("Unknown error"));
5229 case COMPOSE_QUEUE_ERROR_CHAR_CONVERSION:
5230 alertpanel_error(_("Could not queue message for sending:\n\n"
5231 "Charset conversion failed."));
5233 case COMPOSE_QUEUE_ERROR_NO_ENCRYPTION_KEY:
5234 alertpanel_error(_("Could not queue message for sending:\n\n"
5235 "Couldn't get recipient encryption key."));
5238 /* unhandled error */
5239 debug_print("oops, unhandled compose_queue() return value %d\n",
5245 gint compose_send(Compose *compose)
5248 FolderItem *folder = NULL;
5249 ComposeQueueResult val = COMPOSE_QUEUE_ERROR_NO_MSG;
5250 gchar *msgpath = NULL;
5251 gboolean discard_window = FALSE;
5252 gchar *errstr = NULL;
5253 gchar *tmsgid = NULL;
5254 MainWindow *mainwin = mainwindow_get_mainwindow();
5255 gboolean queued_removed = FALSE;
5257 if (prefs_common.send_dialog_invisible
5258 || compose->batch == TRUE)
5259 discard_window = TRUE;
5261 compose_allow_user_actions (compose, FALSE);
5262 compose->sending = TRUE;
5264 if (compose_check_entries(compose, TRUE) == FALSE) {
5265 if (compose->batch) {
5266 gtk_widget_show_all(compose->window);
5272 val = compose_queue(compose, &msgnum, &folder, &msgpath, TRUE);
5274 if (val != COMPOSE_QUEUE_SUCCESS) {
5275 if (compose->batch) {
5276 gtk_widget_show_all(compose->window);
5279 _display_queue_error(val);
5284 tmsgid = compose->msgid ? g_strdup(compose->msgid) : NULL;
5285 if (discard_window) {
5286 compose->sending = FALSE;
5287 compose_close(compose);
5288 /* No more compose access in the normal codepath
5289 * after this point! */
5294 alertpanel_error(_("The message was queued but could not be "
5295 "sent.\nUse \"Send queued messages\" from "
5296 "the main window to retry."));
5297 if (!discard_window) {
5304 if (msgpath == NULL) {
5305 msgpath = folder_item_fetch_msg(folder, msgnum);
5306 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5309 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5310 claws_unlink(msgpath);
5313 if (!discard_window) {
5315 if (!queued_removed)
5316 folder_item_remove_msg(folder, msgnum);
5317 folder_item_scan(folder);
5319 /* make sure we delete that */
5320 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5322 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5323 folder_item_remove_msg(folder, tmp->msgnum);
5324 procmsg_msginfo_free(&tmp);
5331 if (!queued_removed)
5332 folder_item_remove_msg(folder, msgnum);
5333 folder_item_scan(folder);
5335 /* make sure we delete that */
5336 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5338 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5339 folder_item_remove_msg(folder, tmp->msgnum);
5340 procmsg_msginfo_free(&tmp);
5343 if (!discard_window) {
5344 compose->sending = FALSE;
5345 compose_allow_user_actions (compose, TRUE);
5346 compose_close(compose);
5350 alertpanel_error_log(_("%s\nUse \"Send queued messages\" from "
5351 "the main window to retry."), errstr);
5354 alertpanel_error_log(_("The message was queued but could not be "
5355 "sent.\nUse \"Send queued messages\" from "
5356 "the main window to retry."));
5358 if (!discard_window) {
5367 toolbar_main_set_sensitive(mainwin);
5368 main_window_set_menu_sensitive(mainwin);
5374 compose_allow_user_actions (compose, TRUE);
5375 compose->sending = FALSE;
5376 compose->modified = TRUE;
5377 toolbar_main_set_sensitive(mainwin);
5378 main_window_set_menu_sensitive(mainwin);
5383 static gboolean compose_use_attach(Compose *compose)
5385 GtkTreeModel *model = gtk_tree_view_get_model
5386 (GTK_TREE_VIEW(compose->attach_clist));
5387 return gtk_tree_model_iter_n_children(model, NULL) > 0;
5390 static gint compose_redirect_write_headers_from_headerlist(Compose *compose,
5393 gchar buf[BUFFSIZE];
5395 gboolean first_to_address;
5396 gboolean first_cc_address;
5398 ComposeHeaderEntry *headerentry;
5399 const gchar *headerentryname;
5400 const gchar *cc_hdr;
5401 const gchar *to_hdr;
5402 gboolean err = FALSE;
5404 debug_print("Writing redirect header\n");
5406 cc_hdr = prefs_common_translated_header_name("Cc:");
5407 to_hdr = prefs_common_translated_header_name("To:");
5409 first_to_address = TRUE;
5410 for (list = compose->header_list; list; list = list->next) {
5411 headerentry = ((ComposeHeaderEntry *)list->data);
5412 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5414 if (g_utf8_collate(headerentryname, to_hdr) == 0) {
5415 const gchar *entstr = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5416 Xstrdup_a(str, entstr, return -1);
5418 if (str[0] != '\0') {
5419 compose_convert_header
5420 (compose, buf, sizeof(buf), str,
5421 strlen("Resent-To") + 2, TRUE);
5423 if (first_to_address) {
5424 err |= (fprintf(fp, "Resent-To: ") < 0);
5425 first_to_address = FALSE;
5427 err |= (fprintf(fp, ",") < 0);
5429 err |= (fprintf(fp, "%s", buf) < 0);
5433 if (!first_to_address) {
5434 err |= (fprintf(fp, "\n") < 0);
5437 first_cc_address = TRUE;
5438 for (list = compose->header_list; list; list = list->next) {
5439 headerentry = ((ComposeHeaderEntry *)list->data);
5440 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5442 if (g_utf8_collate(headerentryname, cc_hdr) == 0) {
5443 const gchar *strg = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5444 Xstrdup_a(str, strg, return -1);
5446 if (str[0] != '\0') {
5447 compose_convert_header
5448 (compose, buf, sizeof(buf), str,
5449 strlen("Resent-Cc") + 2, TRUE);
5451 if (first_cc_address) {
5452 err |= (fprintf(fp, "Resent-Cc: ") < 0);
5453 first_cc_address = FALSE;
5455 err |= (fprintf(fp, ",") < 0);
5457 err |= (fprintf(fp, "%s", buf) < 0);
5461 if (!first_cc_address) {
5462 err |= (fprintf(fp, "\n") < 0);
5465 return (err ? -1:0);
5468 static gint compose_redirect_write_headers(Compose *compose, FILE *fp)
5470 gchar date[RFC822_DATE_BUFFSIZE];
5471 gchar buf[BUFFSIZE];
5473 const gchar *entstr;
5474 /* struct utsname utsbuf; */
5475 gboolean err = FALSE;
5477 cm_return_val_if_fail(fp != NULL, -1);
5478 cm_return_val_if_fail(compose->account != NULL, -1);
5479 cm_return_val_if_fail(compose->account->address != NULL, -1);
5482 if (prefs_common.hide_timezone)
5483 get_rfc822_date_hide_tz(date, sizeof(date));
5485 get_rfc822_date(date, sizeof(date));
5486 err |= (fprintf(fp, "Resent-Date: %s\n", date) < 0);
5489 if (compose->account->name && *compose->account->name) {
5490 compose_convert_header
5491 (compose, buf, sizeof(buf), compose->account->name,
5492 strlen("From: "), TRUE);
5493 err |= (fprintf(fp, "Resent-From: %s <%s>\n",
5494 buf, compose->account->address) < 0);
5496 err |= (fprintf(fp, "Resent-From: %s\n", compose->account->address) < 0);
5499 entstr = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5500 if (*entstr != '\0') {
5501 Xstrdup_a(str, entstr, return -1);
5504 compose_convert_header(compose, buf, sizeof(buf), str,
5505 strlen("Subject: "), FALSE);
5506 err |= (fprintf(fp, "Subject: %s\n", buf) < 0);
5510 /* Resent-Message-ID */
5511 if (compose->account->gen_msgid) {
5512 gchar *addr = prefs_account_generate_msgid(compose->account);
5513 err |= (fprintf(fp, "Resent-Message-ID: <%s>\n", addr) < 0);
5515 g_free(compose->msgid);
5516 compose->msgid = addr;
5518 compose->msgid = NULL;
5521 if (compose_redirect_write_headers_from_headerlist(compose, fp))
5524 /* separator between header and body */
5525 err |= (fputs("\n", fp) == EOF);
5527 return (err ? -1:0);
5530 static gint compose_redirect_write_to_file(Compose *compose, FILE *fdest)
5535 gchar rewrite_buf[BUFFSIZE];
5537 gboolean skip = FALSE;
5538 gboolean err = FALSE;
5539 gchar *not_included[]={
5540 "Return-Path:", "Delivered-To:", "Received:",
5541 "Subject:", "X-UIDL:", "AF:",
5542 "NF:", "PS:", "SRH:",
5543 "SFN:", "DSR:", "MID:",
5544 "CFG:", "PT:", "S:",
5545 "RQ:", "SSV:", "NSV:",
5546 "SSH:", "R:", "MAID:",
5547 "NAID:", "RMID:", "FMID:",
5548 "SCF:", "RRCPT:", "NG:",
5549 "X-Claws-Privacy", "X-Claws-Sign:", "X-Claws-Encrypt",
5550 "X-Claws-End-Special-Headers:", "X-Claws-Account-Id:",
5551 "X-Sylpheed-Privacy", "X-Sylpheed-Sign:", "X-Sylpheed-Encrypt",
5552 "X-Sylpheed-End-Special-Headers:", "X-Sylpheed-Account-Id:",
5553 "X-Claws-Auto-Wrapping:", "X-Claws-Auto-Indent:",
5558 if ((fp = g_fopen(compose->redirect_filename, "rb")) == NULL) {
5559 FILE_OP_ERROR(compose->redirect_filename, "fopen");
5563 while ((ret = procheader_get_one_field_asis(&buf, fp)) != -1) {
5565 for (i = 0; not_included[i] != NULL; i++) {
5566 if (g_ascii_strncasecmp(buf, not_included[i],
5567 strlen(not_included[i])) == 0) {
5577 if (fputs(buf, fdest) == -1) {
5583 if (!prefs_common.redirect_keep_from) {
5584 if (g_ascii_strncasecmp(buf, "From:",
5585 strlen("From:")) == 0) {
5586 err |= (fputs(" (by way of ", fdest) == EOF);
5587 if (compose->account->name
5588 && *compose->account->name) {
5589 gchar buffer[BUFFSIZE];
5591 compose_convert_header
5592 (compose, buffer, sizeof(buffer),
5593 compose->account->name,
5596 err |= (fprintf(fdest, "%s <%s>",
5598 compose->account->address) < 0);
5600 err |= (fprintf(fdest, "%s",
5601 compose->account->address) < 0);
5602 err |= (fputs(")", fdest) == EOF);
5608 if (fputs("\n", fdest) == -1)
5615 if (compose_redirect_write_headers(compose, fdest))
5618 while ((len = fread(rewrite_buf, sizeof(gchar), sizeof(rewrite_buf), fp)) > 0) {
5619 if (fwrite(rewrite_buf, sizeof(gchar), len, fdest) != len)
5633 static gint compose_write_to_file(Compose *compose, FILE *fp, gint action, gboolean attach_parts)
5635 GtkTextBuffer *buffer;
5636 GtkTextIter start, end, tmp;
5637 gchar *chars, *tmp_enc_file, *content;
5639 const gchar *out_codeset;
5640 EncodingType encoding = ENC_UNKNOWN;
5641 MimeInfo *mimemsg, *mimetext;
5643 const gchar *src_codeset = CS_INTERNAL;
5644 gchar *from_addr = NULL;
5645 gchar *from_name = NULL;
5648 if (action == COMPOSE_WRITE_FOR_SEND) {
5649 attach_parts = TRUE;
5651 /* We're sending the message, generate a Message-ID
5653 if (compose->msgid == NULL &&
5654 compose->account->gen_msgid) {
5655 compose->msgid = prefs_account_generate_msgid(compose->account);
5659 /* create message MimeInfo */
5660 mimemsg = procmime_mimeinfo_new();
5661 mimemsg->type = MIMETYPE_MESSAGE;
5662 mimemsg->subtype = g_strdup("rfc822");
5663 mimemsg->content = MIMECONTENT_MEM;
5664 mimemsg->tmp = TRUE; /* must free content later */
5665 mimemsg->data.mem = compose_get_header(compose);
5667 /* Create text part MimeInfo */
5668 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5669 gtk_text_buffer_get_end_iter(buffer, &end);
5672 /* We make sure that there is a newline at the end. */
5673 if (action == COMPOSE_WRITE_FOR_SEND && gtk_text_iter_backward_char(&tmp)) {
5674 chars = gtk_text_buffer_get_text(buffer, &tmp, &end, FALSE);
5675 if (*chars != '\n') {
5676 gtk_text_buffer_insert(buffer, &end, "\n", 1);
5681 /* get all composed text */
5682 gtk_text_buffer_get_start_iter(buffer, &start);
5683 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5685 out_codeset = conv_get_charset_str(compose->out_encoding);
5687 if (!out_codeset && is_ascii_str(chars)) {
5688 out_codeset = CS_US_ASCII;
5689 } else if (prefs_common.outgoing_fallback_to_ascii &&
5690 is_ascii_str(chars)) {
5691 out_codeset = CS_US_ASCII;
5692 encoding = ENC_7BIT;
5696 gchar *test_conv_global_out = NULL;
5697 gchar *test_conv_reply = NULL;
5699 /* automatic mode. be automatic. */
5700 codeconv_set_strict(TRUE);
5702 out_codeset = conv_get_outgoing_charset_str();
5704 debug_print("trying to convert to %s\n", out_codeset);
5705 test_conv_global_out = conv_codeset_strdup(chars, src_codeset, out_codeset);
5708 if (!test_conv_global_out && compose->orig_charset
5709 && strcmp(compose->orig_charset, CS_US_ASCII)) {
5710 out_codeset = compose->orig_charset;
5711 debug_print("failure; trying to convert to %s\n", out_codeset);
5712 test_conv_reply = conv_codeset_strdup(chars, src_codeset, out_codeset);
5715 if (!test_conv_global_out && !test_conv_reply) {
5717 out_codeset = CS_INTERNAL;
5718 debug_print("failure; finally using %s\n", out_codeset);
5720 g_free(test_conv_global_out);
5721 g_free(test_conv_reply);
5722 codeconv_set_strict(FALSE);
5725 if (encoding == ENC_UNKNOWN) {
5726 if (prefs_common.encoding_method == CTE_BASE64)
5727 encoding = ENC_BASE64;
5728 else if (prefs_common.encoding_method == CTE_QUOTED_PRINTABLE)
5729 encoding = ENC_QUOTED_PRINTABLE;
5730 else if (prefs_common.encoding_method == CTE_8BIT)
5731 encoding = ENC_8BIT;
5733 encoding = procmime_get_encoding_for_charset(out_codeset);
5736 debug_print("src encoding = %s, out encoding = %s, transfer encoding = %s\n",
5737 src_codeset, out_codeset, procmime_get_encoding_str(encoding));
5739 if (action == COMPOSE_WRITE_FOR_SEND) {
5740 codeconv_set_strict(TRUE);
5741 buf = conv_codeset_strdup(chars, src_codeset, out_codeset);
5742 codeconv_set_strict(FALSE);
5747 msg = g_strdup_printf(_("Can't convert the character encoding of the message \n"
5748 "to the specified %s charset.\n"
5749 "Send it as %s?"), out_codeset, src_codeset);
5750 aval = alertpanel_full(_("Error"), msg, GTK_STOCK_CANCEL,
5751 _("_Send"), NULL, ALERTFOCUS_SECOND, FALSE,
5755 if (aval != G_ALERTALTERNATE) {
5757 return COMPOSE_QUEUE_ERROR_CHAR_CONVERSION;
5760 out_codeset = src_codeset;
5766 out_codeset = src_codeset;
5771 if (prefs_common.rewrite_first_from && (encoding == ENC_8BIT || encoding == ENC_7BIT)) {
5772 if (!strncmp(buf, "From ", sizeof("From ")-1) ||
5773 strstr(buf, "\nFrom ") != NULL) {
5774 encoding = ENC_QUOTED_PRINTABLE;
5778 mimetext = procmime_mimeinfo_new();
5779 mimetext->content = MIMECONTENT_MEM;
5780 mimetext->tmp = TRUE; /* must free content later */
5781 /* dup'ed because procmime_encode_content can turn it into a tmpfile
5782 * and free the data, which we need later. */
5783 mimetext->data.mem = g_strdup(buf);
5784 mimetext->type = MIMETYPE_TEXT;
5785 mimetext->subtype = g_strdup("plain");
5786 g_hash_table_insert(mimetext->typeparameters, g_strdup("charset"),
5787 g_strdup(out_codeset));
5789 /* protect trailing spaces when signing message */
5790 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5791 privacy_system_can_sign(compose->privacy_system)) {
5792 encoding = ENC_QUOTED_PRINTABLE;
5796 debug_print("main text: %Id bytes encoded as %s in %d\n",
5798 debug_print("main text: %zd bytes encoded as %s in %d\n",
5800 strlen(buf), out_codeset, encoding);
5802 /* check for line length limit */
5803 if (action == COMPOSE_WRITE_FOR_SEND &&
5804 encoding != ENC_QUOTED_PRINTABLE && encoding != ENC_BASE64 &&
5805 check_line_length(buf, 1000, &line) < 0) {
5808 msg = g_strdup_printf
5809 (_("Line %d exceeds the line length limit (998 bytes).\n"
5810 "The contents of the message might be broken on the way to the delivery.\n"
5812 "Send it anyway?"), line + 1);
5813 aval = alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, GTK_STOCK_OK, NULL,
5816 if (aval != G_ALERTALTERNATE) {
5818 return COMPOSE_QUEUE_ERROR_NO_MSG;
5822 if (encoding != ENC_UNKNOWN)
5823 procmime_encode_content(mimetext, encoding);
5825 /* append attachment parts */
5826 if (compose_use_attach(compose) && attach_parts) {
5827 MimeInfo *mimempart;
5828 gchar *boundary = NULL;
5829 mimempart = procmime_mimeinfo_new();
5830 mimempart->content = MIMECONTENT_EMPTY;
5831 mimempart->type = MIMETYPE_MULTIPART;
5832 mimempart->subtype = g_strdup("mixed");
5836 boundary = generate_mime_boundary(NULL);
5837 } while (strstr(buf, boundary) != NULL);
5839 g_hash_table_insert(mimempart->typeparameters, g_strdup("boundary"),
5842 mimetext->disposition = DISPOSITIONTYPE_INLINE;
5844 g_node_append(mimempart->node, mimetext->node);
5845 g_node_append(mimemsg->node, mimempart->node);
5847 if (compose_add_attachments(compose, mimempart) < 0)
5848 return COMPOSE_QUEUE_ERROR_NO_MSG;
5850 g_node_append(mimemsg->node, mimetext->node);
5854 if (strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) != 0) {
5855 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
5856 /* extract name and address */
5857 if (strstr(spec, " <") && strstr(spec, ">")) {
5858 from_addr = g_strdup(strrchr(spec, '<')+1);
5859 *(strrchr(from_addr, '>')) = '\0';
5860 from_name = g_strdup(spec);
5861 *(strrchr(from_name, '<')) = '\0';
5868 /* sign message if sending */
5869 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5870 privacy_system_can_sign(compose->privacy_system))
5871 if (!privacy_sign(compose->privacy_system, mimemsg,
5872 compose->account, from_addr)) {
5875 return COMPOSE_QUEUE_ERROR_SIGNING_FAILED;
5880 if (compose->use_encryption) {
5881 if (compose->encdata != NULL &&
5882 strcmp(compose->encdata, "_DONT_ENCRYPT_")) {
5884 /* First, write an unencrypted copy and save it to outbox, if
5885 * user wants that. */
5886 if (compose->account->save_encrypted_as_clear_text) {
5887 debug_print("saving sent message unencrypted...\n");
5888 FILE *tmpfp = get_tmpfile_in_dir(get_mime_tmp_dir(), &tmp_enc_file);
5892 /* fp now points to a file with headers written,
5893 * let's make a copy. */
5895 content = file_read_stream_to_str(fp);
5897 str_write_to_file(content, tmp_enc_file);
5900 /* Now write the unencrypted body. */
5901 if ((tmpfp = g_fopen(tmp_enc_file, "a")) != NULL) {
5902 procmime_write_mimeinfo(mimemsg, tmpfp);
5905 outbox = folder_find_item_from_identifier(compose_get_save_to(compose));
5907 outbox = folder_get_default_outbox();
5909 procmsg_save_to_outbox(outbox, tmp_enc_file, TRUE);
5910 claws_unlink(tmp_enc_file);
5912 g_warning("Can't open file '%s'", tmp_enc_file);
5915 g_warning("couldn't get tempfile");
5918 if (!privacy_encrypt(compose->privacy_system, mimemsg, compose->encdata)) {
5919 debug_print("Couldn't encrypt mime structure: %s.\n",
5920 privacy_get_error());
5921 return COMPOSE_QUEUE_ERROR_ENCRYPT_FAILED;
5926 procmime_write_mimeinfo(mimemsg, fp);
5928 procmime_mimeinfo_free_all(&mimemsg);
5933 static gint compose_write_body_to_file(Compose *compose, const gchar *file)
5935 GtkTextBuffer *buffer;
5936 GtkTextIter start, end;
5941 if ((fp = g_fopen(file, "wb")) == NULL) {
5942 FILE_OP_ERROR(file, "fopen");
5946 /* chmod for security */
5947 if (change_file_mode_rw(fp, file) < 0) {
5948 FILE_OP_ERROR(file, "chmod");
5949 g_warning("can't change file mode");
5952 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5953 gtk_text_buffer_get_start_iter(buffer, &start);
5954 gtk_text_buffer_get_end_iter(buffer, &end);
5955 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5957 chars = conv_codeset_strdup
5958 (tmp, CS_INTERNAL, conv_get_locale_charset_str());
5967 len = strlen(chars);
5968 if (fwrite(chars, sizeof(gchar), len, fp) != len) {
5969 FILE_OP_ERROR(file, "fwrite");
5978 if (fclose(fp) == EOF) {
5979 FILE_OP_ERROR(file, "fclose");
5986 static gint compose_remove_reedit_target(Compose *compose, gboolean force)
5989 MsgInfo *msginfo = compose->targetinfo;
5991 cm_return_val_if_fail(compose->mode == COMPOSE_REEDIT, -1);
5992 if (!msginfo) return -1;
5994 if (!force && MSG_IS_LOCKED(msginfo->flags))
5997 item = msginfo->folder;
5998 cm_return_val_if_fail(item != NULL, -1);
6000 if (procmsg_msg_exist(msginfo) &&
6001 (folder_has_parent_of_type(item, F_QUEUE) ||
6002 folder_has_parent_of_type(item, F_DRAFT)
6003 || msginfo == compose->autosaved_draft)) {
6004 if (folder_item_remove_msg(item, msginfo->msgnum) < 0) {
6005 g_warning("can't remove the old message");
6008 debug_print("removed reedit target %d\n", msginfo->msgnum);
6015 static void compose_remove_draft(Compose *compose)
6018 MsgInfo *msginfo = compose->targetinfo;
6019 drafts = account_get_special_folder(compose->account, F_DRAFT);
6021 if (procmsg_msg_exist(msginfo)) {
6022 folder_item_remove_msg(drafts, msginfo->msgnum);
6027 ComposeQueueResult compose_queue(Compose *compose, gint *msgnum, FolderItem **item, gchar **msgpath,
6028 gboolean remove_reedit_target)
6030 return compose_queue_sub (compose, msgnum, item, msgpath, FALSE, remove_reedit_target);
6033 static gboolean compose_warn_encryption(Compose *compose)
6035 const gchar *warning = privacy_get_encrypt_warning(compose->privacy_system);
6036 AlertValue val = G_ALERTALTERNATE;
6038 if (warning == NULL)
6041 val = alertpanel_full(_("Encryption warning"), warning,
6042 GTK_STOCK_CANCEL, _("C_ontinue"), NULL, ALERTFOCUS_SECOND,
6043 TRUE, NULL, ALERT_WARNING);
6044 if (val & G_ALERTDISABLE) {
6045 val &= ~G_ALERTDISABLE;
6046 if (val == G_ALERTALTERNATE)
6047 privacy_inhibit_encrypt_warning(compose->privacy_system,
6051 if (val == G_ALERTALTERNATE) {
6058 static ComposeQueueResult compose_queue_sub(Compose *compose, gint *msgnum, FolderItem **item,
6059 gchar **msgpath, gboolean perform_checks,
6060 gboolean remove_reedit_target)
6067 PrefsAccount *mailac = NULL, *newsac = NULL;
6068 gboolean err = FALSE;
6070 debug_print("queueing message...\n");
6071 cm_return_val_if_fail(compose->account != NULL, -1);
6073 if (compose_check_entries(compose, perform_checks) == FALSE) {
6074 if (compose->batch) {
6075 gtk_widget_show_all(compose->window);
6077 return COMPOSE_QUEUE_ERROR_NO_MSG;
6080 if (!compose->to_list && !compose->newsgroup_list) {
6081 g_warning("can't get recipient list.");
6082 return COMPOSE_QUEUE_ERROR_NO_MSG;
6085 if (compose->to_list) {
6086 if (compose->account->protocol != A_NNTP)
6087 mailac = compose->account;
6088 else if (cur_account && cur_account->protocol != A_NNTP)
6089 mailac = cur_account;
6090 else if (!(mailac = compose_current_mail_account())) {
6091 alertpanel_error(_("No account for sending mails available!"));
6092 return COMPOSE_QUEUE_ERROR_NO_MSG;
6096 if (compose->newsgroup_list) {
6097 if (compose->account->protocol == A_NNTP)
6098 newsac = compose->account;
6100 alertpanel_error(_("Selected account isn't NNTP: Posting is impossible."));
6101 return COMPOSE_QUEUE_ERROR_NO_MSG;
6105 /* write queue header */
6106 tmp = g_strdup_printf("%s%cqueue.%p%08x", get_tmp_dir(),
6107 G_DIR_SEPARATOR, compose, (guint) rand());
6108 debug_print("queuing to %s\n", tmp);
6109 if ((fp = g_fopen(tmp, "w+b")) == NULL) {
6110 FILE_OP_ERROR(tmp, "fopen");
6112 return COMPOSE_QUEUE_ERROR_WITH_ERRNO;
6115 if (change_file_mode_rw(fp, tmp) < 0) {
6116 FILE_OP_ERROR(tmp, "chmod");
6117 g_warning("can't change file mode");
6120 /* queueing variables */
6121 err |= (fprintf(fp, "AF:\n") < 0);
6122 err |= (fprintf(fp, "NF:0\n") < 0);
6123 err |= (fprintf(fp, "PS:10\n") < 0);
6124 err |= (fprintf(fp, "SRH:1\n") < 0);
6125 err |= (fprintf(fp, "SFN:\n") < 0);
6126 err |= (fprintf(fp, "DSR:\n") < 0);
6128 err |= (fprintf(fp, "MID:<%s>\n", compose->msgid) < 0);
6130 err |= (fprintf(fp, "MID:\n") < 0);
6131 err |= (fprintf(fp, "CFG:\n") < 0);
6132 err |= (fprintf(fp, "PT:0\n") < 0);
6133 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
6134 err |= (fprintf(fp, "RQ:\n") < 0);
6136 err |= (fprintf(fp, "SSV:%s\n", mailac->smtp_server) < 0);
6138 err |= (fprintf(fp, "SSV:\n") < 0);
6140 err |= (fprintf(fp, "NSV:%s\n", newsac->nntp_server) < 0);
6142 err |= (fprintf(fp, "NSV:\n") < 0);
6143 err |= (fprintf(fp, "SSH:\n") < 0);
6144 /* write recepient list */
6145 if (compose->to_list) {
6146 err |= (fprintf(fp, "R:<%s>", (gchar *)compose->to_list->data) < 0);
6147 for (cur = compose->to_list->next; cur != NULL;
6149 err |= (fprintf(fp, ",<%s>", (gchar *)cur->data) < 0);
6150 err |= (fprintf(fp, "\n") < 0);
6152 /* write newsgroup list */
6153 if (compose->newsgroup_list) {
6154 err |= (fprintf(fp, "NG:") < 0);
6155 err |= (fprintf(fp, "%s", (gchar *)compose->newsgroup_list->data) < 0);
6156 for (cur = compose->newsgroup_list->next; cur != NULL; cur = cur->next)
6157 err |= (fprintf(fp, ",%s", (gchar *)cur->data) < 0);
6158 err |= (fprintf(fp, "\n") < 0);
6160 /* Sylpheed account IDs */
6162 err |= (fprintf(fp, "MAID:%d\n", mailac->account_id) < 0);
6164 err |= (fprintf(fp, "NAID:%d\n", newsac->account_id) < 0);
6167 if (compose->privacy_system != NULL) {
6168 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
6169 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
6170 if (compose->use_encryption) {
6171 if (!compose_warn_encryption(compose)) {
6175 return COMPOSE_QUEUE_ERROR_NO_MSG;
6177 if (mailac && mailac->encrypt_to_self) {
6178 GSList *tmp_list = g_slist_copy(compose->to_list);
6179 tmp_list = g_slist_append(tmp_list, compose->account->address);
6180 compose->encdata = privacy_get_encrypt_data(compose->privacy_system, tmp_list);
6181 g_slist_free(tmp_list);
6183 compose->encdata = privacy_get_encrypt_data(compose->privacy_system, compose->to_list);
6185 if (compose->encdata != NULL) {
6186 if (strcmp(compose->encdata, "_DONT_ENCRYPT_")) {
6187 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
6188 err |= (fprintf(fp, "X-Claws-Encrypt-Data:%s\n",
6189 compose->encdata) < 0);
6190 } /* else we finally dont want to encrypt */
6192 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
6193 /* and if encdata was null, it means there's been a problem in
6196 g_warning("failed to write queue message");
6200 return COMPOSE_QUEUE_ERROR_NO_ENCRYPTION_KEY;
6205 /* Save copy folder */
6206 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
6207 gchar *savefolderid;
6209 savefolderid = compose_get_save_to(compose);
6210 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
6211 g_free(savefolderid);
6213 /* Save copy folder */
6214 if (compose->return_receipt) {
6215 err |= (fprintf(fp, "RRCPT:1\n") < 0);
6217 /* Message-ID of message replying to */
6218 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
6219 gchar *folderid = NULL;
6221 if (compose->replyinfo->folder)
6222 folderid = folder_item_get_identifier(compose->replyinfo->folder);
6223 if (folderid == NULL)
6224 folderid = g_strdup("NULL");
6226 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
6229 /* Message-ID of message forwarding to */
6230 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
6231 gchar *folderid = NULL;
6233 if (compose->fwdinfo->folder)
6234 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
6235 if (folderid == NULL)
6236 folderid = g_strdup("NULL");
6238 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
6242 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
6243 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
6245 /* end of headers */
6246 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
6248 if (compose->redirect_filename != NULL) {
6249 if (compose_redirect_write_to_file(compose, fp) < 0) {
6253 return COMPOSE_QUEUE_ERROR_WITH_ERRNO;
6257 if ((result = compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_SEND, TRUE)) < 0) {
6265 g_warning("failed to write queue message");
6269 return COMPOSE_QUEUE_ERROR_WITH_ERRNO;
6271 if (fclose(fp) == EOF) {
6272 FILE_OP_ERROR(tmp, "fclose");
6275 return COMPOSE_QUEUE_ERROR_WITH_ERRNO;
6278 if (item && *item) {
6281 queue = account_get_special_folder(compose->account, F_QUEUE);
6284 g_warning("can't find queue folder");
6287 return COMPOSE_QUEUE_ERROR_NO_MSG;
6289 folder_item_scan(queue);
6290 if ((num = folder_item_add_msg(queue, tmp, NULL, FALSE)) < 0) {
6291 g_warning("can't queue the message");
6294 return COMPOSE_QUEUE_ERROR_NO_MSG;
6297 if (msgpath == NULL) {
6303 if (compose->mode == COMPOSE_REEDIT && remove_reedit_target) {
6304 compose_remove_reedit_target(compose, FALSE);
6307 if ((msgnum != NULL) && (item != NULL)) {
6312 return COMPOSE_QUEUE_SUCCESS;
6315 static int compose_add_attachments(Compose *compose, MimeInfo *parent)
6318 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
6323 GError *error = NULL;
6328 gchar *type, *subtype;
6329 GtkTreeModel *model;
6332 model = gtk_tree_view_get_model(tree_view);
6334 if (!gtk_tree_model_get_iter_first(model, &iter))
6337 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
6339 if (!is_file_exist(ainfo->file)) {
6340 gchar *msg = g_strdup_printf(_("Attachment %s doesn't exist anymore. Ignore?"), ainfo->file);
6341 AlertValue val = alertpanel_full(_("Warning"), msg,
6342 _("Cancel sending"), _("Ignore attachment"), NULL,
6343 ALERTFOCUS_FIRST, FALSE, NULL, ALERT_WARNING);
6345 if (val == G_ALERTDEFAULT) {
6351 f = g_file_new_for_path(ainfo->file);
6352 fi = g_file_query_info(f, "standard::size",
6353 G_FILE_QUERY_INFO_NONE, NULL, &error);
6354 if (error != NULL) {
6355 g_warning(error->message);
6356 g_error_free(error);
6360 size = g_file_info_get_size(fi);
6364 if (g_stat(ainfo->file, &statbuf) < 0)
6366 size = statbuf.st_size;
6369 mimepart = procmime_mimeinfo_new();
6370 mimepart->content = MIMECONTENT_FILE;
6371 mimepart->data.filename = g_strdup(ainfo->file);
6372 mimepart->tmp = FALSE; /* or we destroy our attachment */
6373 mimepart->offset = 0;
6374 mimepart->length = size;
6376 type = g_strdup(ainfo->content_type);
6378 if (!strchr(type, '/')) {
6380 type = g_strdup("application/octet-stream");
6383 subtype = strchr(type, '/') + 1;
6384 *(subtype - 1) = '\0';
6385 mimepart->type = procmime_get_media_type(type);
6386 mimepart->subtype = g_strdup(subtype);
6389 if (mimepart->type == MIMETYPE_MESSAGE &&
6390 !g_ascii_strcasecmp(mimepart->subtype, "rfc822")) {
6391 mimepart->disposition = DISPOSITIONTYPE_INLINE;
6392 } else if (mimepart->type == MIMETYPE_TEXT) {
6393 if (!ainfo->name && g_ascii_strcasecmp(mimepart->subtype, "plain")) {
6394 /* Text parts with no name come from multipart/alternative
6395 * forwards. Make sure the recipient won't look at the
6396 * original HTML part by mistake. */
6397 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6398 ainfo->name = g_strdup_printf(_("Original %s part"),
6402 g_hash_table_insert(mimepart->typeparameters,
6403 g_strdup("charset"), g_strdup(ainfo->charset));
6405 if (ainfo->name && mimepart->type != MIMETYPE_MESSAGE) {
6406 if (mimepart->type == MIMETYPE_APPLICATION &&
6407 !strcmp2(mimepart->subtype, "octet-stream"))
6408 g_hash_table_insert(mimepart->typeparameters,
6409 g_strdup("name"), g_strdup(ainfo->name));
6410 g_hash_table_insert(mimepart->dispositionparameters,
6411 g_strdup("filename"), g_strdup(ainfo->name));
6412 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6415 if (mimepart->type == MIMETYPE_MESSAGE
6416 || mimepart->type == MIMETYPE_MULTIPART)
6417 ainfo->encoding = ENC_BINARY;
6418 else if (compose->use_signing) {
6419 if (ainfo->encoding == ENC_7BIT)
6420 ainfo->encoding = ENC_QUOTED_PRINTABLE;
6421 else if (ainfo->encoding == ENC_8BIT)
6422 ainfo->encoding = ENC_BASE64;
6425 procmime_encode_content(mimepart, ainfo->encoding);
6427 g_node_append(parent->node, mimepart->node);
6428 } while (gtk_tree_model_iter_next(model, &iter));
6433 static gchar *compose_quote_list_of_addresses(gchar *str)
6435 GSList *list = NULL, *item = NULL;
6436 gchar *qname = NULL, *faddr = NULL, *result = NULL;
6438 list = address_list_append_with_comments(list, str);
6439 for (item = list; item != NULL; item = item->next) {
6440 gchar *spec = item->data;
6441 gchar *endofname = strstr(spec, " <");
6442 if (endofname != NULL) {
6445 QUOTE_IF_REQUIRED_NORMAL(qname, spec, return NULL);
6446 qqname = escape_internal_quotes(qname, '"');
6448 if (*qname != *spec || qqname != qname) { /* has been quoted, compute new */
6449 gchar *addr = g_strdup(endofname);
6450 gchar *name = (qqname != qname)? qqname: g_strdup(qname);
6451 faddr = g_strconcat(name, addr, NULL);
6454 debug_print("new auto-quoted address: '%s'\n", faddr);
6458 result = g_strdup((faddr != NULL)? faddr: spec);
6460 result = g_strconcat(result,
6462 (faddr != NULL)? faddr: spec,
6465 if (faddr != NULL) {
6470 slist_free_strings_full(list);
6475 #define IS_IN_CUSTOM_HEADER(header) \
6476 (compose->account->add_customhdr && \
6477 custom_header_find(compose->account->customhdr_list, header) != NULL)
6479 static const gchar *compose_untranslated_header_name(gchar *header_name)
6481 /* return the untranslated header name, if header_name is a known
6482 header name, in either its translated or untranslated form, with
6483 or without trailing colon. otherwise, returns header_name. */
6484 gchar *translated_header_name;
6485 gchar *translated_header_name_wcolon;
6486 const gchar *untranslated_header_name;
6487 const gchar *untranslated_header_name_wcolon;
6490 cm_return_val_if_fail(header_name != NULL, NULL);
6492 for (i = 0; HEADERS[i].header_name != NULL; i++) {
6493 untranslated_header_name = HEADERS[i].header_name;
6494 untranslated_header_name_wcolon = HEADERS[i].header_name_w_colon;
6496 translated_header_name = gettext(untranslated_header_name);
6497 translated_header_name_wcolon = gettext(untranslated_header_name_wcolon);
6499 if (!strcmp(header_name, untranslated_header_name) ||
6500 !strcmp(header_name, translated_header_name)) {
6501 return untranslated_header_name;
6503 if (!strcmp(header_name, untranslated_header_name_wcolon) ||
6504 !strcmp(header_name, translated_header_name_wcolon)) {
6505 return untranslated_header_name_wcolon;
6509 debug_print("compose_untranslated_header_name: unknown header '%s'\n", header_name);
6513 static void compose_add_headerfield_from_headerlist(Compose *compose,
6515 const gchar *fieldname,
6516 const gchar *seperator)
6518 gchar *str, *fieldname_w_colon;
6519 gboolean add_field = FALSE;
6521 ComposeHeaderEntry *headerentry;
6522 const gchar *headerentryname;
6523 const gchar *trans_fieldname;
6526 if (IS_IN_CUSTOM_HEADER(fieldname))
6529 debug_print("Adding %s-fields\n", fieldname);
6531 fieldstr = g_string_sized_new(64);
6533 fieldname_w_colon = g_strconcat(fieldname, ":", NULL);
6534 trans_fieldname = prefs_common_translated_header_name(fieldname_w_colon);
6536 for (list = compose->header_list; list; list = list->next) {
6537 headerentry = ((ComposeHeaderEntry *)list->data);
6538 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
6540 if (!g_utf8_collate(trans_fieldname, headerentryname)) {
6541 gchar * ustr = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
6543 str = compose_quote_list_of_addresses(ustr);
6545 if (str != NULL && str[0] != '\0') {
6547 g_string_append(fieldstr, seperator);
6548 g_string_append(fieldstr, str);
6557 buf = g_new0(gchar, fieldstr->len * 4 + 256);
6558 compose_convert_header
6559 (compose, buf, fieldstr->len * 4 + 256, fieldstr->str,
6560 strlen(fieldname) + 2, TRUE);
6561 g_string_append_printf(header, "%s: %s\n", fieldname, buf);
6565 g_free(fieldname_w_colon);
6566 g_string_free(fieldstr, TRUE);
6571 static gchar *compose_get_manual_headers_info(Compose *compose)
6573 GString *sh_header = g_string_new(" ");
6575 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6577 for (list = compose->header_list; list; list = list->next) {
6578 ComposeHeaderEntry *headerentry;
6581 gchar *headername_wcolon;
6582 const gchar *headername_trans;
6584 gboolean standard_header = FALSE;
6586 headerentry = ((ComposeHeaderEntry *)list->data);
6588 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6590 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6595 if (!strstr(tmp, ":")) {
6596 headername_wcolon = g_strconcat(tmp, ":", NULL);
6597 headername = g_strdup(tmp);
6599 headername_wcolon = g_strdup(tmp);
6600 headername = g_strdup(strtok(tmp, ":"));
6604 string = std_headers;
6605 while (*string != NULL) {
6606 headername_trans = prefs_common_translated_header_name(*string);
6607 if (!strcmp(headername_trans, headername_wcolon))
6608 standard_header = TRUE;
6611 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
6612 g_string_append_printf(sh_header, "%s ", headername);
6614 g_free(headername_wcolon);
6616 g_string_truncate(sh_header, strlen(sh_header->str) - 1); /* remove last space */
6617 return g_string_free(sh_header, FALSE);
6620 static gchar *compose_get_header(Compose *compose)
6622 gchar date[RFC822_DATE_BUFFSIZE];
6623 gchar buf[BUFFSIZE];
6624 const gchar *entry_str;
6628 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6630 gchar *from_name = NULL, *from_address = NULL;
6633 cm_return_val_if_fail(compose->account != NULL, NULL);
6634 cm_return_val_if_fail(compose->account->address != NULL, NULL);
6636 header = g_string_sized_new(64);
6639 if (prefs_common.hide_timezone)
6640 get_rfc822_date_hide_tz(date, sizeof(date));
6642 get_rfc822_date(date, sizeof(date));
6643 g_string_append_printf(header, "Date: %s\n", date);
6647 if (compose->account->name && *compose->account->name) {
6649 QUOTE_IF_REQUIRED(buf, compose->account->name);
6650 tmp = g_strdup_printf("%s <%s>",
6651 buf, compose->account->address);
6653 tmp = g_strdup_printf("%s",
6654 compose->account->address);
6656 if (!strcmp(gtk_entry_get_text(GTK_ENTRY(compose->from_name)), tmp)
6657 || strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) == 0) {
6659 from_name = compose->account->name ? g_strdup(compose->account->name):NULL;
6660 from_address = g_strdup(compose->account->address);
6662 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
6663 /* extract name and address */
6664 if (strstr(spec, " <") && strstr(spec, ">")) {
6665 from_address = g_strdup(strrchr(spec, '<')+1);
6666 *(strrchr(from_address, '>')) = '\0';
6667 from_name = g_strdup(spec);
6668 *(strrchr(from_name, '<')) = '\0';
6671 from_address = g_strdup(spec);
6678 if (from_name && *from_name) {
6680 compose_convert_header
6681 (compose, buf, sizeof(buf), from_name,
6682 strlen("From: "), TRUE);
6683 QUOTE_IF_REQUIRED(name, buf);
6684 qname = escape_internal_quotes(name, '"');
6686 g_string_append_printf(header, "From: %s <%s>\n",
6687 qname, from_address);
6688 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To") &&
6689 compose->return_receipt) {
6690 compose_convert_header(compose, buf, sizeof(buf), from_name,
6691 strlen("Disposition-Notification-To: "),
6693 g_string_append_printf(header, "Disposition-Notification-To: %s <%s>\n", buf, from_address);
6698 g_string_append_printf(header, "From: %s\n", from_address);
6699 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To") &&
6700 compose->return_receipt)
6701 g_string_append_printf(header, "Disposition-Notification-To: %s\n", from_address);
6705 g_free(from_address);
6708 compose_add_headerfield_from_headerlist(compose, header, "To", ", ");
6711 compose_add_headerfield_from_headerlist(compose, header, "Newsgroups", ",");
6714 compose_add_headerfield_from_headerlist(compose, header, "Cc", ", ");
6718 * If this account is a NNTP account remove Bcc header from
6719 * message body since it otherwise will be publicly shown
6721 if (compose->account->protocol != A_NNTP)
6722 compose_add_headerfield_from_headerlist(compose, header, "Bcc", ", ");
6725 str = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
6727 if (*str != '\0' && !IS_IN_CUSTOM_HEADER("Subject")) {
6730 compose_convert_header(compose, buf, sizeof(buf), str,
6731 strlen("Subject: "), FALSE);
6732 g_string_append_printf(header, "Subject: %s\n", buf);
6738 if (compose->msgid != NULL && strlen(compose->msgid) > 0) {
6739 g_string_append_printf(header, "Message-ID: <%s>\n",
6743 if (compose->remove_references == FALSE) {
6745 if (compose->inreplyto && compose->to_list)
6746 g_string_append_printf(header, "In-Reply-To: <%s>\n", compose->inreplyto);
6749 if (compose->references)
6750 g_string_append_printf(header, "References: %s\n", compose->references);
6754 compose_add_headerfield_from_headerlist(compose, header, "Followup-To", ",");
6757 compose_add_headerfield_from_headerlist(compose, header, "Reply-To", ", ");
6760 if (compose->account->organization &&
6761 strlen(compose->account->organization) &&
6762 !IS_IN_CUSTOM_HEADER("Organization")) {
6763 compose_convert_header(compose, buf, sizeof(buf),
6764 compose->account->organization,
6765 strlen("Organization: "), FALSE);
6766 g_string_append_printf(header, "Organization: %s\n", buf);
6769 /* Program version and system info */
6770 if (compose->account->gen_xmailer &&
6771 g_slist_length(compose->to_list) && !IS_IN_CUSTOM_HEADER("X-Mailer") &&
6772 !compose->newsgroup_list) {
6773 g_string_append_printf(header, "X-Mailer: %s (GTK+ %d.%d.%d; %s)\n",
6775 gtk_major_version, gtk_minor_version, gtk_micro_version,
6778 if (compose->account->gen_xmailer &&
6779 g_slist_length(compose->newsgroup_list) && !IS_IN_CUSTOM_HEADER("X-Newsreader")) {
6780 g_string_append_printf(header, "X-Newsreader: %s (GTK+ %d.%d.%d; %s)\n",
6782 gtk_major_version, gtk_minor_version, gtk_micro_version,
6786 /* custom headers */
6787 if (compose->account->add_customhdr) {
6790 for (cur = compose->account->customhdr_list; cur != NULL;
6792 CustomHeader *chdr = (CustomHeader *)cur->data;
6794 if (custom_header_is_allowed(chdr->name)
6795 && chdr->value != NULL
6796 && *(chdr->value) != '\0') {
6797 compose_convert_header
6798 (compose, buf, sizeof(buf),
6800 strlen(chdr->name) + 2, FALSE);
6801 g_string_append_printf(header, "%s: %s\n", chdr->name, buf);
6806 /* Automatic Faces and X-Faces */
6807 if (get_account_xface (buf, sizeof(buf), compose->account->account_name) == 0) {
6808 g_string_append_printf(header, "X-Face: %s\n", buf);
6810 else if (get_default_xface (buf, sizeof(buf)) == 0) {
6811 g_string_append_printf(header, "X-Face: %s\n", buf);
6813 if (get_account_face (buf, sizeof(buf), compose->account->account_name) == 0) {
6814 g_string_append_printf(header, "Face: %s\n", buf);
6816 else if (get_default_face (buf, sizeof(buf)) == 0) {
6817 g_string_append_printf(header, "Face: %s\n", buf);
6821 switch (compose->priority) {
6822 case PRIORITY_HIGHEST: g_string_append_printf(header, "Importance: high\n"
6823 "X-Priority: 1 (Highest)\n");
6825 case PRIORITY_HIGH: g_string_append_printf(header, "Importance: high\n"
6826 "X-Priority: 2 (High)\n");
6828 case PRIORITY_NORMAL: break;
6829 case PRIORITY_LOW: g_string_append_printf(header, "Importance: low\n"
6830 "X-Priority: 4 (Low)\n");
6832 case PRIORITY_LOWEST: g_string_append_printf(header, "Importance: low\n"
6833 "X-Priority: 5 (Lowest)\n");
6835 default: debug_print("compose: priority unknown : %d\n",
6839 /* get special headers */
6840 for (list = compose->header_list; list; list = list->next) {
6841 ComposeHeaderEntry *headerentry;
6844 gchar *headername_wcolon;
6845 const gchar *headername_trans;
6848 gboolean standard_header = FALSE;
6850 headerentry = ((ComposeHeaderEntry *)list->data);
6852 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6854 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6859 if (!strstr(tmp, ":")) {
6860 headername_wcolon = g_strconcat(tmp, ":", NULL);
6861 headername = g_strdup(tmp);
6863 headername_wcolon = g_strdup(tmp);
6864 headername = g_strdup(strtok(tmp, ":"));
6868 entry_str = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
6869 Xstrdup_a(headervalue, entry_str, return NULL);
6870 subst_char(headervalue, '\r', ' ');
6871 subst_char(headervalue, '\n', ' ');
6872 g_strstrip(headervalue);
6873 if (*headervalue != '\0') {
6874 string = std_headers;
6875 while (*string != NULL && !standard_header) {
6876 headername_trans = prefs_common_translated_header_name(*string);
6877 /* support mixed translated and untranslated headers */
6878 if (!strcmp(headername_trans, headername_wcolon) || !strcmp(*string, headername_wcolon))
6879 standard_header = TRUE;
6882 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername)) {
6883 /* store untranslated header name */
6884 g_string_append_printf(header, "%s %s\n",
6885 compose_untranslated_header_name(headername_wcolon), headervalue);
6889 g_free(headername_wcolon);
6893 g_string_free(header, FALSE);
6898 #undef IS_IN_CUSTOM_HEADER
6900 static void compose_convert_header(Compose *compose, gchar *dest, gint len, gchar *src,
6901 gint header_len, gboolean addr_field)
6903 gchar *tmpstr = NULL;
6904 const gchar *out_codeset = NULL;
6906 cm_return_if_fail(src != NULL);
6907 cm_return_if_fail(dest != NULL);
6909 if (len < 1) return;
6911 tmpstr = g_strdup(src);
6913 subst_char(tmpstr, '\n', ' ');
6914 subst_char(tmpstr, '\r', ' ');
6917 if (!g_utf8_validate(tmpstr, -1, NULL)) {
6918 gchar *mybuf = g_malloc(strlen(tmpstr)*2 +1);
6919 conv_localetodisp(mybuf, strlen(tmpstr)*2 +1, tmpstr);
6924 codeconv_set_strict(TRUE);
6925 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6926 conv_get_charset_str(compose->out_encoding));
6927 codeconv_set_strict(FALSE);
6929 if (!dest || *dest == '\0') {
6930 gchar *test_conv_global_out = NULL;
6931 gchar *test_conv_reply = NULL;
6933 /* automatic mode. be automatic. */
6934 codeconv_set_strict(TRUE);
6936 out_codeset = conv_get_outgoing_charset_str();
6938 debug_print("trying to convert to %s\n", out_codeset);
6939 test_conv_global_out = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6942 if (!test_conv_global_out && compose->orig_charset
6943 && strcmp(compose->orig_charset, CS_US_ASCII)) {
6944 out_codeset = compose->orig_charset;
6945 debug_print("failure; trying to convert to %s\n", out_codeset);
6946 test_conv_reply = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6949 if (!test_conv_global_out && !test_conv_reply) {
6951 out_codeset = CS_INTERNAL;
6952 debug_print("finally using %s\n", out_codeset);
6954 g_free(test_conv_global_out);
6955 g_free(test_conv_reply);
6956 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6958 codeconv_set_strict(FALSE);
6963 static void compose_add_to_addressbook_cb(GtkMenuItem *menuitem, gpointer user_data)
6967 cm_return_if_fail(user_data != NULL);
6969 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(user_data)));
6970 g_strstrip(address);
6971 if (*address != '\0') {
6972 gchar *name = procheader_get_fromname(address);
6973 extract_address(address);
6974 #ifndef USE_ALT_ADDRBOOK
6975 addressbook_add_contact(name, address, NULL, NULL);
6977 debug_print("%s: %s\n", name, address);
6978 if (addressadd_selection(name, address, NULL, NULL)) {
6979 debug_print( "addressbook_add_contact - added\n" );
6986 static void compose_entry_popup_extend(GtkEntry *entry, GtkMenu *menu, gpointer user_data)
6988 GtkWidget *menuitem;
6991 cm_return_if_fail(menu != NULL);
6992 cm_return_if_fail(GTK_IS_MENU_SHELL(menu));
6994 menuitem = gtk_separator_menu_item_new();
6995 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6996 gtk_widget_show(menuitem);
6998 menuitem = gtk_menu_item_new_with_mnemonic(_("Add to address _book"));
6999 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
7001 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
7002 g_strstrip(address);
7003 if (*address == '\0') {
7004 gtk_widget_set_sensitive(GTK_WIDGET(menuitem), FALSE);
7007 g_signal_connect(G_OBJECT(menuitem), "activate",
7008 G_CALLBACK(compose_add_to_addressbook_cb), entry);
7009 gtk_widget_show(menuitem);
7012 void compose_add_extra_header(gchar *header, GtkListStore *model)
7015 if (strcmp(header, "")) {
7016 COMBOBOX_ADD(model, header, COMPOSE_TO);
7020 void compose_add_extra_header_entries(GtkListStore *model)
7024 gchar buf[BUFFSIZE];
7027 if (extra_headers == NULL) {
7028 exhrc = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, "extraheaderrc", NULL);
7029 if ((exh = g_fopen(exhrc, "rb")) == NULL) {
7030 debug_print("extra headers file not found\n");
7031 goto extra_headers_done;
7033 while (fgets(buf, BUFFSIZE, exh) != NULL) {
7034 lastc = strlen(buf) - 1; /* remove trailing control chars */
7035 while (lastc >= 0 && buf[lastc] != ':')
7036 buf[lastc--] = '\0';
7037 if (lastc > 0 && buf[0] != '#' && buf[lastc] == ':') {
7038 buf[lastc] = '\0'; /* remove trailing : for comparison */
7039 if (custom_header_is_allowed(buf)) {
7041 extra_headers = g_slist_prepend(extra_headers, g_strdup(buf));
7044 g_message("disallowed extra header line: %s\n", buf);
7048 g_message("invalid extra header line: %s\n", buf);
7054 extra_headers = g_slist_prepend(extra_headers, g_strdup("")); /* end of list */
7055 extra_headers = g_slist_reverse(extra_headers);
7057 g_slist_foreach(extra_headers, (GFunc)compose_add_extra_header, (gpointer)model);
7061 static void _ldap_srv_func(gpointer data, gpointer user_data)
7063 LdapServer *server = (LdapServer *)data;
7064 gboolean *enable = (gboolean *)user_data;
7066 debug_print("%s server '%s'\n", (*enable == TRUE ? "enabling" : "disabling"), server->control->hostName);
7067 server->searchFlag = *enable;
7071 static void compose_create_header_entry(Compose *compose)
7073 gchar *headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
7080 const gchar *header = NULL;
7081 ComposeHeaderEntry *headerentry;
7082 gboolean standard_header = FALSE;
7083 GtkListStore *model;
7086 headerentry = g_new0(ComposeHeaderEntry, 1);
7088 /* Combo box model */
7089 model = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_BOOLEAN);
7090 COMBOBOX_ADD(model, prefs_common_translated_header_name("To:"),
7092 COMBOBOX_ADD(model, prefs_common_translated_header_name("Cc:"),
7094 COMBOBOX_ADD(model, prefs_common_translated_header_name("Bcc:"),
7096 COMBOBOX_ADD(model, prefs_common_translated_header_name("Newsgroups:"),
7097 COMPOSE_NEWSGROUPS);
7098 COMBOBOX_ADD(model, prefs_common_translated_header_name("Reply-To:"),
7100 COMBOBOX_ADD(model, prefs_common_translated_header_name("Followup-To:"),
7101 COMPOSE_FOLLOWUPTO);
7102 compose_add_extra_header_entries(model);
7105 combo = gtk_combo_box_new_with_model_and_entry(GTK_TREE_MODEL(model));
7106 GtkCellRenderer *cell = gtk_cell_renderer_text_new();
7107 gtk_cell_renderer_set_alignment(cell, 0.0, 0.5);
7108 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), cell, TRUE);
7109 gtk_combo_box_set_entry_text_column(GTK_COMBO_BOX(combo), 0);
7110 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
7111 g_signal_connect(G_OBJECT(gtk_bin_get_child(GTK_BIN(combo))), "grab_focus",
7112 G_CALLBACK(compose_grab_focus_cb), compose);
7113 gtk_widget_show(combo);
7115 /* Putting only the combobox child into focus chain of its parent causes
7116 * the parent to be skipped when changing focus via Tab or Shift+Tab.
7117 * This eliminates need to pres Tab twice in order to really get from the
7118 * combobox to next widget. */
7120 l = g_list_prepend(l, gtk_bin_get_child(GTK_BIN(combo)));
7121 gtk_container_set_focus_chain(GTK_CONTAINER(combo), l);
7124 gtk_table_attach(GTK_TABLE(compose->header_table), combo, 0, 1,
7125 compose->header_nextrow, compose->header_nextrow+1,
7126 GTK_SHRINK, GTK_FILL, 0, 0);
7127 if (compose->header_last && (compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN)) {
7128 const gchar *last_header_entry = gtk_entry_get_text(
7129 GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
7131 while (*string != NULL) {
7132 if (!strcmp(prefs_common_translated_header_name(*string), last_header_entry))
7133 standard_header = TRUE;
7136 if (standard_header)
7137 header = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
7139 if (!compose->header_last || !standard_header) {
7140 switch(compose->account->protocol) {
7142 header = prefs_common_translated_header_name("Newsgroups:");
7145 header = prefs_common_translated_header_name("To:");
7150 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((combo)))), header);
7152 gtk_editable_set_editable(
7153 GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((combo)))),
7154 prefs_common.type_any_header);
7156 g_signal_connect_after(G_OBJECT(gtk_bin_get_child(GTK_BIN((combo)))), "grab_focus",
7157 G_CALLBACK(compose_grab_focus_cb), compose);
7159 /* Entry field with cleanup button */
7160 button = gtk_button_new();
7161 gtk_button_set_image(GTK_BUTTON(button),
7162 gtk_image_new_from_stock(GTK_STOCK_CLEAR, GTK_ICON_SIZE_MENU));
7163 gtk_widget_show(button);
7164 CLAWS_SET_TIP(button,
7165 _("Delete entry contents"));
7166 entry = gtk_entry_new();
7167 gtk_widget_show(entry);
7168 CLAWS_SET_TIP(entry,
7169 _("Use <tab> to autocomplete from addressbook"));
7170 hbox = gtk_hbox_new (FALSE, 0);
7171 gtk_widget_show(hbox);
7172 gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, TRUE, 0);
7173 gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
7174 gtk_table_attach(GTK_TABLE(compose->header_table), hbox, 1, 2,
7175 compose->header_nextrow, compose->header_nextrow+1,
7176 GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
7178 g_signal_connect(G_OBJECT(entry), "key-press-event",
7179 G_CALLBACK(compose_headerentry_key_press_event_cb),
7181 g_signal_connect(G_OBJECT(entry), "changed",
7182 G_CALLBACK(compose_headerentry_changed_cb),
7184 g_signal_connect_after(G_OBJECT(entry), "grab_focus",
7185 G_CALLBACK(compose_grab_focus_cb), compose);
7187 g_signal_connect(G_OBJECT(button), "clicked",
7188 G_CALLBACK(compose_headerentry_button_clicked_cb),
7192 gtk_drag_dest_set(entry, GTK_DEST_DEFAULT_ALL, compose_mime_types,
7193 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7194 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7195 g_signal_connect(G_OBJECT(entry), "drag_data_received",
7196 G_CALLBACK(compose_header_drag_received_cb),
7198 g_signal_connect(G_OBJECT(entry), "drag-drop",
7199 G_CALLBACK(compose_drag_drop),
7201 g_signal_connect(G_OBJECT(entry), "populate-popup",
7202 G_CALLBACK(compose_entry_popup_extend),
7206 #ifndef PASSWORD_CRYPTO_OLD
7207 GSList *pwd_servers = addrindex_get_password_protected_ldap_servers();
7208 if (pwd_servers != NULL && master_passphrase() == NULL) {
7209 gboolean enable = FALSE;
7210 debug_print("Master passphrase not available, disabling password-protected LDAP servers for this compose window.\n");
7211 /* Temporarily disable password-protected LDAP servers,
7212 * because user did not provide a master passphrase.
7213 * We can safely enable searchFlag on all servers in this list
7214 * later, since addrindex_get_password_protected_ldap_servers()
7215 * includes servers which have it enabled initially. */
7216 g_slist_foreach(pwd_servers, _ldap_srv_func, &enable);
7217 compose->passworded_ldap_servers = pwd_servers;
7219 #endif /* PASSWORD_CRYPTO_OLD */
7220 #endif /* USE_LDAP */
7222 address_completion_register_entry(GTK_ENTRY(entry), TRUE);
7224 headerentry->compose = compose;
7225 headerentry->combo = combo;
7226 headerentry->entry = entry;
7227 headerentry->button = button;
7228 headerentry->hbox = hbox;
7229 headerentry->headernum = compose->header_nextrow;
7230 headerentry->type = PREF_NONE;
7232 compose->header_nextrow++;
7233 compose->header_last = headerentry;
7234 compose->header_list =
7235 g_slist_append(compose->header_list,
7239 static void compose_add_header_entry(Compose *compose, const gchar *header,
7240 gchar *text, ComposePrefType pref_type)
7242 ComposeHeaderEntry *last_header = compose->header_last;
7243 gchar *tmp = g_strdup(text), *email;
7244 gboolean replyto_hdr;
7246 replyto_hdr = (!strcasecmp(header,
7247 prefs_common_translated_header_name("Reply-To:")) ||
7249 prefs_common_translated_header_name("Followup-To:")) ||
7251 prefs_common_translated_header_name("In-Reply-To:")));
7253 extract_address(tmp);
7254 email = g_utf8_strdown(tmp, -1);
7256 if (replyto_hdr == FALSE &&
7257 g_hash_table_lookup(compose->email_hashtable, email) != NULL)
7259 debug_print("Ignoring duplicate address - %s %s, pref_type: %d\n",
7260 header, text, (gint) pref_type);
7266 if (!strcasecmp(header, prefs_common_translated_header_name("In-Reply-To:")))
7267 gtk_entry_set_text(GTK_ENTRY(
7268 gtk_bin_get_child(GTK_BIN(last_header->combo))), header);
7270 combobox_select_by_text(GTK_COMBO_BOX(last_header->combo), header);
7271 gtk_entry_set_text(GTK_ENTRY(last_header->entry), text);
7272 last_header->type = pref_type;
7274 if (replyto_hdr == FALSE)
7275 g_hash_table_insert(compose->email_hashtable, email,
7276 GUINT_TO_POINTER(1));
7283 static void compose_destroy_headerentry(Compose *compose,
7284 ComposeHeaderEntry *headerentry)
7286 gchar *text = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
7289 extract_address(text);
7290 email = g_utf8_strdown(text, -1);
7291 g_hash_table_remove(compose->email_hashtable, email);
7295 gtk_widget_destroy(headerentry->combo);
7296 gtk_widget_destroy(headerentry->entry);
7297 gtk_widget_destroy(headerentry->button);
7298 gtk_widget_destroy(headerentry->hbox);
7299 g_free(headerentry);
7302 static void compose_remove_header_entries(Compose *compose)
7305 for (list = compose->header_list; list; list = list->next)
7306 compose_destroy_headerentry(compose, (ComposeHeaderEntry *)list->data);
7308 compose->header_last = NULL;
7309 g_slist_free(compose->header_list);
7310 compose->header_list = NULL;
7311 compose->header_nextrow = 1;
7312 compose_create_header_entry(compose);
7315 static GtkWidget *compose_create_header(Compose *compose)
7317 GtkWidget *from_optmenu_hbox;
7318 GtkWidget *header_table_main;
7319 GtkWidget *header_scrolledwin;
7320 GtkWidget *header_table;
7322 /* parent with account selection and from header */
7323 header_table_main = gtk_table_new(2, 2, FALSE);
7324 gtk_widget_show(header_table_main);
7325 gtk_container_set_border_width(GTK_CONTAINER(header_table_main), BORDER_WIDTH);
7327 from_optmenu_hbox = compose_account_option_menu_create(compose);
7328 gtk_table_attach(GTK_TABLE(header_table_main), from_optmenu_hbox,
7329 0, 2, 0, 1, GTK_EXPAND | GTK_FILL, GTK_SHRINK, 0, 0);
7331 /* child with header labels and entries */
7332 header_scrolledwin = gtk_scrolled_window_new(NULL, NULL);
7333 gtk_widget_show(header_scrolledwin);
7334 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
7336 header_table = gtk_table_new(2, 2, FALSE);
7337 gtk_widget_show(header_table);
7338 gtk_container_set_border_width(GTK_CONTAINER(header_table), 0);
7339 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin), header_table);
7340 gtk_container_set_focus_vadjustment(GTK_CONTAINER(header_table),
7341 gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(header_scrolledwin)));
7342 gtk_viewport_set_shadow_type(GTK_VIEWPORT(gtk_bin_get_child(GTK_BIN(header_scrolledwin))), GTK_SHADOW_NONE);
7344 gtk_table_attach(GTK_TABLE(header_table_main), header_scrolledwin,
7345 0, 2, 1, 2, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 2);
7347 compose->header_table = header_table;
7348 compose->header_list = NULL;
7349 compose->header_nextrow = 0;
7351 compose_create_header_entry(compose);
7353 compose->table = NULL;
7355 return header_table_main;
7358 static gboolean popup_attach_button_pressed(GtkWidget *widget, gpointer data)
7360 Compose *compose = (Compose *)data;
7361 GdkEventButton event;
7364 event.time = gtk_get_current_event_time();
7366 return attach_button_pressed(compose->attach_clist, &event, compose);
7369 static GtkWidget *compose_create_attach(Compose *compose)
7371 GtkWidget *attach_scrwin;
7372 GtkWidget *attach_clist;
7374 GtkListStore *store;
7375 GtkCellRenderer *renderer;
7376 GtkTreeViewColumn *column;
7377 GtkTreeSelection *selection;
7379 /* attachment list */
7380 attach_scrwin = gtk_scrolled_window_new(NULL, NULL);
7381 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(attach_scrwin),
7382 GTK_POLICY_AUTOMATIC,
7383 GTK_POLICY_AUTOMATIC);
7384 gtk_widget_set_size_request(attach_scrwin, -1, 80);
7386 store = gtk_list_store_new(N_ATTACH_COLS,
7392 G_TYPE_AUTO_POINTER,
7394 attach_clist = GTK_WIDGET(gtk_tree_view_new_with_model
7395 (GTK_TREE_MODEL(store)));
7396 gtk_container_add(GTK_CONTAINER(attach_scrwin), attach_clist);
7397 g_object_unref(store);
7399 renderer = gtk_cell_renderer_text_new();
7400 column = gtk_tree_view_column_new_with_attributes
7401 (_("Mime type"), renderer, "text",
7402 COL_MIMETYPE, NULL);
7403 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7405 renderer = gtk_cell_renderer_text_new();
7406 column = gtk_tree_view_column_new_with_attributes
7407 (_("Size"), renderer, "text",
7409 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7411 renderer = gtk_cell_renderer_text_new();
7412 column = gtk_tree_view_column_new_with_attributes
7413 (_("Name"), renderer, "text",
7415 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7417 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(attach_clist),
7418 prefs_common.use_stripes_everywhere);
7419 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(attach_clist));
7420 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
7422 g_signal_connect(G_OBJECT(attach_clist), "row_activated",
7423 G_CALLBACK(attach_selected), compose);
7424 g_signal_connect(G_OBJECT(attach_clist), "button_press_event",
7425 G_CALLBACK(attach_button_pressed), compose);
7426 g_signal_connect(G_OBJECT(attach_clist), "popup-menu",
7427 G_CALLBACK(popup_attach_button_pressed), compose);
7428 g_signal_connect(G_OBJECT(attach_clist), "key_press_event",
7429 G_CALLBACK(attach_key_pressed), compose);
7432 gtk_drag_dest_set(attach_clist,
7433 GTK_DEST_DEFAULT_ALL, compose_mime_types,
7434 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7435 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7436 g_signal_connect(G_OBJECT(attach_clist), "drag_data_received",
7437 G_CALLBACK(compose_attach_drag_received_cb),
7439 g_signal_connect(G_OBJECT(attach_clist), "drag-drop",
7440 G_CALLBACK(compose_drag_drop),
7443 compose->attach_scrwin = attach_scrwin;
7444 compose->attach_clist = attach_clist;
7446 return attach_scrwin;
7449 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose);
7450 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose);
7452 static GtkWidget *compose_create_others(Compose *compose)
7455 GtkWidget *savemsg_checkbtn;
7456 GtkWidget *savemsg_combo;
7457 GtkWidget *savemsg_select;
7460 gchar *folderidentifier;
7462 /* Table for settings */
7463 table = gtk_table_new(3, 1, FALSE);
7464 gtk_container_set_border_width(GTK_CONTAINER(table), BORDER_WIDTH);
7465 gtk_widget_show(table);
7466 gtk_table_set_row_spacings(GTK_TABLE(table), VSPACING_NARROW);
7469 /* Save Message to folder */
7470 savemsg_checkbtn = gtk_check_button_new_with_label(_("Save Message to "));
7471 gtk_widget_show(savemsg_checkbtn);
7472 gtk_table_attach(GTK_TABLE(table), savemsg_checkbtn, 0, 1, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7473 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7474 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), prefs_common.savemsg);
7476 g_signal_connect(G_OBJECT(savemsg_checkbtn), "toggled",
7477 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
7479 savemsg_combo = gtk_combo_box_text_new_with_entry();
7480 compose->savemsg_checkbtn = savemsg_checkbtn;
7481 compose->savemsg_combo = savemsg_combo;
7482 gtk_widget_show(savemsg_combo);
7484 if (prefs_common.compose_save_to_history)
7485 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(savemsg_combo),
7486 prefs_common.compose_save_to_history);
7487 gtk_table_attach(GTK_TABLE(table), savemsg_combo, 1, 2, rowcount, rowcount + 1, GTK_FILL|GTK_EXPAND, GTK_SHRINK, 0, 0);
7488 gtk_widget_set_sensitive(GTK_WIDGET(savemsg_combo), prefs_common.savemsg);
7489 g_signal_connect_after(G_OBJECT(savemsg_combo), "grab_focus",
7490 G_CALLBACK(compose_grab_focus_cb), compose);
7491 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7492 folderidentifier = folder_item_get_identifier(account_get_special_folder
7493 (compose->account, F_OUTBOX));
7494 compose_set_save_to(compose, folderidentifier);
7495 g_free(folderidentifier);
7498 savemsg_select = gtkut_get_browse_file_btn(_("_Browse"));
7499 gtk_widget_show(savemsg_select);
7500 gtk_table_attach(GTK_TABLE(table), savemsg_select, 2, 3, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7501 g_signal_connect(G_OBJECT(savemsg_select), "clicked",
7502 G_CALLBACK(compose_savemsg_select_cb),
7508 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose)
7510 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo),
7511 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn)));
7514 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose)
7519 dest = foldersel_folder_sel(NULL, FOLDER_SEL_COPY, NULL, FALSE,
7520 _("Select folder to save message to"));
7523 path = folder_item_get_identifier(dest);
7525 compose_set_save_to(compose, path);
7529 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry, gboolean wrap,
7530 GdkAtom clip, GtkTextIter *insert_place);
7533 static gboolean text_clicked(GtkWidget *text, GdkEventButton *event,
7537 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
7539 if (event->button == 3) {
7541 GtkTextIter sel_start, sel_end;
7542 gboolean stuff_selected;
7544 /* move the cursor to allow GtkAspell to check the word
7545 * under the mouse */
7546 if (event->x && event->y) {
7547 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7548 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7550 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7553 GtkTextMark *mark = gtk_text_buffer_get_insert(buffer);
7554 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7557 stuff_selected = gtk_text_buffer_get_selection_bounds(
7559 &sel_start, &sel_end);
7561 gtk_text_buffer_place_cursor (buffer, &iter);
7562 /* reselect stuff */
7564 && gtk_text_iter_in_range(&iter, &sel_start, &sel_end)) {
7565 gtk_text_buffer_select_range(buffer,
7566 &sel_start, &sel_end);
7568 return FALSE; /* pass the event so that the right-click goes through */
7571 if (event->button == 2) {
7576 /* get the middle-click position to paste at the correct place */
7577 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7578 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7580 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7583 entry_paste_clipboard(compose, text,
7584 prefs_common.linewrap_pastes,
7585 GDK_SELECTION_PRIMARY, &iter);
7593 static void compose_spell_menu_changed(void *data)
7595 Compose *compose = (Compose *)data;
7597 GtkWidget *menuitem;
7598 GtkWidget *parent_item;
7599 GtkMenu *menu = GTK_MENU(gtk_menu_new());
7602 if (compose->gtkaspell == NULL)
7605 parent_item = gtk_ui_manager_get_widget(compose->ui_manager,
7606 "/Menu/Spelling/Options");
7608 /* setting the submenu removes /Spelling/Options from the factory
7609 * so we need to save it */
7611 if (parent_item == NULL) {
7612 parent_item = compose->aspell_options_menu;
7613 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), NULL);
7615 compose->aspell_options_menu = parent_item;
7617 spell_menu = gtkaspell_make_config_menu(compose->gtkaspell);
7619 spell_menu = g_slist_reverse(spell_menu);
7620 for (items = spell_menu;
7621 items; items = items->next) {
7622 menuitem = GTK_WIDGET(GTK_MENU_ITEM(items->data));
7623 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), GTK_WIDGET(menuitem));
7624 gtk_widget_show(GTK_WIDGET(menuitem));
7626 g_slist_free(spell_menu);
7628 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), GTK_WIDGET(menu));
7629 gtk_widget_show(parent_item);
7632 static void compose_dict_changed(void *data)
7634 Compose *compose = (Compose *) data;
7636 if(!compose->gtkaspell)
7638 if(compose->gtkaspell->recheck_when_changing_dict == FALSE)
7641 gtkaspell_highlight_all(compose->gtkaspell);
7642 claws_spell_entry_recheck_all(CLAWS_SPELL_ENTRY(compose->subject_entry));
7646 static gboolean compose_popup_menu(GtkWidget *widget, gpointer data)
7648 Compose *compose = (Compose *)data;
7649 GdkEventButton event;
7652 event.time = gtk_get_current_event_time();
7656 return text_clicked(compose->text, &event, compose);
7659 static gboolean compose_force_window_origin = TRUE;
7660 static Compose *compose_create(PrefsAccount *account,
7669 GtkWidget *handlebox;
7671 GtkWidget *notebook;
7673 GtkWidget *attach_hbox;
7674 GtkWidget *attach_lab1;
7675 GtkWidget *attach_lab2;
7680 GtkWidget *subject_hbox;
7681 GtkWidget *subject_frame;
7682 GtkWidget *subject_entry;
7686 GtkWidget *edit_vbox;
7687 GtkWidget *ruler_hbox;
7689 GtkWidget *scrolledwin;
7691 GtkTextBuffer *buffer;
7692 GtkClipboard *clipboard;
7694 UndoMain *undostruct;
7696 GtkWidget *popupmenu;
7697 GtkWidget *tmpl_menu;
7698 GtkActionGroup *action_group = NULL;
7701 GtkAspell * gtkaspell = NULL;
7704 static GdkGeometry geometry;
7706 cm_return_val_if_fail(account != NULL, NULL);
7708 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_DEFAULT_HEADER_BG],
7709 &default_header_bgcolor);
7710 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_DEFAULT_HEADER],
7711 &default_header_color);
7713 debug_print("Creating compose window...\n");
7714 compose = g_new0(Compose, 1);
7716 compose->batch = batch;
7717 compose->account = account;
7718 compose->folder = folder;
7720 compose->mutex = cm_mutex_new();
7721 compose->set_cursor_pos = -1;
7723 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose");
7725 gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
7726 gtk_widget_set_size_request(window, prefs_common.compose_width,
7727 prefs_common.compose_height);
7729 if (!geometry.max_width) {
7730 geometry.max_width = gdk_screen_width();
7731 geometry.max_height = gdk_screen_height();
7734 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7735 &geometry, GDK_HINT_MAX_SIZE);
7736 if (!geometry.min_width) {
7737 geometry.min_width = 600;
7738 geometry.min_height = 440;
7740 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7741 &geometry, GDK_HINT_MIN_SIZE);
7743 #ifndef GENERIC_UMPC
7744 if (compose_force_window_origin)
7745 gtk_window_move(GTK_WINDOW(window), prefs_common.compose_x,
7746 prefs_common.compose_y);
7748 g_signal_connect(G_OBJECT(window), "delete_event",
7749 G_CALLBACK(compose_delete_cb), compose);
7750 MANAGE_WINDOW_SIGNALS_CONNECT(window);
7751 gtk_widget_realize(window);
7753 gtkut_widget_set_composer_icon(window);
7755 vbox = gtk_vbox_new(FALSE, 0);
7756 gtk_container_add(GTK_CONTAINER(window), vbox);
7758 compose->ui_manager = gtk_ui_manager_new();
7759 action_group = cm_menu_create_action_group_full(compose->ui_manager,"Menu", compose_entries,
7760 G_N_ELEMENTS(compose_entries), (gpointer)compose);
7761 gtk_action_group_add_toggle_actions(action_group, compose_toggle_entries,
7762 G_N_ELEMENTS(compose_toggle_entries), (gpointer)compose);
7763 gtk_action_group_add_radio_actions(action_group, compose_radio_rm_entries,
7764 G_N_ELEMENTS(compose_radio_rm_entries), COMPOSE_REPLY, G_CALLBACK(compose_reply_change_mode_cb), (gpointer)compose);
7765 gtk_action_group_add_radio_actions(action_group, compose_radio_prio_entries,
7766 G_N_ELEMENTS(compose_radio_prio_entries), PRIORITY_NORMAL, G_CALLBACK(compose_set_priority_cb), (gpointer)compose);
7767 gtk_action_group_add_radio_actions(action_group, compose_radio_enc_entries,
7768 G_N_ELEMENTS(compose_radio_enc_entries), C_AUTO, G_CALLBACK(compose_set_encoding_cb), (gpointer)compose);
7770 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Menu", NULL, GTK_UI_MANAGER_MENUBAR)
7772 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Message", "Message", GTK_UI_MANAGER_MENU)
7773 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Edit", "Edit", GTK_UI_MANAGER_MENU)
7775 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Spelling", "Spelling", GTK_UI_MANAGER_MENU)
7777 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Options", "Options", GTK_UI_MANAGER_MENU)
7778 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Tools", "Tools", GTK_UI_MANAGER_MENU)
7779 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Help", "Help", GTK_UI_MANAGER_MENU)
7782 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Send", "Message/Send", GTK_UI_MANAGER_MENUITEM)
7783 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "SendLater", "Message/SendLater", GTK_UI_MANAGER_MENUITEM)
7784 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator1", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7785 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "AttachFile", "Message/AttachFile", GTK_UI_MANAGER_MENUITEM)
7786 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertFile", "Message/InsertFile", GTK_UI_MANAGER_MENUITEM)
7787 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertSig", "Message/InsertSig", GTK_UI_MANAGER_MENUITEM)
7788 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "ReplaceSig", "Message/ReplaceSig", GTK_UI_MANAGER_MENUITEM)
7789 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator2", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7790 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Save", "Message/Save", GTK_UI_MANAGER_MENUITEM)
7791 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator3", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7792 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Print", "Message/Print", GTK_UI_MANAGER_MENUITEM)
7793 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator4", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7794 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Close", "Message/Close", GTK_UI_MANAGER_MENUITEM)
7797 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Undo", "Edit/Undo", GTK_UI_MANAGER_MENUITEM)
7798 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Redo", "Edit/Redo", GTK_UI_MANAGER_MENUITEM)
7799 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator1", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7801 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Cut", "Edit/Cut", GTK_UI_MANAGER_MENUITEM)
7802 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Copy", "Edit/Copy", GTK_UI_MANAGER_MENUITEM)
7803 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Paste", "Edit/Paste", GTK_UI_MANAGER_MENUITEM)
7805 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SpecialPaste", "Edit/SpecialPaste", GTK_UI_MANAGER_MENU)
7806 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "AsQuotation", "Edit/SpecialPaste/AsQuotation", GTK_UI_MANAGER_MENUITEM)
7807 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Wrapped", "Edit/SpecialPaste/Wrapped", GTK_UI_MANAGER_MENUITEM)
7808 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Unwrapped", "Edit/SpecialPaste/Unwrapped", GTK_UI_MANAGER_MENUITEM)
7810 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SelectAll", "Edit/SelectAll", GTK_UI_MANAGER_MENUITEM)
7812 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Advanced", "Edit/Advanced", GTK_UI_MANAGER_MENU)
7813 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackChar", "Edit/Advanced/BackChar", GTK_UI_MANAGER_MENUITEM)
7814 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwChar", "Edit/Advanced/ForwChar", GTK_UI_MANAGER_MENUITEM)
7815 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackWord", "Edit/Advanced/BackWord", GTK_UI_MANAGER_MENUITEM)
7816 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwWord", "Edit/Advanced/ForwWord", GTK_UI_MANAGER_MENUITEM)
7817 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BegLine", "Edit/Advanced/BegLine", GTK_UI_MANAGER_MENUITEM)
7818 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "EndLine", "Edit/Advanced/EndLine", GTK_UI_MANAGER_MENUITEM)
7819 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "PrevLine", "Edit/Advanced/PrevLine", GTK_UI_MANAGER_MENUITEM)
7820 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "NextLine", "Edit/Advanced/NextLine", GTK_UI_MANAGER_MENUITEM)
7821 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackChar", "Edit/Advanced/DelBackChar", GTK_UI_MANAGER_MENUITEM)
7822 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwChar", "Edit/Advanced/DelForwChar", GTK_UI_MANAGER_MENUITEM)
7823 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackWord", "Edit/Advanced/DelBackWord", GTK_UI_MANAGER_MENUITEM)
7824 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwWord", "Edit/Advanced/DelForwWord", GTK_UI_MANAGER_MENUITEM)
7825 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelLine", "Edit/Advanced/DelLine", GTK_UI_MANAGER_MENUITEM)
7826 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelEndLine", "Edit/Advanced/DelEndLine", GTK_UI_MANAGER_MENUITEM)
7828 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator2", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7830 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Find", "Edit/Find", GTK_UI_MANAGER_MENUITEM)
7831 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapPara", "Edit/WrapPara", GTK_UI_MANAGER_MENUITEM)
7832 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapAllLines", "Edit/WrapAllLines", GTK_UI_MANAGER_MENUITEM)
7833 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoWrap", "Edit/AutoWrap", GTK_UI_MANAGER_MENUITEM)
7834 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoIndent", "Edit/AutoIndent", GTK_UI_MANAGER_MENUITEM)
7836 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator3", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7838 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "ExtEditor", "Edit/ExtEditor", GTK_UI_MANAGER_MENUITEM)
7842 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckAllSel", "Spelling/CheckAllSel", GTK_UI_MANAGER_MENUITEM)
7843 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "HighlightAll", "Spelling/HighlightAll", GTK_UI_MANAGER_MENUITEM)
7844 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckBackwards", "Spelling/CheckBackwards", GTK_UI_MANAGER_MENUITEM)
7845 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "ForwardNext", "Spelling/ForwardNext", GTK_UI_MANAGER_MENUITEM)
7846 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Separator1", "Spelling/---", GTK_UI_MANAGER_SEPARATOR)
7847 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Options", "Spelling/Options", GTK_UI_MANAGER_MENU)
7851 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "ReplyMode", "Options/ReplyMode", GTK_UI_MANAGER_MENU)
7852 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Normal", "Options/ReplyMode/Normal", GTK_UI_MANAGER_MENUITEM)
7853 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "All", "Options/ReplyMode/All", GTK_UI_MANAGER_MENUITEM)
7854 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Sender", "Options/ReplyMode/Sender", GTK_UI_MANAGER_MENUITEM)
7855 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "List", "Options/ReplyMode/List", GTK_UI_MANAGER_MENUITEM)
7857 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator1", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7858 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "PrivacySystem", "Options/PrivacySystem", GTK_UI_MANAGER_MENU)
7859 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/PrivacySystem", "PlaceHolder", "Options/PrivacySystem/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7860 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Sign", "Options/Sign", GTK_UI_MANAGER_MENUITEM)
7861 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encrypt", "Options/Encrypt", GTK_UI_MANAGER_MENUITEM)
7864 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator2", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7865 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Priority", "Options/Priority", GTK_UI_MANAGER_MENU)
7866 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Highest", "Options/Priority/Highest", GTK_UI_MANAGER_MENUITEM)
7867 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "High", "Options/Priority/High", GTK_UI_MANAGER_MENUITEM)
7868 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Normal", "Options/Priority/Normal", GTK_UI_MANAGER_MENUITEM)
7869 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Low", "Options/Priority/Low", GTK_UI_MANAGER_MENUITEM)
7870 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Lowest", "Options/Priority/Lowest", GTK_UI_MANAGER_MENUITEM)
7872 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator3", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7873 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RequestRetRcpt", "Options/RequestRetRcpt", GTK_UI_MANAGER_MENUITEM)
7874 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator4", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7875 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RemoveReferences", "Options/RemoveReferences", GTK_UI_MANAGER_MENUITEM)
7876 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator5", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7878 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encoding", "Options/Encoding", GTK_UI_MANAGER_MENU)
7880 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_AUTO, "Options/Encoding/"CS_AUTO, GTK_UI_MANAGER_MENUITEM)
7881 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator1", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7882 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_US_ASCII, "Options/Encoding/"CS_US_ASCII, GTK_UI_MANAGER_MENUITEM)
7883 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_UTF_8, "Options/Encoding/"CS_UTF_8, GTK_UI_MANAGER_MENUITEM)
7884 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator2", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7886 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Western", "Options/Encoding/Western", GTK_UI_MANAGER_MENU)
7887 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)
7888 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)
7889 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Western", CS_WINDOWS_1252, "Options/Encoding/Western/"CS_WINDOWS_1252, GTK_UI_MANAGER_MENUITEM)
7891 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_2, "Options/Encoding/"CS_ISO_8859_2, GTK_UI_MANAGER_MENUITEM)
7893 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Baltic", "Options/Encoding/Baltic", GTK_UI_MANAGER_MENU)
7894 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)
7895 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)
7897 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_7, "Options/Encoding/"CS_ISO_8859_7, GTK_UI_MANAGER_MENUITEM)
7899 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Hebrew", "Options/Encoding/Hebrew", GTK_UI_MANAGER_MENU)
7900 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)
7901 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Hebrew", CS_WINDOWS_1255, "Options/Encoding/Hebrew/"CS_WINDOWS_1255, GTK_UI_MANAGER_MENUITEM)
7903 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Arabic", "Options/Encoding/Arabic", GTK_UI_MANAGER_MENU)
7904 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)
7905 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Arabic", CS_WINDOWS_1256, "Options/Encoding/Arabic/"CS_WINDOWS_1256, GTK_UI_MANAGER_MENUITEM)
7907 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_9, "Options/Encoding/"CS_ISO_8859_9, GTK_UI_MANAGER_MENUITEM)
7909 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Cyrillic", "Options/Encoding/Cyrillic", GTK_UI_MANAGER_MENU)
7910 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)
7911 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_R, "Options/Encoding/Cyrillic/"CS_KOI8_R, GTK_UI_MANAGER_MENUITEM)
7912 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_MACCYR, "Options/Encoding/Cyrillic/"CS_MACCYR, GTK_UI_MANAGER_MENUITEM)
7913 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_U, "Options/Encoding/Cyrillic/"CS_KOI8_U, GTK_UI_MANAGER_MENUITEM)
7914 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_WINDOWS_1251, "Options/Encoding/Cyrillic/"CS_WINDOWS_1251, GTK_UI_MANAGER_MENUITEM)
7916 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Japanese", "Options/Encoding/Japanese", GTK_UI_MANAGER_MENU)
7917 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)
7918 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)
7919 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_EUC_JP, "Options/Encoding/Japanese/"CS_EUC_JP, GTK_UI_MANAGER_MENUITEM)
7920 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_SHIFT_JIS, "Options/Encoding/Japanese/"CS_SHIFT_JIS, GTK_UI_MANAGER_MENUITEM)
7922 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Chinese", "Options/Encoding/Chinese", GTK_UI_MANAGER_MENU)
7923 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB18030, "Options/Encoding/Chinese/"CS_GB18030, GTK_UI_MANAGER_MENUITEM)
7924 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB2312, "Options/Encoding/Chinese/"CS_GB2312, GTK_UI_MANAGER_MENUITEM)
7925 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GBK, "Options/Encoding/Chinese/"CS_GBK, GTK_UI_MANAGER_MENUITEM)
7926 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_BIG5, "Options/Encoding/Chinese/"CS_BIG5, GTK_UI_MANAGER_MENUITEM)
7927 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_EUC_TW, "Options/Encoding/Chinese/"CS_EUC_TW, GTK_UI_MANAGER_MENUITEM)
7929 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Korean", "Options/Encoding/Korean", GTK_UI_MANAGER_MENU)
7930 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Korean", CS_EUC_KR, "Options/Encoding/Korean/"CS_EUC_KR, GTK_UI_MANAGER_MENUITEM)
7931 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)
7933 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Thai", "Options/Encoding/Thai", GTK_UI_MANAGER_MENU)
7934 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_TIS_620, "Options/Encoding/Thai/"CS_TIS_620, GTK_UI_MANAGER_MENUITEM)
7935 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_WINDOWS_874, "Options/Encoding/Thai/"CS_WINDOWS_874, GTK_UI_MANAGER_MENUITEM)
7939 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "ShowRuler", "Tools/ShowRuler", GTK_UI_MANAGER_MENUITEM)
7940 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "AddressBook", "Tools/AddressBook", GTK_UI_MANAGER_MENUITEM)
7941 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Template", "Tools/Template", GTK_UI_MANAGER_MENU)
7942 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Template", "PlaceHolder", "Tools/Template/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7943 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Actions", "Tools/Actions", GTK_UI_MANAGER_MENU)
7944 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Actions", "PlaceHolder", "Tools/Actions/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7947 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Help", "About", "Help/About", GTK_UI_MANAGER_MENUITEM)
7949 menubar = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu");
7950 gtk_widget_show_all(menubar);
7952 gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(compose->ui_manager));
7953 gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, TRUE, 0);
7955 if (prefs_common.toolbar_detachable) {
7956 handlebox = gtk_handle_box_new();
7958 handlebox = gtk_hbox_new(FALSE, 0);
7960 gtk_box_pack_start(GTK_BOX(vbox), handlebox, FALSE, FALSE, 0);
7962 gtk_widget_realize(handlebox);
7963 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, handlebox,
7966 vbox2 = gtk_vbox_new(FALSE, 2);
7967 gtk_box_pack_start(GTK_BOX(vbox), vbox2, TRUE, TRUE, 0);
7968 gtk_container_set_border_width(GTK_CONTAINER(vbox2), 0);
7971 notebook = gtk_notebook_new();
7972 gtk_widget_show(notebook);
7974 /* header labels and entries */
7975 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7976 compose_create_header(compose),
7977 gtk_label_new_with_mnemonic(_("Hea_der")));
7978 /* attachment list */
7979 attach_hbox = gtk_hbox_new(FALSE, 0);
7980 gtk_widget_show(attach_hbox);
7982 attach_lab1 = gtk_label_new_with_mnemonic(_("_Attachments"));
7983 gtk_widget_show(attach_lab1);
7984 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab1, TRUE, TRUE, 0);
7986 attach_lab2 = gtk_label_new("");
7987 gtk_widget_show(attach_lab2);
7988 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab2, FALSE, FALSE, 0);
7990 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7991 compose_create_attach(compose),
7994 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7995 compose_create_others(compose),
7996 gtk_label_new_with_mnemonic(_("Othe_rs")));
7999 subject_hbox = gtk_hbox_new(FALSE, 0);
8000 gtk_widget_show(subject_hbox);
8002 subject_frame = gtk_frame_new(NULL);
8003 gtk_frame_set_shadow_type(GTK_FRAME(subject_frame), GTK_SHADOW_NONE);
8004 gtk_box_pack_start(GTK_BOX(subject_hbox), subject_frame, TRUE, TRUE, 0);
8005 gtk_widget_show(subject_frame);
8007 subject = gtk_hbox_new(FALSE, HSPACING_NARROW);
8008 gtk_container_set_border_width(GTK_CONTAINER(subject), 0);
8009 gtk_widget_show(subject);
8011 label = gtk_label_new_with_mnemonic(_("S_ubject:"));
8012 gtk_box_pack_start(GTK_BOX(subject), label, FALSE, FALSE, 0);
8013 gtk_widget_show(label);
8016 subject_entry = claws_spell_entry_new();
8018 subject_entry = gtk_entry_new();
8020 gtk_box_pack_start(GTK_BOX(subject), subject_entry, TRUE, TRUE, 0);
8021 g_signal_connect_after(G_OBJECT(subject_entry), "grab_focus",
8022 G_CALLBACK(compose_grab_focus_cb), compose);
8023 gtk_label_set_mnemonic_widget(GTK_LABEL(label), subject_entry);
8024 gtk_widget_show(subject_entry);
8025 compose->subject_entry = subject_entry;
8026 gtk_container_add(GTK_CONTAINER(subject_frame), subject);
8028 edit_vbox = gtk_vbox_new(FALSE, 0);
8030 gtk_box_pack_start(GTK_BOX(edit_vbox), subject_hbox, FALSE, FALSE, 0);
8033 ruler_hbox = gtk_hbox_new(FALSE, 0);
8034 gtk_box_pack_start(GTK_BOX(edit_vbox), ruler_hbox, FALSE, FALSE, 0);
8036 ruler = gtk_shruler_new(GTK_ORIENTATION_HORIZONTAL);
8037 gtk_shruler_set_range(GTK_SHRULER(ruler), 0.0, 100.0, 1.0);
8038 gtk_box_pack_start(GTK_BOX(ruler_hbox), ruler, TRUE, TRUE,
8042 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
8043 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
8044 GTK_POLICY_AUTOMATIC,
8045 GTK_POLICY_AUTOMATIC);
8046 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
8048 gtk_box_pack_start(GTK_BOX(edit_vbox), scrolledwin, TRUE, TRUE, 0);
8050 text = gtk_text_view_new();
8051 if (prefs_common.show_compose_margin) {
8052 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text), 6);
8053 gtk_text_view_set_right_margin(GTK_TEXT_VIEW(text), 6);
8055 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
8056 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
8057 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), TRUE);
8058 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
8059 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
8061 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
8062 g_signal_connect_after(G_OBJECT(text), "size_allocate",
8063 G_CALLBACK(compose_edit_size_alloc),
8065 g_signal_connect(G_OBJECT(buffer), "changed",
8066 G_CALLBACK(compose_changed_cb), compose);
8067 g_signal_connect(G_OBJECT(text), "grab_focus",
8068 G_CALLBACK(compose_grab_focus_cb), compose);
8069 g_signal_connect(G_OBJECT(buffer), "insert_text",
8070 G_CALLBACK(text_inserted), compose);
8071 g_signal_connect(G_OBJECT(text), "button_press_event",
8072 G_CALLBACK(text_clicked), compose);
8073 g_signal_connect(G_OBJECT(text), "popup-menu",
8074 G_CALLBACK(compose_popup_menu), compose);
8075 g_signal_connect(G_OBJECT(subject_entry), "changed",
8076 G_CALLBACK(compose_changed_cb), compose);
8077 g_signal_connect(G_OBJECT(subject_entry), "activate",
8078 G_CALLBACK(compose_subject_entry_activated), compose);
8081 gtk_drag_dest_set(text, GTK_DEST_DEFAULT_ALL, compose_mime_types,
8082 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
8083 GDK_ACTION_COPY | GDK_ACTION_MOVE);
8084 g_signal_connect(G_OBJECT(text), "drag_data_received",
8085 G_CALLBACK(compose_insert_drag_received_cb),
8087 g_signal_connect(G_OBJECT(text), "drag-drop",
8088 G_CALLBACK(compose_drag_drop),
8090 g_signal_connect(G_OBJECT(text), "key-press-event",
8091 G_CALLBACK(completion_set_focus_to_subject),
8093 gtk_widget_show_all(vbox);
8095 /* pane between attach clist and text */
8096 paned = gtk_vpaned_new();
8097 gtk_container_add(GTK_CONTAINER(vbox2), paned);
8098 gtk_paned_pack1(GTK_PANED(paned), notebook, FALSE, FALSE);
8099 gtk_paned_pack2(GTK_PANED(paned), edit_vbox, TRUE, FALSE);
8100 gtk_paned_set_position(GTK_PANED(paned), prefs_common.compose_notebook_height);
8101 g_signal_connect(G_OBJECT(notebook), "size_allocate",
8102 G_CALLBACK(compose_notebook_size_alloc), paned);
8104 gtk_widget_show_all(paned);
8107 if (prefs_common.textfont) {
8108 PangoFontDescription *font_desc;
8110 font_desc = pango_font_description_from_string
8111 (prefs_common.textfont);
8113 gtk_widget_modify_font(text, font_desc);
8114 pango_font_description_free(font_desc);
8118 gtk_action_group_add_actions(action_group, compose_popup_entries,
8119 G_N_ELEMENTS(compose_popup_entries), (gpointer)compose);
8120 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Popup", NULL, GTK_UI_MANAGER_MENUBAR)
8121 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup", "Compose", "Compose", GTK_UI_MANAGER_MENU)
8122 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Add", "Compose/Add", GTK_UI_MANAGER_MENUITEM)
8123 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Remove", "Compose/Remove", GTK_UI_MANAGER_MENUITEM)
8124 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Separator1", "Compose/---", GTK_UI_MANAGER_SEPARATOR)
8125 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Properties", "Compose/Properties", GTK_UI_MANAGER_MENUITEM)
8127 popupmenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(gtk_ui_manager_get_widget(compose->ui_manager, "/Popup/Compose")));
8129 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
8130 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
8131 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
8133 tmpl_menu = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu/Tools/Template");
8135 undostruct = undo_init(text);
8136 undo_set_change_state_func(undostruct, &compose_undo_state_changed,
8139 address_completion_start(window);
8141 compose->window = window;
8142 compose->vbox = vbox;
8143 compose->menubar = menubar;
8144 compose->handlebox = handlebox;
8146 compose->vbox2 = vbox2;
8148 compose->paned = paned;
8150 compose->attach_label = attach_lab2;
8152 compose->notebook = notebook;
8153 compose->edit_vbox = edit_vbox;
8154 compose->ruler_hbox = ruler_hbox;
8155 compose->ruler = ruler;
8156 compose->scrolledwin = scrolledwin;
8157 compose->text = text;
8159 compose->focused_editable = NULL;
8161 compose->popupmenu = popupmenu;
8163 compose->tmpl_menu = tmpl_menu;
8165 compose->mode = mode;
8166 compose->rmode = mode;
8168 compose->targetinfo = NULL;
8169 compose->replyinfo = NULL;
8170 compose->fwdinfo = NULL;
8172 compose->email_hashtable = g_hash_table_new_full(g_str_hash,
8173 g_str_equal, (GDestroyNotify) g_free, NULL);
8175 compose->replyto = NULL;
8177 compose->bcc = NULL;
8178 compose->followup_to = NULL;
8180 compose->ml_post = NULL;
8182 compose->inreplyto = NULL;
8183 compose->references = NULL;
8184 compose->msgid = NULL;
8185 compose->boundary = NULL;
8187 compose->autowrap = prefs_common.autowrap;
8188 compose->autoindent = prefs_common.auto_indent;
8189 compose->use_signing = FALSE;
8190 compose->use_encryption = FALSE;
8191 compose->privacy_system = NULL;
8192 compose->encdata = NULL;
8194 compose->modified = FALSE;
8196 compose->return_receipt = FALSE;
8198 compose->to_list = NULL;
8199 compose->newsgroup_list = NULL;
8201 compose->undostruct = undostruct;
8203 compose->sig_str = NULL;
8205 compose->exteditor_file = NULL;
8206 compose->exteditor_pid = -1;
8207 compose->exteditor_tag = -1;
8208 compose->exteditor_socket = NULL;
8209 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN; /* inhibit auto-drafting while loading */
8211 compose->folder_update_callback_id =
8212 hooks_register_hook(FOLDER_UPDATE_HOOKLIST,
8213 compose_update_folder_hook,
8214 (gpointer) compose);
8217 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
8218 if (mode != COMPOSE_REDIRECT) {
8219 if (prefs_common.enable_aspell && prefs_common.dictionary &&
8220 strcmp(prefs_common.dictionary, "")) {
8221 gtkaspell = gtkaspell_new(prefs_common.dictionary,
8222 prefs_common.alt_dictionary,
8223 conv_get_locale_charset_str(),
8224 prefs_common.color[COL_MISSPELLED],
8225 prefs_common.check_while_typing,
8226 prefs_common.recheck_when_changing_dict,
8227 prefs_common.use_alternate,
8228 prefs_common.use_both_dicts,
8229 GTK_TEXT_VIEW(text),
8230 GTK_WINDOW(compose->window),
8231 compose_dict_changed,
8232 compose_spell_menu_changed,
8235 alertpanel_error(_("Spell checker could not "
8237 gtkaspell_checkers_strerror());
8238 gtkaspell_checkers_reset_error();
8240 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", TRUE);
8244 compose->gtkaspell = gtkaspell;
8245 compose_spell_menu_changed(compose);
8246 claws_spell_entry_set_gtkaspell(CLAWS_SPELL_ENTRY(subject_entry), gtkaspell);
8249 compose_select_account(compose, account, TRUE);
8251 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", prefs_common.autowrap);
8252 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", prefs_common.auto_indent);
8254 if (account->set_autocc && account->auto_cc && mode != COMPOSE_REEDIT)
8255 compose_entry_append(compose, account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8257 if (account->set_autobcc && account->auto_bcc && mode != COMPOSE_REEDIT)
8258 compose_entry_append(compose, account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8260 if (account->set_autoreplyto && account->auto_replyto && mode != COMPOSE_REEDIT)
8261 compose_entry_append(compose, account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8263 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", compose->mode == COMPOSE_REPLY);
8264 if (account->protocol != A_NNTP)
8265 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
8266 prefs_common_translated_header_name("To:"));
8268 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
8269 prefs_common_translated_header_name("Newsgroups:"));
8271 #ifndef USE_ALT_ADDRBOOK
8272 addressbook_set_target_compose(compose);
8274 if (mode != COMPOSE_REDIRECT)
8275 compose_set_template_menu(compose);
8277 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Template", FALSE);
8280 compose_list = g_list_append(compose_list, compose);
8282 if (!prefs_common.show_ruler)
8283 gtk_widget_hide(ruler_hbox);
8285 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Tools/ShowRuler", prefs_common.show_ruler);
8288 compose->priority = PRIORITY_NORMAL;
8289 compose_update_priority_menu_item(compose);
8291 compose_set_out_encoding(compose);
8294 compose_update_actions_menu(compose);
8296 /* Privacy Systems menu */
8297 compose_update_privacy_systems_menu(compose);
8299 activate_privacy_system(compose, account, TRUE);
8300 toolbar_set_style(compose->toolbar->toolbar, compose->handlebox, prefs_common.toolbar_style);
8302 gtk_widget_realize(window);
8304 gtk_widget_show(window);
8310 static GtkWidget *compose_account_option_menu_create(Compose *compose)
8315 GtkWidget *optmenubox;
8316 GtkWidget *fromlabel;
8319 GtkWidget *from_name = NULL;
8321 gint num = 0, def_menu = 0;
8323 accounts = account_get_list();
8324 cm_return_val_if_fail(accounts != NULL, NULL);
8326 optmenubox = gtk_event_box_new();
8327 optmenu = gtkut_sc_combobox_create(optmenubox, FALSE);
8328 menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
8330 hbox = gtk_hbox_new(FALSE, 4);
8331 from_name = gtk_entry_new();
8333 g_signal_connect_after(G_OBJECT(from_name), "grab_focus",
8334 G_CALLBACK(compose_grab_focus_cb), compose);
8335 g_signal_connect_after(G_OBJECT(from_name), "activate",
8336 G_CALLBACK(from_name_activate_cb), optmenu);
8338 for (; accounts != NULL; accounts = accounts->next, num++) {
8339 PrefsAccount *ac = (PrefsAccount *)accounts->data;
8340 gchar *name, *from = NULL;
8342 if (ac == compose->account) def_menu = num;
8344 name = g_markup_printf_escaped("<i>%s</i>",
8347 if (ac == compose->account) {
8348 if (ac->name && *ac->name) {
8350 QUOTE_IF_REQUIRED_NORMAL(buf, ac->name, return NULL);
8351 from = g_strdup_printf("%s <%s>",
8353 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8355 from = g_strdup_printf("%s",
8357 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8359 if (cur_account != compose->account) {
8360 gtk_widget_modify_base(
8361 GTK_WIDGET(from_name),
8362 GTK_STATE_NORMAL, &default_header_bgcolor);
8363 gtk_widget_modify_text(
8364 GTK_WIDGET(from_name),
8365 GTK_STATE_NORMAL, &default_header_color);
8368 COMBOBOX_ADD(menu, name, ac->account_id);
8373 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), def_menu);
8375 g_signal_connect(G_OBJECT(optmenu), "changed",
8376 G_CALLBACK(account_activated),
8378 g_signal_connect(G_OBJECT(from_name), "populate-popup",
8379 G_CALLBACK(compose_entry_popup_extend),
8382 fromlabel = gtk_label_new_with_mnemonic(_("_From:"));
8383 gtk_label_set_mnemonic_widget(GTK_LABEL(fromlabel), from_name);
8385 gtk_box_pack_start(GTK_BOX(hbox), fromlabel, FALSE, FALSE, 4);
8386 gtk_box_pack_start(GTK_BOX(hbox), optmenubox, FALSE, FALSE, 0);
8387 gtk_box_pack_start(GTK_BOX(hbox), from_name, TRUE, TRUE, 0);
8389 /* Putting only the GtkEntry into focus chain of parent hbox causes
8390 * the account selector combobox next to it to be unreachable when
8391 * navigating widgets in GtkTable with up/down arrow keys.
8392 * Note: gtk_widget_set_can_focus() was not enough. */
8394 l = g_list_prepend(l, from_name);
8395 gtk_container_set_focus_chain(GTK_CONTAINER(hbox), l);
8398 CLAWS_SET_TIP(optmenubox,
8399 _("Account to use for this email"));
8400 CLAWS_SET_TIP(from_name,
8401 _("Sender address to be used"));
8403 compose->account_combo = optmenu;
8404 compose->from_name = from_name;
8409 static void compose_set_priority_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8411 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8412 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8413 Compose *compose = (Compose *) data;
8415 compose->priority = value;
8419 static void compose_reply_change_mode(Compose *compose,
8422 gboolean was_modified = compose->modified;
8424 gboolean all = FALSE, ml = FALSE, sender = FALSE, followup = FALSE;
8426 cm_return_if_fail(compose->replyinfo != NULL);
8428 if (action == COMPOSE_REPLY && prefs_common.default_reply_list)
8430 if (action == COMPOSE_REPLY && compose->rmode == COMPOSE_FOLLOWUP_AND_REPLY_TO)
8432 if (action == COMPOSE_REPLY_TO_ALL)
8434 if (action == COMPOSE_REPLY_TO_SENDER)
8436 if (action == COMPOSE_REPLY_TO_LIST)
8439 compose_remove_header_entries(compose);
8440 compose_reply_set_entry(compose, compose->replyinfo, all, ml, sender, followup);
8441 if (compose->account->set_autocc && compose->account->auto_cc)
8442 compose_entry_append(compose, compose->account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8444 if (compose->account->set_autobcc && compose->account->auto_bcc)
8445 compose_entry_append(compose, compose->account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8447 if (compose->account->set_autoreplyto && compose->account->auto_replyto)
8448 compose_entry_append(compose, compose->account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8449 compose_show_first_last_header(compose, TRUE);
8450 compose->modified = was_modified;
8451 compose_set_title(compose);
8454 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8456 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8457 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8458 Compose *compose = (Compose *) data;
8461 compose_reply_change_mode(compose, value);
8464 static void compose_update_priority_menu_item(Compose * compose)
8466 GtkWidget *menuitem = NULL;
8467 switch (compose->priority) {
8468 case PRIORITY_HIGHEST:
8469 menuitem = gtk_ui_manager_get_widget
8470 (compose->ui_manager, "/Menu/Options/Priority/Highest");
8473 menuitem = gtk_ui_manager_get_widget
8474 (compose->ui_manager, "/Menu/Options/Priority/High");
8476 case PRIORITY_NORMAL:
8477 menuitem = gtk_ui_manager_get_widget
8478 (compose->ui_manager, "/Menu/Options/Priority/Normal");
8481 menuitem = gtk_ui_manager_get_widget
8482 (compose->ui_manager, "/Menu/Options/Priority/Low");
8484 case PRIORITY_LOWEST:
8485 menuitem = gtk_ui_manager_get_widget
8486 (compose->ui_manager, "/Menu/Options/Priority/Lowest");
8489 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8492 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data)
8494 Compose *compose = (Compose *) data;
8496 gboolean can_sign = FALSE, can_encrypt = FALSE;
8498 cm_return_if_fail(GTK_IS_CHECK_MENU_ITEM(widget));
8500 if (!gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget)))
8503 systemid = g_object_get_data(G_OBJECT(widget), "privacy_system");
8504 g_free(compose->privacy_system);
8505 compose->privacy_system = NULL;
8506 g_free(compose->encdata);
8507 compose->encdata = NULL;
8508 if (systemid != NULL) {
8509 compose->privacy_system = g_strdup(systemid);
8511 can_sign = privacy_system_can_sign(systemid);
8512 can_encrypt = privacy_system_can_encrypt(systemid);
8515 debug_print("activated privacy system: %s\n", systemid != NULL ? systemid : "None");
8517 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8518 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8519 if (compose->toolbar->privacy_sign_btn != NULL) {
8520 gtk_widget_set_sensitive(
8521 GTK_WIDGET(compose->toolbar->privacy_sign_btn),
8523 gtk_toggle_tool_button_set_active(
8524 GTK_TOGGLE_TOOL_BUTTON(compose->toolbar->privacy_sign_btn),
8525 can_sign ? compose->use_signing : FALSE);
8527 if (compose->toolbar->privacy_encrypt_btn != NULL) {
8528 gtk_widget_set_sensitive(
8529 GTK_WIDGET(compose->toolbar->privacy_encrypt_btn),
8531 gtk_toggle_tool_button_set_active(
8532 GTK_TOGGLE_TOOL_BUTTON(compose->toolbar->privacy_encrypt_btn),
8533 can_encrypt ? compose->use_encryption : FALSE);
8537 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn)
8539 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8540 GtkWidget *menuitem = NULL;
8541 GList *children, *amenu;
8542 gboolean can_sign = FALSE, can_encrypt = FALSE;
8543 gboolean found = FALSE;
8545 if (compose->privacy_system != NULL) {
8547 menuitem = gtk_menu_item_get_submenu(GTK_MENU_ITEM(
8548 gtk_ui_manager_get_widget(compose->ui_manager, branch_path)));
8549 cm_return_if_fail(menuitem != NULL);
8551 children = gtk_container_get_children(GTK_CONTAINER(GTK_MENU_SHELL(menuitem)));
8554 while (amenu != NULL) {
8555 systemid = g_object_get_data(G_OBJECT(amenu->data), "privacy_system");
8556 if (systemid != NULL) {
8557 if (strcmp(systemid, compose->privacy_system) == 0 &&
8558 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8559 menuitem = GTK_WIDGET(amenu->data);
8561 can_sign = privacy_system_can_sign(systemid);
8562 can_encrypt = privacy_system_can_encrypt(systemid);
8566 } else if (strlen(compose->privacy_system) == 0 &&
8567 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8568 menuitem = GTK_WIDGET(amenu->data);
8571 can_encrypt = FALSE;
8576 amenu = amenu->next;
8578 g_list_free(children);
8579 if (menuitem != NULL)
8580 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8582 if (warn && !found && strlen(compose->privacy_system)) {
8583 alertpanel_warning(_("The privacy system '%s' cannot be loaded. You "
8584 "will not be able to sign or encrypt this message."),
8585 compose->privacy_system);
8589 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8590 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8591 if (compose->toolbar->privacy_sign_btn != NULL) {
8592 gtk_widget_set_sensitive(
8593 GTK_WIDGET(compose->toolbar->privacy_sign_btn),
8596 if (compose->toolbar->privacy_encrypt_btn != NULL) {
8597 gtk_widget_set_sensitive(
8598 GTK_WIDGET(compose->toolbar->privacy_encrypt_btn),
8603 static void compose_set_out_encoding(Compose *compose)
8605 CharSet out_encoding;
8606 const gchar *branch = NULL;
8607 out_encoding = conv_get_charset_from_str(prefs_common.outgoing_charset);
8609 switch(out_encoding) {
8610 case C_AUTO: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8611 case C_US_ASCII: branch = "Menu/Options/Encoding/" CS_US_ASCII; break;
8612 case C_UTF_8: branch = "Menu/Options/Encoding/" CS_UTF_8; break;
8613 case C_ISO_8859_2: branch = "Menu/Options/Encoding/" CS_ISO_8859_2; break;
8614 case C_ISO_8859_7: branch = "Menu/Options/Encoding/" CS_ISO_8859_7; break;
8615 case C_ISO_8859_9: branch = "Menu/Options/Encoding/" CS_ISO_8859_9; break;
8616 case C_ISO_8859_1: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_1; break;
8617 case C_ISO_8859_15: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_15; break;
8618 case C_WINDOWS_1252: branch = "Menu/Options/Encoding/Western/" CS_WINDOWS_1252; break;
8619 case C_ISO_8859_13: branch = "Menu/Options/Encoding/Baltic/" CS_ISO_8859_13; break;
8620 case C_ISO_8859_4: branch = "Menu/Options/Encoding/Baltic" CS_ISO_8859_4; break;
8621 case C_ISO_8859_8: branch = "Menu/Options/Encoding/Hebrew/" CS_ISO_8859_8; break;
8622 case C_WINDOWS_1255: branch = "Menu/Options/Encoding/Hebrew/" CS_WINDOWS_1255; break;
8623 case C_ISO_8859_6: branch = "Menu/Options/Encoding/Arabic/" CS_ISO_8859_6; break;
8624 case C_WINDOWS_1256: branch = "Menu/Options/Encoding/Arabic/" CS_WINDOWS_1256; break;
8625 case C_ISO_8859_5: branch = "Menu/Options/Encoding/Cyrillic/" CS_ISO_8859_5; break;
8626 case C_KOI8_R: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_R; break;
8627 case C_MACCYR: branch = "Menu/Options/Encoding/Cyrillic/" CS_MACCYR; break;
8628 case C_KOI8_U: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_U; break;
8629 case C_WINDOWS_1251: branch = "Menu/Options/Encoding/Cyrillic/" CS_WINDOWS_1251; break;
8630 case C_ISO_2022_JP: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP; break;
8631 case C_ISO_2022_JP_2: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP_2; break;
8632 case C_EUC_JP: branch = "Menu/Options/Encoding/Japanese/" CS_EUC_JP; break;
8633 case C_SHIFT_JIS: branch = "Menu/Options/Encoding/Japanese/" CS_SHIFT_JIS; break;
8634 case C_GB18030: branch = "Menu/Options/Encoding/Chinese/" CS_GB18030; break;
8635 case C_GB2312: branch = "Menu/Options/Encoding/Chinese/" CS_GB2312; break;
8636 case C_GBK: branch = "Menu/Options/Encoding/Chinese/" CS_GBK; break;
8637 case C_BIG5: branch = "Menu/Options/Encoding/Chinese/" CS_BIG5; break;
8638 case C_EUC_TW: branch = "Menu/Options/Encoding/Chinese/" CS_EUC_TW; break;
8639 case C_EUC_KR: branch = "Menu/Options/Encoding/Korean/" CS_EUC_KR; break;
8640 case C_ISO_2022_KR: branch = "Menu/Options/Encoding/Korean/" CS_ISO_2022_KR; break;
8641 case C_TIS_620: branch = "Menu/Options/Encoding/Thai/" CS_TIS_620; break;
8642 case C_WINDOWS_874: branch = "Menu/Options/Encoding/Thai/" CS_WINDOWS_874; break;
8643 default: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8645 cm_toggle_menu_set_active_full(compose->ui_manager, (gchar *)branch, TRUE);
8648 static void compose_set_template_menu(Compose *compose)
8650 GSList *tmpl_list, *cur;
8654 tmpl_list = template_get_config();
8656 menu = gtk_menu_new();
8658 gtk_menu_set_accel_group (GTK_MENU (menu),
8659 gtk_ui_manager_get_accel_group(compose->ui_manager));
8660 for (cur = tmpl_list; cur != NULL; cur = cur->next) {
8661 Template *tmpl = (Template *)cur->data;
8662 gchar *accel_path = NULL;
8663 item = gtk_menu_item_new_with_label(tmpl->name);
8664 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
8665 g_signal_connect(G_OBJECT(item), "activate",
8666 G_CALLBACK(compose_template_activate_cb),
8668 g_object_set_data(G_OBJECT(item), "template", tmpl);
8669 gtk_widget_show(item);
8670 accel_path = g_strconcat("<ComposeTemplates>" , "/", tmpl->name, NULL);
8671 gtk_menu_item_set_accel_path(GTK_MENU_ITEM(item), accel_path);
8675 gtk_widget_show(menu);
8676 gtk_menu_item_set_submenu(GTK_MENU_ITEM(compose->tmpl_menu), menu);
8679 void compose_update_actions_menu(Compose *compose)
8681 action_update_compose_menu(compose->ui_manager, "/Menu/Tools/Actions", compose);
8684 static void compose_update_privacy_systems_menu(Compose *compose)
8686 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8687 GSList *systems, *cur;
8689 GtkWidget *system_none;
8691 GtkWidget *privacy_menuitem = gtk_ui_manager_get_widget(compose->ui_manager, branch_path);
8692 GtkWidget *privacy_menu = gtk_menu_new();
8694 system_none = gtk_radio_menu_item_new_with_mnemonic(NULL, _("_None"));
8695 g_object_set_data_full(G_OBJECT(system_none), "privacy_system", NULL, NULL);
8697 g_signal_connect(G_OBJECT(system_none), "activate",
8698 G_CALLBACK(compose_set_privacy_system_cb), compose);
8700 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), system_none);
8701 gtk_widget_show(system_none);
8703 systems = privacy_get_system_ids();
8704 for (cur = systems; cur != NULL; cur = g_slist_next(cur)) {
8705 gchar *systemid = cur->data;
8707 group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(system_none));
8708 widget = gtk_radio_menu_item_new_with_label(group,
8709 privacy_system_get_name(systemid));
8710 g_object_set_data_full(G_OBJECT(widget), "privacy_system",
8711 g_strdup(systemid), g_free);
8712 g_signal_connect(G_OBJECT(widget), "activate",
8713 G_CALLBACK(compose_set_privacy_system_cb), compose);
8715 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), widget);
8716 gtk_widget_show(widget);
8719 g_slist_free(systems);
8720 gtk_menu_item_set_submenu(GTK_MENU_ITEM(privacy_menuitem), privacy_menu);
8721 gtk_widget_show_all(privacy_menu);
8722 gtk_widget_show_all(privacy_menuitem);
8725 void compose_reflect_prefs_all(void)
8730 for (cur = compose_list; cur != NULL; cur = cur->next) {
8731 compose = (Compose *)cur->data;
8732 compose_set_template_menu(compose);
8736 void compose_reflect_prefs_pixmap_theme(void)
8741 for (cur = compose_list; cur != NULL; cur = cur->next) {
8742 compose = (Compose *)cur->data;
8743 toolbar_update(TOOLBAR_COMPOSE, compose);
8747 static const gchar *compose_quote_char_from_context(Compose *compose)
8749 const gchar *qmark = NULL;
8751 cm_return_val_if_fail(compose != NULL, NULL);
8753 switch (compose->mode) {
8754 /* use forward-specific quote char */
8755 case COMPOSE_FORWARD:
8756 case COMPOSE_FORWARD_AS_ATTACH:
8757 case COMPOSE_FORWARD_INLINE:
8758 if (compose->folder && compose->folder->prefs &&
8759 compose->folder->prefs->forward_with_format)
8760 qmark = compose->folder->prefs->forward_quotemark;
8761 else if (compose->account->forward_with_format)
8762 qmark = compose->account->forward_quotemark;
8764 qmark = prefs_common.fw_quotemark;
8767 /* use reply-specific quote char in all other modes */
8769 if (compose->folder && compose->folder->prefs &&
8770 compose->folder->prefs->reply_with_format)
8771 qmark = compose->folder->prefs->reply_quotemark;
8772 else if (compose->account->reply_with_format)
8773 qmark = compose->account->reply_quotemark;
8775 qmark = prefs_common.quotemark;
8779 if (qmark == NULL || *qmark == '\0')
8785 static void compose_template_apply(Compose *compose, Template *tmpl,
8789 GtkTextBuffer *buffer;
8793 gchar *parsed_str = NULL;
8794 gint cursor_pos = 0;
8795 const gchar *err_msg = _("The body of the template has an error at line %d.");
8798 /* process the body */
8800 text = GTK_TEXT_VIEW(compose->text);
8801 buffer = gtk_text_view_get_buffer(text);
8804 qmark = compose_quote_char_from_context(compose);
8806 if (compose->replyinfo != NULL) {
8809 gtk_text_buffer_set_text(buffer, "", -1);
8810 mark = gtk_text_buffer_get_insert(buffer);
8811 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8813 parsed_str = compose_quote_fmt(compose, compose->replyinfo,
8814 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8816 } else if (compose->fwdinfo != NULL) {
8819 gtk_text_buffer_set_text(buffer, "", -1);
8820 mark = gtk_text_buffer_get_insert(buffer);
8821 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8823 parsed_str = compose_quote_fmt(compose, compose->fwdinfo,
8824 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8827 MsgInfo* dummyinfo = compose_msginfo_new_from_compose(compose);
8829 GtkTextIter start, end;
8832 gtk_text_buffer_get_start_iter(buffer, &start);
8833 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
8834 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
8836 /* clear the buffer now */
8838 gtk_text_buffer_set_text(buffer, "", -1);
8840 parsed_str = compose_quote_fmt(compose, dummyinfo,
8841 tmpl->value, qmark, tmp, FALSE, FALSE, err_msg);
8842 procmsg_msginfo_free( &dummyinfo );
8848 gtk_text_buffer_set_text(buffer, "", -1);
8849 mark = gtk_text_buffer_get_insert(buffer);
8850 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8853 if (replace && parsed_str && compose->account->auto_sig)
8854 compose_insert_sig(compose, FALSE);
8856 if (replace && parsed_str) {
8857 gtk_text_buffer_get_start_iter(buffer, &iter);
8858 gtk_text_buffer_place_cursor(buffer, &iter);
8862 cursor_pos = quote_fmt_get_cursor_pos();
8863 compose->set_cursor_pos = cursor_pos;
8864 if (cursor_pos == -1)
8866 gtk_text_buffer_get_start_iter(buffer, &iter);
8867 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
8868 gtk_text_buffer_place_cursor(buffer, &iter);
8871 /* process the other fields */
8873 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
8874 compose_template_apply_fields(compose, tmpl);
8875 quote_fmt_reset_vartable();
8876 quote_fmtlex_destroy();
8878 compose_changed_cb(NULL, compose);
8881 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
8882 gtkaspell_highlight_all(compose->gtkaspell);
8886 static void compose_template_apply_fields_error(const gchar *header)
8891 tr = g_strdup(C_("'%s' stands for a header name",
8892 "Template '%s' format error."));
8893 text = g_strdup_printf(tr, prefs_common_translated_header_name(header));
8894 alertpanel_error("%s", text);
8900 static void compose_template_apply_fields(Compose *compose, Template *tmpl)
8902 MsgInfo* dummyinfo = NULL;
8903 MsgInfo *msginfo = NULL;
8906 if (compose->replyinfo != NULL)
8907 msginfo = compose->replyinfo;
8908 else if (compose->fwdinfo != NULL)
8909 msginfo = compose->fwdinfo;
8911 dummyinfo = compose_msginfo_new_from_compose(compose);
8912 msginfo = dummyinfo;
8915 if (tmpl->from && *tmpl->from != '\0') {
8917 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8918 compose->gtkaspell);
8920 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8922 quote_fmt_scan_string(tmpl->from);
8925 buf = quote_fmt_get_buffer();
8927 compose_template_apply_fields_error("From");
8929 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
8932 quote_fmt_reset_vartable();
8933 quote_fmtlex_destroy();
8936 if (tmpl->to && *tmpl->to != '\0') {
8938 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8939 compose->gtkaspell);
8941 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8943 quote_fmt_scan_string(tmpl->to);
8946 buf = quote_fmt_get_buffer();
8948 compose_template_apply_fields_error("To");
8950 compose_entry_append(compose, buf, COMPOSE_TO, PREF_TEMPLATE);
8953 quote_fmt_reset_vartable();
8954 quote_fmtlex_destroy();
8957 if (tmpl->cc && *tmpl->cc != '\0') {
8959 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8960 compose->gtkaspell);
8962 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8964 quote_fmt_scan_string(tmpl->cc);
8967 buf = quote_fmt_get_buffer();
8969 compose_template_apply_fields_error("Cc");
8971 compose_entry_append(compose, buf, COMPOSE_CC, PREF_TEMPLATE);
8974 quote_fmt_reset_vartable();
8975 quote_fmtlex_destroy();
8978 if (tmpl->bcc && *tmpl->bcc != '\0') {
8980 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8981 compose->gtkaspell);
8983 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8985 quote_fmt_scan_string(tmpl->bcc);
8988 buf = quote_fmt_get_buffer();
8990 compose_template_apply_fields_error("Bcc");
8992 compose_entry_append(compose, buf, COMPOSE_BCC, PREF_TEMPLATE);
8995 quote_fmt_reset_vartable();
8996 quote_fmtlex_destroy();
8999 if (tmpl->replyto && *tmpl->replyto != '\0') {
9001 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
9002 compose->gtkaspell);
9004 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
9006 quote_fmt_scan_string(tmpl->replyto);
9009 buf = quote_fmt_get_buffer();
9011 compose_template_apply_fields_error("Reply-To");
9013 compose_entry_append(compose, buf, COMPOSE_REPLYTO, PREF_TEMPLATE);
9016 quote_fmt_reset_vartable();
9017 quote_fmtlex_destroy();
9020 /* process the subject */
9021 if (tmpl->subject && *tmpl->subject != '\0') {
9023 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
9024 compose->gtkaspell);
9026 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
9028 quote_fmt_scan_string(tmpl->subject);
9031 buf = quote_fmt_get_buffer();
9033 compose_template_apply_fields_error("Subject");
9035 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
9038 quote_fmt_reset_vartable();
9039 quote_fmtlex_destroy();
9042 procmsg_msginfo_free( &dummyinfo );
9045 static void compose_destroy(Compose *compose)
9047 GtkAllocation allocation;
9048 GtkTextBuffer *buffer;
9049 GtkClipboard *clipboard;
9051 compose_list = g_list_remove(compose_list, compose);
9054 gboolean enable = TRUE;
9055 g_slist_foreach(compose->passworded_ldap_servers,
9056 _ldap_srv_func, &enable);
9057 g_slist_free(compose->passworded_ldap_servers);
9060 if (compose->updating) {
9061 debug_print("danger, not destroying anything now\n");
9062 compose->deferred_destroy = TRUE;
9066 /* NOTE: address_completion_end() does nothing with the window
9067 * however this may change. */
9068 address_completion_end(compose->window);
9070 slist_free_strings_full(compose->to_list);
9071 slist_free_strings_full(compose->newsgroup_list);
9072 slist_free_strings_full(compose->header_list);
9074 slist_free_strings_full(extra_headers);
9075 extra_headers = NULL;
9077 compose->header_list = compose->newsgroup_list = compose->to_list = NULL;
9079 g_hash_table_destroy(compose->email_hashtable);
9081 hooks_unregister_hook(FOLDER_UPDATE_HOOKLIST,
9082 compose->folder_update_callback_id);
9084 procmsg_msginfo_free(&(compose->targetinfo));
9085 procmsg_msginfo_free(&(compose->replyinfo));
9086 procmsg_msginfo_free(&(compose->fwdinfo));
9088 g_free(compose->replyto);
9089 g_free(compose->cc);
9090 g_free(compose->bcc);
9091 g_free(compose->newsgroups);
9092 g_free(compose->followup_to);
9094 g_free(compose->ml_post);
9096 g_free(compose->inreplyto);
9097 g_free(compose->references);
9098 g_free(compose->msgid);
9099 g_free(compose->boundary);
9101 g_free(compose->redirect_filename);
9102 if (compose->undostruct)
9103 undo_destroy(compose->undostruct);
9105 g_free(compose->sig_str);
9107 g_free(compose->exteditor_file);
9109 g_free(compose->orig_charset);
9111 g_free(compose->privacy_system);
9112 g_free(compose->encdata);
9114 #ifndef USE_ALT_ADDRBOOK
9115 if (addressbook_get_target_compose() == compose)
9116 addressbook_set_target_compose(NULL);
9119 if (compose->gtkaspell) {
9120 gtkaspell_delete(compose->gtkaspell);
9121 compose->gtkaspell = NULL;
9125 if (!compose->batch) {
9126 gtk_widget_get_allocation(compose->window, &allocation);
9127 prefs_common.compose_width = allocation.width;
9128 prefs_common.compose_height = allocation.height;
9131 if (!gtk_widget_get_parent(compose->paned))
9132 gtk_widget_destroy(compose->paned);
9133 gtk_widget_destroy(compose->popupmenu);
9135 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
9136 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
9137 gtk_text_buffer_remove_selection_clipboard(buffer, clipboard);
9139 gtk_widget_destroy(compose->window);
9140 toolbar_destroy(compose->toolbar);
9141 g_free(compose->toolbar);
9142 cm_mutex_free(compose->mutex);
9146 static void compose_attach_info_free(AttachInfo *ainfo)
9148 g_free(ainfo->file);
9149 g_free(ainfo->content_type);
9150 g_free(ainfo->name);
9151 g_free(ainfo->charset);
9155 static void compose_attach_update_label(Compose *compose)
9160 GtkTreeModel *model;
9164 if (compose == NULL)
9167 model = gtk_tree_view_get_model(GTK_TREE_VIEW(compose->attach_clist));
9168 if (!gtk_tree_model_get_iter_first(model, &iter)) {
9169 gtk_label_set_text(GTK_LABEL(compose->attach_label), "");
9173 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
9174 total_size = ainfo->size;
9175 while(gtk_tree_model_iter_next(model, &iter)) {
9176 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
9177 total_size += ainfo->size;
9180 text = g_strdup_printf(" (%d/%s)", i, to_human_readable(total_size));
9181 gtk_label_set_text(GTK_LABEL(compose->attach_label), text);
9185 static void compose_attach_remove_selected(GtkAction *action, gpointer data)
9187 Compose *compose = (Compose *)data;
9188 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
9189 GtkTreeSelection *selection;
9191 GtkTreeModel *model;
9193 selection = gtk_tree_view_get_selection(tree_view);
9194 sel = gtk_tree_selection_get_selected_rows(selection, &model);
9199 for (cur = sel; cur != NULL; cur = cur->next) {
9200 GtkTreePath *path = cur->data;
9201 GtkTreeRowReference *ref = gtk_tree_row_reference_new
9204 gtk_tree_path_free(path);
9207 for (cur = sel; cur != NULL; cur = cur->next) {
9208 GtkTreeRowReference *ref = cur->data;
9209 GtkTreePath *path = gtk_tree_row_reference_get_path(ref);
9212 if (gtk_tree_model_get_iter(model, &iter, path))
9213 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
9215 gtk_tree_path_free(path);
9216 gtk_tree_row_reference_free(ref);
9220 compose_attach_update_label(compose);
9223 static struct _AttachProperty
9226 GtkWidget *mimetype_entry;
9227 GtkWidget *encoding_optmenu;
9228 GtkWidget *path_entry;
9229 GtkWidget *filename_entry;
9231 GtkWidget *cancel_btn;
9234 static void gtk_tree_path_free_(gpointer ptr, gpointer data)
9236 gtk_tree_path_free((GtkTreePath *)ptr);
9239 static void compose_attach_property(GtkAction *action, gpointer data)
9241 Compose *compose = (Compose *)data;
9242 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
9244 GtkComboBox *optmenu;
9245 GtkTreeSelection *selection;
9247 GtkTreeModel *model;
9250 static gboolean cancelled;
9252 /* only if one selected */
9253 selection = gtk_tree_view_get_selection(tree_view);
9254 if (gtk_tree_selection_count_selected_rows(selection) != 1)
9257 sel = gtk_tree_selection_get_selected_rows(selection, &model);
9261 path = (GtkTreePath *) sel->data;
9262 gtk_tree_model_get_iter(model, &iter, path);
9263 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
9266 g_list_foreach(sel, gtk_tree_path_free_, NULL);
9272 if (!attach_prop.window)
9273 compose_attach_property_create(&cancelled);
9274 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), TRUE);
9275 gtk_widget_grab_focus(attach_prop.ok_btn);
9276 gtk_widget_show(attach_prop.window);
9277 gtk_window_set_transient_for(GTK_WINDOW(attach_prop.window),
9278 GTK_WINDOW(compose->window));
9280 optmenu = GTK_COMBO_BOX(attach_prop.encoding_optmenu);
9281 if (ainfo->encoding == ENC_UNKNOWN)
9282 combobox_select_by_data(optmenu, ENC_BASE64);
9284 combobox_select_by_data(optmenu, ainfo->encoding);
9286 gtk_entry_set_text(GTK_ENTRY(attach_prop.mimetype_entry),
9287 ainfo->content_type ? ainfo->content_type : "");
9288 gtk_entry_set_text(GTK_ENTRY(attach_prop.path_entry),
9289 ainfo->file ? ainfo->file : "");
9290 gtk_entry_set_text(GTK_ENTRY(attach_prop.filename_entry),
9291 ainfo->name ? ainfo->name : "");
9294 const gchar *entry_text;
9296 gchar *cnttype = NULL;
9303 gtk_widget_hide(attach_prop.window);
9304 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), FALSE);
9309 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.mimetype_entry));
9310 if (*entry_text != '\0') {
9313 text = g_strstrip(g_strdup(entry_text));
9314 if ((p = strchr(text, '/')) && !strchr(p + 1, '/')) {
9315 cnttype = g_strdup(text);
9318 alertpanel_error(_("Invalid MIME type."));
9324 ainfo->encoding = combobox_get_active_data(optmenu);
9326 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.path_entry));
9327 if (*entry_text != '\0') {
9328 if (is_file_exist(entry_text) &&
9329 (size = get_file_size(entry_text)) > 0)
9330 file = g_strdup(entry_text);
9333 (_("File doesn't exist or is empty."));
9339 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.filename_entry));
9340 if (*entry_text != '\0') {
9341 g_free(ainfo->name);
9342 ainfo->name = g_strdup(entry_text);
9346 g_free(ainfo->content_type);
9347 ainfo->content_type = cnttype;
9350 g_free(ainfo->file);
9354 ainfo->size = (goffset)size;
9356 /* update tree store */
9357 text = to_human_readable(ainfo->size);
9358 gtk_tree_model_get_iter(model, &iter, path);
9359 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
9360 COL_MIMETYPE, ainfo->content_type,
9362 COL_NAME, ainfo->name,
9363 COL_CHARSET, ainfo->charset,
9369 gtk_tree_path_free(path);
9372 #define SET_LABEL_AND_ENTRY(str, entry, top) \
9374 label = gtk_label_new(str); \
9375 gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), \
9376 GTK_FILL, 0, 0, 0); \
9377 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); \
9379 entry = gtk_entry_new(); \
9380 gtk_table_attach(GTK_TABLE(table), entry, 1, 2, top, (top + 1), \
9381 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); \
9384 static void compose_attach_property_create(gboolean *cancelled)
9390 GtkWidget *mimetype_entry;
9393 GtkListStore *optmenu_menu;
9394 GtkWidget *path_entry;
9395 GtkWidget *filename_entry;
9398 GtkWidget *cancel_btn;
9399 GList *mime_type_list, *strlist;
9402 debug_print("Creating attach_property window...\n");
9404 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose_attach_property");
9405 gtk_widget_set_size_request(window, 480, -1);
9406 gtk_container_set_border_width(GTK_CONTAINER(window), 8);
9407 gtk_window_set_title(GTK_WINDOW(window), _("Properties"));
9408 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
9409 g_signal_connect(G_OBJECT(window), "delete_event",
9410 G_CALLBACK(attach_property_delete_event),
9412 g_signal_connect(G_OBJECT(window), "key_press_event",
9413 G_CALLBACK(attach_property_key_pressed),
9416 vbox = gtk_vbox_new(FALSE, 8);
9417 gtk_container_add(GTK_CONTAINER(window), vbox);
9419 table = gtk_table_new(4, 2, FALSE);
9420 gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
9421 gtk_table_set_row_spacings(GTK_TABLE(table), 8);
9422 gtk_table_set_col_spacings(GTK_TABLE(table), 8);
9424 label = gtk_label_new(_("MIME type"));
9425 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, (0 + 1),
9427 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
9428 mimetype_entry = gtk_combo_box_text_new_with_entry();
9429 gtk_table_attach(GTK_TABLE(table), mimetype_entry, 1, 2, 0, (0 + 1),
9430 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
9432 /* stuff with list */
9433 mime_type_list = procmime_get_mime_type_list();
9435 for (; mime_type_list != NULL; mime_type_list = mime_type_list->next) {
9436 MimeType *type = (MimeType *) mime_type_list->data;
9439 tmp = g_strdup_printf("%s/%s", type->type, type->sub_type);
9441 if (g_list_find_custom(strlist, tmp, (GCompareFunc)strcmp2))
9444 strlist = g_list_insert_sorted(strlist, (gpointer)tmp,
9445 (GCompareFunc)strcmp2);
9448 for (mime_type_list = strlist; mime_type_list != NULL;
9449 mime_type_list = mime_type_list->next) {
9450 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(mimetype_entry), mime_type_list->data);
9451 g_free(mime_type_list->data);
9453 g_list_free(strlist);
9454 gtk_combo_box_set_active(GTK_COMBO_BOX(mimetype_entry), 0);
9455 mimetype_entry = gtk_bin_get_child(GTK_BIN((mimetype_entry)));
9457 label = gtk_label_new(_("Encoding"));
9458 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2,
9460 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
9462 hbox = gtk_hbox_new(FALSE, 0);
9463 gtk_table_attach(GTK_TABLE(table), hbox, 1, 2, 1, 2,
9464 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
9466 optmenu = gtkut_sc_combobox_create(NULL, TRUE);
9467 optmenu_menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
9469 COMBOBOX_ADD(optmenu_menu, "7bit", ENC_7BIT);
9470 COMBOBOX_ADD(optmenu_menu, "8bit", ENC_8BIT);
9471 COMBOBOX_ADD(optmenu_menu, "quoted-printable", ENC_QUOTED_PRINTABLE);
9472 COMBOBOX_ADD(optmenu_menu, "base64", ENC_BASE64);
9473 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), 0);
9475 gtk_box_pack_start(GTK_BOX(hbox), optmenu, TRUE, TRUE, 0);
9477 SET_LABEL_AND_ENTRY(_("Path"), path_entry, 2);
9478 SET_LABEL_AND_ENTRY(_("File name"), filename_entry, 3);
9480 gtkut_stock_button_set_create(&hbbox, &cancel_btn, GTK_STOCK_CANCEL,
9481 &ok_btn, GTK_STOCK_OK,
9483 gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
9484 gtk_widget_grab_default(ok_btn);
9486 g_signal_connect(G_OBJECT(ok_btn), "clicked",
9487 G_CALLBACK(attach_property_ok),
9489 g_signal_connect(G_OBJECT(cancel_btn), "clicked",
9490 G_CALLBACK(attach_property_cancel),
9493 gtk_widget_show_all(vbox);
9495 attach_prop.window = window;
9496 attach_prop.mimetype_entry = mimetype_entry;
9497 attach_prop.encoding_optmenu = optmenu;
9498 attach_prop.path_entry = path_entry;
9499 attach_prop.filename_entry = filename_entry;
9500 attach_prop.ok_btn = ok_btn;
9501 attach_prop.cancel_btn = cancel_btn;
9504 #undef SET_LABEL_AND_ENTRY
9506 static void attach_property_ok(GtkWidget *widget, gboolean *cancelled)
9512 static void attach_property_cancel(GtkWidget *widget, gboolean *cancelled)
9518 static gint attach_property_delete_event(GtkWidget *widget, GdkEventAny *event,
9519 gboolean *cancelled)
9527 static gboolean attach_property_key_pressed(GtkWidget *widget,
9529 gboolean *cancelled)
9531 if (event && event->keyval == GDK_KEY_Escape) {
9535 if (event && event->keyval == GDK_KEY_Return) {
9543 static void compose_exec_ext_editor(Compose *compose)
9548 GdkNativeWindow socket_wid = 0;
9552 tmp = g_strdup_printf("%s%ctmpmsg.%p", get_tmp_dir(),
9553 G_DIR_SEPARATOR, compose);
9555 if (compose_get_ext_editor_uses_socket()) {
9556 /* Only allow one socket */
9557 if (compose->exteditor_socket != NULL) {
9558 if (gtk_widget_is_focus(compose->exteditor_socket)) {
9559 /* Move the focus off of the socket */
9560 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9565 /* Create the receiving GtkSocket */
9566 socket = gtk_socket_new ();
9567 g_signal_connect (GTK_OBJECT(socket), "plug-removed",
9568 G_CALLBACK(compose_ext_editor_plug_removed_cb),
9570 gtk_box_pack_start(GTK_BOX(compose->edit_vbox), socket, TRUE, TRUE, 0);
9571 gtk_widget_set_size_request(socket, prefs_common.compose_width, -1);
9572 /* Realize the socket so that we can use its ID */
9573 gtk_widget_realize(socket);
9574 socket_wid = gtk_socket_get_id(GTK_SOCKET (socket));
9575 compose->exteditor_socket = socket;
9578 if (pipe(pipe_fds) < 0) {
9584 if ((pid = fork()) < 0) {
9591 /* close the write side of the pipe */
9594 compose->exteditor_file = g_strdup(tmp);
9595 compose->exteditor_pid = pid;
9597 compose_set_ext_editor_sensitive(compose, FALSE);
9600 compose->exteditor_ch = g_io_channel_unix_new(pipe_fds[0]);
9602 compose->exteditor_ch = g_io_channel_win32_new_fd(pipe_fds[0]);
9604 compose->exteditor_tag = g_io_add_watch(compose->exteditor_ch,
9608 } else { /* process-monitoring process */
9614 /* close the read side of the pipe */
9617 if (compose_write_body_to_file(compose, tmp) < 0) {
9618 fd_write_all(pipe_fds[1], "2\n", 2);
9622 pid_ed = compose_exec_ext_editor_real(tmp, socket_wid);
9624 fd_write_all(pipe_fds[1], "1\n", 2);
9628 /* wait until editor is terminated */
9629 waitpid(pid_ed, NULL, 0);
9631 fd_write_all(pipe_fds[1], "0\n", 2);
9638 #endif /* G_OS_UNIX */
9641 static gboolean compose_can_autosave(Compose *compose)
9643 if (compose->privacy_system && compose->use_encryption)
9644 return prefs_common.autosave && prefs_common.autosave_encrypted;
9646 return prefs_common.autosave;
9650 static gboolean compose_get_ext_editor_cmd_valid()
9652 gboolean has_s = FALSE;
9653 gboolean has_w = FALSE;
9654 const gchar *p = prefs_common_get_ext_editor_cmd();
9657 while ((p = strchr(p, '%'))) {
9663 } else if (*p == 'w') {
9674 static gint compose_exec_ext_editor_real(const gchar *file, GdkNativeWindow socket_wid)
9681 cm_return_val_if_fail(file != NULL, -1);
9683 if ((pid = fork()) < 0) {
9688 if (pid != 0) return pid;
9690 /* grandchild process */
9692 if (setpgid(0, getppid()))
9695 if (compose_get_ext_editor_cmd_valid()) {
9696 if (compose_get_ext_editor_uses_socket()) {
9697 p = g_strdup(prefs_common_get_ext_editor_cmd());
9698 s = strstr(p, "%w");
9700 if (strstr(p, "%s") < s)
9701 buf = g_strdup_printf(p, file, socket_wid);
9703 buf = g_strdup_printf(p, socket_wid, file);
9706 buf = g_strdup_printf(prefs_common_get_ext_editor_cmd(), file);
9709 if (prefs_common_get_ext_editor_cmd())
9710 g_warning("External editor command-line is invalid: '%s'",
9711 prefs_common_get_ext_editor_cmd());
9712 buf = g_strdup_printf(DEFAULT_EDITOR_CMD, file);
9715 cmdline = strsplit_with_quote(buf, " ", 0);
9717 execvp(cmdline[0], cmdline);
9720 g_strfreev(cmdline);
9725 static gboolean compose_ext_editor_kill(Compose *compose)
9727 pid_t pgid = compose->exteditor_pid * -1;
9730 ret = kill(pgid, 0);
9732 if (ret == 0 || (ret == -1 && EPERM == errno)) {
9736 msg = g_strdup_printf
9737 (_("The external editor is still working.\n"
9738 "Force terminating the process?\n"
9739 "process group id: %d"), -pgid);
9740 val = alertpanel_full(_("Notice"), msg, GTK_STOCK_NO, GTK_STOCK_YES,
9741 NULL, ALERTFOCUS_FIRST, FALSE, NULL,
9746 if (val == G_ALERTALTERNATE) {
9747 g_source_remove(compose->exteditor_tag);
9748 g_io_channel_shutdown(compose->exteditor_ch,
9750 g_io_channel_unref(compose->exteditor_ch);
9752 if (kill(pgid, SIGTERM) < 0) perror("kill");
9753 waitpid(compose->exteditor_pid, NULL, 0);
9755 g_warning("Terminated process group id: %d. "
9756 "Temporary file: %s", -pgid, compose->exteditor_file);
9758 compose_set_ext_editor_sensitive(compose, TRUE);
9760 g_free(compose->exteditor_file);
9761 compose->exteditor_file = NULL;
9762 compose->exteditor_pid = -1;
9763 compose->exteditor_ch = NULL;
9764 compose->exteditor_tag = -1;
9772 static gboolean compose_input_cb(GIOChannel *source, GIOCondition condition,
9776 Compose *compose = (Compose *)data;
9779 debug_print("Compose: input from monitoring process\n");
9781 if (g_io_channel_read_chars(source, buf, sizeof(buf), &bytes_read, NULL) != G_IO_STATUS_NORMAL) {
9786 g_io_channel_shutdown(source, FALSE, NULL);
9787 g_io_channel_unref(source);
9789 waitpid(compose->exteditor_pid, NULL, 0);
9791 if (buf[0] == '0') { /* success */
9792 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9793 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
9794 GtkTextIter start, end;
9797 gtk_text_buffer_set_text(buffer, "", -1);
9798 compose_insert_file(compose, compose->exteditor_file);
9799 compose_changed_cb(NULL, compose);
9801 /* Check if we should save the draft or not */
9802 if (compose_can_autosave(compose))
9803 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
9805 if (claws_unlink(compose->exteditor_file) < 0)
9806 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9808 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
9809 gtk_text_buffer_get_start_iter(buffer, &start);
9810 gtk_text_buffer_get_end_iter(buffer, &end);
9811 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
9812 if (chars && strlen(chars) > 0)
9813 compose->modified = TRUE;
9815 } else if (buf[0] == '1') { /* failed */
9816 g_warning("Couldn't exec external editor");
9817 if (claws_unlink(compose->exteditor_file) < 0)
9818 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9819 } else if (buf[0] == '2') {
9820 g_warning("Couldn't write to file");
9821 } else if (buf[0] == '3') {
9822 g_warning("Pipe read failed");
9825 compose_set_ext_editor_sensitive(compose, TRUE);
9827 g_free(compose->exteditor_file);
9828 compose->exteditor_file = NULL;
9829 compose->exteditor_pid = -1;
9830 compose->exteditor_ch = NULL;
9831 compose->exteditor_tag = -1;
9832 if (compose->exteditor_socket) {
9833 gtk_widget_destroy(compose->exteditor_socket);
9834 compose->exteditor_socket = NULL;
9841 static char *ext_editor_menu_entries[] = {
9842 "Menu/Message/Send",
9843 "Menu/Message/SendLater",
9844 "Menu/Message/InsertFile",
9845 "Menu/Message/InsertSig",
9846 "Menu/Message/ReplaceSig",
9847 "Menu/Message/Save",
9848 "Menu/Message/Print",
9853 "Menu/Tools/ShowRuler",
9854 "Menu/Tools/Actions",
9859 static void compose_set_ext_editor_sensitive(Compose *compose,
9864 for (i = 0; ext_editor_menu_entries[i]; ++i) {
9865 cm_menu_set_sensitive_full(compose->ui_manager,
9866 ext_editor_menu_entries[i], sensitive);
9869 if (compose_get_ext_editor_uses_socket()) {
9871 if (compose->exteditor_socket)
9872 gtk_widget_hide(compose->exteditor_socket);
9873 gtk_widget_show(compose->scrolledwin);
9874 if (prefs_common.show_ruler)
9875 gtk_widget_show(compose->ruler_hbox);
9876 /* Fix the focus, as it doesn't go anywhere when the
9877 * socket is hidden or destroyed */
9878 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9880 g_assert (compose->exteditor_socket != NULL);
9881 /* Fix the focus, as it doesn't go anywhere when the
9882 * edit box is hidden */
9883 if (gtk_widget_is_focus(compose->text))
9884 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9885 gtk_widget_hide(compose->scrolledwin);
9886 gtk_widget_hide(compose->ruler_hbox);
9887 gtk_widget_show(compose->exteditor_socket);
9890 gtk_widget_set_sensitive(compose->text, sensitive);
9892 if (compose->toolbar->send_btn)
9893 gtk_widget_set_sensitive(compose->toolbar->send_btn, sensitive);
9894 if (compose->toolbar->sendl_btn)
9895 gtk_widget_set_sensitive(compose->toolbar->sendl_btn, sensitive);
9896 if (compose->toolbar->draft_btn)
9897 gtk_widget_set_sensitive(compose->toolbar->draft_btn, sensitive);
9898 if (compose->toolbar->insert_btn)
9899 gtk_widget_set_sensitive(compose->toolbar->insert_btn, sensitive);
9900 if (compose->toolbar->sig_btn)
9901 gtk_widget_set_sensitive(compose->toolbar->sig_btn, sensitive);
9902 if (compose->toolbar->exteditor_btn)
9903 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, sensitive);
9904 if (compose->toolbar->linewrap_current_btn)
9905 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, sensitive);
9906 if (compose->toolbar->linewrap_all_btn)
9907 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, sensitive);
9910 static gboolean compose_get_ext_editor_uses_socket()
9912 return (prefs_common_get_ext_editor_cmd() &&
9913 strstr(prefs_common_get_ext_editor_cmd(), "%w"));
9916 static gboolean compose_ext_editor_plug_removed_cb(GtkSocket *socket, Compose *compose)
9918 compose->exteditor_socket = NULL;
9919 /* returning FALSE allows destruction of the socket */
9922 #endif /* G_OS_UNIX */
9925 * compose_undo_state_changed:
9927 * Change the sensivity of the menuentries undo and redo
9929 static void compose_undo_state_changed(UndoMain *undostruct, gint undo_state,
9930 gint redo_state, gpointer data)
9932 Compose *compose = (Compose *)data;
9934 switch (undo_state) {
9935 case UNDO_STATE_TRUE:
9936 if (!undostruct->undo_state) {
9937 undostruct->undo_state = TRUE;
9938 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", TRUE);
9941 case UNDO_STATE_FALSE:
9942 if (undostruct->undo_state) {
9943 undostruct->undo_state = FALSE;
9944 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
9947 case UNDO_STATE_UNCHANGED:
9949 case UNDO_STATE_REFRESH:
9950 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", undostruct->undo_state);
9953 g_warning("Undo state not recognized");
9957 switch (redo_state) {
9958 case UNDO_STATE_TRUE:
9959 if (!undostruct->redo_state) {
9960 undostruct->redo_state = TRUE;
9961 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", TRUE);
9964 case UNDO_STATE_FALSE:
9965 if (undostruct->redo_state) {
9966 undostruct->redo_state = FALSE;
9967 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
9970 case UNDO_STATE_UNCHANGED:
9972 case UNDO_STATE_REFRESH:
9973 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", undostruct->redo_state);
9976 g_warning("Redo state not recognized");
9981 /* callback functions */
9983 static void compose_notebook_size_alloc(GtkNotebook *notebook,
9984 GtkAllocation *allocation,
9987 prefs_common.compose_notebook_height = gtk_paned_get_position(paned);
9990 /* compose_edit_size_alloc() - called when resized. don't know whether Gtk
9991 * includes "non-client" (windows-izm) in calculation, so this calculation
9992 * may not be accurate.
9994 static gboolean compose_edit_size_alloc(GtkEditable *widget,
9995 GtkAllocation *allocation,
9996 GtkSHRuler *shruler)
9998 if (prefs_common.show_ruler) {
9999 gint char_width = 0, char_height = 0;
10000 gint line_width_in_chars;
10002 gtkut_get_font_size(GTK_WIDGET(widget),
10003 &char_width, &char_height);
10004 line_width_in_chars =
10005 (allocation->width - allocation->x) / char_width;
10007 /* got the maximum */
10008 gtk_shruler_set_range(GTK_SHRULER(shruler),
10009 0.0, line_width_in_chars, 0);
10018 ComposePrefType type;
10019 gboolean entry_marked;
10020 } HeaderEntryState;
10022 static void account_activated(GtkComboBox *optmenu, gpointer data)
10024 Compose *compose = (Compose *)data;
10027 gchar *folderidentifier;
10028 gint account_id = 0;
10029 GtkTreeModel *menu;
10031 GSList *list, *saved_list = NULL;
10032 HeaderEntryState *state;
10034 /* Get ID of active account in the combo box */
10035 menu = gtk_combo_box_get_model(optmenu);
10036 cm_return_if_fail(gtk_combo_box_get_active_iter(optmenu, &iter));
10037 gtk_tree_model_get(menu, &iter, 1, &account_id, -1);
10039 ac = account_find_from_id(account_id);
10040 cm_return_if_fail(ac != NULL);
10042 if (ac != compose->account) {
10043 compose_select_account(compose, ac, FALSE);
10045 for (list = compose->header_list; list; list = list->next) {
10046 ComposeHeaderEntry *hentry=(ComposeHeaderEntry *)list->data;
10048 if (hentry->type == PREF_ACCOUNT || !list->next) {
10049 compose_destroy_headerentry(compose, hentry);
10052 state = g_malloc0(sizeof(HeaderEntryState));
10053 state->header = gtk_editable_get_chars(GTK_EDITABLE(
10054 gtk_bin_get_child(GTK_BIN(hentry->combo))), 0, -1);
10055 state->entry = gtk_editable_get_chars(
10056 GTK_EDITABLE(hentry->entry), 0, -1);
10057 state->type = hentry->type;
10059 saved_list = g_slist_append(saved_list, state);
10060 compose_destroy_headerentry(compose, hentry);
10063 compose->header_last = NULL;
10064 g_slist_free(compose->header_list);
10065 compose->header_list = NULL;
10066 compose->header_nextrow = 1;
10067 compose_create_header_entry(compose);
10069 if (ac->set_autocc && ac->auto_cc)
10070 compose_entry_append(compose, ac->auto_cc,
10071 COMPOSE_CC, PREF_ACCOUNT);
10072 if (ac->set_autobcc && ac->auto_bcc)
10073 compose_entry_append(compose, ac->auto_bcc,
10074 COMPOSE_BCC, PREF_ACCOUNT);
10075 if (ac->set_autoreplyto && ac->auto_replyto)
10076 compose_entry_append(compose, ac->auto_replyto,
10077 COMPOSE_REPLYTO, PREF_ACCOUNT);
10079 for (list = saved_list; list; list = list->next) {
10080 state = (HeaderEntryState *) list->data;
10082 compose_add_header_entry(compose, state->header,
10083 state->entry, state->type);
10085 g_free(state->header);
10086 g_free(state->entry);
10089 g_slist_free(saved_list);
10091 combobox_select_by_data(GTK_COMBO_BOX(compose->header_last->combo),
10092 (ac->protocol == A_NNTP) ?
10093 COMPOSE_NEWSGROUPS : COMPOSE_TO);
10096 /* Set message save folder */
10097 if (account_get_special_folder(compose->account, F_OUTBOX)) {
10098 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
10100 g_signal_connect(G_OBJECT(compose->savemsg_checkbtn), "toggled",
10101 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
10103 compose_set_save_to(compose, NULL);
10104 if (account_get_special_folder(compose->account, F_OUTBOX)) {
10105 folderidentifier = folder_item_get_identifier(account_get_special_folder
10106 (compose->account, F_OUTBOX));
10107 compose_set_save_to(compose, folderidentifier);
10108 g_free(folderidentifier);
10112 static void attach_selected(GtkTreeView *tree_view, GtkTreePath *tree_path,
10113 GtkTreeViewColumn *column, Compose *compose)
10115 compose_attach_property(NULL, compose);
10118 static gboolean attach_button_pressed(GtkWidget *widget, GdkEventButton *event,
10121 Compose *compose = (Compose *)data;
10122 GtkTreeSelection *attach_selection;
10123 gint attach_nr_selected;
10126 if (!event) return FALSE;
10128 if (event->button == 3) {
10129 attach_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
10130 attach_nr_selected = gtk_tree_selection_count_selected_rows(attach_selection);
10132 /* If no rows, or just one row is selected, right-click should
10133 * open menu relevant to the row being right-clicked on. We
10134 * achieve that by selecting the clicked row first. If more
10135 * than one row is selected, we shouldn't modify the selection,
10136 * as user may want to remove selected rows (attachments). */
10137 if (attach_nr_selected < 2) {
10138 gtk_tree_selection_unselect_all(attach_selection);
10139 attach_nr_selected = 0;
10140 gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget),
10141 event->x, event->y, &path, NULL, NULL, NULL);
10142 if (path != NULL) {
10143 gtk_tree_selection_select_path(attach_selection, path);
10144 gtk_tree_path_free(path);
10145 attach_nr_selected++;
10149 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", (attach_nr_selected > 0));
10150 /* Properties menu item makes no sense with more than one row
10151 * selected, the properties dialog can only edit one attachment. */
10152 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", (attach_nr_selected == 1));
10154 gtk_menu_popup(GTK_MENU(compose->popupmenu), NULL, NULL,
10155 NULL, NULL, event->button, event->time);
10162 static gboolean attach_key_pressed(GtkWidget *widget, GdkEventKey *event,
10165 Compose *compose = (Compose *)data;
10167 if (!event) return FALSE;
10169 switch (event->keyval) {
10170 case GDK_KEY_Delete:
10171 compose_attach_remove_selected(NULL, compose);
10177 static void compose_allow_user_actions (Compose *compose, gboolean allow)
10179 toolbar_comp_set_sensitive(compose, allow);
10180 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message", allow);
10181 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", allow);
10183 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", allow);
10185 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", allow);
10186 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools", allow);
10187 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Help", allow);
10189 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), allow);
10193 static void compose_send_cb(GtkAction *action, gpointer data)
10195 Compose *compose = (Compose *)data;
10198 if (compose->exteditor_tag != -1) {
10199 debug_print("ignoring send: external editor still open\n");
10203 if (prefs_common.work_offline &&
10204 !inc_offline_should_override(TRUE,
10205 _("Claws Mail needs network access in order "
10206 "to send this email.")))
10209 if (compose->draft_timeout_tag >= 0) { /* CLAWS: disable draft timeout */
10210 g_source_remove(compose->draft_timeout_tag);
10211 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
10214 compose_send(compose);
10217 static void compose_send_later_cb(GtkAction *action, gpointer data)
10219 Compose *compose = (Compose *)data;
10220 ComposeQueueResult val;
10223 compose_allow_user_actions(compose, FALSE);
10224 val = compose_queue_sub(compose, NULL, NULL, NULL, TRUE, TRUE);
10225 compose_allow_user_actions(compose, TRUE);
10228 if (val == COMPOSE_QUEUE_SUCCESS) {
10229 compose_close(compose);
10231 _display_queue_error(val);
10234 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
10237 #define DRAFTED_AT_EXIT "drafted_at_exit"
10238 static void compose_register_draft(MsgInfo *info)
10240 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10241 DRAFTED_AT_EXIT, NULL);
10242 FILE *fp = g_fopen(filepath, "ab");
10245 fprintf(fp, "%s\t%d\n", folder_item_get_identifier(info->folder),
10253 gboolean compose_draft (gpointer data, guint action)
10255 Compose *compose = (Compose *)data;
10260 MsgFlags flag = {0, 0};
10261 static gboolean lock = FALSE;
10262 MsgInfo *newmsginfo;
10264 gboolean target_locked = FALSE;
10265 gboolean err = FALSE;
10267 if (lock) return FALSE;
10269 if (compose->sending)
10272 draft = account_get_special_folder(compose->account, F_DRAFT);
10273 cm_return_val_if_fail(draft != NULL, FALSE);
10275 if (!g_mutex_trylock(compose->mutex)) {
10276 /* we don't want to lock the mutex once it's available,
10277 * because as the only other part of compose.c locking
10278 * it is compose_close - which means once unlocked,
10279 * the compose struct will be freed */
10280 debug_print("couldn't lock mutex, probably sending\n");
10286 tmp = g_strdup_printf("%s%cdraft.%p", get_tmp_dir(),
10287 G_DIR_SEPARATOR, compose);
10288 if ((fp = g_fopen(tmp, "wb")) == NULL) {
10289 FILE_OP_ERROR(tmp, "fopen");
10293 /* chmod for security */
10294 if (change_file_mode_rw(fp, tmp) < 0) {
10295 FILE_OP_ERROR(tmp, "chmod");
10296 g_warning("can't change file mode");
10299 /* Save draft infos */
10300 err |= (fprintf(fp, "X-Claws-Account-Id:%d\n", compose->account->account_id) < 0);
10301 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
10303 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
10304 gchar *savefolderid;
10306 savefolderid = compose_get_save_to(compose);
10307 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
10308 g_free(savefolderid);
10310 if (compose->return_receipt) {
10311 err |= (fprintf(fp, "RRCPT:1\n") < 0);
10313 if (compose->privacy_system) {
10314 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
10315 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
10316 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
10319 /* Message-ID of message replying to */
10320 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
10321 gchar *folderid = NULL;
10323 if (compose->replyinfo->folder)
10324 folderid = folder_item_get_identifier(compose->replyinfo->folder);
10325 if (folderid == NULL)
10326 folderid = g_strdup("NULL");
10328 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
10331 /* Message-ID of message forwarding to */
10332 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
10333 gchar *folderid = NULL;
10335 if (compose->fwdinfo->folder)
10336 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
10337 if (folderid == NULL)
10338 folderid = g_strdup("NULL");
10340 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
10344 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
10345 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
10347 sheaders = compose_get_manual_headers_info(compose);
10348 err |= (fprintf(fp, "X-Claws-Manual-Headers:%s\n", sheaders) < 0);
10351 /* end of headers */
10352 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
10359 if (compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_STORE, action != COMPOSE_AUTO_SAVE) < 0) {
10363 if (fclose(fp) == EOF) {
10367 flag.perm_flags = MSG_NEW|MSG_UNREAD;
10368 if (compose->targetinfo) {
10369 target_locked = MSG_IS_LOCKED(compose->targetinfo->flags);
10371 flag.perm_flags |= MSG_LOCKED;
10373 flag.tmp_flags = MSG_DRAFT;
10375 folder_item_scan(draft);
10376 if ((msgnum = folder_item_add_msg(draft, tmp, &flag, TRUE)) < 0) {
10377 MsgInfo *tmpinfo = NULL;
10378 debug_print("didn't get msgnum after adding draft [%s]\n", compose->msgid?compose->msgid:"no msgid");
10379 if (compose->msgid) {
10380 tmpinfo = folder_item_get_msginfo_by_msgid(draft, compose->msgid);
10383 msgnum = tmpinfo->msgnum;
10384 procmsg_msginfo_free(&tmpinfo);
10385 debug_print("got draft msgnum %d from scanning\n", msgnum);
10387 debug_print("didn't get draft msgnum after scanning\n");
10390 debug_print("got draft msgnum %d from adding\n", msgnum);
10396 if (action != COMPOSE_AUTO_SAVE) {
10397 if (action != COMPOSE_DRAFT_FOR_EXIT)
10398 alertpanel_error(_("Could not save draft."));
10401 gtkut_window_popup(compose->window);
10402 val = alertpanel_full(_("Could not save draft"),
10403 _("Could not save draft.\n"
10404 "Do you want to cancel exit or discard this email?"),
10405 _("_Cancel exit"), _("_Discard email"), NULL, ALERTFOCUS_FIRST,
10406 FALSE, NULL, ALERT_QUESTION);
10407 if (val == G_ALERTALTERNATE) {
10409 g_mutex_unlock(compose->mutex); /* must be done before closing */
10410 compose_close(compose);
10414 g_mutex_unlock(compose->mutex); /* must be done before closing */
10423 if (compose->mode == COMPOSE_REEDIT) {
10424 compose_remove_reedit_target(compose, TRUE);
10427 newmsginfo = folder_item_get_msginfo(draft, msgnum);
10430 procmsg_msginfo_unset_flags(newmsginfo, ~0, ~0);
10432 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD|MSG_LOCKED, MSG_DRAFT);
10434 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD, MSG_DRAFT);
10435 if (compose_use_attach(compose) && action != COMPOSE_AUTO_SAVE)
10436 procmsg_msginfo_set_flags(newmsginfo, 0,
10437 MSG_HAS_ATTACHMENT);
10439 if (action == COMPOSE_DRAFT_FOR_EXIT) {
10440 compose_register_draft(newmsginfo);
10442 procmsg_msginfo_free(&newmsginfo);
10445 folder_item_scan(draft);
10447 if (action == COMPOSE_QUIT_EDITING || action == COMPOSE_DRAFT_FOR_EXIT) {
10449 g_mutex_unlock(compose->mutex); /* must be done before closing */
10450 compose_close(compose);
10457 GError *error = NULL;
10462 goffset size, mtime;
10464 path = folder_item_fetch_msg(draft, msgnum);
10465 if (path == NULL) {
10466 debug_print("can't fetch %s:%d\n", draft->path, msgnum);
10470 f = g_file_new_for_path(path);
10471 fi = g_file_query_info(f, "standard::size,time::modified",
10472 G_FILE_QUERY_INFO_NONE, NULL, &error);
10473 if (error != NULL) {
10474 debug_print("couldn't query file info for '%s': %s\n",
10475 path, error->message);
10476 g_error_free(error);
10481 size = g_file_info_get_size(fi);
10482 g_file_info_get_modification_time(fi, &tv);
10484 g_object_unref(fi);
10487 if (g_stat(path, &s) < 0) {
10488 FILE_OP_ERROR(path, "stat");
10493 mtime = s.st_mtime;
10497 procmsg_msginfo_free(&(compose->targetinfo));
10498 compose->targetinfo = procmsg_msginfo_new();
10499 compose->targetinfo->msgnum = msgnum;
10500 compose->targetinfo->size = size;
10501 compose->targetinfo->mtime = mtime;
10502 compose->targetinfo->folder = draft;
10504 procmsg_msginfo_set_flags(compose->targetinfo, MSG_LOCKED, 0);
10505 compose->mode = COMPOSE_REEDIT;
10507 if (action == COMPOSE_AUTO_SAVE) {
10508 compose->autosaved_draft = compose->targetinfo;
10510 compose->modified = FALSE;
10511 compose_set_title(compose);
10515 g_mutex_unlock(compose->mutex);
10519 void compose_clear_exit_drafts(void)
10521 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10522 DRAFTED_AT_EXIT, NULL);
10523 if (is_file_exist(filepath))
10524 claws_unlink(filepath);
10529 void compose_reopen_exit_drafts(void)
10531 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10532 DRAFTED_AT_EXIT, NULL);
10533 FILE *fp = g_fopen(filepath, "rb");
10537 while (fgets(buf, sizeof(buf), fp)) {
10538 gchar **parts = g_strsplit(buf, "\t", 2);
10539 const gchar *folder = parts[0];
10540 int msgnum = parts[1] ? atoi(parts[1]):-1;
10542 if (folder && *folder && msgnum > -1) {
10543 FolderItem *item = folder_find_item_from_identifier(folder);
10544 MsgInfo *info = folder_item_get_msginfo(item, msgnum);
10546 compose_reedit(info, FALSE);
10553 compose_clear_exit_drafts();
10556 static void compose_save_cb(GtkAction *action, gpointer data)
10558 Compose *compose = (Compose *)data;
10559 compose_draft(compose, COMPOSE_KEEP_EDITING);
10560 compose->rmode = COMPOSE_REEDIT;
10563 void compose_attach_from_list(Compose *compose, GList *file_list, gboolean free_data)
10565 if (compose && file_list) {
10568 for ( tmp = file_list; tmp; tmp = tmp->next) {
10569 gchar *file = (gchar *) tmp->data;
10570 gchar *utf8_filename = conv_filename_to_utf8(file);
10571 compose_attach_append(compose, file, utf8_filename, NULL, NULL);
10572 compose_changed_cb(NULL, compose);
10577 g_free(utf8_filename);
10582 static void compose_attach_cb(GtkAction *action, gpointer data)
10584 Compose *compose = (Compose *)data;
10587 if (compose->redirect_filename != NULL)
10590 /* Set focus_window properly, in case we were called via popup menu,
10591 * which unsets it (via focus_out_event callback on compose window). */
10592 manage_window_focus_in(compose->window, NULL, NULL);
10594 file_list = filesel_select_multiple_files_open(_("Select file"), NULL);
10597 compose_attach_from_list(compose, file_list, TRUE);
10598 g_list_free(file_list);
10602 static void compose_insert_file_cb(GtkAction *action, gpointer data)
10604 Compose *compose = (Compose *)data;
10606 gint files_inserted = 0;
10608 file_list = filesel_select_multiple_files_open(_("Select file"), NULL);
10613 for ( tmp = file_list; tmp; tmp = tmp->next) {
10614 gchar *file = (gchar *) tmp->data;
10615 gchar *filedup = g_strdup(file);
10616 gchar *shortfile = g_path_get_basename(filedup);
10617 ComposeInsertResult res;
10618 /* insert the file if the file is short or if the user confirmed that
10619 he/she wants to insert the large file */
10620 res = compose_insert_file(compose, file);
10621 if (res == COMPOSE_INSERT_READ_ERROR) {
10622 alertpanel_error(_("File '%s' could not be read."), shortfile);
10623 } else if (res == COMPOSE_INSERT_INVALID_CHARACTER) {
10624 alertpanel_error(_("File '%s' contained invalid characters\n"
10625 "for the current encoding, insertion may be incorrect."),
10627 } else if (res == COMPOSE_INSERT_SUCCESS)
10634 g_list_free(file_list);
10638 if (files_inserted > 0 && compose->gtkaspell &&
10639 compose->gtkaspell->check_while_typing)
10640 gtkaspell_highlight_all(compose->gtkaspell);
10644 static void compose_insert_sig_cb(GtkAction *action, gpointer data)
10646 Compose *compose = (Compose *)data;
10648 compose_insert_sig(compose, FALSE);
10651 static void compose_replace_sig_cb(GtkAction *action, gpointer data)
10653 Compose *compose = (Compose *)data;
10655 compose_insert_sig(compose, TRUE);
10658 static gint compose_delete_cb(GtkWidget *widget, GdkEventAny *event,
10662 Compose *compose = (Compose *)data;
10664 gtkut_widget_get_uposition(widget, &x, &y);
10665 if (!compose->batch) {
10666 prefs_common.compose_x = x;
10667 prefs_common.compose_y = y;
10669 if (compose->sending || compose->updating)
10671 compose_close_cb(NULL, compose);
10675 void compose_close_toolbar(Compose *compose)
10677 compose_close_cb(NULL, compose);
10680 static void compose_close_cb(GtkAction *action, gpointer data)
10682 Compose *compose = (Compose *)data;
10686 if (compose->exteditor_tag != -1) {
10687 if (!compose_ext_editor_kill(compose))
10692 if (compose->modified) {
10693 gboolean reedit = (compose->rmode == COMPOSE_REEDIT);
10694 if (!g_mutex_trylock(compose->mutex)) {
10695 /* we don't want to lock the mutex once it's available,
10696 * because as the only other part of compose.c locking
10697 * it is compose_close - which means once unlocked,
10698 * the compose struct will be freed */
10699 debug_print("couldn't lock mutex, probably sending\n");
10703 val = alertpanel(_("Discard message"),
10704 _("This message has been modified. Discard it?"),
10705 _("_Discard"), _("_Save to Drafts"), GTK_STOCK_CANCEL,
10708 val = alertpanel(_("Save changes"),
10709 _("This message has been modified. Save the latest changes?"),
10710 _("_Don't save"), _("_Save to Drafts"), GTK_STOCK_CANCEL,
10711 ALERTFOCUS_SECOND);
10713 g_mutex_unlock(compose->mutex);
10715 case G_ALERTDEFAULT:
10716 if (compose_can_autosave(compose) && !reedit)
10717 compose_remove_draft(compose);
10719 case G_ALERTALTERNATE:
10720 compose_draft(data, COMPOSE_QUIT_EDITING);
10727 compose_close(compose);
10730 static void compose_print_cb(GtkAction *action, gpointer data)
10732 Compose *compose = (Compose *) data;
10734 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
10735 if (compose->targetinfo)
10736 messageview_print(compose->targetinfo, FALSE, -1, -1, 0);
10739 static void compose_set_encoding_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
10741 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
10742 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
10743 Compose *compose = (Compose *) data;
10746 compose->out_encoding = (CharSet)value;
10749 static void compose_address_cb(GtkAction *action, gpointer data)
10751 Compose *compose = (Compose *)data;
10753 #ifndef USE_ALT_ADDRBOOK
10754 addressbook_open(compose);
10756 GError* error = NULL;
10757 addressbook_connect_signals(compose);
10758 addressbook_dbus_open(TRUE, &error);
10760 g_warning("%s", error->message);
10761 g_error_free(error);
10766 static void about_show_cb(GtkAction *action, gpointer data)
10771 static void compose_template_activate_cb(GtkWidget *widget, gpointer data)
10773 Compose *compose = (Compose *)data;
10778 tmpl = g_object_get_data(G_OBJECT(widget), "template");
10779 cm_return_if_fail(tmpl != NULL);
10781 msg = g_strdup_printf(_("Do you want to apply the template '%s'?"),
10783 val = alertpanel(_("Apply template"), msg,
10784 _("_Replace"), _("_Insert"), GTK_STOCK_CANCEL, ALERTFOCUS_FIRST);
10787 if (val == G_ALERTDEFAULT)
10788 compose_template_apply(compose, tmpl, TRUE);
10789 else if (val == G_ALERTALTERNATE)
10790 compose_template_apply(compose, tmpl, FALSE);
10793 static void compose_ext_editor_cb(GtkAction *action, gpointer data)
10795 Compose *compose = (Compose *)data;
10798 if (compose->exteditor_tag != -1) {
10799 debug_print("ignoring open external editor: external editor still open\n");
10803 compose_exec_ext_editor(compose);
10806 static void compose_undo_cb(GtkAction *action, gpointer data)
10808 Compose *compose = (Compose *)data;
10809 gboolean prev_autowrap = compose->autowrap;
10811 compose->autowrap = FALSE;
10812 undo_undo(compose->undostruct);
10813 compose->autowrap = prev_autowrap;
10816 static void compose_redo_cb(GtkAction *action, gpointer data)
10818 Compose *compose = (Compose *)data;
10819 gboolean prev_autowrap = compose->autowrap;
10821 compose->autowrap = FALSE;
10822 undo_redo(compose->undostruct);
10823 compose->autowrap = prev_autowrap;
10826 static void entry_cut_clipboard(GtkWidget *entry)
10828 if (GTK_IS_EDITABLE(entry))
10829 gtk_editable_cut_clipboard (GTK_EDITABLE(entry));
10830 else if (GTK_IS_TEXT_VIEW(entry))
10831 gtk_text_buffer_cut_clipboard(
10832 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10833 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
10837 static void entry_copy_clipboard(GtkWidget *entry)
10839 if (GTK_IS_EDITABLE(entry))
10840 gtk_editable_copy_clipboard (GTK_EDITABLE(entry));
10841 else if (GTK_IS_TEXT_VIEW(entry))
10842 gtk_text_buffer_copy_clipboard(
10843 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10844 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
10847 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry,
10848 gboolean wrap, GdkAtom clip, GtkTextIter *insert_place)
10850 if (GTK_IS_TEXT_VIEW(entry)) {
10851 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10852 GtkTextMark *mark_start = gtk_text_buffer_get_insert(buffer);
10853 GtkTextIter start_iter, end_iter;
10855 gchar *contents = gtk_clipboard_wait_for_text(gtk_clipboard_get(clip));
10857 if (contents == NULL)
10860 /* we shouldn't delete the selection when middle-click-pasting, or we
10861 * can't mid-click-paste our own selection */
10862 if (clip != GDK_SELECTION_PRIMARY) {
10863 undo_paste_clipboard(GTK_TEXT_VIEW(compose->text), compose->undostruct);
10864 gtk_text_buffer_delete_selection(buffer, FALSE, TRUE);
10867 if (insert_place == NULL) {
10868 /* if insert_place isn't specified, insert at the cursor.
10869 * used for Ctrl-V pasting */
10870 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10871 start = gtk_text_iter_get_offset(&start_iter);
10872 gtk_text_buffer_insert(buffer, &start_iter, contents, strlen(contents));
10874 /* if insert_place is specified, paste here.
10875 * used for mid-click-pasting */
10876 start = gtk_text_iter_get_offset(insert_place);
10877 gtk_text_buffer_insert(buffer, insert_place, contents, strlen(contents));
10878 if (prefs_common.primary_paste_unselects)
10879 gtk_text_buffer_select_range(buffer, insert_place, insert_place);
10883 /* paste unwrapped: mark the paste so it's not wrapped later */
10884 end = start + strlen(contents);
10885 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, start);
10886 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, end);
10887 gtk_text_buffer_apply_tag_by_name(buffer, "no_wrap", &start_iter, &end_iter);
10888 } else if (wrap && clip == GDK_SELECTION_PRIMARY) {
10889 /* rewrap paragraph now (after a mid-click-paste) */
10890 mark_start = gtk_text_buffer_get_insert(buffer);
10891 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10892 gtk_text_iter_backward_char(&start_iter);
10893 compose_beautify_paragraph(compose, &start_iter, TRUE);
10895 } else if (GTK_IS_EDITABLE(entry))
10896 gtk_editable_paste_clipboard (GTK_EDITABLE(entry));
10898 compose->modified = TRUE;
10901 static void entry_allsel(GtkWidget *entry)
10903 if (GTK_IS_EDITABLE(entry))
10904 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
10905 else if (GTK_IS_TEXT_VIEW(entry)) {
10906 GtkTextIter startiter, enditer;
10907 GtkTextBuffer *textbuf;
10909 textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10910 gtk_text_buffer_get_start_iter(textbuf, &startiter);
10911 gtk_text_buffer_get_end_iter(textbuf, &enditer);
10913 gtk_text_buffer_move_mark_by_name(textbuf,
10914 "selection_bound", &startiter);
10915 gtk_text_buffer_move_mark_by_name(textbuf,
10916 "insert", &enditer);
10920 static void compose_cut_cb(GtkAction *action, gpointer data)
10922 Compose *compose = (Compose *)data;
10923 if (compose->focused_editable
10924 #ifndef GENERIC_UMPC
10925 && gtk_widget_has_focus(compose->focused_editable)
10928 entry_cut_clipboard(compose->focused_editable);
10931 static void compose_copy_cb(GtkAction *action, gpointer data)
10933 Compose *compose = (Compose *)data;
10934 if (compose->focused_editable
10935 #ifndef GENERIC_UMPC
10936 && gtk_widget_has_focus(compose->focused_editable)
10939 entry_copy_clipboard(compose->focused_editable);
10942 static void compose_paste_cb(GtkAction *action, gpointer data)
10944 Compose *compose = (Compose *)data;
10945 gint prev_autowrap;
10946 GtkTextBuffer *buffer;
10948 if (compose->focused_editable &&
10949 #ifndef GENERIC_UMPC
10950 gtk_widget_has_focus(compose->focused_editable)
10953 entry_paste_clipboard(compose, compose->focused_editable,
10954 prefs_common.linewrap_pastes,
10955 GDK_SELECTION_CLIPBOARD, NULL);
10960 #ifndef GENERIC_UMPC
10961 gtk_widget_has_focus(compose->text) &&
10963 compose->gtkaspell &&
10964 compose->gtkaspell->check_while_typing)
10965 gtkaspell_highlight_all(compose->gtkaspell);
10969 static void compose_paste_as_quote_cb(GtkAction *action, gpointer data)
10971 Compose *compose = (Compose *)data;
10972 gint wrap_quote = prefs_common.linewrap_quote;
10973 if (compose->focused_editable
10974 #ifndef GENERIC_UMPC
10975 && gtk_widget_has_focus(compose->focused_editable)
10978 /* let text_insert() (called directly or at a later time
10979 * after the gtk_editable_paste_clipboard) know that
10980 * text is to be inserted as a quotation. implemented
10981 * by using a simple refcount... */
10982 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data(
10983 G_OBJECT(compose->focused_editable),
10984 "paste_as_quotation"));
10985 g_object_set_data(G_OBJECT(compose->focused_editable),
10986 "paste_as_quotation",
10987 GINT_TO_POINTER(paste_as_quotation + 1));
10988 prefs_common.linewrap_quote = prefs_common.linewrap_pastes;
10989 entry_paste_clipboard(compose, compose->focused_editable,
10990 prefs_common.linewrap_pastes,
10991 GDK_SELECTION_CLIPBOARD, NULL);
10992 prefs_common.linewrap_quote = wrap_quote;
10996 static void compose_paste_no_wrap_cb(GtkAction *action, gpointer data)
10998 Compose *compose = (Compose *)data;
10999 gint prev_autowrap;
11000 GtkTextBuffer *buffer;
11002 if (compose->focused_editable
11003 #ifndef GENERIC_UMPC
11004 && gtk_widget_has_focus(compose->focused_editable)
11007 entry_paste_clipboard(compose, compose->focused_editable, FALSE,
11008 GDK_SELECTION_CLIPBOARD, NULL);
11013 #ifndef GENERIC_UMPC
11014 gtk_widget_has_focus(compose->text) &&
11016 compose->gtkaspell &&
11017 compose->gtkaspell->check_while_typing)
11018 gtkaspell_highlight_all(compose->gtkaspell);
11022 static void compose_paste_wrap_cb(GtkAction *action, gpointer data)
11024 Compose *compose = (Compose *)data;
11025 gint prev_autowrap;
11026 GtkTextBuffer *buffer;
11028 if (compose->focused_editable
11029 #ifndef GENERIC_UMPC
11030 && gtk_widget_has_focus(compose->focused_editable)
11033 entry_paste_clipboard(compose, compose->focused_editable, TRUE,
11034 GDK_SELECTION_CLIPBOARD, NULL);
11039 #ifndef GENERIC_UMPC
11040 gtk_widget_has_focus(compose->text) &&
11042 compose->gtkaspell &&
11043 compose->gtkaspell->check_while_typing)
11044 gtkaspell_highlight_all(compose->gtkaspell);
11048 static void compose_allsel_cb(GtkAction *action, gpointer data)
11050 Compose *compose = (Compose *)data;
11051 if (compose->focused_editable
11052 #ifndef GENERIC_UMPC
11053 && gtk_widget_has_focus(compose->focused_editable)
11056 entry_allsel(compose->focused_editable);
11059 static void textview_move_beginning_of_line (GtkTextView *text)
11061 GtkTextBuffer *buffer;
11065 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11067 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11068 mark = gtk_text_buffer_get_insert(buffer);
11069 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11070 gtk_text_iter_set_line_offset(&ins, 0);
11071 gtk_text_buffer_place_cursor(buffer, &ins);
11074 static void textview_move_forward_character (GtkTextView *text)
11076 GtkTextBuffer *buffer;
11080 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11082 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11083 mark = gtk_text_buffer_get_insert(buffer);
11084 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11085 if (gtk_text_iter_forward_cursor_position(&ins))
11086 gtk_text_buffer_place_cursor(buffer, &ins);
11089 static void textview_move_backward_character (GtkTextView *text)
11091 GtkTextBuffer *buffer;
11095 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11097 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11098 mark = gtk_text_buffer_get_insert(buffer);
11099 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11100 if (gtk_text_iter_backward_cursor_position(&ins))
11101 gtk_text_buffer_place_cursor(buffer, &ins);
11104 static void textview_move_forward_word (GtkTextView *text)
11106 GtkTextBuffer *buffer;
11111 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11113 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11114 mark = gtk_text_buffer_get_insert(buffer);
11115 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11116 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
11117 if (gtk_text_iter_forward_word_ends(&ins, count)) {
11118 gtk_text_iter_backward_word_start(&ins);
11119 gtk_text_buffer_place_cursor(buffer, &ins);
11123 static void textview_move_backward_word (GtkTextView *text)
11125 GtkTextBuffer *buffer;
11129 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11131 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11132 mark = gtk_text_buffer_get_insert(buffer);
11133 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11134 if (gtk_text_iter_backward_word_starts(&ins, 1))
11135 gtk_text_buffer_place_cursor(buffer, &ins);
11138 static void textview_move_end_of_line (GtkTextView *text)
11140 GtkTextBuffer *buffer;
11144 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11146 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11147 mark = gtk_text_buffer_get_insert(buffer);
11148 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11149 if (gtk_text_iter_forward_to_line_end(&ins))
11150 gtk_text_buffer_place_cursor(buffer, &ins);
11153 static void textview_move_next_line (GtkTextView *text)
11155 GtkTextBuffer *buffer;
11160 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11162 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11163 mark = gtk_text_buffer_get_insert(buffer);
11164 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11165 offset = gtk_text_iter_get_line_offset(&ins);
11166 if (gtk_text_iter_forward_line(&ins)) {
11167 gtk_text_iter_set_line_offset(&ins, offset);
11168 gtk_text_buffer_place_cursor(buffer, &ins);
11172 static void textview_move_previous_line (GtkTextView *text)
11174 GtkTextBuffer *buffer;
11179 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11181 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11182 mark = gtk_text_buffer_get_insert(buffer);
11183 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11184 offset = gtk_text_iter_get_line_offset(&ins);
11185 if (gtk_text_iter_backward_line(&ins)) {
11186 gtk_text_iter_set_line_offset(&ins, offset);
11187 gtk_text_buffer_place_cursor(buffer, &ins);
11191 static void textview_delete_forward_character (GtkTextView *text)
11193 GtkTextBuffer *buffer;
11195 GtkTextIter ins, end_iter;
11197 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11199 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11200 mark = gtk_text_buffer_get_insert(buffer);
11201 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11203 if (gtk_text_iter_forward_char(&end_iter)) {
11204 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11208 static void textview_delete_backward_character (GtkTextView *text)
11210 GtkTextBuffer *buffer;
11212 GtkTextIter ins, end_iter;
11214 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11216 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11217 mark = gtk_text_buffer_get_insert(buffer);
11218 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11220 if (gtk_text_iter_backward_char(&end_iter)) {
11221 gtk_text_buffer_delete(buffer, &end_iter, &ins);
11225 static void textview_delete_forward_word (GtkTextView *text)
11227 GtkTextBuffer *buffer;
11229 GtkTextIter ins, end_iter;
11231 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11233 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11234 mark = gtk_text_buffer_get_insert(buffer);
11235 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11237 if (gtk_text_iter_forward_word_end(&end_iter)) {
11238 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11242 static void textview_delete_backward_word (GtkTextView *text)
11244 GtkTextBuffer *buffer;
11246 GtkTextIter ins, end_iter;
11248 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11250 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11251 mark = gtk_text_buffer_get_insert(buffer);
11252 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11254 if (gtk_text_iter_backward_word_start(&end_iter)) {
11255 gtk_text_buffer_delete(buffer, &end_iter, &ins);
11259 static void textview_delete_line (GtkTextView *text)
11261 GtkTextBuffer *buffer;
11263 GtkTextIter ins, start_iter, end_iter;
11265 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11267 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11268 mark = gtk_text_buffer_get_insert(buffer);
11269 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11272 gtk_text_iter_set_line_offset(&start_iter, 0);
11275 if (gtk_text_iter_ends_line(&end_iter)){
11276 if (!gtk_text_iter_forward_char(&end_iter))
11277 gtk_text_iter_backward_char(&start_iter);
11280 gtk_text_iter_forward_to_line_end(&end_iter);
11281 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
11284 static void textview_delete_to_line_end (GtkTextView *text)
11286 GtkTextBuffer *buffer;
11288 GtkTextIter ins, end_iter;
11290 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11292 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11293 mark = gtk_text_buffer_get_insert(buffer);
11294 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11296 if (gtk_text_iter_ends_line(&end_iter))
11297 gtk_text_iter_forward_char(&end_iter);
11299 gtk_text_iter_forward_to_line_end(&end_iter);
11300 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11303 #define DO_ACTION(name, act) { \
11304 if(!strcmp(name, a_name)) { \
11308 static ComposeCallAdvancedAction compose_call_advanced_action_from_path(GtkAction *action)
11310 const gchar *a_name = gtk_action_get_name(action);
11311 DO_ACTION("Edit/Advanced/BackChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER);
11312 DO_ACTION("Edit/Advanced/ForwChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER);
11313 DO_ACTION("Edit/Advanced/BackWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD);
11314 DO_ACTION("Edit/Advanced/ForwWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD);
11315 DO_ACTION("Edit/Advanced/BegLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE);
11316 DO_ACTION("Edit/Advanced/EndLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE);
11317 DO_ACTION("Edit/Advanced/PrevLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE);
11318 DO_ACTION("Edit/Advanced/NextLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE);
11319 DO_ACTION("Edit/Advanced/DelBackChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER);
11320 DO_ACTION("Edit/Advanced/DelForwChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER);
11321 DO_ACTION("Edit/Advanced/DelBackWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD);
11322 DO_ACTION("Edit/Advanced/DelForwWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD);
11323 DO_ACTION("Edit/Advanced/DelLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE);
11324 DO_ACTION("Edit/Advanced/DelEndLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END);
11325 return COMPOSE_CALL_ADVANCED_ACTION_UNDEFINED;
11328 static void compose_advanced_action_cb(GtkAction *gaction, gpointer data)
11330 Compose *compose = (Compose *)data;
11331 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11332 ComposeCallAdvancedAction action = COMPOSE_CALL_ADVANCED_ACTION_UNDEFINED;
11334 action = compose_call_advanced_action_from_path(gaction);
11337 void (*do_action) (GtkTextView *text);
11338 } action_table[] = {
11339 {textview_move_beginning_of_line},
11340 {textview_move_forward_character},
11341 {textview_move_backward_character},
11342 {textview_move_forward_word},
11343 {textview_move_backward_word},
11344 {textview_move_end_of_line},
11345 {textview_move_next_line},
11346 {textview_move_previous_line},
11347 {textview_delete_forward_character},
11348 {textview_delete_backward_character},
11349 {textview_delete_forward_word},
11350 {textview_delete_backward_word},
11351 {textview_delete_line},
11352 {textview_delete_to_line_end}
11355 if (!gtk_widget_has_focus(GTK_WIDGET(text))) return;
11357 if (action >= COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE &&
11358 action <= COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END) {
11359 if (action_table[action].do_action)
11360 action_table[action].do_action(text);
11362 g_warning("Not implemented yet.");
11366 static void compose_grab_focus_cb(GtkWidget *widget, Compose *compose)
11368 GtkAllocation allocation;
11372 if (GTK_IS_EDITABLE(widget)) {
11373 str = gtk_editable_get_chars(GTK_EDITABLE(widget), 0, -1);
11374 gtk_editable_set_position(GTK_EDITABLE(widget),
11377 if ((parent = gtk_widget_get_parent(widget))
11378 && (parent = gtk_widget_get_parent(parent))
11379 && (parent = gtk_widget_get_parent(parent))) {
11380 if (GTK_IS_SCROLLED_WINDOW(parent)) {
11381 gtk_widget_get_allocation(widget, &allocation);
11382 gint y = allocation.y;
11383 gint height = allocation.height;
11384 GtkAdjustment *shown = gtk_scrolled_window_get_vadjustment
11385 (GTK_SCROLLED_WINDOW(parent));
11387 gfloat value = gtk_adjustment_get_value(shown);
11388 gfloat upper = gtk_adjustment_get_upper(shown);
11389 gfloat page_size = gtk_adjustment_get_page_size(shown);
11390 if (y < (int)value) {
11391 gtk_adjustment_set_value(shown, y - 1);
11393 if ((y + height) > ((int)value + (int)page_size)) {
11394 if ((y - height - 1) < ((int)upper - (int)page_size)) {
11395 gtk_adjustment_set_value(shown,
11396 y + height - (int)page_size - 1);
11398 gtk_adjustment_set_value(shown,
11399 (int)upper - (int)page_size - 1);
11406 if (GTK_IS_EDITABLE(widget) || GTK_IS_TEXT_VIEW(widget))
11407 compose->focused_editable = widget;
11409 #ifdef GENERIC_UMPC
11410 if (GTK_IS_TEXT_VIEW(widget)
11411 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->edit_vbox) {
11412 g_object_ref(compose->notebook);
11413 g_object_ref(compose->edit_vbox);
11414 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
11415 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
11416 gtk_paned_add1(GTK_PANED(compose->paned), compose->edit_vbox);
11417 gtk_paned_add2(GTK_PANED(compose->paned), compose->notebook);
11418 g_object_unref(compose->notebook);
11419 g_object_unref(compose->edit_vbox);
11420 g_signal_handlers_block_by_func(G_OBJECT(widget),
11421 G_CALLBACK(compose_grab_focus_cb),
11423 gtk_widget_grab_focus(widget);
11424 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
11425 G_CALLBACK(compose_grab_focus_cb),
11427 } else if (!GTK_IS_TEXT_VIEW(widget)
11428 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->notebook) {
11429 g_object_ref(compose->notebook);
11430 g_object_ref(compose->edit_vbox);
11431 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
11432 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
11433 gtk_paned_add1(GTK_PANED(compose->paned), compose->notebook);
11434 gtk_paned_add2(GTK_PANED(compose->paned), compose->edit_vbox);
11435 g_object_unref(compose->notebook);
11436 g_object_unref(compose->edit_vbox);
11437 g_signal_handlers_block_by_func(G_OBJECT(widget),
11438 G_CALLBACK(compose_grab_focus_cb),
11440 gtk_widget_grab_focus(widget);
11441 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
11442 G_CALLBACK(compose_grab_focus_cb),
11448 static void compose_changed_cb(GtkTextBuffer *textbuf, Compose *compose)
11450 compose->modified = TRUE;
11451 /* compose_beautify_paragraph(compose, NULL, TRUE); */
11452 #ifndef GENERIC_UMPC
11453 compose_set_title(compose);
11457 static void compose_wrap_cb(GtkAction *action, gpointer data)
11459 Compose *compose = (Compose *)data;
11460 compose_beautify_paragraph(compose, NULL, TRUE);
11463 static void compose_wrap_all_cb(GtkAction *action, gpointer data)
11465 Compose *compose = (Compose *)data;
11466 compose_wrap_all_full(compose, TRUE);
11469 static void compose_find_cb(GtkAction *action, gpointer data)
11471 Compose *compose = (Compose *)data;
11473 message_search_compose(compose);
11476 static void compose_toggle_autowrap_cb(GtkToggleAction *action,
11479 Compose *compose = (Compose *)data;
11480 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11481 if (compose->autowrap)
11482 compose_wrap_all_full(compose, TRUE);
11483 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11486 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
11489 Compose *compose = (Compose *)data;
11490 compose->autoindent = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11493 static void compose_toggle_sign_cb(GtkToggleAction *action, gpointer data)
11495 Compose *compose = (Compose *)data;
11497 compose->use_signing = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11498 gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(compose->toolbar->privacy_sign_btn), compose->use_signing);
11501 static void compose_toggle_encrypt_cb(GtkToggleAction *action, gpointer data)
11503 Compose *compose = (Compose *)data;
11505 compose->use_encryption = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11506 gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(compose->toolbar->privacy_encrypt_btn), compose->use_encryption);
11509 static void activate_privacy_system(Compose *compose, PrefsAccount *account, gboolean warn)
11511 g_free(compose->privacy_system);
11512 g_free(compose->encdata);
11514 compose->privacy_system = g_strdup(account->default_privacy_system);
11515 compose_update_privacy_system_menu_item(compose, warn);
11518 static void compose_toggle_ruler_cb(GtkToggleAction *action, gpointer data)
11520 Compose *compose = (Compose *)data;
11522 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action))) {
11523 gtk_widget_show(compose->ruler_hbox);
11524 prefs_common.show_ruler = TRUE;
11526 gtk_widget_hide(compose->ruler_hbox);
11527 gtk_widget_queue_resize(compose->edit_vbox);
11528 prefs_common.show_ruler = FALSE;
11532 static void compose_attach_drag_received_cb (GtkWidget *widget,
11533 GdkDragContext *context,
11536 GtkSelectionData *data,
11539 gpointer user_data)
11541 Compose *compose = (Compose *)user_data;
11545 type = gtk_selection_data_get_data_type(data);
11546 if ((gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list"))
11547 && gtk_drag_get_source_widget(context) !=
11548 summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
11549 list = uri_list_extract_filenames(
11550 (const gchar *)gtk_selection_data_get_data(data));
11551 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11552 gchar *utf8_filename = conv_filename_to_utf8((const gchar *)tmp->data);
11553 compose_attach_append
11554 (compose, (const gchar *)tmp->data,
11555 utf8_filename, NULL, NULL);
11556 g_free(utf8_filename);
11558 if (list) compose_changed_cb(NULL, compose);
11559 list_free_strings(list);
11561 } else if (gtk_drag_get_source_widget(context)
11562 == summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
11563 /* comes from our summaryview */
11564 SummaryView * summaryview = NULL;
11565 GSList * list = NULL, *cur = NULL;
11567 if (mainwindow_get_mainwindow())
11568 summaryview = mainwindow_get_mainwindow()->summaryview;
11571 list = summary_get_selected_msg_list(summaryview);
11573 for (cur = list; cur; cur = cur->next) {
11574 MsgInfo *msginfo = (MsgInfo *)cur->data;
11575 gchar *file = NULL;
11577 file = procmsg_get_message_file_full(msginfo,
11580 compose_attach_append(compose, (const gchar *)file,
11581 (const gchar *)file, "message/rfc822", NULL);
11585 g_slist_free(list);
11589 static gboolean compose_drag_drop(GtkWidget *widget,
11590 GdkDragContext *drag_context,
11592 guint time, gpointer user_data)
11594 /* not handling this signal makes compose_insert_drag_received_cb
11599 static gboolean completion_set_focus_to_subject
11600 (GtkWidget *widget,
11601 GdkEventKey *event,
11604 cm_return_val_if_fail(compose != NULL, FALSE);
11606 /* make backtab move to subject field */
11607 if(event->keyval == GDK_KEY_ISO_Left_Tab) {
11608 gtk_widget_grab_focus(compose->subject_entry);
11614 static void compose_insert_drag_received_cb (GtkWidget *widget,
11615 GdkDragContext *drag_context,
11618 GtkSelectionData *data,
11621 gpointer user_data)
11623 Compose *compose = (Compose *)user_data;
11629 /* strangely, testing data->type == gdk_atom_intern("text/uri-list", TRUE)
11631 type = gtk_selection_data_get_data_type(data);
11632 if (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list")) {
11633 AlertValue val = G_ALERTDEFAULT;
11634 const gchar* ddata = (const gchar *)gtk_selection_data_get_data(data);
11636 list = uri_list_extract_filenames(ddata);
11637 num_files = g_list_length(list);
11638 if (list == NULL && strstr(ddata, "://")) {
11639 /* Assume a list of no files, and data has ://, is a remote link */
11640 gchar *tmpdata = g_strstrip(g_strdup(ddata));
11641 gchar *tmpfile = get_tmp_file();
11642 str_write_to_file(tmpdata, tmpfile);
11644 compose_insert_file(compose, tmpfile);
11645 claws_unlink(tmpfile);
11647 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11648 compose_beautify_paragraph(compose, NULL, TRUE);
11651 switch (prefs_common.compose_dnd_mode) {
11652 case COMPOSE_DND_ASK:
11653 msg = g_strdup_printf(
11655 "Do you want to insert the contents of the file "
11656 "into the message body, or attach it to the email?",
11657 "Do you want to insert the contents of the %d files "
11658 "into the message body, or attach them to the email?",
11661 val = alertpanel_full(_("Insert or attach?"), msg,
11662 GTK_STOCK_CANCEL, _("_Insert"), _("_Attach"),
11664 TRUE, NULL, ALERT_QUESTION);
11667 case COMPOSE_DND_INSERT:
11668 val = G_ALERTALTERNATE;
11670 case COMPOSE_DND_ATTACH:
11671 val = G_ALERTOTHER;
11674 /* unexpected case */
11675 g_warning("error: unexpected compose_dnd_mode option value in compose_insert_drag_received_cb()");
11678 if (val & G_ALERTDISABLE) {
11679 val &= ~G_ALERTDISABLE;
11680 /* remember what action to perform by default, only if we don't click Cancel */
11681 if (val == G_ALERTALTERNATE)
11682 prefs_common.compose_dnd_mode = COMPOSE_DND_INSERT;
11683 else if (val == G_ALERTOTHER)
11684 prefs_common.compose_dnd_mode = COMPOSE_DND_ATTACH;
11687 if (val == G_ALERTDEFAULT || val == G_ALERTCANCEL) {
11688 gtk_drag_finish(drag_context, FALSE, FALSE, time);
11689 list_free_strings(list);
11692 } else if (val == G_ALERTOTHER) {
11693 compose_attach_drag_received_cb(widget, drag_context, x, y, data, info, time, user_data);
11694 list_free_strings(list);
11699 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11700 compose_insert_file(compose, (const gchar *)tmp->data);
11702 list_free_strings(list);
11704 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11709 static void compose_header_drag_received_cb (GtkWidget *widget,
11710 GdkDragContext *drag_context,
11713 GtkSelectionData *data,
11716 gpointer user_data)
11718 GtkEditable *entry = (GtkEditable *)user_data;
11719 const gchar *email = (const gchar *)gtk_selection_data_get_data(data);
11721 /* strangely, testing data->type == gdk_atom_intern("text/plain", TRUE)
11724 if (!strncmp(email, "mailto:", strlen("mailto:"))) {
11725 gchar *decoded=g_new(gchar, strlen(email));
11728 decode_uri(decoded, email + strlen("mailto:")); /* will fit */
11729 gtk_editable_delete_text(entry, 0, -1);
11730 gtk_editable_insert_text(entry, decoded, strlen(decoded), &start);
11731 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11735 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11738 static void compose_toggle_return_receipt_cb(GtkToggleAction *action, gpointer data)
11740 Compose *compose = (Compose *)data;
11742 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11743 compose->return_receipt = TRUE;
11745 compose->return_receipt = FALSE;
11748 static void compose_toggle_remove_refs_cb(GtkToggleAction *action, gpointer data)
11750 Compose *compose = (Compose *)data;
11752 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11753 compose->remove_references = TRUE;
11755 compose->remove_references = FALSE;
11758 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
11759 ComposeHeaderEntry *headerentry)
11761 gtk_entry_set_text(GTK_ENTRY(headerentry->entry), "");
11762 gtk_widget_modify_base(GTK_WIDGET(headerentry->entry), GTK_STATE_NORMAL, NULL);
11763 gtk_widget_modify_text(GTK_WIDGET(headerentry->entry), GTK_STATE_NORMAL, NULL);
11767 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
11768 GdkEventKey *event,
11769 ComposeHeaderEntry *headerentry)
11771 if ((g_slist_length(headerentry->compose->header_list) > 0) &&
11772 ((headerentry->headernum + 1) != headerentry->compose->header_nextrow) &&
11773 !(event->state & GDK_MODIFIER_MASK) &&
11774 (event->keyval == GDK_KEY_BackSpace) &&
11775 (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) == 0)) {
11776 gtk_container_remove
11777 (GTK_CONTAINER(headerentry->compose->header_table),
11778 headerentry->combo);
11779 gtk_container_remove
11780 (GTK_CONTAINER(headerentry->compose->header_table),
11781 headerentry->entry);
11782 headerentry->compose->header_list =
11783 g_slist_remove(headerentry->compose->header_list,
11785 g_free(headerentry);
11786 } else if (event->keyval == GDK_KEY_Tab) {
11787 if (headerentry->compose->header_last == headerentry) {
11788 /* Override default next focus, and give it to subject_entry
11789 * instead of notebook tabs
11791 g_signal_stop_emission_by_name(G_OBJECT(entry), "key-press-event");
11792 gtk_widget_grab_focus(headerentry->compose->subject_entry);
11799 static gboolean scroll_postpone(gpointer data)
11801 Compose *compose = (Compose *)data;
11803 if (compose->batch)
11806 GTK_EVENTS_FLUSH();
11807 compose_show_first_last_header(compose, FALSE);
11811 static void compose_headerentry_changed_cb(GtkWidget *entry,
11812 ComposeHeaderEntry *headerentry)
11814 if (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) != 0) {
11815 compose_create_header_entry(headerentry->compose);
11816 g_signal_handlers_disconnect_matched
11817 (G_OBJECT(entry), G_SIGNAL_MATCH_DATA,
11818 0, 0, NULL, NULL, headerentry);
11820 if (!headerentry->compose->batch)
11821 g_timeout_add(0, scroll_postpone, headerentry->compose);
11825 static gboolean compose_defer_auto_save_draft(Compose *compose)
11827 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
11828 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
11832 static void compose_show_first_last_header(Compose *compose, gboolean show_first)
11834 GtkAdjustment *vadj;
11836 cm_return_if_fail(compose);
11841 cm_return_if_fail(GTK_IS_WIDGET(compose->header_table));
11842 cm_return_if_fail(GTK_IS_VIEWPORT(gtk_widget_get_parent(compose->header_table)));
11843 vadj = gtk_viewport_get_vadjustment(GTK_VIEWPORT(
11844 gtk_widget_get_parent(compose->header_table)));
11845 gtk_adjustment_set_value(vadj, (show_first ?
11846 gtk_adjustment_get_lower(vadj) :
11847 (gtk_adjustment_get_upper(vadj) -
11848 gtk_adjustment_get_page_size(vadj))));
11849 gtk_adjustment_changed(vadj);
11852 static void text_inserted(GtkTextBuffer *buffer, GtkTextIter *iter,
11853 const gchar *text, gint len, Compose *compose)
11855 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data
11856 (G_OBJECT(compose->text), "paste_as_quotation"));
11859 cm_return_if_fail(text != NULL);
11861 g_signal_handlers_block_by_func(G_OBJECT(buffer),
11862 G_CALLBACK(text_inserted),
11864 if (paste_as_quotation) {
11866 const gchar *qmark;
11868 GtkTextIter start_iter;
11871 len = strlen(text);
11873 new_text = g_strndup(text, len);
11875 qmark = compose_quote_char_from_context(compose);
11877 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11878 gtk_text_buffer_place_cursor(buffer, iter);
11880 pos = gtk_text_iter_get_offset(iter);
11882 compose_quote_fmt(compose, NULL, "%Q", qmark, new_text, TRUE, FALSE,
11883 _("Quote format error at line %d."));
11884 quote_fmt_reset_vartable();
11886 g_object_set_data(G_OBJECT(compose->text), "paste_as_quotation",
11887 GINT_TO_POINTER(paste_as_quotation - 1));
11889 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11890 gtk_text_buffer_place_cursor(buffer, iter);
11891 gtk_text_buffer_delete_mark(buffer, mark);
11893 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, pos);
11894 mark = gtk_text_buffer_create_mark(buffer, NULL, &start_iter, FALSE);
11895 compose_beautify_paragraph(compose, &start_iter, FALSE);
11896 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark);
11897 gtk_text_buffer_delete_mark(buffer, mark);
11899 if (strcmp(text, "\n") || compose->automatic_break
11900 || gtk_text_iter_starts_line(iter)) {
11901 GtkTextIter before_ins;
11902 gtk_text_buffer_insert(buffer, iter, text, len);
11903 if (!strstr(text, "\n") && gtk_text_iter_has_tag(iter, compose->no_join_tag)) {
11904 before_ins = *iter;
11905 gtk_text_iter_backward_chars(&before_ins, len);
11906 gtk_text_buffer_remove_tag_by_name(buffer, "no_join", &before_ins, iter);
11909 /* check if the preceding is just whitespace or quote */
11910 GtkTextIter start_line;
11911 gchar *tmp = NULL, *quote = NULL;
11912 gint quote_len = 0, is_normal = 0;
11913 start_line = *iter;
11914 gtk_text_iter_set_line_offset(&start_line, 0);
11915 tmp = gtk_text_buffer_get_text(buffer, &start_line, iter, FALSE);
11918 if (*tmp == '\0') {
11921 quote = compose_get_quote_str(buffer, &start_line, "e_len);
11929 gtk_text_buffer_insert(buffer, iter, text, len);
11931 gtk_text_buffer_insert_with_tags_by_name(buffer,
11932 iter, text, len, "no_join", NULL);
11937 if (!paste_as_quotation) {
11938 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11939 compose_beautify_paragraph(compose, iter, FALSE);
11940 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11941 gtk_text_buffer_delete_mark(buffer, mark);
11944 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
11945 G_CALLBACK(text_inserted),
11947 g_signal_stop_emission_by_name(G_OBJECT(buffer), "insert-text");
11949 if (compose_can_autosave(compose) &&
11950 gtk_text_buffer_get_char_count(buffer) % prefs_common.autosave_length == 0 &&
11951 compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN /* disabled while loading */)
11952 compose->draft_timeout_tag = g_timeout_add
11953 (500, (GSourceFunc) compose_defer_auto_save_draft, compose);
11957 static void compose_check_all(GtkAction *action, gpointer data)
11959 Compose *compose = (Compose *)data;
11960 if (!compose->gtkaspell)
11963 if (gtk_widget_has_focus(compose->subject_entry))
11964 claws_spell_entry_check_all(
11965 CLAWS_SPELL_ENTRY(compose->subject_entry));
11967 gtkaspell_check_all(compose->gtkaspell);
11970 static void compose_highlight_all(GtkAction *action, gpointer data)
11972 Compose *compose = (Compose *)data;
11973 if (compose->gtkaspell) {
11974 claws_spell_entry_recheck_all(
11975 CLAWS_SPELL_ENTRY(compose->subject_entry));
11976 gtkaspell_highlight_all(compose->gtkaspell);
11980 static void compose_check_backwards(GtkAction *action, gpointer data)
11982 Compose *compose = (Compose *)data;
11983 if (!compose->gtkaspell) {
11984 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
11988 if (gtk_widget_has_focus(compose->subject_entry))
11989 claws_spell_entry_check_backwards(
11990 CLAWS_SPELL_ENTRY(compose->subject_entry));
11992 gtkaspell_check_backwards(compose->gtkaspell);
11995 static void compose_check_forwards_go(GtkAction *action, gpointer data)
11997 Compose *compose = (Compose *)data;
11998 if (!compose->gtkaspell) {
11999 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
12003 if (gtk_widget_has_focus(compose->subject_entry))
12004 claws_spell_entry_check_forwards_go(
12005 CLAWS_SPELL_ENTRY(compose->subject_entry));
12007 gtkaspell_check_forwards_go(compose->gtkaspell);
12012 *\brief Guess originating forward account from MsgInfo and several
12013 * "common preference" settings. Return NULL if no guess.
12015 static PrefsAccount *compose_find_account(MsgInfo *msginfo)
12017 PrefsAccount *account = NULL;
12019 cm_return_val_if_fail(msginfo, NULL);
12020 cm_return_val_if_fail(msginfo->folder, NULL);
12021 cm_return_val_if_fail(msginfo->folder->prefs, NULL);
12023 if (msginfo->folder->prefs->enable_default_account)
12024 account = account_find_from_id(msginfo->folder->prefs->default_account);
12026 if (!account && msginfo->to && prefs_common.forward_account_autosel) {
12028 Xstrdup_a(to, msginfo->to, return NULL);
12029 extract_address(to);
12030 account = account_find_from_address(to, FALSE);
12033 if (!account && prefs_common.forward_account_autosel) {
12035 if (!procheader_get_header_from_msginfo
12036 (msginfo, &cc, "Cc:")) {
12037 gchar *buf = cc + strlen("Cc:");
12038 extract_address(buf);
12039 account = account_find_from_address(buf, FALSE);
12044 if (!account && prefs_common.forward_account_autosel) {
12045 gchar *deliveredto = NULL;
12046 if (!procheader_get_header_from_msginfo
12047 (msginfo, &deliveredto, "Delivered-To:")) {
12048 gchar *buf = deliveredto + strlen("Delivered-To:");
12049 extract_address(buf);
12050 account = account_find_from_address(buf, FALSE);
12051 g_free(deliveredto);
12056 account = msginfo->folder->folder->account;
12061 gboolean compose_close(Compose *compose)
12065 cm_return_val_if_fail(compose, FALSE);
12067 if (!g_mutex_trylock(compose->mutex)) {
12068 /* we have to wait for the (possibly deferred by auto-save)
12069 * drafting to be done, before destroying the compose under
12071 debug_print("waiting for drafting to finish...\n");
12072 compose_allow_user_actions(compose, FALSE);
12073 if (compose->close_timeout_tag == 0) {
12074 compose->close_timeout_tag =
12075 g_timeout_add (500, (GSourceFunc) compose_close,
12081 if (compose->draft_timeout_tag >= 0) {
12082 g_source_remove(compose->draft_timeout_tag);
12083 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN;
12086 gtkut_widget_get_uposition(compose->window, &x, &y);
12087 if (!compose->batch) {
12088 prefs_common.compose_x = x;
12089 prefs_common.compose_y = y;
12091 g_mutex_unlock(compose->mutex);
12092 compose_destroy(compose);
12097 * Add entry field for each address in list.
12098 * \param compose E-Mail composition object.
12099 * \param listAddress List of (formatted) E-Mail addresses.
12101 static void compose_add_field_list( Compose *compose, GList *listAddress ) {
12104 node = listAddress;
12106 addr = ( gchar * ) node->data;
12107 compose_entry_append( compose, addr, COMPOSE_TO, PREF_NONE );
12108 node = g_list_next( node );
12112 static void compose_reply_from_messageview_real(MessageView *msgview, GSList *msginfo_list,
12113 guint action, gboolean opening_multiple)
12115 gchar *body = NULL;
12116 GSList *new_msglist = NULL;
12117 MsgInfo *tmp_msginfo = NULL;
12118 gboolean originally_enc = FALSE;
12119 gboolean originally_sig = FALSE;
12120 Compose *compose = NULL;
12121 gchar *s_system = NULL;
12123 cm_return_if_fail(msgview != NULL);
12125 cm_return_if_fail(msginfo_list != NULL);
12127 if (g_slist_length(msginfo_list) == 1 && !opening_multiple) {
12128 MimeInfo *mimeinfo = messageview_get_selected_mime_part(msgview);
12129 MsgInfo *orig_msginfo = (MsgInfo *)msginfo_list->data;
12131 if (mimeinfo != NULL && mimeinfo->type == MIMETYPE_MESSAGE &&
12132 !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
12133 tmp_msginfo = procmsg_msginfo_new_from_mimeinfo(
12134 orig_msginfo, mimeinfo);
12135 if (tmp_msginfo != NULL) {
12136 new_msglist = g_slist_append(NULL, tmp_msginfo);
12138 originally_enc = MSG_IS_ENCRYPTED(orig_msginfo->flags);
12139 privacy_msginfo_get_signed_state(orig_msginfo, &s_system);
12140 originally_sig = MSG_IS_SIGNED(orig_msginfo->flags);
12142 tmp_msginfo->folder = orig_msginfo->folder;
12143 tmp_msginfo->msgnum = orig_msginfo->msgnum;
12144 if (orig_msginfo->tags) {
12145 tmp_msginfo->tags = g_slist_copy(orig_msginfo->tags);
12146 tmp_msginfo->folder->tags_dirty = TRUE;
12152 if (!opening_multiple)
12153 body = messageview_get_selection(msgview);
12156 compose = compose_reply_mode((ComposeMode)action, new_msglist, body);
12157 procmsg_msginfo_free(&tmp_msginfo);
12158 g_slist_free(new_msglist);
12160 compose = compose_reply_mode((ComposeMode)action, msginfo_list, body);
12162 if (compose && originally_enc) {
12163 compose_force_encryption(compose, compose->account, FALSE, s_system);
12166 if (compose && originally_sig && compose->account->default_sign_reply) {
12167 compose_force_signing(compose, compose->account, s_system);
12171 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
12174 void compose_reply_from_messageview(MessageView *msgview, GSList *msginfo_list,
12177 if ((!prefs_common.forward_as_attachment || action != COMPOSE_FORWARD)
12178 && action != COMPOSE_FORWARD_AS_ATTACH && g_slist_length(msginfo_list) > 1) {
12179 GSList *cur = msginfo_list;
12180 gchar *msg = g_strdup_printf(_("You are about to reply to %d "
12181 "messages. Opening the windows "
12182 "could take some time. Do you "
12183 "want to continue?"),
12184 g_slist_length(msginfo_list));
12185 if (g_slist_length(msginfo_list) > 9
12186 && alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, GTK_STOCK_YES, NULL,
12187 ALERTFOCUS_SECOND) != G_ALERTALTERNATE) {
12192 /* We'll open multiple compose windows */
12193 /* let the WM place the next windows */
12194 compose_force_window_origin = FALSE;
12195 for (; cur; cur = cur->next) {
12197 tmplist.data = cur->data;
12198 tmplist.next = NULL;
12199 compose_reply_from_messageview_real(msgview, &tmplist, action, TRUE);
12201 compose_force_window_origin = TRUE;
12203 /* forwarding multiple mails as attachments is done via a
12204 * single compose window */
12205 compose_reply_from_messageview_real(msgview, msginfo_list, action, FALSE);
12209 void compose_check_for_email_account(Compose *compose)
12211 PrefsAccount *ac = NULL, *curr = NULL;
12217 if (compose->account && compose->account->protocol == A_NNTP) {
12218 ac = account_get_cur_account();
12219 if (ac->protocol == A_NNTP) {
12220 list = account_get_list();
12222 for( ; list != NULL ; list = g_list_next(list)) {
12223 curr = (PrefsAccount *) list->data;
12224 if (curr->protocol != A_NNTP) {
12230 combobox_select_by_data(GTK_COMBO_BOX(compose->account_combo),
12235 void compose_reply_to_address(MessageView *msgview, MsgInfo *msginfo,
12236 const gchar *address)
12238 GSList *msginfo_list = NULL;
12239 gchar *body = messageview_get_selection(msgview);
12242 msginfo_list = g_slist_prepend(msginfo_list, msginfo);
12244 compose = compose_reply_mode(COMPOSE_REPLY_TO_ADDRESS, msginfo_list, body);
12245 compose_check_for_email_account(compose);
12246 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
12247 compose_entry_append(compose, address, COMPOSE_TO, PREF_NONE);
12248 compose_reply_set_subject(compose, msginfo);
12251 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
12254 void compose_set_position(Compose *compose, gint pos)
12256 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
12258 gtkut_text_view_set_position(text, pos);
12261 gboolean compose_search_string(Compose *compose,
12262 const gchar *str, gboolean case_sens)
12264 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
12266 return gtkut_text_view_search_string(text, str, case_sens);
12269 gboolean compose_search_string_backward(Compose *compose,
12270 const gchar *str, gboolean case_sens)
12272 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
12274 return gtkut_text_view_search_string_backward(text, str, case_sens);
12277 /* allocate a msginfo structure and populate its data from a compose data structure */
12278 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose)
12280 MsgInfo *newmsginfo;
12282 gchar date[RFC822_DATE_BUFFSIZE];
12284 cm_return_val_if_fail( compose != NULL, NULL );
12286 newmsginfo = procmsg_msginfo_new();
12289 get_rfc822_date(date, sizeof(date));
12290 newmsginfo->date = g_strdup(date);
12293 if (compose->from_name) {
12294 newmsginfo->from = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
12295 newmsginfo->fromname = procheader_get_fromname(newmsginfo->from);
12299 if (compose->subject_entry)
12300 newmsginfo->subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
12302 /* to, cc, reply-to, newsgroups */
12303 for (list = compose->header_list; list; list = list->next) {
12304 gchar *header = gtk_editable_get_chars(
12306 gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
12307 gchar *entry = gtk_editable_get_chars(
12308 GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
12310 if ( strcasecmp(header, prefs_common_translated_header_name("To:")) == 0 ) {
12311 if ( newmsginfo->to == NULL ) {
12312 newmsginfo->to = g_strdup(entry);
12313 } else if (entry && *entry) {
12314 gchar *tmp = g_strconcat(newmsginfo->to, ", ", entry, NULL);
12315 g_free(newmsginfo->to);
12316 newmsginfo->to = tmp;
12319 if ( strcasecmp(header, prefs_common_translated_header_name("Cc:")) == 0 ) {
12320 if ( newmsginfo->cc == NULL ) {
12321 newmsginfo->cc = g_strdup(entry);
12322 } else if (entry && *entry) {
12323 gchar *tmp = g_strconcat(newmsginfo->cc, ", ", entry, NULL);
12324 g_free(newmsginfo->cc);
12325 newmsginfo->cc = tmp;
12328 if ( strcasecmp(header,
12329 prefs_common_translated_header_name("Newsgroups:")) == 0 ) {
12330 if ( newmsginfo->newsgroups == NULL ) {
12331 newmsginfo->newsgroups = g_strdup(entry);
12332 } else if (entry && *entry) {
12333 gchar *tmp = g_strconcat(newmsginfo->newsgroups, ", ", entry, NULL);
12334 g_free(newmsginfo->newsgroups);
12335 newmsginfo->newsgroups = tmp;
12343 /* other data is unset */
12349 /* update compose's dictionaries from folder dict settings */
12350 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
12351 FolderItem *folder_item)
12353 cm_return_if_fail(compose != NULL);
12355 if (compose->gtkaspell && folder_item && folder_item->prefs) {
12356 FolderItemPrefs *prefs = folder_item->prefs;
12358 if (prefs->enable_default_dictionary)
12359 gtkaspell_change_dict(compose->gtkaspell,
12360 prefs->default_dictionary, FALSE);
12361 if (folder_item->prefs->enable_default_alt_dictionary)
12362 gtkaspell_change_alt_dict(compose->gtkaspell,
12363 prefs->default_alt_dictionary);
12364 if (prefs->enable_default_dictionary
12365 || prefs->enable_default_alt_dictionary)
12366 compose_spell_menu_changed(compose);
12371 static void compose_subject_entry_activated(GtkWidget *widget, gpointer data)
12373 Compose *compose = (Compose *)data;
12375 cm_return_if_fail(compose != NULL);
12377 gtk_widget_grab_focus(compose->text);
12380 static void from_name_activate_cb(GtkWidget *widget, gpointer data)
12382 gtk_combo_box_popup(GTK_COMBO_BOX(data));